Files
HealthLog/src/app/admin/[section]/renderer.tsx
T
Marc-André BombeckandClaude Opus 4.7 88c8db1abc refactor(v1.5): apply simplify-review safe-to-apply suggestions
Drops dead `id` props and `scroll-mt-28` classes from admin section
components (load-bearing in v1.4 monolithic admin, dead since the v1.5
phase-4b dynamic-route split). Also removes the unused
`LEGACY_ANCHOR_TO_SLUG` map, the `admin-shell` re-export shim, the
`BERLIN_TZ` and duplicate `MS_PER_DAY` constants, the verbose
`AdminSectionPage` docstring, the `useEffect` paraphrase comment in
`ai-section`, and rewrites the `_exhaustive: never` exhaustiveness
guard to the modern `slug satisfies never` form. `UserManagementSection`
now reads `currentUserId` via `useAuth()` instead of accepting it as a
prop.

Findings 1, 2, 3, 4, 6, 7, 8, 11, 15, 16, 17 from
.planning/phase-6-simplify-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:41:02 +02:00

173 lines
5.3 KiB
TypeScript

"use client";
/**
* Client-only renderer that picks the right admin section component for
* a given slug. Lives next to the route page so the shell can stay a
* server component while the auth-gated body remains a client component.
*
* This is the equivalent of `SECTION_COMPONENTS` in
* `src/app/settings/[section]/page.tsx`, lifted to a separate file so
* the route page can be a server component (Settings can keep
* `"use client"`-leaning components in its page because none of them
* read `useAuth()` to gate rendering).
*/
import type { JSX } from "react";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { ApiTokenOverviewSection } from "@/components/admin/api-token-overview-section";
import { BackupsSection } from "@/components/admin/backups-section";
import { DangerZoneSection } from "@/components/admin/danger-zone-section";
import { FeedbackInboxSection } from "@/components/admin/feedback-inbox-section";
import { GeneralSettingsSection } from "@/components/admin/general-settings-section";
import { IntegrationsGroupSection } from "@/components/admin/integrations-group-section";
import { LoginOverviewSection } from "@/components/admin/login-overview-section";
import { RemindersSection } from "@/components/admin/reminders-section";
import { ServicesSection } from "@/components/admin/services-section";
import { SystemStatusSection } from "@/components/admin/system-status-section";
import { UserManagementSection } from "@/components/admin/user-management-section";
import type { AdminSectionSlug } from "@/components/admin/section-slugs";
interface RendererProps {
slug: AdminSectionSlug;
}
export function AdminSectionRenderer({
slug,
}: RendererProps): JSX.Element | null {
const { user } = useAuth();
const { t } = useTranslations();
// The admin shell layout already gates non-admins via `<AuthShell>`,
// but the auth hook is async — return nothing on the first render so
// we don't flash sub-section UI to a non-admin who's about to get
// redirected to `/`.
if (!user || user.role !== "ADMIN") return null;
switch (slug) {
case "system-status":
return (
<SectionFrame
title={t("admin.section.system-status.title")}
subtitle={t("admin.section.system-status.subtitle")}
>
<SystemStatusSection />
</SectionFrame>
);
case "general":
return (
<SectionFrame
title={t("admin.section.general.title")}
subtitle={t("admin.section.general.subtitle")}
>
<GeneralSettingsSection />
</SectionFrame>
);
case "services":
return (
<SectionFrame
title={t("admin.section.services.title")}
subtitle={t("admin.section.services.subtitle")}
>
<ServicesSection />
</SectionFrame>
);
case "integrations":
return (
<SectionFrame
title={t("admin.section.integrations.title")}
subtitle={t("admin.section.integrations.subtitle")}
>
<IntegrationsGroupSection />
</SectionFrame>
);
case "feedback":
return (
<SectionFrame
title={t("admin.section.feedback.title")}
subtitle={t("admin.section.feedback.subtitle")}
>
<FeedbackInboxSection />
</SectionFrame>
);
case "reminders":
return (
<SectionFrame
title={t("admin.section.reminders.title")}
subtitle={t("admin.section.reminders.subtitle")}
>
<RemindersSection />
</SectionFrame>
);
case "users":
return (
<SectionFrame
title={t("admin.section.users.title")}
subtitle={t("admin.section.users.subtitle")}
>
<UserManagementSection />
</SectionFrame>
);
case "api-tokens":
return (
<SectionFrame
title={t("admin.section.api-tokens.title")}
subtitle={t("admin.section.api-tokens.subtitle")}
>
<ApiTokenOverviewSection />
</SectionFrame>
);
case "login-overview":
return (
<SectionFrame
title={t("admin.section.login-overview.title")}
subtitle={t("admin.section.login-overview.subtitle")}
>
<LoginOverviewSection />
</SectionFrame>
);
case "backups":
return (
<SectionFrame
title={t("admin.section.backups.title")}
subtitle={t("admin.section.backups.subtitle")}
>
<BackupsSection />
</SectionFrame>
);
case "danger-zone":
return (
<SectionFrame
title={t("admin.section.danger-zone.title")}
subtitle={t("admin.section.danger-zone.subtitle")}
>
<DangerZoneSection />
</SectionFrame>
);
default:
slug satisfies never;
return null;
}
}
interface SectionFrameProps {
title: string;
subtitle?: string;
children: React.ReactNode;
}
function SectionFrame({ title, subtitle, children }: SectionFrameProps) {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
{subtitle && (
<p className="text-muted-foreground text-sm">{subtitle}</p>
)}
</div>
{children}
</div>
);
}