diff --git a/src/app/c/[token]/page.tsx b/src/app/c/[token]/page.tsx
index 06c9738..1133da4 100644
--- a/src/app/c/[token]/page.tsx
+++ b/src/app/c/[token]/page.tsx
@@ -1,28 +1,42 @@
+import { cache } from 'react';
import { getClientView } from '@/lib/client-view';
+import { ClientDashboard } from '@/components/client-dashboard';
import { notFound } from 'next/navigation';
-export const revalidate = 60; // ISR: revalidate every 60 seconds
+export const revalidate = 60; // ISR: revalidate ogni 60 secondi
-export default async function ClientDashboard({
+// React cache deduplicates DB calls within the same render
+const getCachedClientView = cache(getClientView);
+
+export async function generateMetadata({
params,
}: {
params: Promise<{ token: string }>;
}) {
const { token } = await params;
- const view = await getClientView(token);
+ const view = await getCachedClientView(token);
+
+ if (!view) {
+ return { title: 'Not Found' };
+ }
+
+ return {
+ title: `${view.client.brand_name} — Stato Progetto | iamcavalli`,
+ description: view.client.brief || 'Dashboard stato progetto',
+ };
+}
+
+export default async function ClientPage({
+ params,
+}: {
+ params: Promise<{ token: string }>;
+}) {
+ const { token } = await params;
+ const view = await getCachedClientView(token);
if (!view) {
notFound();
}
- return (
-
- {/* Placeholder: Dashboard UI will be built in Plan 04 */}
-
-
{view.client.brand_name}
-
{view.client.brief}
-
Token: {token}
-
-
- );
+ return ;
}
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..0aa95a5 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,26 +1,48 @@
@import "tailwindcss";
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
+/* =========================================================
+ Design tokens — light & clean palette (Tailwind v4 @theme)
+ ========================================================= */
@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
+ /* Colori base */
+ --color-background: #ffffff;
+ --color-foreground: #171717;
+
+ /* Font */
+ --font-sans: var(--font-geist-sans), system-ui, -apple-system, BlinkMacSystemFont,
+ "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
--font-mono: var(--font-geist-mono);
+
+ /* Palette brand iamcavalli — light & clean */
+ --color-primary: #1a1a1a; /* charcoal per testo principale */
+ --color-secondary: #666666; /* grigio medio per testo secondario */
+ --color-tertiary: #999999; /* grigio chiaro per hint e timestamp */
+ --color-bg-subtle: #f9f9f9; /* grigio molto chiaro per sfondi sezione */
+ --color-border-light: #e5e5e5; /* bordo sottile */
+ --color-accent: #0066cc; /* blu accent */
+ --color-success: #16a34a; /* verde per done/saldato */
+ --color-warning: #ca8a04; /* giallo per in_progress/inviata */
+ --color-info: #2563eb; /* blu per pending/da_saldare */
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+/* =========================================================
+ Base styles
+ ========================================================= */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
}
body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
-}
+ background-color: #ffffff;
+ color: #171717;
+ font-family: var(--font-sans);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
\ No newline at end of file