diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 8bb13132c..c6d9a4cbe 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -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 ( -
-
-

- {t("admin.title")} -

-

{t("admin.subtitle")}

-
- - {settingsError && ( -
- {t("admin.adminSettingsLoadError")} -
- )} - - - +
- +
+

+ {t("admin.title")} +

+

{t("admin.subtitle")}

+
-

- {t("admin.sectionGeneral")} -

- - + {settingsError && ( +
+ {t("admin.adminSettingsLoadError")} +
+ )} -

- {t("admin.sectionIntegrations")} -

- - - - + -

- {t("admin.feedback.sectionTitle")} -

- - -

- {t("admin.sectionMedication")} -

- - -

- {t("admin.sectionManagement")} -

- - - - +
+

+ {t("admin.shell.sectionsNav")} +

+
    + {ADMIN_SECTIONS.map((section) => { + const Icon = section.icon; + return ( +
  • + + + +
  • + ); + })} +
+
-
+ ); } diff --git a/src/components/admin/api-token-overview-section.tsx b/src/components/admin/api-token-overview-section.tsx index 6e76e5b5c..48f8931f3 100644 --- a/src/components/admin/api-token-overview-section.tsx +++ b/src/components/admin/api-token-overview-section.tsx @@ -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); diff --git a/src/components/admin/bug-report-section.tsx b/src/components/admin/bug-report-section.tsx index b0cd9a392..b1bbc0b32 100644 --- a/src/components/admin/bug-report-section.tsx +++ b/src/components/admin/bug-report-section.tsx @@ -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(); diff --git a/src/components/admin/danger-zone-section.tsx b/src/components/admin/danger-zone-section.tsx index 3476476ce..ba5c6b33f 100644 --- a/src/components/admin/danger-zone-section.tsx +++ b/src/components/admin/danger-zone-section.tsx @@ -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(null); diff --git a/src/components/admin/feedback-inbox-section.tsx b/src/components/admin/feedback-inbox-section.tsx index 42a8d52c4..b187189b1 100644 --- a/src/components/admin/feedback-inbox-section.tsx +++ b/src/components/admin/feedback-inbox-section.tsx @@ -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(); diff --git a/src/components/admin/general-settings-section.tsx b/src/components/admin/general-settings-section.tsx index bda40aa42..acfea9162 100644 --- a/src/components/admin/general-settings-section.tsx +++ b/src/components/admin/general-settings-section.tsx @@ -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(); diff --git a/src/components/admin/glitchtip-section.tsx b/src/components/admin/glitchtip-section.tsx index eae4189c6..818c017f7 100644 --- a/src/components/admin/glitchtip-section.tsx +++ b/src/components/admin/glitchtip-section.tsx @@ -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(); diff --git a/src/components/admin/integrations-group-section.tsx b/src/components/admin/integrations-group-section.tsx new file mode 100644 index 000000000..b230485e5 --- /dev/null +++ b/src/components/admin/integrations-group-section.tsx @@ -0,0 +1,26 @@ +"use client"; + +/** + * `` — 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 ( +
+ + + + +
+ ); +} diff --git a/src/components/admin/login-overview-section.tsx b/src/components/admin/login-overview-section.tsx index 548debca6..c50d9cde1 100644 --- a/src/components/admin/login-overview-section.tsx +++ b/src/components/admin/login-overview-section.tsx @@ -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"); diff --git a/src/components/admin/reminders-section.tsx b/src/components/admin/reminders-section.tsx index e36133a93..6a560a491 100644 --- a/src/components/admin/reminders-section.tsx +++ b/src/components/admin/reminders-section.tsx @@ -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(); diff --git a/src/components/admin/services-section.tsx b/src/components/admin/services-section.tsx index 4b9eec39e..7980bb7a0 100644 --- a/src/components/admin/services-section.tsx +++ b/src/components/admin/services-section.tsx @@ -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(); diff --git a/src/components/admin/system-status-section.tsx b/src/components/admin/system-status-section.tsx index 527edc1be..f54995da2 100644 --- a/src/components/admin/system-status-section.tsx +++ b/src/components/admin/system-status-section.tsx @@ -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(); diff --git a/src/components/admin/umami-section.tsx b/src/components/admin/umami-section.tsx index 6c696af09..28db85189 100644 --- a/src/components/admin/umami-section.tsx +++ b/src/components/admin/umami-section.tsx @@ -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(); diff --git a/src/components/admin/web-push-vapid-section.tsx b/src/components/admin/web-push-vapid-section.tsx index 4cf6e1c40..7c1c79dae 100644 --- a/src/components/admin/web-push-vapid-section.tsx +++ b/src/components/admin/web-push-vapid-section.tsx @@ -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();