mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
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>
173 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|