/** * @file StatusBar — the one-line bar along the bottom of the app shell, on * every page: the live-connection state (click: the connection details modal, * which the sidebar owns), the version, how many workspaces are running and how * many need the user (from the shared lane list, no request of its own), and * today's spend — the `daily_costs` bucket for the local date from * `GET /api/pricing/cost`, the Dashboard's own cost call, re-read every 30 s * while the tab is visible. No provider quota: reading Claude's quota rotates * the stored OAuth token (`cc-usage.js`), and the CLI shows it in the pane. * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Wifi, WifiOff } from "lucide-react"; import { api } from "../lib/api"; import { useLanes } from "../hooks/useLanes"; const COST_POLL_MS = 30_000; function localDate(d = new Date()): string { const pad = (n: number) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } export function StatusBar({ wsConnected, left, onConnectionClick, }: { wsConnected: boolean; /** Sidebar width, so the bar starts where the main column does. */ left: string; onConnectionClick: () => void; }) { const { t } = useTranslation("nav"); const { lanes } = useLanes(); const running = lanes.filter((l) => l.status === "running").length; const needYou = lanes.filter((l) => l.needs_action).length; const [today, setToday] = useState(null); useEffect(() => { const tick = () => { if (document.visibilityState !== "visible") return; api.pricing .totalCost() .then((r) => setToday(r.daily_costs.find((d) => d.date === localDate())?.cost ?? 0)) .catch(() => undefined); }; tick(); const id = setInterval(tick, COST_POLL_MS); document.addEventListener("visibilitychange", tick); return () => { clearInterval(id); document.removeEventListener("visibilitychange", tick); }; }, []); return ( ); }