From 33fec28b297a06836a3176249623ecbc1257f8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 18 Jun 2026 19:49:37 +0200 Subject: [PATCH] feat(onboarding): per-module tour re-entry + consolidated replay Add a 'Diese Tour zeigen' affordance to every module header that opens the tour narrowed to that module's card in place, plus additive data-tour anchors (measurements/medications/labs/illness/vorsorge/cycle/mood/ insights/coach/integrations/export/achievements) so the cross-page walk has a real target on each page. Consolidate the full-tour replay onto a single Settings -> Advanced card, relabel it, and drop the duplicate from About. The replay reset clears the stored progress so it restarts cleanly. --- messages/de.json | 4 +- messages/en.json | 4 +- messages/es.json | 4 +- messages/fr.json | 4 +- messages/it.json | 4 +- messages/pl.json | 4 +- src/app/achievements/page.tsx | 23 +++- src/app/admin/[section]/renderer.tsx | 6 +- src/app/coach/page.tsx | 29 +++-- src/app/labs/page.tsx | 7 +- src/app/measurements/page.tsx | 23 ++-- src/app/medications/page.tsx | 7 +- src/app/mood/page.tsx | 7 +- src/components/cycle/cycle-view.tsx | 23 ++-- src/components/dashboard/dashboard-header.tsx | 3 + src/components/illness/illness-view.tsx | 23 ++-- .../insights/insights-tab-strip.tsx | 6 + .../vorsorge-section.tsx | 3 + .../onboarding/module-tour-trigger.tsx | 53 ++++++++ .../settings/__tests__/sections.test.tsx | 35 ++--- src/components/settings/about-section.tsx | 121 +----------------- .../settings/gesundheitsakte-section.tsx | 8 +- .../settings/integrations-section.tsx | 9 +- src/lib/onboarding/tour-state.ts | 2 +- 24 files changed, 202 insertions(+), 210 deletions(-) create mode 100644 src/components/onboarding/module-tour-trigger.tsx diff --git a/messages/de.json b/messages/de.json index 83131c9e2..d67ae50ec 100644 --- a/messages/de.json +++ b/messages/de.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Dokumentation", "linksHeading": "Quellen & Dokumentation", - "newerAvailable": "Neue Version: {tag}", - "tourReplay": "Tour ansehen", - "tourReplayHint": "Zeige die Dashboard-Tour — praktisch, falls du sie beim ersten Besuch übersprungen hast." + "newerAvailable": "Neue Version: {tag}" }, "testConnection": { "test": "Verbindung testen", diff --git a/messages/en.json b/messages/en.json index 092fe42e9..79fe8ba31 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Documentation", "linksHeading": "Sources & docs", - "newerAvailable": "Newer version: {tag}", - "tourReplay": "Replay the tour", - "tourReplayHint": "Watch the dashboard walkthrough — handy if you skipped it on first visit." + "newerAvailable": "Newer version: {tag}" }, "testConnection": { "test": "Test connection", diff --git a/messages/es.json b/messages/es.json index 2110b8cd6..e979134af 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Documentación", "linksHeading": "Fuentes y documentación", - "newerAvailable": "Nueva versión: {tag}", - "tourReplay": "Volver a ver el recorrido", - "tourReplayHint": "Mira el recorrido del panel — útil si lo saltaste en la primera visita." + "newerAvailable": "Nueva versión: {tag}" }, "testConnection": { "test": "Probar conexión", diff --git a/messages/fr.json b/messages/fr.json index 8f6e5a3d4..ccb1c3e92 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Documentation", "linksHeading": "Sources et documentation", - "newerAvailable": "Nouvelle version : {tag}", - "tourReplay": "Revoir la visite", - "tourReplayHint": "Voir la visite guidée du tableau de bord — pratique si tu l'as sautée à la première visite." + "newerAvailable": "Nouvelle version : {tag}" }, "testConnection": { "test": "Tester la connexion", diff --git a/messages/it.json b/messages/it.json index 2f80c5675..3aea8285f 100644 --- a/messages/it.json +++ b/messages/it.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Documentazione", "linksHeading": "Fonti e documentazione", - "newerAvailable": "Nuova versione: {tag}", - "tourReplay": "Rivedi il tour", - "tourReplayHint": "Guarda il tour della dashboard — utile se l'hai saltato alla prima visita." + "newerAvailable": "Nuova versione: {tag}" }, "testConnection": { "test": "Prova connessione", diff --git a/messages/pl.json b/messages/pl.json index 2c637227b..41f7b38be 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -4621,9 +4621,7 @@ "changelog": "Changelog", "docs": "Dokumentacja", "linksHeading": "Źródła i dokumentacja", - "newerAvailable": "Nowa wersja: {tag}", - "tourReplay": "Obejrzyj samouczek", - "tourReplayHint": "Zobacz przewodnik po panelu — przydatne, jeśli pominąłeś go przy pierwszej wizycie." + "newerAvailable": "Nowa wersja: {tag}" }, "testConnection": { "test": "Testuj połączenie", diff --git a/src/app/achievements/page.tsx b/src/app/achievements/page.tsx index 513927772..f0a9448c3 100644 --- a/src/app/achievements/page.tsx +++ b/src/app/achievements/page.tsx @@ -34,6 +34,7 @@ import { Badge } from "@/components/ui/badge"; import { EmptyState } from "@/components/ui/empty-state"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { formatDate } from "@/lib/format"; import { useAchievementsQuery } from "@/lib/queries/use-achievements-query"; import { @@ -317,13 +318,21 @@ export default function AchievementsPage() { return (
-
-

- {t("achievements.title")} -

-

- {t("achievements.subtitle")} -

+
+
+

+ {t("achievements.title")} +

+

+ {t("achievements.subtitle")} +

+
+
+ +
diff --git a/src/app/admin/[section]/renderer.tsx b/src/app/admin/[section]/renderer.tsx index 8bd9e34be..aeea25435 100644 --- a/src/app/admin/[section]/renderer.tsx +++ b/src/app/admin/[section]/renderer.tsx @@ -194,9 +194,9 @@ export function AdminSectionRenderer({ // v1.4.36 W4e — About section reused as-is from the settings // surface. The component owns its own heading + cards layout // so no SectionFrame wrapper. - // v1.18.1 E5 — hide the onboarding "Replay tour" card on the admin - // surface; it's a user-facing affordance with no admin-side use. - return ; + // v1.18.6 — the About surface no longer carries the tour-replay card + // (consolidated onto Settings → Advanced), so there's nothing to hide. + return ; default: slug satisfies never; return null; diff --git a/src/app/coach/page.tsx b/src/app/coach/page.tsx index 23363d12e..d8c0d5402 100644 --- a/src/app/coach/page.tsx +++ b/src/app/coach/page.tsx @@ -6,6 +6,7 @@ import { Minimize2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { CoachConversation } from "@/components/insights/coach-panel/coach-conversation"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { useTranslations } from "@/lib/i18n/context"; import { useCoachLaunch } from "@/lib/insights/coach-launch-context"; import { useFeatureFlags } from "@/hooks/use-feature-flags"; @@ -61,6 +62,7 @@ export default function CoachPage() { return (
{title} )} leadingHeaderActions={ - + <> + + + } />
diff --git a/src/app/labs/page.tsx b/src/app/labs/page.tsx index 8ef9835e3..98196c102 100644 --- a/src/app/labs/page.tsx +++ b/src/app/labs/page.tsx @@ -22,6 +22,7 @@ import { useMounted } from "@/hooks/use-mounted"; import { usePullToRefresh } from "@/hooks/use-pull-to-refresh"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; export default function LabsPage() { const { isAuthenticated, isLoading } = useAuth(); @@ -63,7 +64,10 @@ export default function LabsPage() {
-

+

{t("labs.title")}

@@ -71,6 +75,7 @@ export default function LabsPage() {

+ +
+ + +
-

+

{t("medications.title")}

{/* v1.4.34 IW-G — subtitle stays visible on mobile so the @@ -338,6 +342,7 @@ export default function MedicationsPage() {

+ {/* v1.16.11 (#316) — "Alle fälligen einnehmen". Contextual, not a permanent fixture: it renders only while at least TWO medications are currently due (a single due dose is the diff --git a/src/app/mood/page.tsx b/src/app/mood/page.tsx index 8d959076e..9a8485974 100644 --- a/src/app/mood/page.tsx +++ b/src/app/mood/page.tsx @@ -15,6 +15,7 @@ import { Plus, Loader2, Wrench } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; export default function MoodPage() { const { isAuthenticated, isLoading } = useAuth(); @@ -65,7 +66,10 @@ export default function MoodPage() {
-

+

{t("mood.title")}

{/* v1.4.34 IW-G — subtitle stays visible on mobile so the @@ -75,6 +79,7 @@ export default function MoodPage() {

+ {/* v1.17 — the wrench is the one customize entry point: it links to /settings/mood, which owns the tag groups, custom tags, visibility, and picker order. Same glyph, slot (left diff --git a/src/components/cycle/cycle-view.tsx b/src/components/cycle/cycle-view.tsx index 35e831be1..381316336 100644 --- a/src/components/cycle/cycle-view.tsx +++ b/src/components/cycle/cycle-view.tsx @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { cn } from "@/lib/utils"; import { CycleRing } from "./cycle-ring"; import { BbtChart } from "./bbt-chart"; @@ -139,20 +140,26 @@ export function CycleView() {
-

+

{t("cycle.title")}

{t("cycle.subtitle")}

- +
+ + +
{/* Desktop: ring (left) + tabs (right). Single column below lg. */} diff --git a/src/components/dashboard/dashboard-header.tsx b/src/components/dashboard/dashboard-header.tsx index 79aa15331..54ce87021 100644 --- a/src/components/dashboard/dashboard-header.tsx +++ b/src/components/dashboard/dashboard-header.tsx @@ -23,6 +23,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { useAuth } from "@/hooks/use-auth"; import { useMounted } from "@/hooks/use-mounted"; import { getHourForTimeZone } from "@/components/dashboard/range-display"; @@ -96,6 +97,8 @@ export function DashboardHeader({ ) : null}
+ {/* v1.18.6 — re-enter the guided module tour from the dashboard. */} + {/* Customize shortcut to the dashboard-customization settings (the tile/layout editor at /settings/dashboard). Sits to the left of the add button as a monochrome ghost icon, with diff --git a/src/components/illness/illness-view.tsx b/src/components/illness/illness-view.tsx index 88d533285..49ffe5b9c 100644 --- a/src/components/illness/illness-view.tsx +++ b/src/components/illness/illness-view.tsx @@ -32,6 +32,7 @@ import { EmptyState } from "@/components/ui/empty-state"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { useTranslations, useFormatters } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { LogDaySheet } from "./log-day-sheet"; import { NewEpisodeSheet } from "./new-episode-sheet"; @@ -256,20 +257,26 @@ export function IllnessView() {
-

+

{t("illness.title")}

{t("illness.subtitle")}

- +
+ + +

{t("illness.disclaimer")}

diff --git a/src/components/insights/insights-tab-strip.tsx b/src/components/insights/insights-tab-strip.tsx index 3897468e7..67c74ab16 100644 --- a/src/components/insights/insights-tab-strip.tsx +++ b/src/components/insights/insights-tab-strip.tsx @@ -19,6 +19,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; import { cn } from "@/lib/utils"; import { prefersReducedMotion } from "@/lib/charts/reduced-motion"; import { @@ -656,6 +657,7 @@ function InsightsTabStripImpl({ return (
)} - {/* v1.4.47 W5 — Replay tour card. The dashboard spotlight - tour now auto-launches only ≥ 24 h after the wizard finishes, - so a first-day user who wants to see it still needs a - discoverable manual trigger. About is the surface every user - can find (it's the standard "where am I?" stop in the - settings shell), so the button lives here next to version + - links. Mirrors the stack-on-mobile / right-align-on-desktop - contract used by the Account section's "Restart onboarding - tour" card so the two surfaces feel consistent. - - v1.18.1 E5 — hidden on the admin → Über surface (`hideTourReplay`): - the onboarding tour is a user-facing affordance with no place in - the admin console. */} - {!hideTourReplay && ( -
-
-
- -
-

- {t("settings.about.tourReplay")} -

-

- {t("settings.about.tourReplayHint")} -

-
-
- -
- {tourFeedback && ( -

- {tourFeedback.text} -

- )} -
- )} + {/* v1.18.6 — the tour-replay card consolidated onto a single home in + Settings → Advanced ("Modul-Tour neu starten"). The About surface + no longer duplicates it; per-module "Diese Tour zeigen" affordances + on every module page cover in-context re-entry. */} {/* v1.4.36 W4f — the dedicated "Updates" card with the manual "Check for updates" button is gone. The 24 h auto- diff --git a/src/components/settings/gesundheitsakte-section.tsx b/src/components/settings/gesundheitsakte-section.tsx index dfa9d047d..7bf511e54 100644 --- a/src/components/settings/gesundheitsakte-section.tsx +++ b/src/components/settings/gesundheitsakte-section.tsx @@ -19,6 +19,7 @@ import { HealthRecordExportPanel } from "@/components/settings/health-record-export-panel"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; export function GesundheitsakteSection() { const { t } = useTranslations(); @@ -28,13 +29,18 @@ export function GesundheitsakteSection() { className="space-y-6" > {/* v1.18.1 (D0) — section blurb dropped for consistent top alignment. */} -
+

{t("settings.sections.gesundheitsakte.title")}

+ {/* v1.18.6 — guided-tour re-entry for the doctor-report module + a + real spotlight anchor (the section h1 is sr-only). */} + + +
diff --git a/src/components/settings/integrations-section.tsx b/src/components/settings/integrations-section.tsx index 6ed4792c8..2bd9fc16c 100644 --- a/src/components/settings/integrations-section.tsx +++ b/src/components/settings/integrations-section.tsx @@ -17,6 +17,7 @@ import { ConnectionsPanel } from "@/components/settings/integrations/connections-panel"; import { useTranslations } from "@/lib/i18n/context"; +import { ModuleTourTrigger } from "@/components/onboarding/module-tour-trigger"; export { parseOAuthOutcome, @@ -32,10 +33,16 @@ export function IntegrationsSection() { className="space-y-6" > {/* v1.18.1 (D0) — section blurb dropped for consistent top alignment. */} -
+

{t("settings.sections.integrations.title")}

+ {/* v1.18.6 — guided-tour re-entry for the integrations module. The + visible affordance also gives the spotlight a real anchor (the + section h1 is sr-only). */} + + +
diff --git a/src/lib/onboarding/tour-state.ts b/src/lib/onboarding/tour-state.ts index adff4cd9f..6106f2b67 100644 --- a/src/lib/onboarding/tour-state.ts +++ b/src/lib/onboarding/tour-state.ts @@ -189,7 +189,7 @@ const ALL_STOPS: readonly TourStop[] = [ id: "export", targetId: "export-hero", placement: "bottom", - route: "/settings/advanced", + route: "/settings/gesundheitsakte", titleKey: "onboarding.tour.steps.export.title", bodyKey: "onboarding.tour.steps.export.body", },