Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12208d2fe | ||
|
|
14175d3faa | ||
|
|
c3e78932fe | ||
|
|
a332935ba7 | ||
|
|
51c8d3c92d | ||
|
|
62c3d33350 |
@@ -1,5 +1,26 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.5.3](https://github.com/ginnoir/famapp/compare/v0.5.2...v0.5.3) (2026-06-04)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- use next_public_app_url for share link base url instead of localhost fallback ([14175d3](https://github.com/ginnoir/famapp/commit/14175d3faa146ebff7c825848837c75047a8eb71))
|
||||||
|
|
||||||
|
## [0.5.2](https://github.com/ginnoir/famapp/compare/v0.5.1...v0.5.2) (2026-06-03)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **dashboard:** render greeting and date client-side so local timezone is used instead of server UTC
|
||||||
|
|
||||||
|
## [0.5.1](https://github.com/ginnoir/famapp/compare/v0.5.0...v0.5.1) (2026-06-03)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **dashboard:** always show tab strip and add-dashboard button regardless of dashboard count
|
||||||
|
- **search:** show Ctrl+K on Windows/Linux instead of ⌘K
|
||||||
|
- **mobile:** add search icon button in topbar for mobile viewports
|
||||||
|
- **mobile:** lock html/body overflow and use 100dvh to prevent topbar scrolling off-screen
|
||||||
|
|
||||||
## [0.5.0](https://github.com/ginnoir/famapp/compare/v0.4.10...v0.5.0) (2026-06-03)
|
## [0.5.0](https://github.com/ginnoir/famapp/compare/v0.4.10...v0.5.0) (2026-06-03)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "famapp",
|
"name": "famapp",
|
||||||
"version": "0.5.0",
|
"version": "0.5.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.33.3",
|
"packageManager": "pnpm@10.33.3",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { DashboardEditor } from "@/components/dashboard-editor";
|
|||||||
import { EditDashboardButton } from "@/components/edit-dashboard-button";
|
import { EditDashboardButton } from "@/components/edit-dashboard-button";
|
||||||
import { DashboardSwitcher } from "@/components/dashboard-switcher";
|
import { DashboardSwitcher } from "@/components/dashboard-switcher";
|
||||||
import { DashboardTab } from "@/components/dashboard-tab";
|
import { DashboardTab } from "@/components/dashboard-tab";
|
||||||
|
import { DashboardGreeting } from "@/components/dashboard-greeting";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
@@ -31,9 +32,6 @@ const smColSpan: Record<number, string> = {
|
|||||||
12: "sm:col-span-12",
|
12: "sm:col-span-12",
|
||||||
};
|
};
|
||||||
|
|
||||||
const greetingForHour = (h: number) =>
|
|
||||||
h < 5 ? "Up early" : h < 12 ? "Good morning" : h < 18 ? "Good afternoon" : "Good evening";
|
|
||||||
|
|
||||||
export default async function DashboardPage({
|
export default async function DashboardPage({
|
||||||
params,
|
params,
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -83,17 +81,11 @@ export default async function DashboardPage({
|
|||||||
const dashLayout = (user.themeDashLayout as DashLayout) ?? "classic";
|
const dashLayout = (user.themeDashLayout as DashLayout) ?? "classic";
|
||||||
const containerCls =
|
const containerCls =
|
||||||
dashLayout === "glance" ? "max-w-[760px] mx-auto" : dashLayout === "split" ? "" : "";
|
dashLayout === "glance" ? "max-w-[760px] mx-auto" : dashLayout === "split" ? "" : "";
|
||||||
const greeting = greetingForHour(new Date().getHours());
|
|
||||||
const today = new Date().toLocaleDateString(undefined, {
|
|
||||||
weekday: "long",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
const firstName = (user.name ?? "").split(" ")[0] ?? "";
|
const firstName = (user.name ?? "").split(" ")[0] ?? "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerCls}>
|
<div className={containerCls}>
|
||||||
{userDashboards.length > 1 && (
|
{userDashboards.length >= 1 && (
|
||||||
<div className="flex items-center gap-1 mb-4 overflow-x-auto">
|
<div className="flex items-center gap-1 mb-4 overflow-x-auto">
|
||||||
{userDashboards.map((d) => (
|
{userDashboards.map((d) => (
|
||||||
<DashboardTab key={d.id} slug={d.slug} name={d.name} />
|
<DashboardTab key={d.id} slug={d.slug} name={d.name} />
|
||||||
@@ -103,13 +95,7 @@ export default async function DashboardPage({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-6 flex items-end justify-between gap-4 flex-wrap">
|
<div className="mb-6 flex items-end justify-between gap-4 flex-wrap">
|
||||||
<div>
|
<DashboardGreeting firstName={firstName} />
|
||||||
<h1 className="serif text-[26px] sm:text-[30px] leading-tight tracking-tight">
|
|
||||||
{greeting}
|
|
||||||
{firstName && `, ${firstName}`}.
|
|
||||||
</h1>
|
|
||||||
<p className="muted mt-1 text-[13px]">{today}</p>
|
|
||||||
</div>
|
|
||||||
<EditDashboardButton />
|
<EditDashboardButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -490,6 +490,8 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
@@ -521,6 +523,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 232px 1fr;
|
grid-template-columns: 232px 1fr;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
}
|
}
|
||||||
:where(html[data-nav="rail"]) .app {
|
:where(html[data-nav="rail"]) .app {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
const greetingForHour = (h: number) =>
|
||||||
|
h < 5 ? "Up early" : h < 12 ? "Good morning" : h < 18 ? "Good afternoon" : "Good evening";
|
||||||
|
|
||||||
|
export function DashboardGreeting({ firstName }: { firstName: string }) {
|
||||||
|
const now = new Date();
|
||||||
|
const greeting = greetingForHour(now.getHours());
|
||||||
|
const today = now.toLocaleDateString(undefined, {
|
||||||
|
weekday: "long",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="serif text-[26px] sm:text-[30px] leading-tight tracking-tight">
|
||||||
|
{greeting}
|
||||||
|
{firstName && `, ${firstName}`}.
|
||||||
|
</h1>
|
||||||
|
<p className="muted mt-1 text-[13px]">{today}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -29,8 +29,14 @@ const QUICK_ADD_ICON: Record<string, string> = {
|
|||||||
"file-plus": "note",
|
"file-plus": "note",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function useShortcutLabel() {
|
||||||
|
if (typeof navigator === "undefined") return "⌘K";
|
||||||
|
return /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "⌘K" : "Ctrl+K";
|
||||||
|
}
|
||||||
|
|
||||||
export function QuickAddSheet() {
|
export function QuickAddSheet() {
|
||||||
const { sheetOpen, closeSheet, actions } = useQuickAdd();
|
const { sheetOpen, closeSheet, actions } = useQuickAdd();
|
||||||
|
const shortcut = useShortcutLabel();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const backdropRef = useRef<HTMLDivElement>(null);
|
const backdropRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -110,7 +116,7 @@ export function QuickAddSheet() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Sparkles className="size-3" />
|
<Sparkles className="size-3" />
|
||||||
<span>Pick what to add — or use ⌘K to search.</span>
|
<span>Pick what to add — or use {shortcut} to search.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-[60vh] overflow-y-auto">
|
<div className="max-h-[60vh] overflow-y-auto">
|
||||||
|
|||||||
@@ -3,25 +3,45 @@
|
|||||||
import { useQuickAdd } from "@/components/quick-add-provider";
|
import { useQuickAdd } from "@/components/quick-add-provider";
|
||||||
import { NavIcon } from "@/components/nav-icon";
|
import { NavIcon } from "@/components/nav-icon";
|
||||||
|
|
||||||
|
function useShortcutLabel() {
|
||||||
|
if (typeof navigator === "undefined") return "⌘K";
|
||||||
|
return /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "⌘K" : "Ctrl+K";
|
||||||
|
}
|
||||||
|
|
||||||
export function TopbarSearch() {
|
export function TopbarSearch() {
|
||||||
const { openPalette } = useQuickAdd();
|
const { openPalette } = useQuickAdd();
|
||||||
|
const shortcut = useShortcutLabel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<>
|
||||||
type="button"
|
{/* Desktop: pill with shortcut hint */}
|
||||||
onClick={openPalette}
|
<button
|
||||||
aria-label="Search (⌘K)"
|
type="button"
|
||||||
className="hidden md:flex items-center gap-2 h-8 px-2.5 rounded-md text-[13px]"
|
onClick={openPalette}
|
||||||
style={{
|
aria-label={`Search (${shortcut})`}
|
||||||
border: "0.5px solid var(--hair-2)",
|
className="hidden md:flex items-center gap-2 h-8 px-2.5 rounded-md text-[13px]"
|
||||||
background: "var(--card)",
|
style={{
|
||||||
color: "var(--ink-mute)",
|
border: "0.5px solid var(--hair-2)",
|
||||||
width: 220,
|
background: "var(--card)",
|
||||||
}}
|
color: "var(--ink-mute)",
|
||||||
>
|
width: 220,
|
||||||
<NavIcon name="search" className="size-3.5" />
|
}}
|
||||||
<span style={{ flex: 1, textAlign: "left" }}>Search…</span>
|
>
|
||||||
<span className="kbd">⌘K</span>
|
<NavIcon name="search" className="size-3.5" />
|
||||||
</button>
|
<span style={{ flex: 1, textAlign: "left" }}>Search…</span>
|
||||||
|
<span className="kbd">{shortcut}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Mobile: icon-only */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={openPalette}
|
||||||
|
aria-label="Search"
|
||||||
|
className="flex md:hidden items-center justify-center h-8 w-8 rounded-md"
|
||||||
|
style={{ color: "var(--ink-mute)" }}
|
||||||
|
>
|
||||||
|
<NavIcon name="search" className="size-4" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ function hashToken(raw: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildUrl(token: string): string {
|
function buildUrl(token: string): string {
|
||||||
const base = process.env["NEXTAUTH_URL"] ?? "http://localhost:3000";
|
const base =
|
||||||
|
process.env["NEXT_PUBLIC_APP_URL"] ?? process.env["AUTH_URL"] ?? "http://localhost:3000";
|
||||||
return `${base}/s/${token}`;
|
return `${base}/s/${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user