merge(settings): reminders hub, uniform integration cards, per-integration setup-guide links

This commit is contained in:
Marc-André Bombeck committed 2026-06-15 01:00:43 +02:00
commit a27247f93c
20 files changed
+569 -6

No files matched your search

+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Stimmungs-Tags",
"description": "Gruppen, Tags und Reihenfolge der Tag-Auswahl."
},
"reminders": {
"title": "Erinnerungen & Benachrichtigungen",
"description": "Ein Ort für jede Erinnerung — woran und wann erinnert wird — und die Kanäle, die sie zustellen.",
"categoriesGroup": "Erinnerungen",
"channelsGroup": "Zustellkanäle",
"medication": {
"title": "Medikamenten-Erinnerungen",
"description": "Einnahmezeiten und Zeitfenster, je Medikament eingestellt."
},
"vorsorge": {
"title": "Vorsorge",
"description": "Wiederkehrende Untersuchungen und Messungen, inklusive Ort."
},
"mood": {
"title": "Stimmungs-Check-in",
"description": "Eine tägliche Erinnerung, dein Befinden zu erfassen."
},
"lowStock": {
"title": "Reichweite bei Restbestand",
"description": "Werde gewarnt, bevor ein Medikament zur Neige geht."
},
"coach": {
"title": "Coach-Hinweis",
"description": "Proaktive Impulse vom KI-Coach."
},
"channels": {
"title": "Benachrichtigungskanäle",
"description": "APNs, Telegram, ntfy, Web Push, Webhook und E-Mail."
}
}
},
"profile": "Profil",
@@ -4773,7 +4803,8 @@
"token": "Der Token-Austausch mit Oura ist fehlgeschlagen. Bitte starte die Verbindung erneut.",
"rate_limited": "Zu viele Verbindungsversuche. Warte einen Moment und versuche es erneut.",
"generic": "Unbekannter Fehler bei der Verbindung. Bitte starte die Verbindung erneut."
}
},
"integrationSetupGuide": "Einrichtungsanleitung"
},
"admin": {
"title": "Administration",
+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Mood tags",
"description": "Groups, tags, and order of the mood picker."
},
"reminders": {
"title": "Reminders & Notifications",
"description": "One home for every reminder — what gets reminded and when — plus the channels that deliver them.",
"categoriesGroup": "Reminders",
"channelsGroup": "Delivery channels",
"medication": {
"title": "Medication reminders",
"description": "Dose times and windows, configured on each medication."
},
"vorsorge": {
"title": "Preventive care",
"description": "Recurring check-ups and measurements, with where to go."
},
"mood": {
"title": "Mood check-in",
"description": "A daily nudge to log how you feel."
},
"lowStock": {
"title": "Low-stock runway",
"description": "Get warned before a medication runs out."
},
"coach": {
"title": "Coach nudge",
"description": "Proactive prompts from the AI coach."
},
"channels": {
"title": "Notification channels",
"description": "APNs, Telegram, ntfy, Web Push, Webhook, and Email."
}
}
},
"profile": "Profile",
@@ -4773,7 +4803,8 @@
"token": "The token exchange with Oura failed. Please start the connection again.",
"rate_limited": "Too many connection attempts. Wait a moment and try again.",
"generic": "Unknown error while connecting. Please start the connection again."
}
},
"integrationSetupGuide": "Setup guide"
},
"admin": {
"title": "Administration",
+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Etiquetas de ánimo",
"description": "Grupos, etiquetas y orden del selector de ánimo."
},
"reminders": {
"title": "Recordatorios y notificaciones",
"description": "Un único lugar para cada recordatorio — qué se recuerda y cuándo — y los canales que lo entregan.",
"categoriesGroup": "Recordatorios",
"channelsGroup": "Canales de entrega",
"medication": {
"title": "Recordatorios de medicación",
"description": "Horas y ventanas de dosis, configuradas en cada medicamento."
},
"vorsorge": {
"title": "Cuidado preventivo",
"description": "Revisiones y mediciones periódicas, con el lugar al que acudir."
},
"mood": {
"title": "Registro de ánimo",
"description": "Un aviso diario para registrar cómo te sientes."
},
"lowStock": {
"title": "Margen por bajo stock",
"description": "Recibe un aviso antes de que se acabe un medicamento."
},
"coach": {
"title": "Sugerencia del coach",
"description": "Indicaciones proactivas del coach de IA."
},
"channels": {
"title": "Canales de notificación",
"description": "APNs, Telegram, ntfy, Web Push, Webhook y correo."
}
}
},
"profile": "Perfil",
@@ -4773,7 +4803,8 @@
"token": "El intercambio de token con Oura falló. Inicia la conexión de nuevo.",
"rate_limited": "Demasiados intentos de conexión. Espera un momento e inténtalo de nuevo.",
"generic": "Error desconocido al conectar. Inicia la conexión de nuevo."
}
},
"integrationSetupGuide": "Guía de configuración"
},
"admin": {
"title": "Administration",
+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Étiquettes d’humeur",
"description": "Groupes, étiquettes et ordre du sélecteur d’humeur."
},
"reminders": {
"title": "Rappels et notifications",
"description": "Un seul endroit pour chaque rappel — quoi et quand — ainsi que les canaux qui les délivrent.",
"categoriesGroup": "Rappels",
"channelsGroup": "Canaux de diffusion",
"medication": {
"title": "Rappels de médication",
"description": "Heures et fenêtres de prise, configurées sur chaque médicament."
},
"vorsorge": {
"title": "Prévention",
"description": "Bilans et mesures récurrents, avec le lieu où se rendre."
},
"mood": {
"title": "Suivi de l'humeur",
"description": "Un rappel quotidien pour noter votre ressenti."
},
"lowStock": {
"title": "Marge de stock faible",
"description": "Soyez averti avant qu'un médicament ne s'épuise."
},
"coach": {
"title": "Suggestion du coach",
"description": "Invitations proactives du coach IA."
},
"channels": {
"title": "Canaux de notification",
"description": "APNs, Telegram, ntfy, Web Push, Webhook et e-mail."
}
}
},
"profile": "Profil",
@@ -4773,7 +4803,8 @@
"token": "L'échange de jeton avec Oura a échoué. Veuillez relancer la connexion.",
"rate_limited": "Trop de tentatives de connexion. Patientez un instant et réessayez.",
"generic": "Erreur inconnue lors de la connexion. Veuillez relancer la connexion."
}
},
"integrationSetupGuide": "Guide de configuration"
},
"admin": {
"title": "Administration",
+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Tag dell’umore",
"description": "Gruppi, tag e ordine del selettore dell’umore."
},
"reminders": {
"title": "Promemoria e notifiche",
"description": "Un'unica sede per ogni promemoria — cosa ricordare e quando — e i canali che li recapitano.",
"categoriesGroup": "Promemoria",
"channelsGroup": "Canali di consegna",
"medication": {
"title": "Promemoria dei farmaci",
"description": "Orari e finestre delle dosi, impostati su ogni farmaco."
},
"vorsorge": {
"title": "Prevenzione",
"description": "Controlli e misurazioni ricorrenti, con il luogo dove recarsi."
},
"mood": {
"title": "Check-in dell'umore",
"description": "Un promemoria quotidiano per registrare come ti senti."
},
"lowStock": {
"title": "Autonomia scorte",
"description": "Ricevi un avviso prima che un farmaco finisca."
},
"coach": {
"title": "Suggerimento del coach",
"description": "Stimoli proattivi dal coach IA."
},
"channels": {
"title": "Canali di notifica",
"description": "APNs, Telegram, ntfy, Web Push, Webhook ed e-mail."
}
}
},
"profile": "Profilo",
@@ -4773,7 +4803,8 @@
"token": "Lo scambio del token con Oura è fallito. Riavvia la connessione.",
"rate_limited": "Troppi tentativi di connessione. Attendi un momento e riprova.",
"generic": "Errore sconosciuto durante la connessione. Riavvia la connessione."
}
},
"integrationSetupGuide": "Guida alla configurazione"
},
"admin": {
"title": "Administration",
+32 -1
View File
@@ -4087,6 +4087,36 @@
"mood": {
"title": "Tagi nastroju",
"description": "Grupy, tagi i kolejność w wyborze nastroju."
},
"reminders": {
"title": "Przypomnienia i powiadomienia",
"description": "Jedno miejsce dla każdego przypomnienia — o czym i kiedy — oraz kanały, które je dostarczają.",
"categoriesGroup": "Przypomnienia",
"channelsGroup": "Kanały dostarczania",
"medication": {
"title": "Przypomnienia o lekach",
"description": "Godziny i okna dawek, ustawiane przy każdym leku."
},
"vorsorge": {
"title": "Profilaktyka",
"description": "Cykliczne badania i pomiary wraz z miejscem, gdzie się udać."
},
"mood": {
"title": "Zapis nastroju",
"description": "Codzienne przypomnienie, by zapisać samopoczucie."
},
"lowStock": {
"title": "Zapas na wyczerpaniu",
"description": "Otrzymaj ostrzeżenie, zanim lek się skończy."
},
"coach": {
"title": "Podpowiedź trenera",
"description": "Proaktywne wskazówki od trenera AI."
},
"channels": {
"title": "Kanały powiadomień",
"description": "APNs, Telegram, ntfy, Web Push, Webhook i e-mail."
}
}
},
"profile": "Profil",
@@ -4773,7 +4803,8 @@
"token": "Wymiana tokena z Oura nie powiodła się. Rozpocznij połączenie ponownie.",
"rate_limited": "Zbyt wiele prób połączenia. Poczekaj chwilę i spróbuj ponownie.",
"generic": "Nieznany błąd podczas łączenia. Rozpocznij połączenie ponownie."
}
},
"integrationSetupGuide": "Przewodnik konfiguracji"
},
"admin": {
"title": "Administracja",
+2
View File
@@ -14,6 +14,7 @@ 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";
import { RemindersSection } from "@/components/settings/reminders-section";
import { SectionPlaceholder } from "@/components/settings/section-placeholder";
// v1.8.7.1 — `thresholds` (Targets) and `sources` (Sources) are two
// separate sections again. `ThresholdsSection` renders the target-range
@@ -52,6 +53,7 @@ const SECTION_COMPONENTS: Record<
ai: AiSection,
integrations: IntegrationsSection,
notifications: NotificationsSection,
reminders: RemindersSection,
layout: LayoutSection,
dashboard: DashboardSection,
insights: InsightsSection,
@@ -303,4 +303,43 @@ describe("IntegrationsSection — single-status-display contract (A5)", () => {
// Fitbit / Google Health). The moodLog integration was removed.
expect(count(html, 'data-testid="integration-card-divider"')).toBe(3);
});
// v1.17.1 — every integration card carries the same discreet "Setup guide"
// doc-link pointing at its runbook under the single shared docs base. The
// affordance is one family across all six providers.
it("every integration card carries the shared Setup-guide doc-link", () => {
setIntegrationStatus({
threshold: 3,
integrations: [
{
integration: "withings",
state: "connected",
lastSuccessAt: "2026-05-09T18:00:00.000Z",
lastAttemptAt: "2026-05-09T18:00:00.000Z",
lastError: null,
connected: true,
configured: true,
legacyLastSyncedAt: "2026-05-09T18:00:00.000Z",
hasActivityScope: true,
},
],
});
const html = render();
// One setup-guide link per card → six providers.
expect(count(html, 'data-slot="integration-setup-guide"')).toBe(6);
for (const provider of [
"withings",
"whoop",
"fitbit",
"polar",
"oura",
"nightscout",
]) {
expect(html).toContain(`data-testid="${provider}-setup-guide"`);
expect(html).toContain(
`href="https://docs.healthlog.dev/integrations/${provider}"`,
);
}
});
});
@@ -0,0 +1,67 @@
/**
* v1.17.1 — the "Reminders & Notifications" hub.
*
* The hub is the single front door for every reminder concept. It groups the
* reminder CATEGORIES (medication / Vorsorge / mood / low-stock / coach) and
* links to the notification CHANNELS, with each link deep-linking into the
* canonical editor so nothing is rewritten — only gathered in one place.
*/
import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import { RemindersSection } from "../reminders-section";
function render(locale: "en" | "de" = "en") {
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>
<RemindersSection />
</I18nProvider>,
);
}
describe("<RemindersSection>", () => {
it("deep-links to every canonical reminder editor", () => {
const html = render();
for (const href of [
"/medications",
"/vorsorge",
"/settings/notifications#mood-reminder",
"/settings/notifications#low-stock",
"/settings/notifications#coach-nudge",
]) {
expect(html).toContain(`href="${href}"`);
}
});
it("links to the notification channels screen (the how/where)", () => {
const html = render();
expect(html).toContain('href="/settings/notifications"');
expect(html).toContain('data-testid="reminders-link-channels"');
});
it("renders a card for each reminder category", () => {
const html = render();
for (const testId of [
"reminders-link-medication",
"reminders-link-vorsorge",
"reminders-link-mood",
"reminders-link-low-stock",
"reminders-link-coach",
]) {
expect(html).toContain(`data-testid="${testId}"`);
}
});
it("separates categories (what/when) from channels (how/where)", () => {
const html = render();
expect(html).toContain("Reminders");
expect(html).toContain("Delivery channels");
});
it("resolves its copy in German too", () => {
const html = render("de");
expect(html).toContain("Medikamenten-Erinnerungen");
expect(html).toContain("Zustellkanäle");
});
});
@@ -61,10 +61,13 @@ describe("SETTINGS_SECTION_SLUGS", () => {
// 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.
// v1.17.1 — `reminders` is the one "Reminders & Notifications" home and
// sits right after `notifications`.
expect([...SETTINGS_SECTION_SLUGS]).toEqual([
"account",
"integrations",
"notifications",
"reminders",
"layout",
"dashboard",
"insights",
@@ -51,6 +51,7 @@ import {
pillStateFor,
type IntegrationStatusViewModel,
} from "./shared";
import { IntegrationSetupGuideLink } from "./setup-guide-link";
export function FitbitCard({
viewModel,
@@ -484,6 +485,8 @@ export function FitbitCard({
{t("settings.fitbitNoCredentials")}
</div>
)}
<IntegrationSetupGuideLink provider="fitbit" />
</div>
</div>
);
@@ -39,6 +39,7 @@ import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, measurementDependentKeys, queryKeys } from "@/lib/query-keys";
import { IntegrationErrorMessage } from "./shared";
import { IntegrationSetupGuideLink } from "./setup-guide-link";
interface NightscoutStatus {
connected: boolean;
@@ -366,6 +367,8 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
</Link>
</>
)}
<IntegrationSetupGuideLink provider="nightscout" />
</div>
</div>
);
@@ -37,6 +37,7 @@ import { TestConnectionButton } from "@/components/settings/test-connection-butt
import { apiFetchRaw, apiGet, apiPost } from "@/lib/api/api-fetch";
import { useTranslations } from "@/lib/i18n/context";
import { invalidateKeys, measurementDependentKeys } from "@/lib/query-keys";
import { IntegrationSetupGuideLink } from "./setup-guide-link";
export interface OAuthProviderStatus {
connected: boolean;
@@ -398,6 +399,8 @@ export function OAuthProviderCard({
{msg}
</p>
)}
<IntegrationSetupGuideLink provider={provider} />
</div>
</div>
);
@@ -0,0 +1,57 @@
"use client";
/**
* v1.17.1 — shared "Setup guide" doc-link for every Settings → Integrations
* card. Each card paints the same affordance: one discreet, external link to
* the provider's setup runbook ("was eingeben, wo klicken"). Single-sourced
* here so all six cards (WHOOP / Withings / Fitbit / Polar / Oura / Nightscout)
* read as one family and the docs host lives in exactly one place.
*
* The runbooks themselves are authored separately; this link wires the
* destination now so a user who is mid-setup always knows where to go.
*/
import { ExternalLink } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
/**
* Base for every provider setup runbook. The provider key is appended as a
* path segment, e.g. `https://docs.healthlog.dev/integrations/whoop`. Kept as
* a single constant so the host never drifts across cards.
*/
export const INTEGRATION_DOCS_BASE =
"https://docs.healthlog.dev/integrations";
export type IntegrationDocsProvider =
| "whoop"
| "withings"
| "fitbit"
| "polar"
| "oura"
| "nightscout";
export function integrationDocsHref(provider: IntegrationDocsProvider): string {
return `${INTEGRATION_DOCS_BASE}/${provider}`;
}
export function IntegrationSetupGuideLink({
provider,
}: {
provider: IntegrationDocsProvider;
}) {
const { t } = useTranslations();
return (
<a
href={integrationDocsHref(provider)}
target="_blank"
rel="noopener noreferrer"
data-testid={`${provider}-setup-guide`}
data-slot="integration-setup-guide"
className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs underline-offset-2 hover:underline"
>
{t("settings.integrationSetupGuide")}
<ExternalLink className="h-3 w-3" aria-hidden="true" />
</a>
);
}
@@ -43,6 +43,7 @@ import {
pillStateFor,
type IntegrationStatusViewModel,
} from "./shared";
import { IntegrationSetupGuideLink } from "./setup-guide-link";
export function WhoopCard({
viewModel,
@@ -456,6 +457,8 @@ export function WhoopCard({
{t("settings.whoopNoCredentials")}
</div>
)}
<IntegrationSetupGuideLink provider="whoop" />
</div>
</div>
);
@@ -36,6 +36,7 @@ import {
pillStateFor,
type IntegrationStatusViewModel,
} from "./shared";
import { IntegrationSetupGuideLink } from "./setup-guide-link";
export function WithingsCard({
viewModel,
@@ -491,6 +492,8 @@ export function WithingsCard({
{t("settings.withingsNoCredentials")}
</div>
)}
<IntegrationSetupGuideLink provider="withings" />
</div>
</div>
);
@@ -0,0 +1,176 @@
"use client";
import Link from "next/link";
import {
Bell,
ChevronRight,
HeartPulse,
MessageCircle,
Package,
Pill,
Smile,
Stethoscope,
type LucideIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useTranslations } from "@/lib/i18n/context";
/**
* v1.17.1 — the one "Reminders & Notifications" home.
*
* Before this section, "what gets reminded and when" was scattered across
* three unrelated homes: medication reminders lived per-medication on the med
* cards, mood + low-stock + coach-nudge lived inside the Notifications channel
* screen, and the new Vorsorge (preventive-care) reminders had their own
* page. There was no single place that answered "was erinnert mich woran,
* wann, und worüber".
*
* This hub is the single front door. It draws a clean line between two
* genuinely different concepts (so the maintainer rule "never split the same
* concept across a page" still holds):
*
* - CHANNELS — the *how/where* a reminder is delivered (APNs / Telegram /
* ntfy / Web Push / Webhook / Email). These keep living on the
* Notifications channel screen; this hub links to them.
* - CATEGORIES — the *what/when*: medication, mood, Vorsorge, low-stock
* runway, and the proactive Coach nudge. Each keeps its own canonical
* editor and deep link; the hub just gathers them so the concept reads
* as one place.
*
* Consolidation by linking — every existing editor stays intact and
* deep-linkable; nothing here is a rewrite.
*/
interface ReminderLink {
href: string;
icon: LucideIcon;
titleKey: string;
descriptionKey: string;
/** External-to-settings link (opens a feature page, not a settings tab). */
testId: string;
}
const CHANNEL_LINKS: ReadonlyArray<ReminderLink> = [
{
href: "/settings/notifications",
icon: Bell,
titleKey: "settings.sections.reminders.channels.title",
descriptionKey: "settings.sections.reminders.channels.description",
testId: "reminders-link-channels",
},
];
const CATEGORY_LINKS: ReadonlyArray<ReminderLink> = [
{
href: "/medications",
icon: Pill,
titleKey: "settings.sections.reminders.medication.title",
descriptionKey: "settings.sections.reminders.medication.description",
testId: "reminders-link-medication",
},
{
href: "/vorsorge",
icon: Stethoscope,
titleKey: "settings.sections.reminders.vorsorge.title",
descriptionKey: "settings.sections.reminders.vorsorge.description",
testId: "reminders-link-vorsorge",
},
{
href: "/settings/notifications#mood-reminder",
icon: Smile,
titleKey: "settings.sections.reminders.mood.title",
descriptionKey: "settings.sections.reminders.mood.description",
testId: "reminders-link-mood",
},
{
href: "/settings/notifications#low-stock",
icon: Package,
titleKey: "settings.sections.reminders.lowStock.title",
descriptionKey: "settings.sections.reminders.lowStock.description",
testId: "reminders-link-low-stock",
},
{
href: "/settings/notifications#coach-nudge",
icon: MessageCircle,
titleKey: "settings.sections.reminders.coach.title",
descriptionKey: "settings.sections.reminders.coach.description",
testId: "reminders-link-coach",
},
];
function ReminderLinkCard({ link }: { link: ReminderLink }) {
const { t } = useTranslations();
const Icon = link.icon;
return (
<li>
<Link
href={link.href}
data-testid={link.testId}
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>
);
}
export function RemindersSection() {
const { t } = useTranslations();
return (
<section
aria-labelledby="settings-section-reminders-title"
className="space-y-6"
>
<header className="space-y-1">
<h1 id="settings-section-reminders-title" className="sr-only">
{t("settings.sections.reminders.title")}
</h1>
<p className="text-muted-foreground text-sm">
{t("settings.sections.reminders.description")}
</p>
</header>
{/* CATEGORIES — the what/when. */}
<div className="space-y-3">
<div className="text-muted-foreground flex items-center gap-2 text-xs font-medium tracking-wide uppercase">
<HeartPulse className="h-3.5 w-3.5" aria-hidden="true" />
{t("settings.sections.reminders.categoriesGroup")}
</div>
<ul className="space-y-3">
{CATEGORY_LINKS.map((link) => (
<ReminderLinkCard key={link.href} link={link} />
))}
</ul>
</div>
{/* CHANNELS — the how/where. */}
<div className="space-y-3">
<div className="text-muted-foreground flex items-center gap-2 text-xs font-medium tracking-wide uppercase">
<Bell className="h-3.5 w-3.5" aria-hidden="true" />
{t("settings.sections.reminders.channelsGroup")}
</div>
<ul className="space-y-3">
{CHANNEL_LINKS.map((link) => (
<ReminderLinkCard key={link.href} link={link} />
))}
</ul>
</div>
</section>
);
}
@@ -12,6 +12,7 @@
*/
import {
AlarmClock,
Bell,
Download,
Info,
@@ -37,6 +38,7 @@ const SLUG_ICON: Record<SettingsSectionSlug, LucideIcon> = {
account: User,
integrations: Link2,
notifications: Bell,
reminders: AlarmClock,
layout: LayoutDashboard,
dashboard: LayoutDashboard,
insights: TrendingUp,
+6
View File
@@ -27,10 +27,16 @@
// `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.
// v1.17.1 — `reminders` sits right after `notifications`: the one
// "Reminders & Notifications" home that gathers the scattered reminder
// categories (medication, mood, Vorsorge, low-stock, coach nudge) and links
// to the notification channels. The canonical editors keep their own routes;
// the hub is consolidation by linking.
export const SETTINGS_SECTION_SLUGS = [
"account",
"integrations",
"notifications",
"reminders",
"layout",
"dashboard",
"insights",
@@ -18,6 +18,7 @@ import * as React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
AlarmClock,
Bell,
Download,
Info,
@@ -89,6 +90,15 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
titleKey: "settings.sections.notifications.title",
icon: Bell,
},
// v1.17.1 — the one "Reminders & Notifications" home. Gathers the
// scattered reminder categories (medication / mood / Vorsorge / low-stock
// / coach nudge) and links to the notification channels. The canonical
// editors keep their own routes; this nav entry is the single front door.
{
slug: "reminders",
titleKey: "settings.sections.reminders.title",
icon: AlarmClock,
},
// 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