Files
HealthLog/src/components/settings/section-placeholder.tsx
T
Marc-André Bombeck 5199de78af feat(sharing): the shared-access panel and the record-activity view
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.
2026-08-02 14:26:39 +02:00

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>
);
}