'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import type { FirmConfig, FirmState, AccountState, AccountConfig } from '@/types'; function getAccountConfig(name: string, firm: FirmConfig): AccountConfig | undefined { return [...firm.accounts] .sort((a, b) => b.prefix.length - a.prefix.length) .find((a) => name.startsWith(a.prefix)); } function fmt(value: number) { return value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } /** Returns true when an account's balance has breached Tradovate's auto-liquidation floor. */ function isAccountDead(account: AccountState): boolean { return account.autoLiqThreshold > 0 && account.amount <= account.autoLiqThreshold; } function SettingsIcon() { return ( ); } function ChevronIcon({ open }: { open: boolean }) { return ( ); } function DetailsIcon() { return ( ); } function AccountRow({ account, firm, hideDead }: { account: AccountState; firm: FirmConfig; hideDead: boolean }) { const cfg = getAccountConfig(account.name, firm); const dead = isAccountDead(account); if (hideDead && dead) return null; const pnlColor = account.realizedPnL > 0 ? 'text-green-600 font-medium' : account.realizedPnL < 0 ? 'text-red-600 font-medium' : 'text-slate-400'; return (
{account.name}
${fmt(account.amount)} {account.realizedPnL >= 0 ? '+' : ''}${fmt(account.realizedPnL)} {account.daysTraded} / {cfg?.minTradingDays ?? '—'} ${cfg?.profitTarget?.toLocaleString() ?? '—'}
{dead ? Dead : !account.active ? Inactive : account.hasPosition ? In Trade : Flat} e.stopPropagation()} >
); } function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead }: { state: FirmState; firm: FirmConfig; deleteMode: boolean; selected: boolean; onToggle: () => void; hideDead: boolean; }) { const [open, setOpen] = useState(true); return ( <> setOpen((o) => !o)} >
{deleteMode && ( e.stopPropagation()} className="w-4 h-4 accent-red-500 cursor-pointer flex-shrink-0" /> )} {state.firm}
e.stopPropagation()} title="Settings" >
{open && ( state.accounts.length === 0 ? ( Waiting for data... ) : ( state.accounts.map((acc) => ( )) ) )} ); } export default function Home() { const router = useRouter(); const [config, setConfig] = useState([]); const [firms, setFirms] = useState([]); const [deleteMode, setDeleteMode] = useState(false); const [selected, setSelected] = useState>(new Set()); const [hideDead, setHideDead] = useState(false); const fetchConfig = async () => { try { const res = await fetch('/api/firms'); if (res.ok) { const data: FirmConfig[] = await res.json(); setConfig(data); } } catch { // server not running yet } }; useEffect(() => { fetchConfig(); const fetchState = async () => { try { const res = await fetch('/api/state'); if (res.ok) { const data: FirmState[] = await res.json(); setFirms(data); } } catch { // server not running yet } }; fetchState(); const interval = setInterval(fetchState, 5000); return () => clearInterval(interval); }, []); // Count dead accounts across all firms for the toggle button label const deadCount = firms.reduce((total, firmState) => { const firmCfg = config.find((c) => c.firm === firmState.firm); if (!firmCfg) return total; return total + firmState.accounts.filter((acc) => isAccountDead(acc)).length; }, 0); const handleConfirmDelete = async () => { await Promise.all( [...selected].map((id) => fetch(`/api/firms/${id}`, { method: 'DELETE' }).catch(() => {}) ) ); setSelected(new Set()); setDeleteMode(false); fetchConfig(); }; const toggleSelected = (id: number) => { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; return (

AutoTrader

{deleteMode ? ( <> {selected.size > 0 && ( )} ) : ( <> {deadCount > 0 && ( )} )}
{config.length === 0 ? ( ) : ( config.map((cfg) => { const state = firms.find((f) => f.firm === cfg.firm) ?? { firm: cfg.firm, connected: false, accounts: [] }; return ( toggleSelected(cfg.id)} hideDead={hideDead} /> ); }) )}
Account Balance Day P&L Days Traded Target Status
No firms yet — click + Add New to get started
); }