From b108ae7318ae36b851672a7570ef1240c0e7764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 10 Jun 2026 15:40:37 +0200 Subject: [PATCH] feat(ux): close the feedback gaps a new user runs into first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Withings OAuth callback finally reports its outcome — connected or a readable reason — instead of dropping the user on the settings page without a word. The adherence number carries one name everywhere and an explanation right where it is shown. Demo mode announces itself with a banner instead of failing silently in English. Bug reports become reachable from the mobile menu, the about section returns to the settings navigation, charts link to the target settings they draw, integrations and source priority point at each other, and the medication wizard warns when reminders are switched on without a configured channel. The longest explainer texts shrink to two sentences. --- src/app/insights/blood-pressure/page.tsx | 1 + src/app/insights/weight/page.tsx | 1 + src/app/layout.tsx | 8 +- src/app/page.tsx | 2 + src/components/charts/health-chart.tsx | 26 +++++- .../charts/medication-compliance-chart.tsx | 4 + .../doctor-report-section-toggles.test.tsx | 4 +- .../__tests__/correlation-card.test.tsx | 6 +- .../health-score-delta-explainer.test.tsx | 4 +- src/components/layout/auth-shell.tsx | 20 ++++- src/components/layout/bottom-nav.tsx | 24 +++++- src/components/layout/demo-banner.tsx | 38 +++++++++ src/components/layout/sidebar-nav.tsx | 17 +++- .../medications/__tests__/card-parts.test.tsx | 16 ++-- .../dose-history-ledger-compute.test.ts | 28 ++++++ .../card-parts/compliance-info-tip.tsx | 63 ++++++++++++++ .../card-parts/medication-compliance-bars.tsx | 9 +- .../dose-history-ledger-compute.ts | 23 +++++ .../medications/wizard/steps/Step8Summary.tsx | 40 +++++++++ .../__tests__/settings-shell.test.tsx | 24 ++---- .../settings/integrations-section.tsx | 85 ++++++++++++++++++- .../settings/notifications-section.tsx | 5 ++ src/components/settings/settings-shell.tsx | 20 +++-- src/components/settings/sources-section.tsx | 13 +++ .../__tests__/i18n-locale-integrity.test.ts | 2 +- 25 files changed, 433 insertions(+), 50 deletions(-) create mode 100644 src/components/layout/demo-banner.tsx create mode 100644 src/components/medications/card-parts/compliance-info-tip.tsx diff --git a/src/app/insights/blood-pressure/page.tsx b/src/app/insights/blood-pressure/page.tsx index 7cb330f84..f5f6d3b9a 100644 --- a/src/app/insights/blood-pressure/page.tsx +++ b/src/app/insights/blood-pressure/page.tsx @@ -160,6 +160,7 @@ export default function InsightsBlutdruckPage() { unit="mmHg" yAxisUnit="mmHg" targetZones={bpTargetZones} + targetSettingsHref="/settings/thresholds" compareBaseline={compareBaseline} userTimezone={user?.timezone} onVisibleStats={onVisibleStats} diff --git a/src/app/insights/weight/page.tsx b/src/app/insights/weight/page.tsx index b31c56ede..c1b946e72 100644 --- a/src/app/insights/weight/page.tsx +++ b/src/app/insights/weight/page.tsx @@ -106,6 +106,7 @@ export default function InsightsGewichtPage() { colors={["#bd93f9"]} unit="kg" valueBands={weightBands} + targetSettingsHref="/settings/thresholds" compareBaseline={compareBaseline} userTimezone={user?.timezone} onVisibleStats={onVisibleStats} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 32757cd95..caae60d0c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -120,7 +120,13 @@ export default async function RootLayout({ - {children} + {/* `DEMO_MODE` is a server-only env var; the proxy uses it to + block mutations. Resolve it here (server component) and + thread the boolean into the client shell so the demo + banner can render without a client-side detection path. */} + + {children} + diff --git a/src/app/page.tsx b/src/app/page.tsx index 0b940eb31..682319fb9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1424,6 +1424,7 @@ export default function DashboardPage() { colors={["#bd93f9"]} unit="kg" valueBands={weightBands} + targetSettingsHref="/settings/thresholds" compareBaseline={compareBaseline} userTimezone={user?.timezone} /> @@ -1470,6 +1471,7 @@ export default function DashboardPage() { unit="mmHg" yAxisUnit="Hg" targetZones={bpTargetZones} + targetSettingsHref="/settings/thresholds" compareBaseline={compareBaseline} userTimezone={user?.timezone} /> diff --git a/src/components/charts/health-chart.tsx b/src/components/charts/health-chart.tsx index d7462e195..ea5243b18 100644 --- a/src/components/charts/health-chart.tsx +++ b/src/components/charts/health-chart.tsx @@ -17,7 +17,8 @@ import { ReferenceArea, ReferenceDot, } from "recharts"; -import { Loader2 } from "lucide-react"; +import { Loader2, SlidersHorizontal } from "lucide-react"; +import Link from "next/link"; import { useState, useMemo, @@ -106,6 +107,15 @@ interface HealthChartProps { textColor?: string; lineOpacity?: number; }>; + /** + * Optional href for a small "adjust targets" link in the chart + * header. Callers whose charts paint a target band / zones (weight, + * blood pressure) pass `/settings/thresholds` so the band's origin — + * and the page that edits personal target ranges — is one click + * away. Omitted (every other mount) renders no link. Ignored in + * `mini` mode, which has no header. + */ + targetSettingsHref?: string; /** * v1.4.16 phase B5c — compact chart mode used by the Oura-style * rationale card. Drops the range tabs, the moving-average / trend @@ -514,6 +524,7 @@ export function HealthChart({ showYAxisUnit = true, valueBands, targetZones, + targetSettingsHref, mini = false, windowOverride, compareBaseline = "none", @@ -1444,6 +1455,19 @@ export function HealthChart({ )} )} + {/* Discoverability link from the painted target band to the + page that edits personal target ranges. Only mounts when + a caller passes `targetSettingsHref` (weight + BP). */} + {targetSettingsHref ? ( + +