import { useCallback, useEffect, useState } from "react"; import { api, ThreadsAccount } from "../api/haixun"; import { navApps, mobileNavKeys } from "../lib/acAssets"; import { persistActiveThreadsAccountId } from "../lib/activeAccount"; import { useAuth } from "../auth/AuthContext"; import { AcIcon } from "./AcIcon"; import { AuthTicketIcon, SceneDecor } from "./AuthDecor"; import { ThemeToggle } from "./ThemeToggle"; import { Button } from "./ui"; import { JobMonitor } from "./JobMonitor"; export function Layout({ active, onNavigate, children }: { active: string; onNavigate: (key: string) => void; children: React.ReactNode; }) { const { member, logout } = useAuth(); const activeApp = navApps.find((app) => app.key === active) || navApps[0]; const [accounts, setAccounts] = useState([]); const [activeAccountId, setActiveAccountId] = useState(""); const [switching, setSwitching] = useState(false); const loadAccounts = useCallback(async () => { try { const data = await api.threadsAccounts(); const nextActiveId = data.active_account_id || data.list?.[0]?.id || ""; setAccounts(data.list || []); setActiveAccountId(nextActiveId); persistActiveThreadsAccountId(nextActiveId); } catch { setAccounts([]); setActiveAccountId(""); persistActiveThreadsAccountId(""); } }, []); useEffect(() => { let alive = true; async function load() { try { const data = await api.threadsAccounts(); if (!alive) return; const nextActiveId = data.active_account_id || data.list?.[0]?.id || ""; setAccounts(data.list || []); setActiveAccountId(nextActiveId); persistActiveThreadsAccountId(nextActiveId); } catch { if (alive) { setAccounts([]); setActiveAccountId(""); persistActiveThreadsAccountId(""); } } } void load(); return () => { alive = false; }; }, []); useEffect(() => { const reload = () => void loadAccounts(); window.addEventListener("haixun:active-account-changed", reload); return () => window.removeEventListener("haixun:active-account-changed", reload); }, [loadAccounts]); async function switchAccount(accountId: string) { if (!accountId || accountId === activeAccountId) return; setSwitching(true); try { await api.activateThreadsAccount(accountId); setActiveAccountId(accountId); persistActiveThreadsAccountId(accountId); window.dispatchEvent(new CustomEvent("haixun:active-account-changed", { detail: { accountId } })); } finally { setSwitching(false); } } function navigateApp(key: string) { if (accounts.length === 0 && requiresThreadsAccount(key)) { onNavigate("accounts"); return; } onNavigate(key); } return (
{/* 目錄區塊:直接貼最左邊、頂天立地,tiles 字體/圖示/排版完全保留 */} {/* 右側主區域:header 只在右邊,內容盡量寬 */}
巡樓 Console
PATROL PAD
{accounts.length > 0 ? ( ) : ( )} {member?.display_name || member?.email || "島民"}
{activeApp.sublabel}
{children}
0} />
); } function MobileBottomNav({ active, onNavigate, hasAccounts }: { active: string; onNavigate: (key: string) => void; hasAccounts: boolean }) { const apps = navApps.filter((app) => mobileNavKeys.includes(app.key)); return ( ); } function requiresThreadsAccount(key: string) { return key === "publish" || key === "personas" || key === "missions" || key === "patrol"; }