import { ApiOutlined, CheckCircleOutlined, CloudServerOutlined, CloseOutlined, DashboardOutlined, GlobalOutlined, InboxOutlined, KeyOutlined, MailOutlined, MenuOutlined, SafetyCertificateOutlined, SendOutlined, SettingOutlined, UserOutlined, WarningOutlined } from '@ant-design/icons'; import { Avatar, Button, Drawer, Dropdown, Layout, Menu, Select, Space, Tag, Tooltip, Typography } from 'antd'; import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { useAppContext } from '../frontend/app-context'; import { useI18n } from '../frontend/i18n/react'; import { useMediaQuery } from '../frontend/use-media-query'; const { Header, Sider, Content } = Layout; interface NavigationItem { path: string; labelKey: string; icon: ReactNode; adminOnly?: boolean; } interface NavigationGroup { key: string; labelKey: string; items: NavigationItem[]; } export const navigationGroups: NavigationGroup[] = [ { key: 'operations', labelKey: 'nav.group.operations', items: [ { path: '/overview', labelKey: 'nav.overview', icon: }, { path: '/activity', labelKey: 'nav.activity', icon: }, { path: '/domains', labelKey: 'nav.domains', icon: }, { path: '/inbox', labelKey: 'nav.inbox', icon: } ] }, { key: 'integrations', labelKey: 'nav.group.integrations', items: [ { path: '/integrations/smtp', labelKey: 'nav.smtpIntegration', icon: }, { path: '/integrations/api-keys', labelKey: 'nav.apiKeys', icon: }, { path: '/integrations/webhooks', labelKey: 'nav.webhooks', icon: }, { path: '/integrations/dns', labelKey: 'nav.dnsIntegration', icon: } ] }, { key: 'system', labelKey: 'nav.group.system', items: [ { path: '/admin/users', labelKey: 'nav.adminCenter', icon: , adminOnly: true }, { path: '/settings', labelKey: 'nav.settings', icon: , adminOnly: true } ] } ]; export function visibleNavigation(isAdmin: boolean) { return navigationGroups .map((group) => ({ ...group, items: group.items.filter((item) => !item.adminOnly || isAdmin) })) .filter((group) => group.items.length > 0); } export function navigationSelection(pathname: string) { if (pathname.startsWith('/activity')) return '/activity'; if (pathname.startsWith('/domains')) return '/domains'; if (pathname.startsWith('/inbox')) return '/inbox'; if (pathname.startsWith('/integrations/smtp')) return '/integrations/smtp'; if (pathname.startsWith('/integrations/api-keys')) return '/integrations/api-keys'; if (pathname.startsWith('/integrations/webhooks')) return '/integrations/webhooks'; if (pathname.startsWith('/integrations/dns')) return '/integrations/dns'; if (pathname.startsWith('/admin')) return '/admin/users'; if (pathname.startsWith('/settings')) return '/settings'; return '/overview'; } export function AdminLayout() { const { locale, locales, setLocale, t } = useI18n(); const { user, config, logout } = useAppContext(); const location = useLocation(); const navigate = useNavigate(); const isDesktop = useMediaQuery('(min-width: 1024px)'); const [mobileNavigationOpen, setMobileNavigationOpen] = useState(false); const mainRef = useRef(null); const isAdmin = user?.role === 'admin'; useEffect(() => { setMobileNavigationOpen(false); const frame = window.requestAnimationFrame(() => mainRef.current?.focus({ preventScroll: true })); return () => window.cancelAnimationFrame(frame); }, [location.pathname]); useEffect(() => { if (isDesktop) setMobileNavigationOpen(false); }, [isDesktop]); const menuItems = useMemo(() => visibleNavigation(isAdmin).map((group) => ({ type: 'group' as const, key: group.key, label: t(group.labelKey), children: group.items.map((item) => ({ key: item.path, icon: item.icon, label: t(item.labelKey) })) })), [isAdmin, t]); const menu = ( navigate(key)} className="admin-menu" /> ); const environmentReady = Boolean(config?.mailHostname && config?.sendingIp); const environmentDetails = config ? `${config.mailHostname} · ${config.sendingIp || t('common.unsetSendingIp')}` : t('common.loadingConfig'); return ( {t('shell.skipToMain')} setMobileNavigationOpen(false)} closable={false} styles={{ body: { padding: 0, background: '#0F172A' } }} aria-label={t('shell.mainNavigation')} > setMobileNavigationOpen(false)} closeLabel={t('shell.closeNavigation')} /> } aria-label={t('shell.openNavigation')} onClick={() => setMobileNavigationOpen(true)} /> : } color={environmentReady ? 'success' : 'warning'} > {environmentReady ? t('shell.envReady') : t('shell.envNeedsSetup')} {user?.username || t('common.user')} {user?.email || '—'} ), disabled: true }, ...(isAdmin ? [{ key: 'settings', label: t('nav.settings'), onClick: () => navigate('/settings') }] : []), { type: 'divider' as const }, { key: 'logout', label: t('common.logout'), onClick: () => void logout() } ] }} > } /> {user?.username || t('common.user')} ); } function NavigationPanel({ menu, userName, role, onClose, closeLabel }: { menu: ReactNode; userName?: string; role?: string; onClose?: () => void; closeLabel?: string; }) { return ( MH MailHub Delivery Operations {onClose ? ( } aria-label={closeLabel} style={{ minHeight: 44, minWidth: 44 }} onClick={onClose} /> ) : null} {menu} {userName ? ( } className="sider-user-avatar" /> {userName} {role || '—'} ) : null} ); }