'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, 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'; 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/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 [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') { checkAuth(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); function getJmapHeaders(): Record { return getActiveAccountSlotHeaders(); } async function checkAuth() { try { const jmapHeaders = getJmapHeaders(); const res = await fetch('/api/admin/auth', { headers: jmapHeaders }); const data = await res.json(); 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 fetch('/api/admin/auth', { method: 'POST', headers: { 'Content-Type': 'application/json', ...jmapHeaders }, body: JSON.stringify({ stalwartAuth: true }), }); if (loginRes.ok) { setAuthenticated(true); return; } } router.replace('/admin/login'); } catch { router.replace('/admin/login'); } } async function handleLogout() { await fetch('/api/admin/auth', { method: 'DELETE' }); router.replace('/admin/login'); } // Don't gate the login page if (pathname === '/admin/login') { return <>{children}; } if (authenticated === null) { return (
Loading...
); } return (
{/* Slim webmail nav rail */} {/* Admin Sidebar */} {/* Main content */}
{children}
); }