import { Suspense, lazy, useEffect, type ReactNode } from 'react' import { BrowserRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom' import { ConfigProvider, Spin } from 'antd' import deDE from 'antd/locale/de_DE' import enUS from 'antd/locale/en_US' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import AppLayout from './components/Layout/AppLayout' import apiClient, { isEnvelope } from './api/client' import { useAuthStore, type SessionUser } from './stores/auth' const LoginPage = lazy(() => import('./pages/Login')) const SetupPage = lazy(() => import('./pages/Setup')) const DashboardPage = lazy(() => import('./pages/Dashboard')) const DomainsPage = lazy(() => import('./pages/Domains')) const BackendsPage = lazy(() => import('./pages/Backends')) const RoutingRulesPage = lazy(() => import('./pages/RoutingRules')) const NetworksPage = lazy(() => import('./pages/Networks')) const IPAddressesPage = lazy(() => import('./pages/IPAddresses')) const ClusterPage = lazy(() => import('./pages/Cluster')) const SettingsPage = lazy(() => import('./pages/Settings')) const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false }, }, }) // Theme tokens 1:1 wie mail-gateway/enconf — colorPrimary, font, // borderRadius, controlHeight. enterprise.css ergänzt mit eigenen // Layout-Klassen (.app-layout, .sidebar, .header, …). const antdTheme = { token: { colorPrimary: '#0EA5E9', borderRadius: 6, borderRadiusLG: 8, fontSize: 13, fontWeightStrong: 600, fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", colorBgContainer: '#FFFFFF', colorBgLayout: '#F8FAFC', colorBorder: '#E2E8F0', colorText: '#0F172A', colorTextSecondary: '#64748B', controlHeight: 34, }, } function RequireAuth({ children }: { children: ReactNode }) { const user = useAuthStore((s) => s.user) const location = useLocation() if (!user) { return } return <>{children} } function SetupGate({ children }: { children: ReactNode }) { const location = useLocation() useEffect(() => { if (location.pathname.startsWith('/setup')) return apiClient.get('/setup/status').then((r) => { const env = r.data if (isEnvelope(env)) { const data = env.data as { completed?: boolean } | undefined if (data && data.completed === false) { window.location.replace('/setup') } } }).catch(() => { /* setup-gate is best-effort */ }) }, [location.pathname]) return <>{children} } export default function App() { const { i18n } = useTranslation() const antdLocale = i18n.language?.startsWith('de') ? deDE : enUS return ( }> useAuthStore.getState().set(u)} />} /> useAuthStore.getState().set(u)} />} /> }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ) }