mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
refactor(admin): move sections to dynamic-route pattern + reduce overview
Each admin section component now accepts an optional `id` prop (was required) so it works under the new /admin/[section] dynamic route without scroll-anchor wrapping. Adds <IntegrationsGroupSection> as the composite for /admin/integrations (Umami + GlitchTip + WebPush + Bug Report). Reduces /admin to a true overview: status-card grid + welcome card + quick-jump menu of sub-sections; the long monolithic scroll is gone. Bundle-size win: each section now ships under its own route bundle, so the overview no longer drags Umami/GlitchTip/Feedback/Users/etc. trees. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d8b71b3848
commit
12b280bb55
14 files changed
+103
-83
No files matched your search
+65
-71
@@ -1,92 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { ApiTokenOverviewSection } from "@/components/admin/api-token-overview-section";
|
||||
import { BugReportSection } from "@/components/admin/bug-report-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 { GlitchtipSection } from "@/components/admin/glitchtip-section";
|
||||
import { LoginOverviewSection } from "@/components/admin/login-overview-section";
|
||||
import { RemindersSection } from "@/components/admin/reminders-section";
|
||||
import { ServicesSection } from "@/components/admin/services-section";
|
||||
import { AdminShell, ADMIN_SECTIONS } from "@/components/admin/admin-shell";
|
||||
import { StatusCardGrid } from "@/components/admin/status-card-grid";
|
||||
import { SystemStatusSection } from "@/components/admin/system-status-section";
|
||||
import { UmamiSection } from "@/components/admin/umami-section";
|
||||
import { UserManagementSection } from "@/components/admin/user-management-section";
|
||||
import { WebPushVapidSection } from "@/components/admin/web-push-vapid-section";
|
||||
import { useAdminSettings } from "@/components/admin/_shared";
|
||||
|
||||
export default function AdminPage() {
|
||||
/**
|
||||
* `/admin` — overview landing page. The long monolithic admin scroll has
|
||||
* moved to per-section dynamic routes under `/admin/[section]`; this page
|
||||
* is now the system-wide status grid plus a quick-jump menu of the
|
||||
* available sub-sections.
|
||||
*
|
||||
* Bundle-size note: by extracting each section into its own route, the
|
||||
* overview no longer ships the umami / glitchtip / feedback /
|
||||
* users / etc. component trees. See `.planning/phase-4b-report.md`.
|
||||
*/
|
||||
export default function AdminOverviewPage() {
|
||||
const { user } = useAuth();
|
||||
const { t } = useTranslations();
|
||||
// P19: surface admin-settings fetch failures once at the top of the
|
||||
// page. Many child sections share `useAdminSettings()` and render
|
||||
// defaults silently on error — the banner makes the failure visible
|
||||
// so the admin knows the toggles below aren't reflecting real state.
|
||||
// Surface admin-settings fetch failures once at the top of the page.
|
||||
// Many sub-sections share `useAdminSettings()` and render defaults
|
||||
// silently on error — the banner makes the failure visible so the
|
||||
// admin knows the toggles below aren't reflecting real state.
|
||||
const { isError: settingsError } = useAdminSettings();
|
||||
|
||||
if (!user || user.role !== "ADMIN") return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
{t("admin.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">{t("admin.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
{settingsError && (
|
||||
<div
|
||||
role="alert"
|
||||
className="text-destructive bg-destructive/10 border-destructive/30 rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
{t("admin.adminSettingsLoadError")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<StatusCardGrid />
|
||||
|
||||
<AdminShell>
|
||||
<div className="space-y-6">
|
||||
<SystemStatusSection id="section-system-status" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
{t("admin.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">{t("admin.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-muted-foreground text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.sectionGeneral")}
|
||||
</h2>
|
||||
<GeneralSettingsSection id="section-admin-general" />
|
||||
<ServicesSection id="section-admin-services" />
|
||||
{settingsError && (
|
||||
<div
|
||||
role="alert"
|
||||
className="text-destructive bg-destructive/10 border-destructive/30 rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
{t("admin.adminSettingsLoadError")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 className="text-muted-foreground text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.sectionIntegrations")}
|
||||
</h2>
|
||||
<UmamiSection id="section-admin-umami" />
|
||||
<GlitchtipSection id="section-admin-glitchtip" />
|
||||
<WebPushVapidSection id="section-admin-webpush" />
|
||||
<BugReportSection id="section-admin-bugreport" />
|
||||
<StatusCardGrid />
|
||||
|
||||
<h2 className="text-muted-foreground text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.feedback.sectionTitle")}
|
||||
</h2>
|
||||
<FeedbackInboxSection id="section-admin-feedback" />
|
||||
|
||||
<h2 className="text-muted-foreground text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.sectionMedication")}
|
||||
</h2>
|
||||
<RemindersSection id="section-admin-reminders" />
|
||||
|
||||
<h2 className="text-muted-foreground text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.sectionManagement")}
|
||||
</h2>
|
||||
<UserManagementSection
|
||||
id="section-user-management"
|
||||
currentUserId={user.id}
|
||||
/>
|
||||
<ApiTokenOverviewSection id="section-api-tokens" />
|
||||
<LoginOverviewSection id="section-login-overview" />
|
||||
<DangerZoneSection id="section-danger-zone" />
|
||||
<div>
|
||||
<h2 className="text-muted-foreground mb-3 text-sm font-semibold tracking-wider uppercase">
|
||||
{t("admin.shell.sectionsNav")}
|
||||
</h2>
|
||||
<ul className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{ADMIN_SECTIONS.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<li key={section.slug}>
|
||||
<Link
|
||||
href={`/admin/${section.slug}`}
|
||||
className="bg-card border-border hover:bg-accent flex items-center justify-between gap-3 rounded-lg border px-4 py-3 text-sm font-medium transition-colors"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Icon
|
||||
className="text-muted-foreground h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{t(section.titleKey)}
|
||||
</span>
|
||||
<ArrowRight
|
||||
className="text-muted-foreground h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { formatDate, formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { type ApiTokenInfo } from "./_shared";
|
||||
|
||||
export function ApiTokenOverviewSection({ id }: { id: string }) {
|
||||
export function ApiTokenOverviewSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { PasswordInput, useAdminSettings, useUpdateSettings } from "./_shared";
|
||||
|
||||
export function BugReportSection({ id }: { id: string }) {
|
||||
export function BugReportSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
|
||||
export function DangerZoneSection({ id }: { id: string }) {
|
||||
export function DangerZoneSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const [wipeMsg, setWipeMsg] = useState<string | null>(null);
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
useSystemStatus,
|
||||
} from "./_shared";
|
||||
|
||||
export function FeedbackInboxSection({ id }: { id: string }) {
|
||||
export function FeedbackInboxSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: status } = useSystemStatus();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Settings } from "lucide-react";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { SettingsToggle, useAdminSettings, useUpdateSettings } from "./_shared";
|
||||
|
||||
export function GeneralSettingsSection({ id }: { id: string }) {
|
||||
export function GeneralSettingsSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
useUpdateSettings,
|
||||
} from "./_shared";
|
||||
|
||||
export function GlitchtipSection({ id }: { id: string }) {
|
||||
export function GlitchtipSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* `<IntegrationsGroupSection>` — composite admin route at `/admin/integrations`.
|
||||
*
|
||||
* Wraps the four standalone integration sub-sections (Umami, GlitchTip, Web
|
||||
* Push, Bug Report) under one route so each existing component stays the
|
||||
* source of truth for its own settings while admins still get a single
|
||||
* landing page mirroring the v1.4 layout.
|
||||
*/
|
||||
|
||||
import { BugReportSection } from "./bug-report-section";
|
||||
import { GlitchtipSection } from "./glitchtip-section";
|
||||
import { UmamiSection } from "./umami-section";
|
||||
import { WebPushVapidSection } from "./web-push-vapid-section";
|
||||
|
||||
export function IntegrationsGroupSection() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<UmamiSection />
|
||||
<GlitchtipSection />
|
||||
<WebPushVapidSection />
|
||||
<BugReportSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { formatDateTime } from "@/lib/format";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { type AdminAuditEntry } from "./_shared";
|
||||
|
||||
export function LoginOverviewSection({ id }: { id: string }) {
|
||||
export function LoginOverviewSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [filter, setFilter] = useState<"all" | "failed">("all");
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
useUpdateSettings,
|
||||
} from "./_shared";
|
||||
|
||||
export function RemindersSection({ id }: { id: string }) {
|
||||
export function RemindersSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Bell, Globe, Key, MessageCircle } from "lucide-react";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { SettingsToggle, useAdminSettings, useUpdateSettings } from "./_shared";
|
||||
|
||||
export function ServicesSection({ id }: { id: string }) {
|
||||
export function ServicesSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -19,7 +19,7 @@ import { formatDateTime } from "@/lib/format";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import { StatusItem, useSystemStatus } from "./_shared";
|
||||
|
||||
export function SystemStatusSection({ id }: { id: string }) {
|
||||
export function SystemStatusSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
const { data: status, isError } = useSystemStatus();
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
useUpdateSettings,
|
||||
} from "./_shared";
|
||||
|
||||
export function UmamiSection({ id }: { id: string }) {
|
||||
export function UmamiSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { PasswordInput, useAdminSettings, useUpdateSettings } from "./_shared";
|
||||
|
||||
export function WebPushVapidSection({ id }: { id: string }) {
|
||||
export function WebPushVapidSection({ id }: { id?: string } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { data: settings } = useAdminSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
Reference in new issue
Block a user