mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
fix(insights): move coach-ask to the header and de-emphasise the assessment border
The Coach launch sat at the foot of every category page; move it into the
sub-page header as an icon-only button at heading height, top-right, so
"ask the coach" reads as a header action and the page no longer ends on a
stray button. The shared shell mounts it (gated on the Coach flag plus the
per-user opt-out), so every category page picks it up and the foot
instance is dropped. Also drop the purple left accent on the assessment
("Einschätzung") card — the coloured rule made it read as restless against
the calmer surrounding cards.
This commit is contained in:
1 parent
4935a9b000
commit
6e61713fc6
12 files changed
+68
-53
No files matched your search
@@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
@@ -101,6 +100,7 @@ export default function InsightsBlutdruckPage() {
|
||||
title={t("insights.bloodPressureSectionTitle")}
|
||||
description={t("insights.subPage.blutdruckDescription")}
|
||||
explainerMetric="bloodPressure"
|
||||
coachLaunch
|
||||
>
|
||||
<HealthChartDynamic
|
||||
chartKey="bp"
|
||||
@@ -126,8 +126,6 @@ export default function InsightsBlutdruckPage() {
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -110,6 +110,7 @@ export default function InsightsBmiPage() {
|
||||
title={t("insights.bmiSectionTitle")}
|
||||
description={t("insights.subPage.bmiDescription")}
|
||||
explainerMetric="bmi"
|
||||
coachLaunch
|
||||
>
|
||||
<HealthChartDynamic
|
||||
chartKey="bmi"
|
||||
@@ -135,8 +136,6 @@ export default function InsightsBmiPage() {
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -187,6 +187,7 @@ export default function InsightsMedikamentePage() {
|
||||
title={t("insights.medicationCompliance")}
|
||||
description={t("insights.subPage.medikamenteDescription")}
|
||||
explainerMetric="medications"
|
||||
coachLaunch
|
||||
>
|
||||
<div
|
||||
className={
|
||||
@@ -290,8 +291,6 @@ export default function InsightsMedikamentePage() {
|
||||
without an active GLP-1 medication, so the page collapses
|
||||
back to the compliance grid for everyone else. */}
|
||||
<TherapyTimeline />
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChartSkeleton } from "@/components/charts/chart-skeleton";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
@@ -99,6 +98,7 @@ export default function InsightsStimmungPage() {
|
||||
title={t("insights.moodSectionTitle")}
|
||||
description={t("insights.subPage.stimmungDescription")}
|
||||
explainerMetric="mood"
|
||||
coachLaunch
|
||||
>
|
||||
<MoodChart
|
||||
chartKey="mood"
|
||||
@@ -120,8 +120,6 @@ export default function InsightsStimmungPage() {
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricStatStrip } from "@/components/insights/metric-stat-strip";
|
||||
@@ -129,6 +128,7 @@ export default function InsightsPulsPage() {
|
||||
timeZone={user?.timezone ?? undefined}
|
||||
/>
|
||||
}
|
||||
coachLaunch
|
||||
showAllValuesType="PULSE"
|
||||
>
|
||||
<HealthChartDynamic
|
||||
@@ -166,8 +166,6 @@ export default function InsightsPulsPage() {
|
||||
compareBaseline={compareBaseline}
|
||||
userTimezone={user?.timezone}
|
||||
/>
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { Moon } from "lucide-react";
|
||||
import { useInsightsAnalytics } from "@/hooks/use-insights-analytics";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
import { SleepOverview } from "@/components/insights/sleep-overview";
|
||||
@@ -62,6 +61,7 @@ export default function InsightsSchlafPage() {
|
||||
title={t("insights.sleep.title")}
|
||||
description={t("insights.sleep.description")}
|
||||
explainerMetric="sleep"
|
||||
coachLaunch
|
||||
>
|
||||
<SleepOverview />
|
||||
|
||||
@@ -78,8 +78,6 @@ export default function InsightsSchlafPage() {
|
||||
structural slot waits for v1.5 where the route + hook key will
|
||||
wire in.
|
||||
*/}
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricStatStrip } from "@/components/insights/metric-stat-strip";
|
||||
@@ -86,6 +85,7 @@ export default function InsightsGewichtPage() {
|
||||
timeZone={user?.timezone ?? undefined}
|
||||
/>
|
||||
}
|
||||
coachLaunch
|
||||
showAllValuesType="WEIGHT"
|
||||
>
|
||||
<HealthChartDynamic
|
||||
@@ -111,8 +111,6 @@ export default function InsightsGewichtPage() {
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { Activity } from "lucide-react";
|
||||
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useWorkouts } from "@/hooks/use-workouts";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { SubPageShell } from "@/components/insights/sub-page-shell";
|
||||
import { WorkoutList } from "@/components/insights/workout-list";
|
||||
@@ -36,6 +35,7 @@ export default function InsightsWorkoutsPage() {
|
||||
title={t("insights.workouts.title")}
|
||||
description={t("insights.workouts.description")}
|
||||
explainerMetric="workouts"
|
||||
coachLaunch
|
||||
>
|
||||
{isLoading ? (
|
||||
<div data-slot="workouts-loading" className="space-y-2">
|
||||
@@ -52,10 +52,7 @@ export default function InsightsWorkoutsPage() {
|
||||
coachPrefill="I haven't logged any workouts yet — why does tracking them matter, and what should I focus on first?"
|
||||
/>
|
||||
) : data ? (
|
||||
<>
|
||||
<WorkoutList workouts={data.workouts} />
|
||||
<CoachLaunchButton />
|
||||
</>
|
||||
<WorkoutList workouts={data.workouts} />
|
||||
) : null}
|
||||
</SubPageShell>
|
||||
);
|
||||
|
||||
@@ -33,12 +33,24 @@ export interface CoachLaunchButtonProps {
|
||||
prefill?: string;
|
||||
/** Optional className passthrough for inline overrides. */
|
||||
className?: string;
|
||||
/**
|
||||
* v1.8.6 — render the launch as an icon-only affordance instead of the
|
||||
* labelled pill. The sub-page shell mounts this variant at heading
|
||||
* height in the page header so "Coach fragen" sits top-right, aligned
|
||||
* with the title, rather than at the foot of the page. The accessible
|
||||
* label still resolves the shared CTA copy via `aria-label`. The icon
|
||||
* variant stays visible across breakpoints — it is the header's own
|
||||
* Coach entry, not the `lg+`-only inline pill the old foot placement
|
||||
* used (the mobile FAB covers `<lg` for that legacy surface).
|
||||
*/
|
||||
variant?: "inline" | "icon";
|
||||
}
|
||||
|
||||
export function CoachLaunchButton({
|
||||
label,
|
||||
prefill,
|
||||
className,
|
||||
variant = "inline",
|
||||
}: CoachLaunchButtonProps) {
|
||||
const { t } = useTranslations();
|
||||
const launch = useCoachLaunch();
|
||||
@@ -59,6 +71,26 @@ export function CoachLaunchButton({
|
||||
|
||||
const accessibleLabel = label ?? t("insights.heroActionAskCoach");
|
||||
|
||||
if (variant === "icon") {
|
||||
// v1.8.6 — heading-height icon button mounted by the sub-page shell.
|
||||
// Icon-only so it reads as a compact action beside the title; the
|
||||
// CTA copy moves to `aria-label` + a native tooltip.
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-slot="coach-launch-icon"
|
||||
aria-label={accessibleLabel}
|
||||
title={accessibleLabel}
|
||||
onClick={() => launch.askCoach(prefill ?? null)}
|
||||
className={cn("text-muted-foreground hover:text-foreground", className)}
|
||||
>
|
||||
<Sparkles className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
// Inline header-style action on `lg+`. Hidden below `lg` because the
|
||||
// layout-level FAB (`<LayoutCoachFab>`) covers that breakpoint —
|
||||
// mounting both at the same time would put two affordances in front
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
import type { InsightMetric } from "@/lib/insights/metric-availability";
|
||||
import type { ChartOverlayKey } from "@/lib/dashboard-layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
|
||||
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
|
||||
import { MetricStatStrip } from "@/components/insights/metric-stat-strip";
|
||||
@@ -33,8 +32,9 @@ import { SubPageShell } from "@/components/insights/sub-page-shell";
|
||||
* pass `null` to render an onboarding hint without a dead link.
|
||||
* 3. `<HealthChartDynamic>` mounted on the canonical
|
||||
* `ChartOverlayKey` so the chart-cog popover persists per metric.
|
||||
* 4. `<CoachLaunchButton>` at the foot — feature-flag gating runs
|
||||
* inside the button so this scaffold stays agnostic.
|
||||
* 4. A header-height Coach launch icon (`coachLaunch` on the shell) —
|
||||
* feature-flag gating runs inside the button so this scaffold stays
|
||||
* agnostic.
|
||||
*
|
||||
* Adding a new metric is a four-line page module that hands the right
|
||||
* Zod / MeasurementType + ChartOverlayKey + i18n-key prefix to this
|
||||
@@ -181,6 +181,7 @@ export function HealthKitMetricPage({
|
||||
timeZone={user?.timezone ?? undefined}
|
||||
/>
|
||||
}
|
||||
coachLaunch
|
||||
showAllValuesType={measurementType}
|
||||
>
|
||||
<HealthChartDynamic
|
||||
@@ -198,7 +199,6 @@ export function HealthKitMetricPage({
|
||||
{targetSummarySlug ? (
|
||||
<MetricTargetSummary slug={targetSummarySlug} />
|
||||
) : null}
|
||||
<CoachLaunchButton />
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -163,7 +163,12 @@ export function InsightStatusCard({
|
||||
// shell padding to `py-4 md:py-5` so the assessment block reads tight
|
||||
// in the denser sub-page rhythm. The same override lands on every
|
||||
// status variant below so the gap is consistent across states.
|
||||
className="animate-insight-in border-l-dracula-purple gap-2 border-l-2 py-4 md:gap-3 md:py-5"
|
||||
//
|
||||
// v1.8.6 — drop the purple left accent (`border-l-dracula-purple
|
||||
// border-l-2`). The coloured rule made the assessment card read as
|
||||
// restless against the calmer surrounding cards; the plain card
|
||||
// border carries enough separation on its own.
|
||||
className="animate-insight-in gap-2 py-4 md:gap-3 md:py-5"
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
Reference in new issue
Block a user