'use client'; import { useEffect, useState } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import Link from 'next/link'; import { LayoutDashboard, Settings, Palette, Shield, Scale, ScrollText, LogOut, KeyRound, Puzzle, SwatchBook, Activity, Mail, Calendar, BookUser, HardDrive, ArrowLeft, Store, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useConfig } from '@/hooks/use-config'; import { useThemeStore } from '@/stores/theme-store'; import { getActiveAccountSlotHeaders } from '@/lib/auth/active-account-slot'; import { useAuthStore } from '@/stores/auth-store'; import { apiFetch } from '@/lib/browser-navigation'; const NAV_GROUPS = [ { label: 'Overview', items: [ { href: '/admin', label: 'Dashboard', icon: LayoutDashboard }, ], }, { label: 'Configuration', items: [ { href: '/admin/settings', label: 'Settings', icon: Settings }, { href: '/admin/branding', label: 'Branding', icon: Palette }, { href: '/admin/auth', label: 'Authentication', icon: Shield }, { href: '/admin/policy', label: 'Policy', icon: Scale }, ], }, { label: 'Extensions', items: [ { href: '/admin/plugins', label: 'Plugins', icon: Puzzle }, { href: '/admin/themes', label: 'Themes', icon: SwatchBook }, { href: '/admin/marketplace', label: 'Marketplace', icon: Store }, ], }, { label: 'System', items: [ { href: '/admin/telemetry', label: 'Telemetry', icon: Activity }, { href: '/admin/logs', label: 'Audit Log', icon: ScrollText }, ], }, ]; export default function AdminLayout({ children }: { children: React.ReactNode }) { const router = useRouter(); const pathname = usePathname(); const [authenticated, setAuthenticated] = useState(null); const [authError, setAuthError] = useState(null); const [isStalwartAdmin, setIsStalwartAdmin] = useState(false); const { appLogoLightUrl, appLogoDarkUrl, loginLogoLightUrl, loginLogoDarkUrl } = useConfig(); const resolvedTheme = useThemeStore((s) => s.resolvedTheme); const logoUrl = resolvedTheme === 'dark' ? (appLogoDarkUrl || appLogoLightUrl || loginLogoDarkUrl) : (appLogoLightUrl || appLogoDarkUrl || loginLogoLightUrl); useEffect(() => { if (pathname === '/admin/login') return; let cancelled = false; async function checkAuth() { try { const jmapHeaders = getActiveAccountSlotHeaders(); const res = await apiFetch('/api/admin/auth', { headers: jmapHeaders }); const data = await res.json(); if (cancelled) return; const stalwartAdmin = data.stalwartAdmin === true; setIsStalwartAdmin(stalwartAdmin); // If neither password-based admin nor Stalwart admin, redirect away if (!data.enabled && !stalwartAdmin) { router.replace('/'); return; } if (data.authenticated) { setAuthenticated(true); return; } // If Stalwart admin but not yet authenticated, auto-login if (stalwartAdmin) { const loginRes = await apiFetch('/api/admin/auth', { method: 'POST', headers: { 'Content-Type': 'application/json', ...jmapHeaders }, body: JSON.stringify({ stalwartAuth: true }), }); if (cancelled) return; if (loginRes.ok) { setAuthenticated(true); return; } const body = await loginRes.json().catch(() => ({})); setAuthError(body?.error || `Admin auto-login failed (HTTP ${loginRes.status})`); setAuthenticated(false); return; } router.replace('/admin/login'); } catch (err) { if (cancelled) return; setAuthError(err instanceof Error ? err.message : 'Network error during admin check'); setAuthenticated(false); } } checkAuth(); return () => { cancelled = true; }; }, [pathname, router]); async function handleLogout() { await apiFetch('/api/admin/auth', { method: 'DELETE' }); router.replace('/admin/login'); } // Don't gate the login page if (pathname === '/admin/login') { return <>{children}; } return (
{/* Slim webmail nav rail */} {/* Admin Sidebar */} {/* Main content */}
{authError ? (

Admin authentication failed

{authError}

) : authenticated === null ? (
Loading admin panel…
) : authenticated ? ( children ) : null}
); }