From e52b64bf34f27af34047684d9b40eb1cd42a63c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 19:09:34 +0200 Subject: [PATCH] fix(insights): point the empty-state CTAs at the existing measurements route The B4 insights empty-state CTAs (blutdruck, gewicht, puls, bmi) linked to `/measurements/new`, a route that does not exist; a tap landed on a Next.js 404. Re-route via `/measurements?add=`, the parameter the measurements page now consumes. Each CTA picks a sensible default: BLOOD_PRESSURE for the BP page, WEIGHT for the weight and BMI pages, PULSE for the pulse page. The mood, sleep, and medication empty-states already pointed at their own dedicated routes and need no change. Add a `?add=` consumer to the measurements page. The consumer opens the add-measurement dialog with the right `defaultType` on first render, then `router.replace` strips the param so the back button returns to a clean `/measurements`. The allow-list mirrors MEASUREMENT_TYPES inside `measurement-form.tsx` so a stale link cannot trap the user on an unknown form. The render-driven set-state pattern follows account-section.tsx and satisfies the `react-hooks/set-state-in-effect` rule. --- src/app/insights/blutdruck/page.tsx | 11 +++-- src/app/insights/bmi/page.tsx | 6 ++- src/app/insights/gewicht/page.tsx | 2 +- src/app/insights/puls/page.tsx | 8 ++-- src/app/measurements/page.tsx | 65 +++++++++++++++++++++++++---- 5 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/app/insights/blutdruck/page.tsx b/src/app/insights/blutdruck/page.tsx index 046bf3171..e23ecb01a 100644 --- a/src/app/insights/blutdruck/page.tsx +++ b/src/app/insights/blutdruck/page.tsx @@ -27,9 +27,12 @@ import { hasMetricData } from "@/lib/insights/metric-availability"; * assessment, and the chart's target zones tied to the user's age. * * v1.4.27 F17 — when the user has zero BP observations, the page - * short-circuits to an empty-state CTA pointing at `/measurements/new`. - * Apple Health convention: empty surfaces lead to onboarding hints - * rather than chart skeletons. + * short-circuits to an empty-state CTA pointing at + * `/measurements?add=BLOOD_PRESSURE`. Apple Health convention: empty + * surfaces lead to onboarding hints rather than chart skeletons. + * v1.4.27 MB6 — the previous `/measurements/new` href hit a 404; the + * measurements page now consumes `?add=` and auto-opens the + * dialog with the matching default type. */ interface AnalyticsData { summaries: Record; @@ -79,7 +82,7 @@ export default function InsightsBlutdruckPage() { description={t("insights.emptyState.bloodPressure.description")} action={ diff --git a/src/app/insights/bmi/page.tsx b/src/app/insights/bmi/page.tsx index 314a5ae1c..17301fbf6 100644 --- a/src/app/insights/bmi/page.tsx +++ b/src/app/insights/bmi/page.tsx @@ -68,7 +68,9 @@ export default function InsightsBmiPage() { // v1.4.27 F17 — BMI is derived from WEIGHT. When no weight readings // exist yet, the existing "set your height" branch can never compute // anything useful either; surface the empty-state CTA pointing at - // `/measurements/new` instead so the user logs the weight first. + // `/measurements?add=WEIGHT` instead so the user logs the weight + // first. v1.4.27 MB6 — query-param replaces the dead + // `/measurements/new` route. if ( isAuthenticated && analytics && @@ -86,7 +88,7 @@ export default function InsightsBmiPage() { description={t("insights.emptyState.bmi.description")} action={ diff --git a/src/app/insights/gewicht/page.tsx b/src/app/insights/gewicht/page.tsx index 726c7ebbc..2f547ba36 100644 --- a/src/app/insights/gewicht/page.tsx +++ b/src/app/insights/gewicht/page.tsx @@ -76,7 +76,7 @@ export default function InsightsGewichtPage() { description={t("insights.emptyState.weight.description")} action={ diff --git a/src/app/insights/puls/page.tsx b/src/app/insights/puls/page.tsx index d8e5f76b4..3c386dd59 100644 --- a/src/app/insights/puls/page.tsx +++ b/src/app/insights/puls/page.tsx @@ -71,8 +71,10 @@ export default function InsightsPulsPage() { // v1.4.27 F17 — gate the sub-page on at least one pulse observation. // Brand-new accounts (no manual logs, no Apple-Health upload yet) - // see a one-line empty-state with a CTA into `/measurements/new` - // rather than a chart skeleton over a blank target band. + // see a one-line empty-state with a CTA into + // `/measurements?add=PULSE` rather than a chart skeleton over a + // blank target band. v1.4.27 MB6 — query-param replaces the dead + // `/measurements/new` route. if ( isAuthenticated && analytics && @@ -90,7 +92,7 @@ export default function InsightsPulsPage() { description={t("insights.emptyState.pulse.description")} action={ diff --git a/src/app/measurements/page.tsx b/src/app/measurements/page.tsx index 5150141e2..10fcd7fdd 100644 --- a/src/app/measurements/page.tsx +++ b/src/app/measurements/page.tsx @@ -1,22 +1,63 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useAuth } from "@/hooks/use-auth"; import { MeasurementForm } from "@/components/measurements/measurement-form"; import { MeasurementList } from "@/components/measurements/measurement-list"; import { Button } from "@/components/ui/button"; import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Plus, Loader2 } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "@/lib/i18n/context"; +// v1.4.27 MB6 — the insights empty-state CTAs link here with a +// `?add=` query param to auto-open the add-measurement dialog +// with the right default type. Keep the allow-list aligned with +// MEASUREMENT_TYPES inside `measurement-form.tsx`; an unknown value +// is dropped silently so a stale link can never trap the user on a +// broken form. +const ALLOWED_ADD_TYPES = new Set([ + "BLOOD_PRESSURE", + "WEIGHT", + "PULSE", + "BMI", + "GLUCOSE", + "TEMPERATURE", + "BODY_FAT", + "HEART_RATE", +]); + export default function MeasurementsPage() { const { isAuthenticated, isLoading } = useAuth(); const router = useRouter(); - const [dialogOpen, setDialogOpen] = useState(false); + const searchParams = useSearchParams(); const { t } = useTranslations(); + // v1.4.27 MB6 — read the `?add=` query param during render so + // the dialog opens on the first paint (no flash of the page behind + // it). The state-from-prop pattern follows `account-section.tsx`: + // store the "addToken we acted on" sentinel and let render kick off + // the open + replace transition once per param value. The lint rule + // `react-hooks/set-state-in-effect` rejects setState from inside an + // effect, so the open-on-param work is render-driven instead. + const addParam = searchParams.get("add"); + const [dialogOpen, setDialogOpen] = useState(false); + const [defaultType, setDefaultType] = useState( + undefined, + ); + const [consumedAddParam, setConsumedAddParam] = useState(null); + + if (addParam && addParam !== consumedAddParam) { + setConsumedAddParam(addParam); + if (ALLOWED_ADD_TYPES.has(addParam)) { + setDefaultType(addParam); + setDialogOpen(true); + } + // Drop the query string so the back-button leaves the user on + // `/measurements` rather than re-opening the dialog. + router.replace("/measurements"); + } + useEffect(() => { if (!isLoading && !isAuthenticated) { router.push("/auth/login"); @@ -50,12 +91,22 @@ export default function MeasurementsPage() { { + setDialogOpen(open); + if (!open) setDefaultType(undefined); + }} title={t("measurements.addMeasurement")} > setDialogOpen(false)} - onCancel={() => setDialogOpen(false)} + defaultType={defaultType} + onSuccess={() => { + setDialogOpen(false); + setDefaultType(undefined); + }} + onCancel={() => { + setDialogOpen(false); + setDefaultType(undefined); + }} />