'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; 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 }); } function SettingsIcon() { return ( ); } function ChevronIcon({ open }: { open: boolean }) { return ( ); } function AccountRow({ account, firm }: { account: AccountState; firm: FirmConfig }) { const cfg = getAccountConfig(account.name, firm); 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() ?? '—'} {!account.active ? Inactive : account.hasPosition ? In Trade : Flat} ); } function FirmRows({ state, firm, deleteMode, selected, onToggle }: { state: FirmState; firm: FirmConfig; deleteMode: boolean; selected: boolean; onToggle: () => void; }) { 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}
{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 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); }, []); 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 && ( )} ) : ( <> )}
{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)} /> ); }) )}
Account Balance Day P&L Days Traded Target Status
No firms yet — click + Add New to get started
); }