"use client"; import { Check, Landmark, Lock, RefreshCw, Wallet } from "lucide-react"; import { type ReactNode, useMemo, useState } from "react"; import { RunningAvatarLoader } from "@/features/agents/components/RunningAvatarLoader"; import { type OfficeUsageAnalyticsParams, useOfficeUsageAnalyticsViewModel, } from "@/features/office/hooks/useOfficeUsageAnalyticsViewModel"; import { formatCurrency, formatNumber, toDateInputValue, } from "@/lib/office/usageAnalyticsPresentation"; const PIN_STORAGE_KEY = "openclaw_atm_pin_code"; const resolveInitialPinMode = (): "setup" | "verify" => { if (typeof window === "undefined") { return "verify"; } return window.localStorage.getItem(PIN_STORAGE_KEY) ? "verify" : "setup"; }; export function AtmImmersiveScreen(props: OfficeUsageAnalyticsParams) { const [isAuthenticated, setIsAuthenticated] = useState(false); const [pinMode] = useState<"setup" | "verify">(resolveInitialPinMode); const [inputPin, setInputPin] = useState(""); const [error, setError] = useState(null); const { usage, settingsLoaded, startDate, endDate, setStartDate, setEndDate } = useOfficeUsageAnalyticsViewModel(props); const handlePinSubmit = () => { if (inputPin.length < 4) { setError("PIN must be at least 4 digits"); return; } if (pinMode === "setup") { localStorage.setItem(PIN_STORAGE_KEY, inputPin); setIsAuthenticated(true); setError(null); } else { const stored = localStorage.getItem(PIN_STORAGE_KEY); if (inputPin === stored) { setIsAuthenticated(true); setError(null); } else { setError("Incorrect PIN"); setInputPin(""); } } }; const handleKeyPad = (key: string) => { setError(null); if (key === "clear") { setInputPin(""); } else if (key === "backspace") { setInputPin((prev) => prev.slice(0, -1)); } else if (key === "submit") { handlePinSubmit(); } else { if (inputPin.length < 6) { setInputPin((prev) => prev + key); } } }; const recentCostDaily = useMemo(() => usage.costDaily.slice(-7), [usage.costDaily]); const chartMax = useMemo( () => recentCostDaily.reduce((max, entry) => Math.max(max, entry.totalCost), 0), [recentCostDaily], ); const overviewCards = useMemo( () => [ { label: "Total Spend", value: formatCurrency(usage.totals.totalCost) }, { label: "Total Tokens", value: formatNumber(usage.totals.totalTokens) }, { label: "Sessions", value: formatNumber(usage.sessions.length) }, { label: "Messages", value: formatNumber(usage.aggregates.messages.total) }, { label: "Tool Calls", value: formatNumber(usage.aggregates.tools.totalCalls) }, { label: "Unique Tools", value: formatNumber(usage.aggregates.tools.uniqueTools) }, { label: "Errors", value: formatNumber(usage.aggregates.messages.errors) }, { label: "Avg Session Cost", value: usage.sessions.length > 0 ? formatCurrency(usage.totals.totalCost / usage.sessions.length) : formatCurrency(0), }, ], [usage], ); const recentSessions = useMemo( () => [...usage.sessions] .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0)) .slice(0, 18), [usage.sessions], ); const selectedRangeLabel = useMemo(() => { const now = new Date(); const end = toDateInputValue(now); const lastWeek = new Date(now); lastWeek.setDate(lastWeek.getDate() - 6); const lastMonth = new Date(now); lastMonth.setDate(lastMonth.getDate() - 29); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); if (startDate === toDateInputValue(lastWeek) && endDate === end) return "7D"; if (startDate === toDateInputValue(lastMonth) && endDate === end) return "30D"; if (startDate === toDateInputValue(monthStart) && endDate === end) return "MTD"; return "Custom"; }, [endDate, startDate]); const setQuickRange = (days: number | "mtd") => { const end = new Date(); const start = new Date(end); if (days === "mtd") { start.setDate(1); } else { start.setDate(start.getDate() - (days - 1)); } setStartDate(toDateInputValue(start)); setEndDate(toDateInputValue(end)); }; if (!isAuthenticated) { return (

{pinMode === "setup" ? "CREATE ACCESS PIN" : "ENTER PIN CODE"}

{pinMode === "setup" ? "Set a secure code for your treasury ledger" : "Authentication required to view ledger"}

{[...Array(4)].map((_, i) => (
))}
{error ? (
{error}
) : null}
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => ( ))}
); } return (
OpenClaw Treasury ATM
Token Usage Ledger
{formatNumber(usage.totals.totalTokens)}
Total tokens used
USD equivalent {formatCurrency(usage.totals.totalCost)}
Account summary
{[ { label: "7D", value: 7 }, { label: "30D", value: 30 }, { label: "MTD", value: "mtd" as const }, ].map((range) => ( ))}
{usage.lastRefreshedAt ? `Last refresh ${new Date(usage.lastRefreshedAt).toLocaleTimeString()}` : settingsLoaded ? "Awaiting first usage snapshot" : "Loading account preferences"}
void usage.refresh()} className="inline-flex items-center gap-2 rounded-full border border-[#7dfff0]/24 bg-[#072528] px-4 py-2 text-[11px] uppercase tracking-[0.22em] text-[#b7fff8] transition-colors hover:border-[#7dfff0]/40 hover:bg-[#0a3035]" > {usage.loading ? ( ) : ( )} Refresh } > {usage.error ? : null}
{overviewCards.map((card) => ( ))}
{usage.loading && recentCostDaily.length === 0 ? ( ) : recentCostDaily.length === 0 ? ( ) : (
{recentCostDaily.map((entry) => { const heightPct = chartMax > 0 ? (entry.totalCost / chartMax) * 100 : 0; return (
{formatCurrency(entry.totalCost)}
{entry.date.slice(5)}
); })}
)}
{usage.aggregates.daily.map((entry) => ( ))} {usage.aggregates.daily.length === 0 ? ( ) : null}
{usage.budgetAlerts.map((alert) => (
{alert.label}
{formatCurrency(alert.currentUsd)} / {formatCurrency(alert.limitUsd)}.
))} {usage.budgetAlerts.length === 0 ? ( ) : null}
{usage.aggregates.byAgent.map((entry, index) => ( ))} {usage.aggregates.byAgent.length === 0 ? ( ) : null}
{usage.aggregates.byModel.map((entry, index) => ( ))} {usage.aggregates.byModel.length === 0 ? ( ) : null}
{usage.aggregates.tools.tools.map((tool, index) => ( ))} {usage.aggregates.tools.tools.length === 0 ? ( ) : null}
{recentSessions.map((session) => ( ))} {recentSessions.length === 0 ? ( ) : null}
); } function SectionCard({ title, subtitle, action, children, }: { title: string; subtitle: string; action?: ReactNode; children: ReactNode; }) { return (
{title}
{subtitle}
{action}
{children}
); } function SummaryCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function ListRow({ title, primary, secondary, }: { title: string; primary: string; secondary: string; }) { return (
{title}
{secondary}
{primary}
); } function EmptyPanelState({ message, tone = "neutral", }: { message: string; tone?: "neutral" | "success" | "danger"; }) { const toneClass = tone === "danger" ? "border-rose-400/30 bg-rose-500/12 text-rose-100" : tone === "success" ? "border-emerald-400/25 bg-emerald-500/10 text-emerald-100" : "border-[#7dfff0]/10 bg-[#031314]/78 text-[#b6fff7]/70"; return (
{message}
); }