mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
refactor(admin): the console speaks the Settings vocabulary without an accent
Twenty-six of twenty-eight admin files already used the Settings card and header. What made the console read as its own system was everything on top of that: three hand-rolled cards, a header slot that meant five different things, and a heading the frame took but never painted. - `SectionFrame` drops `title` and `subtitle`. Seventeen call sites passed both into a component that has rendered neither since v1.18.6.1 — the shell reads the same keys itself. A prop contract nothing honours is a promise the next reader believes. - The invite card, the four host-metric headers, and the `/admin` welcome card stop hand-rolling `bg-card border rounded-xl` shells with their own padding steps and their own heading weights. `text-sm` as a card title on the first card of a page, directly above a correct one, was the first thing the eye landed on. - The header status slot carries status. The backups run, the invite create, the danger-zone wipe, and the user filters come out of it; the app-log refresh stays but reads as the secondary it is. - Loading states paint their card and header. Encryption returned a bare spinner row and then a card, so the section jumped when the status landed — the fix `coach-feedback-section.tsx` already carried, applied where it was missing. Same for the notification-status card. - One label/value tile. `StatusItem`, the encryption section's private `Stat`, the user-management inline panels and the backups upload box were four fills and three paddings for one role. - The overview stopped printing the running version, build SHA and build time twice, thirty pixels apart. The version tile owns build identity — it is the one that checks for a newer release; the snapshot owns what is running. - `ai-server-key` had three paragraphs stacked in a muted `text-xs` slot; one sentence stays there and the two that carry information move into the body as foreground. The central-codex consent copy moves the same way, and its disconnect is `variant="destructive"` — which `ConfirmButton` refused to accept, so every destructive confirm in the tree had been hand-tinting an outline instead. - Leftovers: the `-mt-2` correcting the card's own rhythm, two alpha hovers on text, `text-[10px]` badges, the redundant `space-y-6` wrapper, the empty state repeating the header's CTA, and the `mt-6` a card added inside a frame that already spaces its children.
This commit is contained in:
1 parent
b2e1aa0cde
commit
330f60115a
27 files changed
+472
-569
No files matched your search
@@ -15,7 +15,6 @@
|
||||
import type { JSX } from "react";
|
||||
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { AboutSection } from "@/components/settings/about-section";
|
||||
import { AiQualitySection } from "@/components/admin/ai-quality-section";
|
||||
import { AiServerKeySection } from "@/components/admin/ai-server-key-section";
|
||||
@@ -46,7 +45,6 @@ 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
|
||||
@@ -57,37 +55,25 @@ export function AdminSectionRenderer({
|
||||
switch (slug) {
|
||||
case "system-status":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.system-status.title")}
|
||||
subtitle={t("admin.section.system-status.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<SystemStatusSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "general":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.general.title")}
|
||||
subtitle={t("admin.section.general.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<GeneralSettingsSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "services":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.services.title")}
|
||||
subtitle={t("admin.section.services.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<ServicesSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "integrations":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.integrations.title")}
|
||||
subtitle={t("admin.section.integrations.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<IntegrationsGroupSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
@@ -99,10 +85,7 @@ export function AdminSectionRenderer({
|
||||
// `module-availability` section: it gates EVERY module, not just the
|
||||
// coach, so it no longer belongs stacked under Coach.
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.coach.title")}
|
||||
subtitle={t("admin.section.coach.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<AssistantSection />
|
||||
<AiServerKeySection />
|
||||
<CentralCodexSection />
|
||||
@@ -115,28 +98,19 @@ export function AdminSectionRenderer({
|
||||
// admin section (was stacked under Coach). Reconciled name: the
|
||||
// user-facing "Module" settings pick from what is available here.
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.module-availability.title")}
|
||||
subtitle={t("admin.section.module-availability.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<ModuleAvailabilitySection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "reminders":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.reminders.title")}
|
||||
subtitle={t("admin.section.reminders.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<RemindersSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "users":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.users.title")}
|
||||
subtitle={t("admin.section.users.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<UserManagementSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
@@ -145,64 +119,43 @@ export function AdminSectionRenderer({
|
||||
// the full table (status, redemptions, revocation) is a workflow
|
||||
// of its own and was drowning under the account list.
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.invites.title")}
|
||||
subtitle={t("admin.section.invites.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<InviteTokensSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "api-tokens":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.api-tokens.title")}
|
||||
subtitle={t("admin.section.api-tokens.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<ApiTokenOverviewSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "login-overview":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.login-overview.title")}
|
||||
subtitle={t("admin.section.login-overview.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<LoginOverviewSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "app-logs":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.app-logs.title")}
|
||||
subtitle={t("admin.section.app-logs.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<AppLogPreviewSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "backups":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.backups.title")}
|
||||
subtitle={t("admin.section.backups.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<BackupsSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "encryption":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.encryption.title")}
|
||||
subtitle={t("admin.section.encryption.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<EncryptionSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
case "danger-zone":
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.danger-zone.title")}
|
||||
subtitle={t("admin.section.danger-zone.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<DangerZoneSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
@@ -212,10 +165,7 @@ export function AdminSectionRenderer({
|
||||
// section so it gets the same visible heading + subtitle (the one admin
|
||||
// page that was bypassing the frame and showing no heading).
|
||||
return (
|
||||
<SectionFrame
|
||||
title={t("admin.section.about.title")}
|
||||
subtitle={t("admin.section.about.subtitle")}
|
||||
>
|
||||
<SectionFrame>
|
||||
<AboutSection />
|
||||
</SectionFrame>
|
||||
);
|
||||
@@ -225,17 +175,13 @@ export function AdminSectionRenderer({
|
||||
}
|
||||
}
|
||||
|
||||
interface SectionFrameProps {
|
||||
// v1.18.6.1 — the visible heading + subtitle moved to `<AdminShell>`, which
|
||||
// places them in their own grid row so the left nav lines up with the first
|
||||
// card. `title` / `subtitle` are still passed by every call site (and remain
|
||||
// the i18n source of truth the shell reads), but the frame no longer paints
|
||||
// them — it is now just the `space-y-6` card-stack wrapper.
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function SectionFrame({ children }: SectionFrameProps) {
|
||||
/**
|
||||
* The card-stack wrapper for a section. It took a `title` and a `subtitle`
|
||||
* from all seventeen call sites and rendered neither — the visible heading
|
||||
* moved to `<AdminShell>` in v1.18.6.1, which reads the same i18n keys
|
||||
* itself. A prop contract nothing honours is a promise a later reader
|
||||
* believes, so it is gone.
|
||||
*/
|
||||
function SectionFrame({ children }: { children: React.ReactNode }) {
|
||||
return <div className="space-y-6">{children}</div>;
|
||||
}
|
||||
@@ -41,22 +41,6 @@ export default function AdminOverviewPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Welcome card — greeting with admin context indicator. */}
|
||||
<section
|
||||
aria-labelledby="admin-overview-welcome-heading"
|
||||
className="bg-card border-border rounded-xl border p-6"
|
||||
>
|
||||
<h2
|
||||
id="admin-overview-welcome-heading"
|
||||
className="text-lg font-semibold"
|
||||
>
|
||||
{t("admin.overview.welcomeTitle", { name: user.username })}
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
{t("admin.overview.welcomeSubtitle")}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<VersionTileSection />
|
||||
|
||||
<SystemStatusSummary />
|
||||
|
||||
Reference in new issue
Block a user