mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Settings gains one section for one concept: who else can open this health record, whose this account may open, and when they were opened. Split across two pages it would be two half-answers. An invitation confers nothing until the other person accepts it, and the card says so above the field rather than after the submit — that is the part somebody needs to know before they type a name. Refusals are named from the stable errorCode, because "no account with that name" and "that person already has access" are different situations with different next steps. Ended grants stay in the list. The row is the consent record, and "who had access, from when to when, and who ended it" is the question the panel exists to answer; a list that dropped them could not. Revoking asks once and then does it: reducing access must never be harder than granting it was. `GET /api/account/activity` answers the owner's other question. One row per person per day with a count, not one per request — a hundred identical lines is a way of answering nothing. The window is published with the rows rather than assumed: audit history is purged on a schedule the operator sets, so the view prints the number the server resolved from the same setting the purge job reads. An instance running ninety days says ninety. Six locales ship with it.
120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* `<SectionPlaceholder>` — the temporary body of every `/settings/[section]`
|
|
* route until the matching extraction PR lands.
|
|
*
|
|
* The shell + routing surface ship in PR A2-shell so reviewers can validate
|
|
* the URL structure in isolation. The follow-up PRs (A2-account, A2-about,
|
|
* A2-ai, A2-integrations, A2-notifications, A2-rest) replace this placeholder
|
|
* with the real content for each slug, copying from
|
|
* `src/app/settings/page.legacy.tsx`.
|
|
*/
|
|
|
|
import {
|
|
Bell,
|
|
Blocks,
|
|
Bot,
|
|
CalendarCheck,
|
|
CloudSun,
|
|
Download,
|
|
ClipboardList,
|
|
FileHeart,
|
|
FlaskConical,
|
|
Gauge,
|
|
Info,
|
|
KeyRound,
|
|
Plug,
|
|
Layers,
|
|
LayoutDashboard,
|
|
Link2,
|
|
Lock,
|
|
Pill,
|
|
Settings2,
|
|
Share2,
|
|
ShieldCheck,
|
|
SlidersHorizontal,
|
|
Smile,
|
|
Sparkles,
|
|
Thermometer,
|
|
TrendingUp,
|
|
User,
|
|
UsersRound,
|
|
type LucideIcon,
|
|
} from "lucide-react";
|
|
|
|
import { EmptyState } from "@/components/ui/empty-state";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import type { SettingsSectionSlug } from "./section-slugs";
|
|
|
|
const SLUG_ICON: Record<SettingsSectionSlug, LucideIcon> = {
|
|
account: User,
|
|
security: ShieldCheck,
|
|
access: UsersRound,
|
|
modules: Blocks,
|
|
integrations: Link2,
|
|
sources: Layers,
|
|
notifications: Bell,
|
|
layout: LayoutDashboard,
|
|
dashboard: LayoutDashboard,
|
|
insights: TrendingUp,
|
|
medications: Pill,
|
|
mood: Smile,
|
|
labs: FlaskConical,
|
|
illness: Thermometer,
|
|
environment: CloudSun,
|
|
anamnesis: ClipboardList,
|
|
score: Gauge,
|
|
vorsorge: CalendarCheck,
|
|
thresholds: SlidersHorizontal,
|
|
ai: Sparkles,
|
|
coach: Bot,
|
|
api: KeyRound,
|
|
mcp: Plug,
|
|
gesundheitsakte: FileHeart,
|
|
sharing: Share2,
|
|
export: Download,
|
|
advanced: Settings2,
|
|
privacy: Lock,
|
|
about: Info,
|
|
};
|
|
|
|
export interface SectionPlaceholderProps {
|
|
slug: SettingsSectionSlug;
|
|
}
|
|
|
|
export function SectionPlaceholder({ slug }: SectionPlaceholderProps) {
|
|
const { t } = useTranslations();
|
|
const Icon = SLUG_ICON[slug];
|
|
const sectionTitle = t(`settings.sections.${slug}.title`);
|
|
const sectionDescription = t(`settings.sections.${slug}.description`);
|
|
|
|
// v1.4.33 A4 — `<SectionPlaceholder>` is a defensive fallback for
|
|
// slugs added to SETTINGS_SECTION_SLUGS without a matching component.
|
|
// The type system blocks that path today, so the EmptyState body is
|
|
// effectively unreachable. We retire the dedicated locale key
|
|
// (`settings.sections.placeholder.coming_soon`) and inherit the
|
|
// section's own description so the guard still renders meaningful
|
|
// copy if it ever does fire — no more dead-string maintenance across
|
|
// six locale files.
|
|
return (
|
|
<section
|
|
aria-labelledby={`settings-section-${slug}-title`}
|
|
className="space-y-6"
|
|
>
|
|
<header className="space-y-1">
|
|
<h1 id={`settings-section-${slug}-title`} className="sr-only">
|
|
{sectionTitle}
|
|
</h1>
|
|
<p className="text-muted-foreground text-sm">{sectionDescription}</p>
|
|
</header>
|
|
|
|
<EmptyState
|
|
icon={<Icon className="size-6" />}
|
|
title={sectionTitle}
|
|
description={sectionDescription}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|