Add React account and user administration flows, including TOTP and SSO handling. Route /next SSO callbacks to React while preserving the existing Vue and native app behavior, and cover the routing policy with API tests.
95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
import { lazy, Suspense, useEffect } from 'react'
|
|
import { App as AntdApp, ConfigProvider, Skeleton } from 'antd'
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
|
import zhCN from 'antd/locale/zh_CN'
|
|
import { useAuthStore } from '../stores/auth'
|
|
|
|
const LoginPage = lazy(() => import('../pages/LoginPage'))
|
|
const DashboardPage = lazy(() => import('../pages/DashboardPage'))
|
|
const AccountPage = lazy(() => import('../pages/AccountPage'))
|
|
const UsersPage = lazy(() => import('../pages/UsersPage'))
|
|
const SsoCallbackPage = lazy(() => import('../pages/SsoCallbackPage'))
|
|
const SsoBindPage = lazy(() => import('../pages/SsoBindPage'))
|
|
const AppShell = lazy(() => import('../layout/AppShell'))
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 30_000,
|
|
retry: 1,
|
|
refetchOnWindowFocus: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
function ProtectedRoute() {
|
|
const token = useAuthStore((state) => state.token)
|
|
const location = useLocation()
|
|
if (!token) return <Navigate to={`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`} replace />
|
|
return <AppShell />
|
|
}
|
|
|
|
function SessionExpiryBridge() {
|
|
const navigate = useNavigate()
|
|
useEffect(() => {
|
|
const onExpired = () => navigate(`/login?redirect=${encodeURIComponent(location.pathname + location.search + location.hash)}`, { replace: true })
|
|
window.addEventListener('mingxu-session-expired', onExpired)
|
|
return () => window.removeEventListener('mingxu-session-expired', onExpired)
|
|
}, [navigate])
|
|
return null
|
|
}
|
|
|
|
function AppRoutes() {
|
|
return (
|
|
<Suspense fallback={<div style={{ maxWidth: 1180, margin: '64px auto', padding: 24 }}><Skeleton active paragraph={{ rows: 8 }} /></div>}>
|
|
<SessionExpiryBridge />
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route path="/sso/callback" element={<SsoCallbackPage />} />
|
|
<Route path="/sso/bind" element={<SsoBindPage />} />
|
|
<Route element={<ProtectedRoute />}>
|
|
<Route path="/dashboard" element={<DashboardPage />} />
|
|
<Route path="/account" element={<AccountPage />} />
|
|
<Route path="/users" element={<UsersPage />} />
|
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
</Route>
|
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
</Routes>
|
|
</Suspense>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<ConfigProvider
|
|
locale={zhCN}
|
|
theme={{
|
|
token: {
|
|
colorPrimary: '#284583',
|
|
colorSuccess: '#087f73',
|
|
colorWarning: '#b8781d',
|
|
colorError: '#b42318',
|
|
colorInfo: '#476282',
|
|
colorText: '#182033',
|
|
colorBgLayout: '#f2f4f7',
|
|
borderRadius: 7,
|
|
fontFamily: '"Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif',
|
|
},
|
|
components: {
|
|
Button: { controlHeightLG: 44, fontWeight: 650 },
|
|
Input: { controlHeightLG: 44 },
|
|
Layout: { headerBg: '#ffffff', siderBg: '#162858' },
|
|
Menu: { darkItemBg: '#162858', darkItemSelectedBg: '#284583', darkItemSelectedColor: '#ffffff' },
|
|
},
|
|
}}
|
|
>
|
|
<AntdApp>
|
|
<QueryClientProvider client={queryClient}>
|
|
<BrowserRouter basename="/next"><AppRoutes /></BrowserRouter>
|
|
</QueryClientProvider>
|
|
</AntdApp>
|
|
</ConfigProvider>
|
|
)
|
|
}
|