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 (
+
+
+
+
+ {t(section.titleKey)}
+
+
+
+
+ );
+ })}
+
+
-
+
);
}
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();