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__/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/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