import { lazy, Suspense, memo, useEffect } from 'react'
import { Routes, Route, Navigate, useLocation } from 'react-router'
import { Toaster } from '@/components/ui/sonner'
import { isLoggedIn, getUser } from '@/lib/auth-storage'
import { routes, findRouteMeta } from '@/router/routes'
import ErrorBoundary from '@/components/ErrorBoundary'
import { LicenseProvider } from '@/hooks/useLicense'
import { useTranslation } from 'react-i18next'
const Layout = lazy(() => import('@/components/Layout'))
// 路由守卫 - 未登录跳登录页并携带来源路径(登录成功后回跳,见 lib/redirect.ts)
const RequireAuth = memo(function RequireAuth({ children }: { children: React.ReactNode }) {
const location = useLocation()
if (!isLoggedIn()) {
const from = location.pathname + location.search
return
}
if (getUser()?.role !== 'admin' && location.pathname !== '/logs') return
// 动态导入Layout以避免在登录页面加载它
return {children}
})
// 懒加载布局包装器(Layout chunk 加载期间的整屏占位)
function LazyLayout({ children }: { children: React.ReactNode }) {
return (
}>
{children}
)
}
// 整屏加载占位
function AppLoadingFallback() {
return (
)
}
// document.title 由路由 meta 单源驱动(meta.title 为 i18n key,common 命名空间)
function useDocumentTitle() {
const location = useLocation()
const { t, i18n } = useTranslation()
useEffect(() => {
const titleKey = findRouteMeta(location.pathname)?.title
const title = titleKey ? t(titleKey) : ''
document.title = title && location.pathname !== '/login' ? `${title} · LokiStack` : 'LokiStack'
}, [location.pathname, t, i18n.language])
}
/**
* App 根组件
* - 路由配置集中在 router/routes.tsx(meta 驱动标题/菜单)
* - 页面全部 React.lazy 懒加载实现代码分割
* - 页面级 Suspense 在 Layout 的内容区,切换页面时布局不闪
*/
const App = memo(function App() {
useDocumentTitle()
return (
{routes.map((route) => (
{/* 页面级错误边界:单页崩溃/chunk 加载失败只影响当前页,侧栏与其它路由仍可用 */}
{}
) : (
}>
)
}
/>
))}
)
})
export default App