refactor(settings): remove the redundant Erinnerungen hub page

The standalone /settings/reminders page was a link-only hub that merely
deep-linked into the canonical editors — duplicating the Notifications
entry. Drop the section, its slug, and its placeholder/route wiring, and
301-redirect /settings/reminders to /settings/notifications. Reminder
types consolidate onto the Notifications home in a follow-up.
This commit is contained in:
Marc-André Bombeck committed 2026-06-15 23:03:52 +02:00
1 parent 7b80a38ea2
commit fccdcb378e
8 files changed
+22 -267

No files matched your search

+10
View File
@@ -130,6 +130,16 @@ const nextConfig: NextConfig = {
destination: "/settings/integrations",
permanent: true,
},
// v1.18.0 (S4) — the standalone "Erinnerungen" hub at
// `/settings/reminders` was a link-only page that duplicated the
// canonical editors. Reminder TYPES now live in Notifications, each
// gated on its module. 301-redirect so bookmarks, the PWA's cached
// navigation, and the old cross-links keep resolving.
{
source: "/settings/reminders",
destination: "/settings/notifications",
permanent: true,
},
];
},
async headers() {
+1 -3
View File
@@ -15,7 +15,6 @@ import { MedicationsSection } from "@/components/settings/medications-section";
import { ModulesSection } from "@/components/settings/modules-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.18.0 (S3) — `sources` no longer has a standalone route; source priority
// renders inside Settings → Integrations as the "Sources" sub-tab.
@@ -30,7 +29,7 @@ import { SettingsShell } from "@/components/settings/settings-shell";
/**
* Dynamic settings section route. Each of the `SETTINGS_SECTION_SLUGS`
* (15 today — 11 nav-visible sections plus the four routable-but-hidden
* (14 today — 10 nav-visible sections plus the four routable-but-hidden
* Layout child editors) is pre-rendered at build via
* `generateStaticParams()` so the URLs are
* statically known to Next.js, while the `dynamicParams = false` flag below
@@ -55,7 +54,6 @@ const SECTION_COMPONENTS: Record<
ai: AiSection,
integrations: IntegrationsSection,
notifications: NotificationsSection,
reminders: RemindersSection,
layout: LayoutSection,
dashboard: DashboardSection,
insights: InsightsSection,
@@ -1,67 +0,0 @@
/**
* 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");
});
});
@@ -62,8 +62,9 @@ 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`.
// v1.18.0 (S4) — the standalone `reminders` hub is gone; reminder TYPES
// live in `notifications`, each gated on its module. `/settings/reminders`
// 301-redirects to `/settings/notifications`.
// v1.18.0 — `modules` ("Was du trackst") sits right after `account` as
// the single front door for enabling/disabling secondary domains.
expect([...SETTINGS_SECTION_SLUGS]).toEqual([
@@ -71,7 +72,6 @@ describe("SETTINGS_SECTION_SLUGS", () => {
"modules",
"integrations",
"notifications",
"reminders",
"layout",
"dashboard",
"insights",
@@ -1,176 +0,0 @@
"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,7 +12,6 @@
*/
import {
AlarmClock,
Bell,
Blocks,
Download,
@@ -39,7 +38,6 @@ const SLUG_ICON: Record<SettingsSectionSlug, LucideIcon> = {
modules: Blocks,
integrations: Link2,
notifications: Bell,
reminders: AlarmClock,
layout: LayoutDashboard,
dashboard: LayoutDashboard,
insights: TrendingUp,
+5 -6
View File
@@ -26,11 +26,11 @@
// `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.
// v1.18.0 (S4) — the standalone `reminders` hub is gone. It was a
// link-only page ("doppelt gemoppelt") that merely deep-linked into the
// canonical editors; reminder TYPES now live centrally in `notifications`,
// each row gated on its module. `/settings/reminders` 301-redirects to
// `/settings/notifications` (next.config.ts).
export const SETTINGS_SECTION_SLUGS = [
"account",
// v1.18.0 — `modules` ("Was du trackst") sits right after account: the
@@ -38,7 +38,6 @@ export const SETTINGS_SECTION_SLUGS = [
"modules",
"integrations",
"notifications",
"reminders",
"layout",
"dashboard",
"insights",
+3 -10
View File
@@ -18,7 +18,6 @@ import * as React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
AlarmClock,
Bell,
Blocks,
Download,
@@ -92,20 +91,14 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
titleKey: "settings.sections.integrations.title",
icon: Link2,
},
// v1.18.0 (S4) — "Benachrichtigungen" is now the single module-gated
// reminder-types home. The separate "Erinnerungen" hub (a link-only page)
// is gone; reminder TYPES live here, each row gated on its module.
{
slug: "notifications",
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