'use client' /** * Open positions panel — what's currently on the book. * * The single most-asked question on any trading dashboard: "what do I hold * right now?" Polls /api/positions/open + /api/positions/today every 15s. * Compact enough to embed at the top of Dashboard AND Trades. * * Empty state is intentional: explicitly says "0 open" rather than hiding, * so the user can confirm the bot isn't doing anything weird off-screen. */ import { useEffect, useState } from 'react' import { useLocale } from 'next-intl' import { useAccount, useSignMessage } from 'wagmi' import { getOpenPositions, getTodayStats, manualCloseTrade, setTradeGrow, type OpenPosition, type TodayStats, } from '@/lib/api' import { getCachedViewEnvelope, getOrCreateViewEnvelope, signRequest } from '@/lib/signedRequest' import { isUserRejection, walletErrorLabel } from '@/lib/walletError' const POLL_MS = 15_000 function fmtMoney(n: number | null | undefined, opts: { sign?: boolean } = {}) { if (n == null || isNaN(n)) return '—' const sign = opts.sign === true const abs = Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) if (n < 0) return '-$' + abs if (sign && n > 0) return '+$' + abs return '$' + abs } function fmtPct(n: number | null | undefined) { if (n == null || isNaN(n)) return '—' return (n >= 0 ? '+' : '') + n.toFixed(2) + '%' } function fmtHold(min: number) { if (min < 60) return `${min}m` const h = Math.floor(min / 60), m = min % 60 if (h < 24) return `${h}h ${m}m` return `${Math.floor(h / 24)}d ${h % 24}h` } function PositionRow({ p, onClose, onToggleGrow, growBusy, isZh }: { p: OpenPosition onClose: (p: OpenPosition) => void onToggleGrow: (p: OpenPosition) => void growBusy: boolean isZh: boolean }) { const pnlTone = (p.unrealized_pct ?? 0) > 0 ? 'up' : (p.unrealized_pct ?? 0) < 0 ? 'down' : 'idle' const pnlColor = pnlTone === 'up' ? 'var(--up)' : pnlTone === 'down' ? 'var(--down)' : 'var(--ink-3)' return (