diff --git a/messages/de.json b/messages/de.json index 8142d82d1..1d1ba962d 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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", diff --git a/messages/en.json b/messages/en.json index 1e7058c04..3a8b08b12 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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", diff --git a/messages/es.json b/messages/es.json index 4988d8cd5..210c603ad 100644 --- a/messages/es.json +++ b/messages/es.json @@ -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", diff --git a/messages/fr.json b/messages/fr.json index c47bf151d..e73f56d89 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -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", diff --git a/messages/it.json b/messages/it.json index cd103de51..5e20abdb5 100644 --- a/messages/it.json +++ b/messages/it.json @@ -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", diff --git a/messages/pl.json b/messages/pl.json index 19cfeb26e..f62178eb5 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -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", diff --git a/src/app/settings/[section]/page.tsx b/src/app/settings/[section]/page.tsx index 20f1d2d25..87b97f90f 100644 --- a/src/app/settings/[section]/page.tsx +++ b/src/app/settings/[section]/page.tsx @@ -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, diff --git a/src/components/settings/__tests__/integrations-section.test.tsx b/src/components/settings/__tests__/integrations-section.test.tsx index 8381da09e..69b49d1fb 100644 --- a/src/components/settings/__tests__/integrations-section.test.tsx +++ b/src/components/settings/__tests__/integrations-section.test.tsx @@ -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}"`, + ); + } + }); }); diff --git a/src/components/settings/__tests__/reminders-section.test.tsx b/src/components/settings/__tests__/reminders-section.test.tsx new file mode 100644 index 000000000..3305f95d1 --- /dev/null +++ b/src/components/settings/__tests__/reminders-section.test.tsx @@ -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( + + + , + ); +} + +describe("", () => { + 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"); + }); +}); diff --git a/src/components/settings/__tests__/settings-shell.test.tsx b/src/components/settings/__tests__/settings-shell.test.tsx index 361c4a524..b10b03c07 100644 --- a/src/components/settings/__tests__/settings-shell.test.tsx +++ b/src/components/settings/__tests__/settings-shell.test.tsx @@ -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", diff --git a/src/components/settings/integrations/fitbit-card.tsx b/src/components/settings/integrations/fitbit-card.tsx index 5b394c615..f2b1c3b04 100644 --- a/src/components/settings/integrations/fitbit-card.tsx +++ b/src/components/settings/integrations/fitbit-card.tsx @@ -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")} )} + + ); diff --git a/src/components/settings/integrations/nightscout-card.tsx b/src/components/settings/integrations/nightscout-card.tsx index 97edbc3e7..6dc028bb9 100644 --- a/src/components/settings/integrations/nightscout-card.tsx +++ b/src/components/settings/integrations/nightscout-card.tsx @@ -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 }) { )} + + ); diff --git a/src/components/settings/integrations/oauth-provider-card.tsx b/src/components/settings/integrations/oauth-provider-card.tsx index 77b2e97fd..d7642ecab 100644 --- a/src/components/settings/integrations/oauth-provider-card.tsx +++ b/src/components/settings/integrations/oauth-provider-card.tsx @@ -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}

)} + + ); diff --git a/src/components/settings/integrations/setup-guide-link.tsx b/src/components/settings/integrations/setup-guide-link.tsx new file mode 100644 index 000000000..a6cf6a652 --- /dev/null +++ b/src/components/settings/integrations/setup-guide-link.tsx @@ -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 ( + + {t("settings.integrationSetupGuide")} + + ); +} diff --git a/src/components/settings/integrations/whoop-card.tsx b/src/components/settings/integrations/whoop-card.tsx index a3acc2077..23b875f4e 100644 --- a/src/components/settings/integrations/whoop-card.tsx +++ b/src/components/settings/integrations/whoop-card.tsx @@ -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")} )} + + ); diff --git a/src/components/settings/integrations/withings-card.tsx b/src/components/settings/integrations/withings-card.tsx index d9613697a..009566d7d 100644 --- a/src/components/settings/integrations/withings-card.tsx +++ b/src/components/settings/integrations/withings-card.tsx @@ -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")} )} + + ); diff --git a/src/components/settings/reminders-section.tsx b/src/components/settings/reminders-section.tsx new file mode 100644 index 000000000..698465751 --- /dev/null +++ b/src/components/settings/reminders-section.tsx @@ -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 = [ + { + 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 = [ + { + 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 ( +
  • + +
  • + ); +} + +export function RemindersSection() { + const { t } = useTranslations(); + + return ( +
    +
    +

    + {t("settings.sections.reminders.title")} +

    +

    + {t("settings.sections.reminders.description")} +

    +
    + + {/* CATEGORIES — the what/when. */} +
    +
    +
    +
      + {CATEGORY_LINKS.map((link) => ( + + ))} +
    +
    + + {/* CHANNELS — the how/where. */} +
    +
    +
    +
      + {CHANNEL_LINKS.map((link) => ( + + ))} +
    +
    +
    + ); +} diff --git a/src/components/settings/section-placeholder.tsx b/src/components/settings/section-placeholder.tsx index 554c26cee..e437f3b95 100644 --- a/src/components/settings/section-placeholder.tsx +++ b/src/components/settings/section-placeholder.tsx @@ -12,6 +12,7 @@ */ import { + AlarmClock, Bell, Download, Info, @@ -37,6 +38,7 @@ const SLUG_ICON: Record = { account: User, integrations: Link2, notifications: Bell, + reminders: AlarmClock, layout: LayoutDashboard, dashboard: LayoutDashboard, insights: TrendingUp, diff --git a/src/components/settings/section-slugs.ts b/src/components/settings/section-slugs.ts index fd886ef78..87e9723b3 100644 --- a/src/components/settings/section-slugs.ts +++ b/src/components/settings/section-slugs.ts @@ -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", diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index 530c48963..28d2c168d 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -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