From ec82ad3a93c68969ba9840d9da2e7ef121d354d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 15:41:06 +0200 Subject: [PATCH] Carry the setup answers into the checklist, Settings and the module pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The getting-started checklist reads the answers: a visit named within a month adds a "prepare the visit" row that is done once one is on the calendar, and the data-source row names the wearable that was chosen. Its per-browser state moves to one module so the done screen, the skip and Settings write the same keys. Settings → Account gains the two ways back in: "Set up again" through the restart route, which resets the ledger, keeps the answers as prefill and touches no module; and "Show the checklist again", which forgets what this browser hid. A page whose module is off now answers with the shared notice and the one action that helps, in one place for every module page: the shell maps the route to the module through the navigation model's own gate, so a page and its nav entry cannot disagree about which switch they follow. And the tour's two dead stops get their anchors on the integrations and health-record group headings. --- messages/en.json | 19 +++- src/components/layout/auth-shell.tsx | 7 +- src/components/layout/module-page-gate.tsx | 63 +++++++++++++ src/components/onboarding/choice-chip.tsx | 2 +- .../onboarding/getting-started-checklist.tsx | 54 ++++++++++-- .../settings/account-section/index.tsx | 5 ++ .../settings/account-section/setup-card.tsx | 88 +++++++++++++++++++ .../settings/gesundheitsakte-section.tsx | 2 + .../settings/integrations-section.tsx | 2 + src/components/ui/section-heading.tsx | 8 ++ .../onboarding/__tests__/checklist.test.ts | 54 +++++++++++- src/lib/onboarding/checklist.ts | 28 +++++- src/lib/query-keys/onboarding.ts | 9 ++ 13 files changed, 325 insertions(+), 16 deletions(-) create mode 100644 src/components/layout/module-page-gate.tsx create mode 100644 src/components/settings/account-section/setup-card.tsx 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 (