Files
live/frontend/src/App.tsx
T

102 lines
3.5 KiB
TypeScript

import { Component, lazy, Suspense, useState, useEffect, type ErrorInfo, type ReactNode } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { Button, ConfigProvider, Result } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import theme from './theme/anthropic'
import { BrandProvider, useBrand } from './brand/BrandContext'
import { verifyToken } from './api/auth'
import { getToken } from './api/client'
const HomePage = lazy(() => import('./viewer/pages/HomePage'))
const WatchPage = lazy(() => import('./viewer/pages/WatchPage'))
const AdminLayout = lazy(() => import('./admin/AdminLayout'))
const LoginPage = lazy(() => import('./admin/LoginPage'))
const Dashboard = lazy(() => import('./admin/pages/Dashboard'))
const LiveBoard = lazy(() => import('./admin/pages/LiveBoard'))
const StreamList = lazy(() => import('./admin/pages/StreamList'))
const StreamForm = lazy(() => import('./admin/pages/StreamForm'))
const ReplayEditor = lazy(() => import('./admin/pages/ReplayEditor'))
const NodeManager = lazy(() => import('./admin/pages/NodeManager'))
const SettingsPage = lazy(() => import('./admin/pages/Settings'))
class PageErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
state = { failed: false }
static getDerivedStateFromError() {
return { failed: true }
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Page render failed', error, info)
}
render() {
if (this.state.failed) {
return (
<Result
status="error"
title="页面加载失败"
subTitle="页面遇到异常,请重新加载后再试。"
extra={<Button type="primary" onClick={() => window.location.reload()}>重新加载</Button>}
/>
)
}
return this.props.children
}
}
function AdminGuard({ children }: { children: React.ReactNode }) {
const [authed, setAuthed] = useState<boolean | null>(null)
useEffect(() => {
const token = getToken()
if (!token) { setAuthed(false); return }
verifyToken().then(setAuthed)
}, [])
if (authed === null) return null
if (!authed) return <Navigate to="/admin/login" replace />
return <>{children}</>
}
function AppRoutes() {
const { brand } = useBrand()
useEffect(() => { document.title = brand.brand_name }, [brand.brand_name])
return (
<BrowserRouter>
<PageErrorBoundary>
<Suspense fallback={<div className="route-loading" role="status" aria-label="页面加载中" />}>
<Routes>
<Route path="/watch/:id" element={<WatchPage />} />
<Route path="/admin/login" element={<LoginPage />} />
<Route path="/admin" element={
<AdminGuard>
<AdminLayout />
</AdminGuard>
}>
<Route index element={<Dashboard />} />
<Route path="board" element={<LiveBoard />} />
<Route path="streams" element={<StreamList />} />
<Route path="streams/new" element={<StreamForm />} />
<Route path="streams/:id/edit" element={<ReplayEditor />} />
<Route path="nodes" element={<NodeManager />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
<Route path="/" element={<HomePage />} />
</Routes>
</Suspense>
</PageErrorBoundary>
</BrowserRouter>
)
}
export default function App() {
return (
<ConfigProvider theme={theme} locale={zhCN}>
<BrandProvider>
<AppRoutes />
</BrandProvider>
</ConfigProvider>
)
}