diff --git a/.gitignore b/.gitignore index 1b532ba..a972c33 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ tests/.auth/ # Backups deploy/backups/data/ + +# Design handoff bundle (reference only, not committed) +.design-tmp/ diff --git a/drizzle/0014_paper_ink_theme.sql b/drizzle/0014_paper_ink_theme.sql new file mode 100644 index 0000000..0d828f7 --- /dev/null +++ b/drizzle/0014_paper_ink_theme.sql @@ -0,0 +1,7 @@ +ALTER TABLE "users" ADD COLUMN "theme_palette" text NOT NULL DEFAULT 'clay';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "theme_font_pair" text NOT NULL DEFAULT 'serif-sans';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "theme_density" text NOT NULL DEFAULT 'regular';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "theme_dash_layout" text NOT NULL DEFAULT 'classic';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "theme_cal_view" text NOT NULL DEFAULT 'month';--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "theme_nav_style" text NOT NULL DEFAULT 'rail-desktop';--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN IF EXISTS "theme"; diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 9057e49..05d9d2f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1778400000000, "tag": "0013_push_notify_reminders", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1778600000000, + "tag": "0014_paper_ink_theme", + "breakpoints": true } ] } \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index ae2d119..d24b82e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,15 @@ import nextConfig from "eslint-config-next/core-web-vitals"; export default tseslint.config( { - ignores: ["node_modules/**", ".next/**", ".claude/**", "dist/**", "drizzle/**", "public/sw.js"], + ignores: [ + "node_modules/**", + ".next/**", + ".claude/**", + ".design-tmp/**", + "dist/**", + "drizzle/**", + "public/sw.js", + ], }, js.configs.recommended, ...tseslint.configs.recommended, diff --git a/src/app/calendar/page.tsx b/src/app/calendar/page.tsx index 55c3df6..bb5582d 100644 --- a/src/app/calendar/page.tsx +++ b/src/app/calendar/page.tsx @@ -1,5 +1,7 @@ import { CalendarShell } from "@/modules/calendar/components/calendar-shell"; import { listCalendars, listEvents } from "@/modules/calendar/server/queries"; +import { getCurrentSession } from "@/lib/session"; +import type { CalView } from "@/modules/_core/themes"; export default async function CalendarPage() { const now = new Date(); @@ -8,10 +10,17 @@ export default async function CalendarPage() { const to = new Date(now); to.setMonth(to.getMonth() + 10); - const [calendars, events] = await Promise.all([ + const [{ user }, calendars, events] = await Promise.all([ + getCurrentSession(), listCalendars(), listEvents({ from, to, calendarIds: "all" }), ]); - return ; + return ( + + ); } diff --git a/src/app/d/[slug]/page.tsx b/src/app/d/[slug]/page.tsx index 2fc8142..86b27d4 100644 --- a/src/app/d/[slug]/page.tsx +++ b/src/app/d/[slug]/page.tsx @@ -1,13 +1,20 @@ import { notFound } from "next/navigation"; import { Suspense } from "react"; import { getCurrentSession } from "@/lib/session"; -import { parseDashboardLayout, computeDefaultLayout } from "@/lib/dashboard"; +import { parseDashboardLayout } from "@/lib/dashboard"; +import { computeDefaultLayout } from "@/lib/dashboard.server"; import { getWidget, getWidgetMetas } from "@/modules/_core"; import { getDashboardBySlug } from "@/app/d/actions"; import { DashboardEditor } from "@/components/dashboard-editor"; -import { QuickAddFab } from "@/components/quick-add-fab"; import { EditDashboardButton } from "@/components/edit-dashboard-button"; +import { DashboardSwitcher } from "@/components/dashboard-switcher"; +import { DashboardTab } from "@/components/dashboard-tab"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { auth } from "@/lib/auth"; +import { db } from "@/lib/db"; +import { dashboards } from "@/modules/_core/schema"; +import { asc, eq } from "drizzle-orm"; +import type { DashLayout } from "@/modules/_core/themes"; const smColSpan: Record = { 1: "sm:col-span-1", @@ -24,6 +31,9 @@ const smColSpan: Record = { 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({ params, searchParams, @@ -52,17 +62,55 @@ export default async function DashboardPage({ ); } + // For dashboard tabs sub-header, pull the user's dashboards (small list). + const session = await auth(); + const userDashboards = session?.user?.id + ? await db + .select({ + id: dashboards.id, + name: dashboards.name, + slug: dashboards.slug, + isDefault: dashboards.isDefault, + position: dashboards.position, + }) + .from(dashboards) + .where(eq(dashboards.userId, session.user.id)) + .orderBy(asc(dashboards.position), asc(dashboards.createdAt)) + : []; + const ctx = { userId: user.id, householdId: household.id }; const placements = [...layout.widgets].sort((a, b) => a.y - b.y || a.x - b.x); + const dashLayout = (user.themeDashLayout as DashLayout) ?? "classic"; + const containerCls = + 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] ?? ""; return ( -
-
-

{dashboard.name}

-
- - +
+ {userDashboards.length > 1 && ( +
+ {userDashboards.map((d) => ( + + ))} +
+ )} + +
+
+

+ {greeting} + {firstName && `, ${firstName}`}. +

+

{today}

+
+
@@ -73,8 +121,8 @@ export default async function DashboardPage({ return (
- - {widget.title} + + {widget.title} ,