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 SSLPage = lazy(() => import('./pages/SSL'))
const FirewallPage = lazy(() => import('./pages/Firewall'))
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)} />} />
}>
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
)
}