From 65faf1d156987f025f1b85b00287c0ea87029d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 9 May 2026 21:53:05 +0200 Subject: [PATCH] feat(insights,dashboard): empty states for first-run views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last two surfaces from the C5 audit. - /insights — Top-level "no data" path replaces a bare "No data available" sentence with EmptyState (TrendingUp icon) + "Add a measurement" CTA into /measurements. The BMI section's height-not-set fallback gets a compact plain-variant EmptyState (Ruler icon) + "Open account settings" CTA instead of a single grey line that looked like a bug. - / (dashboard) — When trendCards.length === 0 AND charts.length === 0 (a brand-new account with no data and the Getting-started checklist already dismissed) the dashboard rendered an empty 0-px tile strip below the welcome banner. Now paints an EmptyState with the same quick-entry dialog wired to the CTA so the user can log their first measurement without leaving the page. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/insights/page.tsx | 38 ++++++++++++++++++++++++++++++++++---- src/app/page.tsx | 30 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 3030eb979..584b87e6f 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -7,7 +7,10 @@ import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { EmptyState } from "@/components/ui/empty-state"; import { Progress } from "@/components/ui/progress"; +import Link from "next/link"; import dynamic from "next/dynamic"; const HealthChart = dynamic( @@ -809,10 +812,20 @@ export default function InsightsPage() { } if (!data) { + // v1.4.15 phase-C5: replace bare "no data" text with EmptyState + + // CTA into /measurements so the user has a single-click path out + // of the empty insights view. return ( -
- {t("common.noData")} -
+ } + title={t("insights.emptyTitle")} + description={t("insights.emptyDescription")} + action={ + + } + /> ); } @@ -1491,7 +1504,24 @@ export default function InsightsPage() { valueBands={bmiBands} /> ) : ( -

{t("common.noData")}

+ // v1.4.15 phase-C5: explicit empty state when the user + // hasn't set a height yet — without it the BMI card looked + // broken. Plain variant so the dashed border doesn't double + // up inside the section card. + } + title={t("insights.bmiEmptyTitle")} + description={t("insights.bmiEmptyDescription")} + action={ + + } + /> )} a.order - b.order); + // v1.4.15 phase-C5: dashboard fully-empty state. When no tile + // and no chart has data the dashboard would otherwise paint a + // 0-px tile strip with the welcome banner above it — visually + // looked like a half-broken page. Render an EmptyState that + // re-uses the existing quick-entry dialog so the user has a + // single click into "Log measurement" without leaving the page. + // The GettingStartedChecklist above renders its own self-gated + // surface for very-new accounts; this empty state covers the + // case where the checklist has been dismissed but no data was + // logged afterwards. + if (trendCards.length === 0 && charts.length === 0) { + return ( + } + title={t("dashboard.emptyTitle")} + description={t("dashboard.emptyDescription")} + action={ + + } + /> + ); + } + return ( <> {/* v1.4: dashboard tiles are *always* a single row.