import { Suspense, lazy, useCallback, useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import { LayoutDashboard, Upload, Cloud, Settings as SettingsIcon, LogOut, Menu, X, Server, Users, Globe, History, Loader2 } from 'lucide-react' import clsx from 'clsx' import LanguageToggle from '../components/LanguageToggle' import { useI18n } from '../i18n' const AccountManagerContainer = lazy(() => import('../features/account/AccountManagerContainer')) const ApiTesterContainer = lazy(() => import('../features/apiTester/ApiTesterContainer')) const ChatHistoryContainer = lazy(() => import('../features/chatHistory/ChatHistoryContainer')) const BatchImport = lazy(() => import('../components/BatchImport')) const VercelSyncContainer = lazy(() => import('../features/vercel/VercelSyncContainer')) const SettingsContainer = lazy(() => import('../features/settings/SettingsContainer')) const ProxyManagerContainer = lazy(() => import('../features/proxy/ProxyManagerContainer')) function TabLoadingFallback({ label }) { return (
{label}
) } export default function DashboardShell({ token, onLogout, config, fetchConfig, showMessage, message, onForceLogout, isVercel }) { const { t } = useI18n() const location = useLocation() const navigate = useNavigate() const [sidebarOpen, setSidebarOpen] = useState(false) const navItems = [ { id: 'accounts', label: t('nav.accounts.label'), icon: Users, description: t('nav.accounts.desc') }, { id: 'proxies', label: t('nav.proxies.label'), icon: Globe, description: t('nav.proxies.desc') }, { id: 'test', label: t('nav.test.label'), icon: Server, description: t('nav.test.desc') }, { id: 'history', label: t('nav.history.label'), icon: History, description: t('nav.history.desc') }, { id: 'import', label: t('nav.import.label'), icon: Upload, description: t('nav.import.desc') }, { id: 'vercel', label: t('nav.vercel.label'), icon: Cloud, description: t('nav.vercel.desc') }, { id: 'settings', label: t('nav.settings.label'), icon: SettingsIcon, description: t('nav.settings.desc') }, ] const tabIds = new Set(navItems.map(item => item.id)) const pathSegments = location.pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean) const routeSegments = pathSegments[0] === 'admin' ? pathSegments.slice(1) : pathSegments const pathTab = routeSegments[0] || '' const activeTab = tabIds.has(pathTab) ? pathTab : 'accounts' const adminBasePath = pathSegments[0] === 'admin' ? '/admin' : '' const activeNavItem = navItems.find(n => n.id === activeTab) const navigateToTab = useCallback((tabID) => { const nextPath = tabID === 'accounts' ? `${adminBasePath || ''}/` : `${adminBasePath}/${tabID}` navigate(nextPath) setSidebarOpen(false) }, [adminBasePath, navigate]) const authFetch = useCallback(async (url, options = {}) => { const headers = { ...options.headers, 'Authorization': `Bearer ${token}` } const res = await fetch(url, { ...options, headers }) if (res.status === 401) { onLogout() throw new Error(t('auth.expired')) } return res }, [onLogout, t, token]) const [versionInfo, setVersionInfo] = useState(null) useEffect(() => { let disposed = false async function loadVersion() { try { const res = await authFetch('/admin/version') const data = await res.json() if (!disposed) { setVersionInfo(data) } } catch (_err) { if (!disposed) { setVersionInfo(null) } } } loadVersion() return () => { disposed = true } }, [authFetch]) const renderTab = () => { switch (activeTab) { case 'accounts': return case 'proxies': return case 'test': return case 'history': return case 'import': return case 'vercel': return case 'settings': return default: return null } } return (
{sidebarOpen && (
setSidebarOpen(false)} /> )}
DS2API

{activeNavItem?.label}

{activeNavItem?.description}

{message && (
{message.type === 'error' ? :
} {message.text}
)}
}> {renderTab()}
) }