164 lines
4.4 KiB
TypeScript
164 lines
4.4 KiB
TypeScript
import {useCallback, useEffect, useState} from 'react';
|
|
|
|
import {
|
|
formatAdminApiError,
|
|
getAdminMe,
|
|
isAdminApiError,
|
|
loginAdmin,
|
|
} from '../api/adminApiClient';
|
|
import type {
|
|
AdminSessionPayload,
|
|
ProfileRedeemCodeAdminResponse,
|
|
} from '../api/adminApiTypes';
|
|
import {
|
|
clearStoredAdminToken,
|
|
getStoredAdminToken,
|
|
setStoredAdminToken,
|
|
} from '../auth/adminAuthStore';
|
|
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage';
|
|
import {AdminLoginPage} from '../pages/AdminLoginPage';
|
|
import {AdminOverviewPage} from '../pages/AdminOverviewPage';
|
|
import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage';
|
|
import {AdminShell} from './AdminShell';
|
|
import type {AdminRouteId} from './adminRoutes';
|
|
import {resolveAdminRoute, routeHash} from './adminRoutes';
|
|
|
|
type SessionStatus = 'checking' | 'guest' | 'authenticated';
|
|
|
|
export function AdminApp() {
|
|
const [status, setStatus] = useState<SessionStatus>('checking');
|
|
const [admin, setAdmin] = useState<AdminSessionPayload | null>(null);
|
|
const [token, setToken] = useState('');
|
|
const [routeId, setRouteId] = useState<AdminRouteId>(() =>
|
|
resolveAdminRoute(window.location.hash),
|
|
);
|
|
const [loginNotice, setLoginNotice] = useState('');
|
|
// 兑换码页会随页签切换卸载,最近操作记录需要放在会话层保留。
|
|
const [redeemResult, setRedeemResult] =
|
|
useState<ProfileRedeemCodeAdminResponse | null>(null);
|
|
|
|
const clearSession = useCallback((message = '') => {
|
|
clearStoredAdminToken();
|
|
setToken('');
|
|
setAdmin(null);
|
|
setRedeemResult(null);
|
|
setStatus('guest');
|
|
setLoginNotice(message);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
const storedToken = getStoredAdminToken();
|
|
|
|
if (!storedToken) {
|
|
setStatus('guest');
|
|
return;
|
|
}
|
|
|
|
void getAdminMe(storedToken)
|
|
.then((response) => {
|
|
if (!isMounted) {
|
|
return;
|
|
}
|
|
|
|
setToken(storedToken);
|
|
setAdmin(response.admin);
|
|
setStatus('authenticated');
|
|
})
|
|
.catch((error: unknown) => {
|
|
if (!isMounted) {
|
|
return;
|
|
}
|
|
|
|
clearStoredAdminToken();
|
|
setToken('');
|
|
setAdmin(null);
|
|
setStatus('guest');
|
|
setLoginNotice(
|
|
isAdminApiError(error) && error.status === 401
|
|
? '登录状态已失效'
|
|
: formatAdminApiError(error),
|
|
);
|
|
});
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const handleHashChange = () => {
|
|
setRouteId(resolveAdminRoute(window.location.hash));
|
|
};
|
|
|
|
window.addEventListener('hashchange', handleHashChange);
|
|
return () => window.removeEventListener('hashchange', handleHashChange);
|
|
}, []);
|
|
|
|
const handleRouteChange = useCallback((nextRouteId: AdminRouteId) => {
|
|
setRouteId(nextRouteId);
|
|
const nextHash = routeHash(nextRouteId);
|
|
if (window.location.hash !== nextHash) {
|
|
window.location.hash = nextHash;
|
|
}
|
|
}, []);
|
|
|
|
const handleLogin = useCallback(async (username: string, password: string) => {
|
|
const response = await loginAdmin(username, password);
|
|
setStoredAdminToken(response.token);
|
|
setToken(response.token);
|
|
setAdmin(response.admin);
|
|
setRedeemResult(null);
|
|
setLoginNotice('');
|
|
setStatus('authenticated');
|
|
}, []);
|
|
|
|
const handleUnauthorized = useCallback(
|
|
(message = '登录状态已失效') => {
|
|
clearSession(message);
|
|
},
|
|
[clearSession],
|
|
);
|
|
|
|
const handleLogout = useCallback(() => {
|
|
clearSession('');
|
|
}, [clearSession]);
|
|
|
|
if (status === 'checking') {
|
|
return (
|
|
<main className="admin-loading-screen">
|
|
<div className="admin-loading-mark" />
|
|
<span>正在校验会话</span>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
if (status === 'guest' || !admin || !token) {
|
|
return <AdminLoginPage notice={loginNotice} onLogin={handleLogin} />;
|
|
}
|
|
|
|
return (
|
|
<AdminShell
|
|
admin={admin}
|
|
routeId={routeId}
|
|
onLogout={handleLogout}
|
|
onRouteChange={handleRouteChange}
|
|
>
|
|
{routeId === 'overview' ? (
|
|
<AdminOverviewPage token={token} onUnauthorized={handleUnauthorized} />
|
|
) : null}
|
|
{routeId === 'debug' ? (
|
|
<AdminDebugHttpPage token={token} onUnauthorized={handleUnauthorized} />
|
|
) : null}
|
|
{routeId === 'redeem' ? (
|
|
<AdminRedeemCodePage
|
|
result={redeemResult}
|
|
token={token}
|
|
onUnauthorized={handleUnauthorized}
|
|
onResultChange={setRedeemResult}
|
|
/>
|
|
) : null}
|
|
</AdminShell>
|
|
);
|
|
}
|