mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(insights): dedupe coach disclaimer and add wellness-strip error affordance
Suppress the generic Insights disclaimer footer on the Coach route, which already renders its own composer disclaimer — the layout shell now skips the footer when the selected segment is the coach page and keeps it everywhere else. Thread the shared derived-batch error state and refetch into the wellness strip so a failed batch surfaces a compact error + Retry in place (the strip is top-of-page) instead of vanishing silently; the one Retry recovers both the wellness and vitals strips. Adds a scores load-error string across all locales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
aac70ec765
commit
22ba4193b2
9 files changed
+82
-9
No files matched your search
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Wellness-Werte",
|
||||
"loadError": "Deine Wellness-Werte konnten gerade nicht geladen werden.",
|
||||
"recovery": "Erholung",
|
||||
"stress": "Stress",
|
||||
"strain": "Belastung"
|
||||
|
||||
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Wellness scores",
|
||||
"loadError": "Could not load your wellness scores right now.",
|
||||
"recovery": "Recovery",
|
||||
"stress": "Stress",
|
||||
"strain": "Strain"
|
||||
|
||||
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Puntuaciones de bienestar",
|
||||
"loadError": "No se pudieron cargar tus puntuaciones de bienestar en este momento.",
|
||||
"recovery": "Recuperación",
|
||||
"stress": "Estrés",
|
||||
"strain": "Esfuerzo"
|
||||
|
||||
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Scores de bien-être",
|
||||
"loadError": "Impossible de charger vos scores de bien-être pour le moment.",
|
||||
"recovery": "Récupération",
|
||||
"stress": "Stress",
|
||||
"strain": "Effort"
|
||||
|
||||
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Punteggi di benessere",
|
||||
"loadError": "Al momento non è stato possibile caricare i tuoi punteggi di benessere.",
|
||||
"recovery": "Recupero",
|
||||
"stress": "Stress",
|
||||
"strain": "Sforzo"
|
||||
|
||||
@@ -2772,6 +2772,7 @@
|
||||
},
|
||||
"scores": {
|
||||
"sectionTitle": "Wyniki dobrostanu",
|
||||
"loadError": "Nie udało się teraz wczytać Twoich wyników dobrostanu.",
|
||||
"recovery": "Regeneracja",
|
||||
"stress": "Stres",
|
||||
"strain": "Obciążenie"
|
||||
|
||||
@@ -326,6 +326,8 @@ export default function InsightsPage() {
|
||||
<WellnessScores
|
||||
read={dashboardDerived.read}
|
||||
isLoading={dashboardDerived.isLoading}
|
||||
isError={dashboardDerived.isError}
|
||||
refetch={dashboardDerived.refetch}
|
||||
/>
|
||||
|
||||
{flags.briefing && (
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import type { ComponentType } from "react";
|
||||
import Link from "next/link";
|
||||
import { Activity, Flame, Gauge, HeartPulse, Moon } from "lucide-react";
|
||||
import { Activity, Flame, Gauge, HeartPulse, Moon, RefreshCw } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { ScoreRing } from "./score-ring";
|
||||
@@ -36,6 +37,15 @@ interface ScoreStripProps {
|
||||
/** The batched-read selector from the parent dashboard's one query. */
|
||||
read: DerivedBatchRead;
|
||||
isLoading: boolean;
|
||||
/**
|
||||
* The shared derived-batch error flag. On a failed batch the strip renders
|
||||
* a compact error + Retry in place instead of vanishing silently (the strip
|
||||
* is now top-of-page, so its only Retry must live here, not far below in the
|
||||
* Vitals slot).
|
||||
*/
|
||||
isError?: boolean;
|
||||
/** Refetch the one shared batch query. Recovers both strips at once. */
|
||||
refetch?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -86,9 +96,51 @@ function RingTile({
|
||||
);
|
||||
}
|
||||
|
||||
export function WellnessScores({ read, isLoading, className }: ScoreStripProps) {
|
||||
export function WellnessScores({
|
||||
read,
|
||||
isLoading,
|
||||
isError = false,
|
||||
refetch,
|
||||
className,
|
||||
}: ScoreStripProps) {
|
||||
const { t } = useTranslations();
|
||||
|
||||
// A failed shared batch must read as an error, not as "no scores" — the
|
||||
// strip is top-of-page, so the only Retry lives here (mirrors the Vitals
|
||||
// error card styling; one Retry recovers both strips).
|
||||
if (isError) {
|
||||
return (
|
||||
<section
|
||||
data-slot="wellness-scores"
|
||||
aria-label={t("insights.derived.scores.sectionTitle")}
|
||||
className={cn("space-y-3", className)}
|
||||
>
|
||||
<TileHeader
|
||||
icon={Activity}
|
||||
title={t("insights.derived.scores.sectionTitle")}
|
||||
/>
|
||||
<div
|
||||
data-slot="wellness-scores-error"
|
||||
role="alert"
|
||||
className="bg-card border-border text-muted-foreground flex flex-col items-start gap-3 rounded-xl border p-4 text-sm sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<span>{t("insights.derived.scores.loadError")}</span>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => refetch?.()}
|
||||
data-slot="wellness-scores-retry"
|
||||
className="gap-1.5"
|
||||
>
|
||||
<RefreshCw className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>{t("common.retry")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const readiness = read<ReadinessValue>({ metric: "READINESS" });
|
||||
const sleep = read<SleepScoreValue>({ metric: "SLEEP_SCORE" });
|
||||
const recovery = read<WellnessScoreValue>({ metric: "RECOVERY_SCORE" });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -57,6 +58,15 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) {
|
||||
const flags = useFeatureFlags();
|
||||
const advisorEnabled =
|
||||
isAuthenticated && flags.enabled && flags.briefing;
|
||||
|
||||
// v1.12.6 — routes that carry their own purpose-built disclaimer must NOT
|
||||
// also show the generic footer below (otherwise two disclaimers stack).
|
||||
// The Coach full page renders its own `composerDisclaimer` under the
|
||||
// composer, so its segment is excluded here. Every other segment — the
|
||||
// overview (null segment), the metric sub-pages, mood, medications, the
|
||||
// raw values table — keeps the single generic footer.
|
||||
const segment = useSelectedLayoutSegment();
|
||||
const showDisclaimerFooter = segment !== "coach";
|
||||
const advisor = useInsightsAdvisorQuery(advisorEnabled);
|
||||
|
||||
// Shared analytics fetch — the layout shell only reads
|
||||
@@ -118,13 +128,16 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) {
|
||||
caveats (health score, correlations, mood relations, derived cards,
|
||||
rhythm events) stay on their own cards — those convey real,
|
||||
non-generic information. Other waves remove the generic line from the
|
||||
overview / mood / medications pages; this footer is where it lives. */}
|
||||
<p
|
||||
data-slot="insights-disclaimer-footer"
|
||||
className="text-muted-foreground border-border/60 border-t pt-4 text-center text-xs leading-relaxed"
|
||||
>
|
||||
{t("insights.disclaimer.footer")}
|
||||
</p>
|
||||
overview / mood / medications pages; this footer is where it lives.
|
||||
Suppressed on the Coach route, which carries its own disclaimer. */}
|
||||
{showDisclaimerFooter ? (
|
||||
<p
|
||||
data-slot="insights-disclaimer-footer"
|
||||
className="text-muted-foreground border-border/60 border-t pt-4 text-center text-xs leading-relaxed"
|
||||
>
|
||||
{t("insights.disclaimer.footer")}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user