mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Settings now reads as nine groups: Account, Tracking, Arrangement, Notifications, Health profile, AI, Connections, Data and System. On a phone one button names the current section and opens the grouped list, replacing the strip of every section. What moved, every old address still working: - Sessions, trusted devices and security activity sit under Security; the privacy page anchors forward there. - Notifications holds every reminder kind: the Coach nudges and the checkup reminder switches joined, and a card names the reminders set where their subject lives (dose times, cycle reminders). - Arrangement holds order and view only. Mood tags, the labs biomarker catalog and on-device text recognition, and the injection sites live on module pages under /settings/modules/<id>, linked from each Modules row and from the module's own wrench. /settings/layout/mood redirects. - The timezone saves on change in the Display card, with a one-tap offer of the device zone. - Screening hints sit beside the life circumstances they come from. - System gains Preview features with the Topics switch. Duplicates: the Coach and MCP pages show their state and link to the one switch in Modules (turning MCP off keeps tokens), the Coach overlay shows the provider and model with a link to Settings > AI instead of a second set of selects, and the privacy page drops its copy of the export links. A guard walks every historical /settings address through the redirects and every forwarded anchor to a page that renders.
179 lines
7.3 KiB
TypeScript
179 lines
7.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { Bell, BellRing, ChevronRight, Smartphone } from "lucide-react";
|
|
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { useMounted } from "@/hooks/use-mounted";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { isSurfaceVisible } from "@/lib/modules/surface";
|
|
import { CoachNudgeCard } from "@/components/settings/coach-nudge-card";
|
|
import { LowStockCard } from "@/components/settings/low-stock-card";
|
|
import { SectionHeading } from "@/components/ui/section-heading";
|
|
import { MoodReminderCard } from "@/components/settings/mood-reminder-card";
|
|
import { NotificationContentCard } from "@/components/settings/notification-content-card";
|
|
import { SettingsCard } from "@/components/settings/settings-card";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { VorsorgeReminderSwitchesCard } from "@/components/measurement-reminders/vorsorge-settings";
|
|
import { Button } from "@/components/ui/button";
|
|
import { InlineAction } from "@/components/ui/inline-action";
|
|
|
|
/**
|
|
* `<NotificationsSection>` — Settings → Notifications ("Benachrichtigungen").
|
|
*
|
|
* The one home for every reminder kind. A reminder whose settings belong to
|
|
* one place is set here; a reminder that is set where its subject lives (a
|
|
* medication's dose times on the medication, the cycle reminders in the
|
|
* per-event matrix) gets a row here that says where, so nothing has to be
|
|
* found by luck.
|
|
*
|
|
* Each card is shown only while its module is on, read from the resolved
|
|
* `useAuth().user.modules` map and failing open, so a stale `/me` payload
|
|
* never blanks a card.
|
|
*
|
|
* v1.18.0 — the separate "Erinnerungen" hub is gone; `/settings/reminders`
|
|
* redirects here. v1.25.7 — the delivery channels live under Integrations
|
|
* (they are delivery providers); `/settings/channels` redirects there and this
|
|
* page links to them. v1.44 — the Coach nudges came back from Settings > Coach
|
|
* and the checkup reminder switches from the checkups arrangement page; both
|
|
* old places link here (`/settings/coach#coach-nudge` forwards).
|
|
*/
|
|
export function NotificationsSection() {
|
|
const { isAuthenticated, user } = useAuth();
|
|
const { t } = useTranslations();
|
|
// The Coach gate reads the surface map like the Coach section does, after
|
|
// hydration, so the server render and the first client paint agree.
|
|
const mounted = useMounted();
|
|
|
|
const moodEnabled = user?.modules?.mood !== false;
|
|
const medsEnabled = user?.modules?.medications !== false;
|
|
const cycleEnabled = user?.modules?.cycle !== false;
|
|
const coachEnabled =
|
|
!mounted || isSurfaceVisible("settings:coach", user?.modules);
|
|
|
|
return (
|
|
<div className="space-y-10">
|
|
{/* Group 1 — Reminders ("what you receive"). The checkup reminders are
|
|
not a module and always show, so the group always has content. */}
|
|
<section className="space-y-4">
|
|
<SectionHeading
|
|
icon={Bell}
|
|
title={t("settings.sections.notifications.remindersHeading")}
|
|
/>
|
|
|
|
{moodEnabled ? (
|
|
<div id="mood-reminder" className="scroll-mt-28">
|
|
<MoodReminderCard isAuthenticated={isAuthenticated} />
|
|
</div>
|
|
) : null}
|
|
|
|
{medsEnabled ? (
|
|
<div id="low-stock" className="scroll-mt-28">
|
|
<LowStockCard isAuthenticated={isAuthenticated} />
|
|
</div>
|
|
) : null}
|
|
|
|
{coachEnabled ? (
|
|
<div id="coach-nudge" className="scroll-mt-28">
|
|
<CoachNudgeCard isAuthenticated={isAuthenticated} />
|
|
</div>
|
|
) : null}
|
|
|
|
<div id="vorsorge-reminders" className="scroll-mt-28">
|
|
<VorsorgeReminderSwitchesCard />
|
|
</div>
|
|
|
|
{/* Reminders set where their subject lives. One row each, saying
|
|
where; nothing here writes. */}
|
|
{medsEnabled || cycleEnabled ? (
|
|
<SettingsCard data-slot="notifications-elsewhere">
|
|
<SettingsCardHeader
|
|
anchor="other-reminders"
|
|
icon={BellRing}
|
|
title={t("settings.sections.notifications.elsewhere.title")}
|
|
description={t(
|
|
"settings.sections.notifications.elsewhere.description",
|
|
)}
|
|
/>
|
|
<ul className="divide-border divide-y">
|
|
{medsEnabled ? (
|
|
<li className="space-y-1 py-3 first:pt-0">
|
|
<p className="text-sm font-medium">
|
|
{t("settings.sections.notifications.elsewhere.doses.title")}
|
|
</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
{t(
|
|
"settings.sections.notifications.elsewhere.doses.description",
|
|
)}
|
|
</p>
|
|
<InlineAction asChild tone="muted">
|
|
<Link href="/medications">
|
|
{t(
|
|
"settings.sections.notifications.elsewhere.doses.link",
|
|
)}
|
|
<ChevronRight className="size-3.5" aria-hidden="true" />
|
|
</Link>
|
|
</InlineAction>
|
|
</li>
|
|
) : null}
|
|
{cycleEnabled ? (
|
|
<li
|
|
id="cycle-reminders"
|
|
className="scroll-mt-28 space-y-1 py-3 first:pt-0"
|
|
>
|
|
<p className="text-sm font-medium">
|
|
{t("settings.sections.notifications.elsewhere.cycle.title")}
|
|
</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
{t(
|
|
"settings.sections.notifications.elsewhere.cycle.description",
|
|
)}
|
|
</p>
|
|
<InlineAction asChild tone="muted">
|
|
<Link href="/notifications">
|
|
{t(
|
|
"settings.sections.notifications.elsewhere.cycle.link",
|
|
)}
|
|
<ChevronRight className="size-3.5" aria-hidden="true" />
|
|
</Link>
|
|
</InlineAction>
|
|
</li>
|
|
) : null}
|
|
</ul>
|
|
</SettingsCard>
|
|
) : null}
|
|
</section>
|
|
|
|
{/* Group 2 — How notifications look on the phone. Not module-gated:
|
|
it applies to every notification the account receives. */}
|
|
<section className="space-y-4">
|
|
<SectionHeading
|
|
icon={Smartphone}
|
|
title={t("settings.sections.notifications.displayHeading")}
|
|
/>
|
|
<div id="notification-content" className="scroll-mt-28">
|
|
<NotificationContentCard isAuthenticated={isAuthenticated} />
|
|
</div>
|
|
</section>
|
|
{/* Cross-links to the other two notification surfaces. At the END of
|
|
the page: a screen that opens with two ways off it never says what
|
|
it is for. */}
|
|
<div
|
|
className="flex flex-wrap gap-2"
|
|
data-slot="notifications-crosslinks"
|
|
>
|
|
<Button asChild variant="outline" size="sm">
|
|
<Link href="/notifications">
|
|
{t("settings.sections.notifications.matrixLink")}
|
|
</Link>
|
|
</Button>
|
|
<Button asChild variant="outline" size="sm">
|
|
<Link href="/settings/integrations#channels">
|
|
{t("settings.sections.notifications.channelsLink")}
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|