import { useState, useEffect, useRef, useContext } from "preact/hooks"; import { createContext } from "preact"; import type { ComponentChildren } from "preact"; import { I18nProvider } from "../../shared/i18n/context"; import { ThemeProvider } from "../../shared/theme/context"; import { Header } from "./components/Header"; import { UpdateModal } from "./components/UpdateModal"; import { AccountList } from "./components/AccountList"; import { AddAccount } from "./components/AddAccount"; import { ProxyPool } from "./components/ProxyPool"; import { ApiConfig } from "./components/ApiConfig"; import { AnthropicSetup } from "./components/AnthropicSetup"; import { CodeExamples } from "./components/CodeExamples"; import { SettingsPanel } from "./components/SettingsPanel"; import { QuotaSettings } from "./components/QuotaSettings"; import { RotationSettings } from "./components/RotationSettings"; import { TestConnection } from "./components/TestConnection"; import { Footer } from "./components/Footer"; import { ProxySettings } from "./pages/ProxySettings"; import { AccountManagement } from "./pages/AccountManagement"; import { UsageStats } from "./pages/UsageStats"; import { useAccounts } from "../../shared/hooks/use-accounts"; import { useProxies } from "../../shared/hooks/use-proxies"; import { useStatus } from "../../shared/hooks/use-status"; import { useUpdateStatus } from "../../shared/hooks/use-update-status"; import { useI18n } from "../../shared/i18n/context"; import { useDashboardAuth } from "../../shared/hooks/use-dashboard-auth"; /** Context for dashboard session state (logout button, remote session indicator). */ const DashboardAuthCtx = createContext<{ onLogout?: () => void }>({}); function useDashboardAuthCtx() { return useContext(DashboardAuthCtx); } function useUpdateMessage() { const { t } = useI18n(); const update = useUpdateStatus(); let msg: string | null = null; let color = "text-primary"; if (!update.checking && update.result) { const parts: string[] = []; const r = update.result; if (r.proxy?.error) { parts.push(`Proxy: ${r.proxy.error}`); color = "text-red-500"; } else if (r.proxy?.update_available) { parts.push(t("updateAvailable")); color = "text-amber-500"; } if (r.codex?.error) { parts.push(`Codex: ${r.codex.error}`); color = "text-red-500"; } else if (r.codex_update_in_progress) { parts.push(t("fingerprintUpdating")); } else if (r.codex?.version_changed) { parts.push(`Codex: v${r.codex.current_version}`); color = "text-blue-500"; } msg = parts.length > 0 ? parts.join(" · ") : t("upToDate"); } else if (!update.checking && update.error) { msg = update.error; color = "text-red-500"; } const hasUpdate = update.status?.proxy.update_available ?? false; const proxyUpdateInfo = hasUpdate ? { mode: update.status!.proxy.mode, commits: update.status!.proxy.commits, release: update.status!.proxy.release, } : null; return { ...update, msg, color, hasUpdate, proxyUpdateInfo }; } function Dashboard() { const accounts = useAccounts(); const proxies = useProxies(); const status = useStatus(accounts.list.length); const update = useUpdateMessage(); const { onLogout } = useDashboardAuthCtx(); const [showModal, setShowModal] = useState(false); const prevUpdateAvailable = useRef(false); // Auto-open modal when update becomes available after a check // (Electron has its own native auto-updater — don't show web modal) useEffect(() => { if (update.hasUpdate && !prevUpdateAvailable.current && update.proxyUpdateInfo?.mode !== "electron") { setShowModal(true); } prevUpdateAvailable.current = update.hasUpdate; }, [update.hasUpdate, update.proxyUpdateInfo?.mode]); const handleProxyChange = async (accountId: string, proxyId: string) => { accounts.patchLocal(accountId, { proxyId }); await proxies.assignProxy(accountId, proxyId); }; return ( <>
setShowModal(true)} checking={update.checking} updateStatusMsg={update.msg} updateStatusColor={update.color} version={update.status?.proxy.version ?? null} commit={update.status?.proxy.commit ?? null} hasUpdate={update.hasUpdate} onLogout={onLogout} />