/* global React */ /** * ExchangeSummary — Live HC Exchange ticker widget (M2 data wiring) * GET /api/v1/public/exchange/tickers (unauthenticated) * GET /api/v1/public/exchange/custody/reserves (unauthenticated) * DATA_CONTRACTS: hc-exchange.json */ const { useState, useEffect } = React; const API_TICKERS = "https://api.conceptualhealth.com/api/v1/public/exchange/tickers"; const API_RESERVES = "https://api.conceptualhealth.com/api/v1/public/exchange/custody/reserves"; const REFRESH_MS = 15 * 1000; const DEMO_TICKERS = []; const DEMO_RESERVES = []; function ExchangeSummary() { const [tickers, setTickers] = useState([]); const [reserves, setReserves] = useState([]); const [live, setLive] = useState(false); const fetch_ = () => { Promise.all([ fetch(API_TICKERS).then(r => r.ok ? r.json() : null).catch(() => null), fetch(API_RESERVES).then(r => r.ok ? r.json() : null).catch(() => null), ]).then(([t, r]) => { if (t && t.length) { setTickers(t); setLive(true); } else { setTickers(DEMO_TICKERS); setLive(false); } if (r && r.length) setReserves(r); else setReserves(DEMO_RESERVES); }); }; useEffect(() => { fetch_(); const id = setInterval(fetch_, REFRESH_MS); return () => clearInterval(id); }, []); if (!tickers.length) return null; const badge = { display: "inline-flex", alignItems: "center", gap: 5, fontSize: 9, fontFamily: "var(--font-mono,monospace)", padding: "3px 9px", borderRadius: 20, background: live ? "rgba(16,185,129,0.12)" : "rgba(156,163,175,0.12)", color: live ? "#10b981" : "#9ca3af", border: live ? "1px solid rgba(16,185,129,0.25)" : "1px solid rgba(156,163,175,0.2)" }; const mono = { fontFamily: "var(--font-mono,monospace)", fontSize: 12 }; const fmt = (n, d=4) => n != null ? Number(n).toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d }) : "—"; const fmtVol = n => n != null ? (n >= 1e6 ? `$${(n/1e6).toFixed(2)}M` : `$${Math.round(n).toLocaleString()}`) : "—"; return (