import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link, NavLink, Outlet, useLocation } from "react-router-dom"; import GuideMascot from "./GuideMascot"; import AIDebugObservatory from "./AIDebugObservatory"; import { usePlayerProgress } from "../hooks/usePlayerProgress"; import { api } from "../lib/api"; import { AppIcon, RouteIcon, type IconName } from "./PixelIcons"; function fmtAssets(v: number | null | undefined) { if (v == null || Number.isNaN(v)) return "—"; const sign = v < 0 ? "-" : ""; return `${sign}$${Math.abs(v).toLocaleString(undefined, { maximumFractionDigits: 0 })}`; } const NAV: { section: string; items: { to: string; icon: IconName; label: string }[] }[] = [ { section: "觀測", items: [ { to: "/", icon: "castle", label: "今日 · 基地" }, { to: "/market", icon: "world", label: "市場 · 世界" }, ], }, { section: "研究", items: [{ to: "/research", icon: "folder", label: "個股 · 背包" }], }, { section: "修練", items: [ { to: "/skills", icon: "scroll", label: "心法 · 技能樹" }, { to: "/patterns", icon: "cards", label: "線型 · 圖鑑" }, { to: "/library", icon: "book", label: "知識 · 圖書館" }, ], }, { section: "內容", items: [ { to: "/content", icon: "folder", label: "內容 · 管理" }, ], }, { section: "我的", items: [ { to: "/journal", icon: "folder", label: "復盤 · 戰績" }, { to: "/profile", icon: "wizard", label: "角色 · 養成" }, { to: "/settings", icon: "gear", label: "設定" }, ], }, ]; const BOTTOM_NAV: { to: string; icon: IconName; label: string }[] = [ { to: "/", icon: "castle", label: "基地" }, { to: "/market", icon: "world", label: "市場" }, { to: "/research", icon: "folder", label: "背包" }, { to: "/skills", icon: "scroll", label: "心法" }, { to: "/library", icon: "book", label: "圖書館" }, ]; function NavLinks({ onNavigate }: { onNavigate?: () => void }) { return ( ); } function useMobileNav() { const [isMobile, setIsMobile] = useState(() => typeof window !== "undefined" ? window.matchMedia("(max-width: 920px)").matches : false ); useEffect(() => { const mq = window.matchMedia("(max-width: 920px)"); const sync = () => setIsMobile(mq.matches); sync(); mq.addEventListener("change", sync); return () => mq.removeEventListener("change", sync); }, []); return isMobile; } export default function Chrome() { const [navOpen, setNavOpen] = useState(false); const isMobile = useMobileNav(); const location = useLocation(); const { player } = usePlayerProgress(); const portfolioQ = useQuery({ queryKey: ["portfolio-total"], queryFn: api.portfolioTotal, staleTime: 45_000, refetchOnWindowFocus: true, }); const xpPct = player?.xpToNext ? Math.round((player.xpInLevel / player.xpToNext) * 100) : 0; const portfolio = portfolioQ.data; useEffect(() => { setNavOpen(false); }, [location.pathname]); useEffect(() => { document.body.classList.toggle("nav-open", navOpen); return () => document.body.classList.remove("nav-open"); }, [navOpen]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setNavOpen(false); }; const onResize = () => { if (!window.matchMedia("(max-width: 920px)").matches) setNavOpen(false); }; window.addEventListener("keydown", onKey); window.addEventListener("resize", onResize); return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("resize", onResize); }; }, []); return (
setNavOpen(false)} />
資料僅供學習
不構成投資建議
{player?.displayName || "投資冒險者"}
{player ? ( <> Lv.{player.level} · {player.title} {player.epithet ? 「{player.epithet}」 : null} ) : ( "載入中…" )}
EXP {player ? `${player.xpInLevel.toLocaleString()} / ${player.xpToNext.toLocaleString()}` : "—"}
總資產 {portfolioQ.isLoading ? "…" : fmtAssets(portfolio?.totalAssets)}
); }