mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
refactor(settings): gather layout personalization under one home
Personalizing how the app is laid out was split across four unrelated settings sections — Dashboard, Insights, Medications, Mood — with no shared framing, so the same concept (arrange, reorder, show/hide) read as four disconnected places, plus the settings nav carried four separate arrange entries. Add one Layout & Personalization hub that presents the four editors as a consistent set of linked cards and replaces the four scattered nav entries with a single home. Each editor keeps its own route and mutation flow — deep links, the page-header cogs, and the hub's links all still resolve — and a Layout child route now highlights the hub in the nav.
This commit is contained in:
1 parent
9bedd6b4f8
commit
cf31211fbc
13 files changed
+361
-42
No files matched your search
@@ -3814,6 +3814,26 @@
|
||||
"title": "Benachrichtigungen",
|
||||
"description": "Live-Übersicht aller konfigurierten Kanäle."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Layout & Personalisierung",
|
||||
"description": "Lege fest, wie deine App aufgebaut ist — ein Ort für Dashboard, Insights, Medikamente und Stimmung.",
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"description": "Kachel-Layout und Reihenfolge."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Insights",
|
||||
"description": "Reihenfolge der Übersicht und der Navigations-Pills."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Medikamente",
|
||||
"description": "Listenansicht und Reihenfolge der Medikamente."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Stimmungs-Tags",
|
||||
"description": "Gruppen, Tags und Reihenfolge der Tag-Auswahl."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"description": "Kachel-Layout und Reihenfolge."
|
||||
|
||||
@@ -3814,6 +3814,26 @@
|
||||
"title": "Notifications",
|
||||
"description": "Live overview of every configured channel."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Layout & Personalization",
|
||||
"description": "Arrange how your app is laid out — one home for the dashboard, insights, medications, and mood.",
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"description": "Tile layout and order."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Insights",
|
||||
"description": "Overview sections and navigation-pill order."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Medications",
|
||||
"description": "List view and medication order."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Mood tags",
|
||||
"description": "Groups, tags, and order of the mood picker."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"description": "Tile layout and order."
|
||||
|
||||
@@ -3814,6 +3814,26 @@
|
||||
"title": "Notificaciones",
|
||||
"description": "Vista en vivo de cada canal configurado."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Diseño y personalización",
|
||||
"description": "Organiza cómo está dispuesta tu app — un único lugar para el panel, los análisis, los medicamentos y el ánimo.",
|
||||
"dashboard": {
|
||||
"title": "Panel",
|
||||
"description": "Diseño y orden de los mosaicos."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Análisis",
|
||||
"description": "Orden de la vista general y de las pestañas de navegación."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Medicamentos",
|
||||
"description": "Vista de lista y orden de los medicamentos."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Etiquetas de ánimo",
|
||||
"description": "Grupos, etiquetas y orden del selector de ánimo."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Panel",
|
||||
"description": "Diseño y orden de los mosaicos."
|
||||
|
||||
@@ -3814,6 +3814,26 @@
|
||||
"title": "Notifications",
|
||||
"description": "Aperçu en direct de chaque canal configuré."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Disposition et personnalisation",
|
||||
"description": "Organisez la disposition de votre application — un seul endroit pour le tableau de bord, les analyses, les médicaments et l’humeur.",
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord",
|
||||
"description": "Disposition et ordre des tuiles."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Analyses",
|
||||
"description": "Ordre de la vue d'ensemble et des onglets de navigation."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Médicaments",
|
||||
"description": "Vue de la liste et ordre des médicaments."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Étiquettes d’humeur",
|
||||
"description": "Groupes, étiquettes et ordre du sélecteur d’humeur."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord",
|
||||
"description": "Disposition et ordre des tuiles."
|
||||
|
||||
@@ -3814,6 +3814,26 @@
|
||||
"title": "Notifiche",
|
||||
"description": "Panoramica in tempo reale di ogni canale configurato."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Layout e personalizzazione",
|
||||
"description": "Organizza la disposizione della tua app — un unico posto per cruscotto, approfondimenti, farmaci e umore.",
|
||||
"dashboard": {
|
||||
"title": "Cruscotto",
|
||||
"description": "Layout e ordine dei riquadri."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Approfondimenti",
|
||||
"description": "Ordine della panoramica e delle schede di navigazione."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Farmaci",
|
||||
"description": "Vista elenco e ordine dei farmaci."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Tag dell’umore",
|
||||
"description": "Gruppi, tag e ordine del selettore dell’umore."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Cruscotto",
|
||||
"description": "Layout e ordine dei riquadri."
|
||||
|
||||
@@ -3814,6 +3814,26 @@
|
||||
"title": "Powiadomienia",
|
||||
"description": "Podgląd na żywo każdego skonfigurowanego kanału."
|
||||
},
|
||||
"layout": {
|
||||
"title": "Układ i personalizacja",
|
||||
"description": "Ustal, jak rozłożona jest Twoja aplikacja — jedno miejsce dla pulpitu, analiz, leków i nastroju.",
|
||||
"dashboard": {
|
||||
"title": "Pulpit",
|
||||
"description": "Układ i kolejność kafelków."
|
||||
},
|
||||
"insights": {
|
||||
"title": "Analizy",
|
||||
"description": "Kolejność przeglądu i pigułek nawigacyjnych."
|
||||
},
|
||||
"medications": {
|
||||
"title": "Leki",
|
||||
"description": "Widok listy i kolejność leków."
|
||||
},
|
||||
"mood": {
|
||||
"title": "Tagi nastroju",
|
||||
"description": "Grupy, tagi i kolejność w wyborze nastroju."
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Pulpit",
|
||||
"description": "Układ i kolejność kafelków."
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DashboardSection } from "@/components/settings/dashboard-section";
|
||||
import { ExportSection } from "@/components/settings/export-section";
|
||||
import { InsightsSection } from "@/components/settings/insights-section";
|
||||
import { IntegrationsSection } from "@/components/settings/integrations-section";
|
||||
import { LayoutSection } from "@/components/settings/layout-section";
|
||||
import { MedicationsSection } from "@/components/settings/medications-section";
|
||||
import { MoodSection } from "@/components/settings/mood-section";
|
||||
import { NotificationsSection } from "@/components/settings/notifications-section";
|
||||
@@ -51,6 +52,7 @@ const SECTION_COMPONENTS: Record<
|
||||
ai: AiSection,
|
||||
integrations: IntegrationsSection,
|
||||
notifications: NotificationsSection,
|
||||
layout: LayoutSection,
|
||||
dashboard: DashboardSection,
|
||||
insights: InsightsSection,
|
||||
medications: MedicationsSection,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* v1.17.1 (F-2) — the "Layout & Personalization" hub.
|
||||
*
|
||||
* The hub is the single front door for the four personalization editors.
|
||||
* It must link to each of them with consistent framing so the concept
|
||||
* reads as one home rather than four scattered settings sections.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { LayoutSection } from "../layout-section";
|
||||
|
||||
function render(locale: "en" | "de" = "en") {
|
||||
return renderToStaticMarkup(
|
||||
<I18nProvider initialLocale={locale}>
|
||||
<LayoutSection />
|
||||
</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("<LayoutSection>", () => {
|
||||
it("links to all four personalization editors", () => {
|
||||
const html = render();
|
||||
for (const href of [
|
||||
"/settings/dashboard",
|
||||
"/settings/insights",
|
||||
"/settings/medications",
|
||||
"/settings/mood",
|
||||
]) {
|
||||
expect(html).toContain(`href="${href}"`);
|
||||
}
|
||||
});
|
||||
|
||||
it("renders the hub heading and a card per editor", () => {
|
||||
const html = render();
|
||||
expect(html).toContain("Dashboard");
|
||||
expect(html).toContain("Insights");
|
||||
expect(html).toContain("Medications");
|
||||
expect(html).toContain("Mood tags");
|
||||
});
|
||||
|
||||
it("resolves its copy in German too", () => {
|
||||
const html = render("de");
|
||||
expect(html).toContain("Medikamente");
|
||||
expect(html).toContain("Stimmungs-Tags");
|
||||
});
|
||||
});
|
||||
@@ -56,10 +56,16 @@ describe("SETTINGS_SECTION_SLUGS", () => {
|
||||
// v1.17 — `mood` sits between `medications` and `thresholds`: the
|
||||
// mood-tag management surface (groups, custom tags, hide/archive,
|
||||
// picker order), reached from the /mood page header's wrench glyph.
|
||||
// v1.17.1 (F-2) — `layout` is the one "Layout & Personalization" home
|
||||
// and sits after `notifications`. The four personalization editor
|
||||
// slugs (`dashboard`, `insights`, `medications`, `mood`) keep their
|
||||
// routes so deep links resolve, but they are reached through the
|
||||
// Layout hub instead of four standalone nav entries.
|
||||
expect([...SETTINGS_SECTION_SLUGS]).toEqual([
|
||||
"account",
|
||||
"integrations",
|
||||
"notifications",
|
||||
"layout",
|
||||
"dashboard",
|
||||
"insights",
|
||||
"medications",
|
||||
@@ -140,6 +146,32 @@ describe("<SettingsShell>", () => {
|
||||
expect(html.match(activeLinkRegex)?.length ?? 0).toBe(2);
|
||||
});
|
||||
|
||||
it("highlights the Layout hub when on a Layout child editor (v1.17.1 F-2)", () => {
|
||||
// On `/settings/dashboard` (a Layout child reached through the hub) the
|
||||
// Layout nav entry must read active even though Dashboard has no nav
|
||||
// entry of its own. Both layouts emit the active link → two matches.
|
||||
for (const child of [
|
||||
"/settings/dashboard",
|
||||
"/settings/insights",
|
||||
"/settings/medications",
|
||||
"/settings/mood",
|
||||
]) {
|
||||
const html = renderShell({ pathname: child });
|
||||
const layoutActive =
|
||||
/<a\b[^>]*\baria-current="page"[^>]*\bhref="\/settings\/layout"|<a\b[^>]*\bhref="\/settings\/layout"[^>]*\baria-current="page"/g;
|
||||
expect(html.match(layoutActive)?.length ?? 0).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it("highlights the Layout hub when the active prop is a Layout child", () => {
|
||||
// The page passes `active={section}` explicitly; a child slug must
|
||||
// still resolve onto the Layout hub for nav highlighting.
|
||||
const html = renderShell({ active: "mood", pathname: "/settings/mood" });
|
||||
const layoutActive =
|
||||
/<a\b[^>]*\baria-current="page"[^>]*\bhref="\/settings\/layout"|<a\b[^>]*\bhref="\/settings\/layout"[^>]*\baria-current="page"/g;
|
||||
expect(html.match(layoutActive)?.length ?? 0).toBe(2);
|
||||
});
|
||||
|
||||
it("falls back to `account` when the pathname doesn't match a known slug", () => {
|
||||
const html = renderShell({ pathname: "/settings/totally-bogus" });
|
||||
const activeLinkRegex =
|
||||
@@ -155,16 +187,14 @@ describe("<SettingsShell>", () => {
|
||||
// (single-line; the longer "Notification channels" wrapped). The
|
||||
// `/notifications` inbox now shares the "Notifications" label.
|
||||
expect(html).toContain("Notifications");
|
||||
expect(html).toContain("Dashboard");
|
||||
expect(html).toContain("Insights");
|
||||
// v1.16.10 — the /medications customise surface is its own nav entry
|
||||
// between Insights and Targets.
|
||||
expect(html).toContain('href="/settings/medications"');
|
||||
expect(html).toContain("Medications");
|
||||
// v1.17 — the mood-tag management surface sits between Medications
|
||||
// and Targets.
|
||||
expect(html).toContain('href="/settings/mood"');
|
||||
expect(html).toContain("Mood tags");
|
||||
// v1.17.1 (F-2) — the four personalization editors (Dashboard,
|
||||
// Insights, Medications, Mood) are reached through one Layout hub
|
||||
// entry; the hub itself is the single nav entry for the concept.
|
||||
expect(html).toContain('href="/settings/layout"');
|
||||
expect(html).toContain("Layout & Personalization");
|
||||
// The four editors are no longer standalone nav entries.
|
||||
expect(html).not.toContain('href="/settings/medications"');
|
||||
expect(html).not.toContain('href="/settings/mood"');
|
||||
// The ampersand is HTML-escaped by React SSR — assert on the encoded
|
||||
// form so we don't accidentally match a parser that double-escapes.
|
||||
expect(html).toContain("API & Tokens");
|
||||
@@ -192,17 +222,13 @@ describe("<SettingsShell>", () => {
|
||||
// compound "Benachrichtigungs-Kanäle" wrapped). The `/notifications`
|
||||
// inbox now shares the "Benachrichtigungen" label.
|
||||
expect(html).toContain("Benachrichtigungen");
|
||||
// v1.4.3: the Settings sub-section formerly labelled "Übersicht" is now
|
||||
// "Dashboard" (matching the term users see in the main nav). The
|
||||
// per-metric overrides moved out into their own "Persönliche Zielwerte"
|
||||
// section, which is the new entry below the Dashboard one.
|
||||
expect(html).toContain("Dashboard");
|
||||
// v1.16.10 — the /medications customise surface ("Medikamente").
|
||||
expect(html).toContain('href="/settings/medications"');
|
||||
expect(html).toContain("Medikamente");
|
||||
// v1.17 — the mood-tag management surface ("Stimmungs-Tags").
|
||||
expect(html).toContain('href="/settings/mood"');
|
||||
expect(html).toContain("Stimmungs-Tags");
|
||||
// v1.17.1 (F-2) — the four personalization editors are reached through
|
||||
// one Layout hub entry ("Layout & Personalisierung"); they are no
|
||||
// longer standalone German nav entries.
|
||||
expect(html).toContain('href="/settings/layout"');
|
||||
expect(html).toContain("Layout & Personalisierung");
|
||||
expect(html).not.toContain('href="/settings/medications"');
|
||||
expect(html).not.toContain('href="/settings/mood"');
|
||||
// v1.8.7.1 — Targets and Sources are two separate German nav
|
||||
// entries again: "Zielwerte" and "Quellen".
|
||||
expect(html).toContain("Zielwerte");
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ChevronRight,
|
||||
LayoutDashboard,
|
||||
Pill,
|
||||
Smile,
|
||||
TrendingUp,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
|
||||
/**
|
||||
* v1.17.1 (F-2) — the one "Layout & Personalization" home.
|
||||
*
|
||||
* Before this section, "how my app is laid out" was scattered across four
|
||||
* unrelated settings sections — Dashboard, Insights, Medications, Mood —
|
||||
* with no shared framing, so the same concept (arrange / reorder / show-
|
||||
* hide) read as four disconnected places. This section is the single
|
||||
* front door: it presents the four personalization surfaces as one
|
||||
* consistent set of cards and links to each editor, which all keep their
|
||||
* own routes and mutation flows intact. One concept, one home.
|
||||
*/
|
||||
interface LayoutLink {
|
||||
href: string;
|
||||
icon: LucideIcon;
|
||||
titleKey: string;
|
||||
descriptionKey: string;
|
||||
}
|
||||
|
||||
const LAYOUT_LINKS: ReadonlyArray<LayoutLink> = [
|
||||
{
|
||||
href: "/settings/dashboard",
|
||||
icon: LayoutDashboard,
|
||||
titleKey: "settings.sections.layout.dashboard.title",
|
||||
descriptionKey: "settings.sections.layout.dashboard.description",
|
||||
},
|
||||
{
|
||||
href: "/settings/insights",
|
||||
icon: TrendingUp,
|
||||
titleKey: "settings.sections.layout.insights.title",
|
||||
descriptionKey: "settings.sections.layout.insights.description",
|
||||
},
|
||||
{
|
||||
href: "/settings/medications",
|
||||
icon: Pill,
|
||||
titleKey: "settings.sections.layout.medications.title",
|
||||
descriptionKey: "settings.sections.layout.medications.description",
|
||||
},
|
||||
{
|
||||
href: "/settings/mood",
|
||||
icon: Smile,
|
||||
titleKey: "settings.sections.layout.mood.title",
|
||||
descriptionKey: "settings.sections.layout.mood.description",
|
||||
},
|
||||
];
|
||||
|
||||
export function LayoutSection() {
|
||||
const { t } = useTranslations();
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-labelledby="settings-section-layout-title"
|
||||
className="space-y-6"
|
||||
>
|
||||
<header className="space-y-1">
|
||||
<h1 id="settings-section-layout-title" className="sr-only">
|
||||
{t("settings.sections.layout.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("settings.sections.layout.description")}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<ul className="space-y-3">
|
||||
{LAYOUT_LINKS.map((link) => {
|
||||
const Icon = link.icon;
|
||||
return (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className={cn(
|
||||
"bg-card border-border hover:bg-accent/40 group flex items-center gap-4 rounded-xl border p-4 transition-colors sm:p-5",
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className="text-muted-foreground h-5 w-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 space-y-0.5">
|
||||
<p className="text-sm font-semibold">{t(link.titleKey)}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t(link.descriptionKey)}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight
|
||||
className="text-muted-foreground/60 group-hover:text-foreground h-4 w-4 shrink-0 transition-colors"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ const SLUG_ICON: Record<SettingsSectionSlug, LucideIcon> = {
|
||||
account: User,
|
||||
integrations: Link2,
|
||||
notifications: Bell,
|
||||
layout: LayoutDashboard,
|
||||
dashboard: LayoutDashboard,
|
||||
insights: TrendingUp,
|
||||
medications: Pill,
|
||||
|
||||
@@ -20,10 +20,18 @@
|
||||
// v1.17 — `mood` sits after `medications`: the mood-tag management
|
||||
// surface (groups, custom tags, hide/archive, picker order), reached
|
||||
// from the /mood page header's wrench glyph.
|
||||
// v1.17.1 (F-2) — `layout` is the one "Layout & Personalization" home.
|
||||
// The dashboard / insights / medications / mood personalization editors
|
||||
// each keep their own route (deep links, page-header cogs, and the hub's
|
||||
// own links all resolve), but the settings NAV now surfaces a single
|
||||
// `layout` entry instead of four scattered "arrange" entries, so the
|
||||
// concept reads as one place. The four editor slugs stay in this list so
|
||||
// their routes still resolve; they are simply not listed in the shell nav.
|
||||
export const SETTINGS_SECTION_SLUGS = [
|
||||
"account",
|
||||
"integrations",
|
||||
"notifications",
|
||||
"layout",
|
||||
"dashboard",
|
||||
"insights",
|
||||
"medications",
|
||||
|
||||
@@ -25,12 +25,9 @@ import {
|
||||
Layers,
|
||||
LayoutDashboard,
|
||||
Link2,
|
||||
Pill,
|
||||
Settings2,
|
||||
SlidersHorizontal,
|
||||
Smile,
|
||||
Sparkles,
|
||||
TrendingUp,
|
||||
User,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
@@ -92,26 +89,16 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
|
||||
titleKey: "settings.sections.notifications.title",
|
||||
icon: Bell,
|
||||
},
|
||||
// v1.17.1 (F-2) — one "Layout & Personalization" nav entry replaces the
|
||||
// four scattered Dashboard / Insights / Medications / Mood "arrange"
|
||||
// entries. Those routes still resolve (deep links, page-header cogs, and
|
||||
// the hub's own links all work); they are simply reached through the
|
||||
// Layout hub now, so "how my app is laid out" reads as one home.
|
||||
{
|
||||
slug: "dashboard",
|
||||
titleKey: "settings.sections.dashboard.title",
|
||||
slug: "layout",
|
||||
titleKey: "settings.sections.layout.title",
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
slug: "insights",
|
||||
titleKey: "settings.sections.insights.title",
|
||||
icon: TrendingUp,
|
||||
},
|
||||
{
|
||||
slug: "medications",
|
||||
titleKey: "settings.sections.medications.title",
|
||||
icon: Pill,
|
||||
},
|
||||
{
|
||||
slug: "mood",
|
||||
titleKey: "settings.sections.mood.title",
|
||||
icon: Smile,
|
||||
},
|
||||
{
|
||||
slug: "thresholds",
|
||||
titleKey: "settings.sections.thresholds.title",
|
||||
@@ -151,16 +138,33 @@ export interface SettingsShellProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// v1.17.1 (F-2) — the four personalization editors live under the Layout
|
||||
// hub. They keep their own routes but are NOT standalone nav entries, so
|
||||
// when the user is on one of them the Layout nav entry is the one that
|
||||
// reads active.
|
||||
const LAYOUT_CHILD_SLUGS: ReadonlySet<string> = new Set([
|
||||
"dashboard",
|
||||
"insights",
|
||||
"medications",
|
||||
"mood",
|
||||
]);
|
||||
|
||||
/** Map a Layout child editor onto the Layout hub for nav highlighting. */
|
||||
function navHighlightSlug(slug: SettingsSectionSlug): SettingsSectionSlug {
|
||||
return LAYOUT_CHILD_SLUGS.has(slug) ? "layout" : slug;
|
||||
}
|
||||
|
||||
function deriveActiveSlug(
|
||||
pathname: string | null,
|
||||
override?: SettingsSectionSlug,
|
||||
): SettingsSectionSlug {
|
||||
if (override) return override;
|
||||
if (override) return navHighlightSlug(override);
|
||||
if (!pathname) return "account";
|
||||
// Match `/settings/<slug>` and ignore any trailing segments (none today,
|
||||
// but cheap insurance for future nested routes).
|
||||
const match = pathname.match(/^\/settings\/([^/]+)/);
|
||||
const candidate = match?.[1] ?? "";
|
||||
if (LAYOUT_CHILD_SLUGS.has(candidate)) return "layout";
|
||||
return isSettingsSectionSlug(candidate) ? candidate : "account";
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user