diff --git a/messages/en.json b/messages/en.json index 5534da01c..19ce48706 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4803,7 +4803,11 @@ "notificationsCta": "Open notifications", "insightsTitle": "Turn on insights (optional)", "insightsDescription": "Add a local model or your own AI key to unlock the daily briefing and Coach.", - "insightsCta": "Turn on insights" + "insightsCta": "Turn on insights", + "visitTitle": "Prepare for the visit", + "visitDescription": "Put the appointment on the calendar; the doctor report bundles what to bring.", + "visitCta": "Open visits", + "dataSourceNamedTitle": "Connect {source}" } }, "trendHints": { @@ -6615,7 +6619,15 @@ }, "dangerZoneDetail": "Your account and the credentials you sign in with are kept.", "deleteAccountCardDetail": "This cannot be undone.", - "passkeyStepUpRequired": "For your security, sign in again before removing a passkey." + "passkeyStepUpRequired": "For your security, sign in again before removing a passkey.", + "setup": { + "title": "Setup", + "description": "The questions that decide what HealthLog shows first, and the getting-started checklist on the dashboard.", + "setUpAgain": "Set up again", + "showChecklist": "Show the checklist again", + "checklistShown": "The checklist is back on the dashboard.", + "checklistNothingHidden": "The checklist was not hidden; it opens on the dashboard." + } }, "admin": { "title": "Administration", @@ -8887,7 +8899,8 @@ "unavailable": { "description": "The operator switched {module} off for every account on this server. No setting of your own can bring it back.", "title": "Not available on this server" - } + }, + "openModules": "Open Settings, Modules" }, "vaccinations": { "title": "Vaccinations", diff --git a/src/components/layout/auth-shell.tsx b/src/components/layout/auth-shell.tsx index 1e48d0367..093044697 100644 --- a/src/components/layout/auth-shell.tsx +++ b/src/components/layout/auth-shell.tsx @@ -27,6 +27,7 @@ import { DemoBanner } from "./demo-banner"; import { OfflineBanner } from "./offline-banner"; import { SharedRecordBanner } from "./shared-record-banner"; import { SharedRecordUnavailable } from "./shared-record-unavailable"; +import { ModulePageGate } from "@/components/layout/module-page-gate"; import { RecordScopeHydrationGate } from "./record-scope-hydration-gate"; import { SidebarNav } from "./sidebar-nav"; import { TopBar } from "./top-bar"; @@ -466,7 +467,11 @@ export function AuthShell({ data-slot="main-content-wrapper" className="mx-auto max-w-screen-xl px-4 pt-6 pb-20 md:px-6" > - {outsideSharedRecord ? : children} + {outsideSharedRecord ? ( + + ) : ( + {children} + )} diff --git a/src/components/layout/module-page-gate.tsx b/src/components/layout/module-page-gate.tsx new file mode 100644 index 000000000..9046f72e9 --- /dev/null +++ b/src/components/layout/module-page-gate.tsx @@ -0,0 +1,63 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type { ReactNode } from "react"; + +import { ModuleDisabledNotice } from "@/components/layout/module-disabled-notice"; +import { + isNavDestinationActive, + NAV_DESTINATIONS, +} from "@/components/layout/nav-model"; +import { Button } from "@/components/ui/button"; +import { useAuth } from "@/hooks/use-auth"; +import { useTranslations } from "@/lib/i18n/context"; + +/** + * v1.39 (C2) — the page of a module that is switched off answers with an + * empty state that names the switch (design spec §After the flow), in one + * place for every module page. + * + * The setup flow switches modules OFF for a new record — an explicit `false` + * for every module the answers did not name — and the navigation drops the + * entry. A person who reaches the page anyway (a bookmark, a link in a + * report, the tour) used to get the page's own empty state, which said + * "nothing here yet" about a module that is not there at all. This maps the + * route to the module through the navigation model's own gate, so a module + * page and its nav entry can never disagree about which switch they follow, + * and renders the shared notice with the one action that can help: the + * Modules settings, offered only when the record's own switch is what is off. + * + * Paint, not enforcement: every route behind a module refuses on its own + * through the server gate. Mounted inside the shell, which renders children + * only after the account payload resolved, so the map is never read on a + * hydration render. + */ +export function ModulePageGate({ children }: { children: ReactNode }) { + const pathname = usePathname(); + const { user } = useAuth(); + const { t } = useTranslations(); + + const destination = NAV_DESTINATIONS.find( + (d) => d.requiresModule && isNavDestinationActive(d.href, pathname), + ); + const moduleKey = destination?.requiresModule; + const off = moduleKey !== undefined && user?.modules?.[moduleKey] === false; + + if (!off || !destination || !moduleKey) return <>{children}; + + const Icon = destination.icon; + return ( + } + action={ + + } + /> + ); +} diff --git a/src/components/onboarding/choice-chip.tsx b/src/components/onboarding/choice-chip.tsx index c2e67c6b7..f2062ca48 100644 --- a/src/components/onboarding/choice-chip.tsx +++ b/src/components/onboarding/choice-chip.tsx @@ -37,7 +37,7 @@ export function ChoiceChip({ return (