Extract shared BackLink and adopt on detail pages

The workout, values, score, and medication detail pages each hand-rolled
the same ghost Button + Link + ArrowLeft back-nav block. Fold it into a
single BackLink (href + label + optional data-slot) so the affordance
reads identically — same hit area, icon, and left-bleed alignment — on
every page and lives in one place. Each page passes its existing label
translation, so no new i18n keys are introduced and the markup stays
byte-identical.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 17:21:45 +02:00
1 parent cc34cbd03d
commit e2f37bbe40
6 files changed
+109 -55

No files matched your search

+6 -15
View File
@@ -1,11 +1,9 @@
"use client";
import Link from "next/link";
import { useParams } from "next/navigation";
import { ArrowLeft } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { Button } from "@/components/ui/button";
import { BackLink } from "@/components/ui/back-link";
import { SubPageShell } from "@/components/insights/sub-page-shell";
import {
CompositeScoreAnatomy,
@@ -88,18 +86,11 @@ export default function CompositeScorePage() {
description={t(header.description)}
explainerMetric={EXPLAINER_METRIC[metric]}
backLink={
<Button
asChild
variant="ghost"
size="sm"
data-slot="composite-score-back"
className="-ml-2 w-fit"
>
<Link href="/insights">
<ArrowLeft className="size-4" aria-hidden="true" />
{t("insights.subPage.scoresBack")}
</Link>
</Button>
<BackLink
href="/insights"
label={t("insights.subPage.scoresBack")}
dataSlot="composite-score-back"
/>
}
coachLaunch
>
+9 -16
View File
@@ -1,9 +1,7 @@
"use client";
import { use } from "react";
import Link from "next/link";
import { notFound, useSearchParams } from "next/navigation";
import { ArrowLeft } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import {
@@ -12,7 +10,7 @@ import {
} from "@/lib/insights/values-back-link";
import { measurementTypeEnum } from "@/lib/validations/measurement";
import { MEASUREMENT_TYPE_LABEL_KEYS } from "@/components/measurements/measurement-list-meta";
import { Button } from "@/components/ui/button";
import { BackLink } from "@/components/ui/back-link";
import { MeasurementList } from "@/components/measurements/measurement-list";
import { SubPageShell } from "@/components/insights/sub-page-shell";
@@ -63,22 +61,17 @@ export default function InsightsMetricValuesPage({
title={t("insights.subPage.valuesPageTitle", { metric: metricLabel })}
description={t("insights.subPage.valuesPageDescription")}
backLink={
<Button
asChild
variant="ghost"
size="sm"
data-slot="metric-values-back"
className="-ml-2 w-fit"
>
<Link href={backHref}>
<ArrowLeft className="size-4" aria-hidden="true" />
{backToOrigin
<BackLink
href={backHref}
label={
backToOrigin
? t("insights.subPage.valuesBackToMetric", {
metric: metricLabel,
})
: t("insights.subPage.valuesBack")}
</Link>
</Button>
: t("insights.subPage.valuesBack")
}
dataSlot="metric-values-back"
/>
}
>
<MeasurementList lockedType={type} />
+6 -15
View File
@@ -1,12 +1,10 @@
"use client";
import { use } from "react";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { useWorkoutDetail } from "@/hooks/use-workouts";
import { Button } from "@/components/ui/button";
import { BackLink } from "@/components/ui/back-link";
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
import { SubPageShell } from "@/components/insights/sub-page-shell";
import { Skeleton } from "@/components/ui/skeleton";
@@ -41,18 +39,11 @@ export default function InsightsWorkoutDetailPage({
title={t("insights.workouts.title")}
description={t("insights.workouts.description")}
backLink={
<Button
asChild
variant="ghost"
size="sm"
data-slot="workout-detail-back"
className="-ml-2 w-fit"
>
<Link href="/insights/workouts">
<ArrowLeft className="size-4" aria-hidden="true" />
{t("insights.workouts.detail.backToList")}
</Link>
</Button>
<BackLink
href="/insights/workouts"
label={t("insights.workouts.detail.backToList")}
dataSlot="workout-detail-back"
/>
}
>
{isLoading ? (
+3 -9
View File
@@ -13,16 +13,15 @@
*/
import { use, useEffect } from "react";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { ArrowLeft, Loader2 } from "lucide-react";
import { Loader2 } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { Button } from "@/components/ui/button";
import { BackLink } from "@/components/ui/back-link";
import { Card } from "@/components/ui/card";
import {
MedicationDetailTabs,
@@ -87,12 +86,7 @@ export default function MedicationDetailPage({
if (isError || !medication) {
return (
<div className="space-y-6">
<Button variant="ghost" size="sm" className="-ml-2 w-fit" asChild>
<Link href="/medications">
<ArrowLeft className="size-4" aria-hidden="true" />
{t("medications.back")}
</Link>
</Button>
<BackLink href="/medications" label={t("medications.back")} />
<Card
className="p-6"
role="alert"