From a5d8dc355d147d81206181e4837b9991fbaef55b Mon Sep 17 00:00:00 2001 From: luciano Date: Thu, 7 May 2026 15:29:43 +0200 Subject: [PATCH] feat(ui): nav app persistente nel masthead (desktop + drawer mobile) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'utente non aveva modo di saltare da una pagina app all'altra senza tornare alla home (solo il logo era cliccabile). Aggiunta nav globale: - **Desktop**: row di link orizzontale a destra del logo (Cucina · Tracking · Digiuno · Spesa · Profilo · Abbonamento). Pagina corrente evidenziata in pomodoro, hover sulle altre. - **Mobile**: bottone hamburger (≡ Menu) che apre un drawer sotto la riga di header con le 6 voci. Si chiude da solo al cambio di pathname. `` ora è un Client Component (serve usePathname). Prop `showNav` (default false) controlla la nav: attivata sulle 7 pagine logged-in (`/`, /plan, /tracking, /fasting, /shopping, /profile, /billing, /recipes/[id]). Lasciata off su welcome, onboarding, sign-in, sign-up. Aggiunti i18n keys: navPlan/navTracking/navFasting/navShopping/navProfile/navBilling. Rimossi gli `issueLabel="N. XX — ..."" dalle pagine app (`issueLabel` resta supportato per pagine senza nav). Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/messages/it.json | 6 + apps/web/src/app/[locale]/billing/page.tsx | 4 +- apps/web/src/app/[locale]/fasting/page.tsx | 2 +- apps/web/src/app/[locale]/page.tsx | 2 +- apps/web/src/app/[locale]/plan/page.tsx | 2 +- apps/web/src/app/[locale]/profile/page.tsx | 2 +- .../src/app/[locale]/recipes/[id]/page.tsx | 2 +- apps/web/src/app/[locale]/shopping/page.tsx | 2 +- apps/web/src/app/[locale]/tracking/page.tsx | 2 +- apps/web/src/components/masthead.tsx | 126 ++++++++++++++++-- 10 files changed, 133 insertions(+), 17 deletions(-) diff --git a/apps/web/messages/it.json b/apps/web/messages/it.json index ac87952..892e972 100644 --- a/apps/web/messages/it.json +++ b/apps/web/messages/it.json @@ -16,6 +16,12 @@ "viewTracking": "Pesata & misure", "viewFasting": "Digiuno intermittente", "viewBilling": "Abbonamento & fatture", + "navPlan": "Cucina", + "navTracking": "Tracking", + "navFasting": "Digiuno", + "navShopping": "Spesa", + "navProfile": "Profilo", + "navBilling": "Abbonamento", "lettura": "Continua la lettura", "inizia": "Inizia", "greeting": "Buongiorno, {name}.", diff --git a/apps/web/src/app/[locale]/billing/page.tsx b/apps/web/src/app/[locale]/billing/page.tsx index 81579bd..2685615 100644 --- a/apps/web/src/app/[locale]/billing/page.tsx +++ b/apps/web/src/app/[locale]/billing/page.tsx @@ -39,7 +39,7 @@ function BillingPageContent({ status }: { status: BillingStatus | null }) { return (
- +

{t('errorLoading')}

@@ -54,7 +54,7 @@ function BillingPageContent({ status }: { status: BillingStatus | null }) { return (
- +
- +
- + {userName ? ( ) : ( diff --git a/apps/web/src/app/[locale]/plan/page.tsx b/apps/web/src/app/[locale]/plan/page.tsx index 64d7639..ae6d782 100644 --- a/apps/web/src/app/[locale]/plan/page.tsx +++ b/apps/web/src/app/[locale]/plan/page.tsx @@ -64,7 +64,7 @@ function PlanPageContent({ return (
- +
- +
- +
- +
- +
= [ + { href: '/plan', key: 'navPlan' }, + { href: '/tracking', key: 'navTracking' }, + { href: '/fasting', key: 'navFasting' }, + { href: '/shopping', key: 'navShopping' }, + { href: '/profile', key: 'navProfile' }, + { href: '/billing', key: 'navBilling' }, +]; + /** - * Top-of-page header minimale: logo a sinistra, sezione corrente a destra (se - * fornita). Una riga sottile sotto fa da divider editoriale. + * Top-of-page header dell'app. Logo a sx, nav orizzontale al centro/destra + * (desktop) o hamburger drawer (mobile). Pagina corrente evidenziata in + * pomodoro. Una riga sottile sotto fa da divider editoriale. */ -export function Masthead({ issueLabel, className }: MastheadProps) { +export function Masthead({ issueLabel, showNav = false, className }: MastheadProps) { + const t = useTranslations('Home'); + const pathname = usePathname(); + const [open, setOpen] = useState(false); + + // Quando il pathname cambia, chiudi il drawer mobile (utente ha navigato). + useEffect(() => setOpen(false), [pathname]); + + // Match di fallback: il pathname è tipo /it/plan, dobbiamo confrontare + // ignorando il segmento di locale. Usiamo `.includes('/plan')` con + // controllo che inizi con //plan oppure /plan. + function isActive(href: string): boolean { + if (!pathname) return false; + // strip locale prefix se presente + const stripped = pathname.replace(/^\/[a-z]{2}(\/|$)/, '/'); + if (href === '/') return stripped === '/'; + return stripped.startsWith(href); + } + return (
-
+
KetoPath. - {issueLabel ?

{issueLabel}

: null} + + {showNav ? ( + <> + {/* Desktop nav */} + + + {/* Mobile: hamburger button */} + + + ) : issueLabel ? ( +

{issueLabel}

+ ) : null}
+ + {/* Mobile drawer: appare sotto la riga di header */} + {showNav && open ? ( + + ) : null} +
);