/* FinPulse — live-data context + Settings modal. Uses FP_API layer. */ function curSym(c) { return window.FinnhubMappers.curSym(c); } function relTime(unixSec) { return window.FinnhubMappers.relTime(unixSec); } function mapNews(items) { return window.FinnhubMappers.mapNewsList(items); } function buildLiveStock(symbol, q, profile, metric) { return window.FinnhubMappers.mapStock(symbol, q, profile, metric); } function mcapM(m, cur) { const s = curSym(cur); if (!m || m <= 0) return '—'; if (m >= 1e6) return s + (m / 1e6).toFixed(2) + 'T'; if (m >= 1e3) return s + (m / 1e3).toFixed(1) + 'B'; return s + m.toFixed(0) + 'M'; } const LiveCtx = React.createContext(null); function useLive() { return React.useContext(LiveCtx); } function LiveProvider({ children }) { const api = window.FP_API.finnhub; const market = window.FP_API.market; const [key, setKeyState] = useState(api.getKey()); const [serverKey, setServerKey] = useState(false); const [live, setLive] = useState(() => localStorage.getItem('fp-live') === '1' && api.hasKey()); const [connected, setConnected] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [rateLimit, setRateLimit] = useState(api.getRateLimit()); useEffect(() => { let on = true; async function initFinnhub() { try { const res = await fetch('/api/config'); if (res.ok) { const cfg = await res.json(); if (cfg.finnhub && cfg.finnhub.available) { api.setServerKeyAvailable(true); if (on) setServerKey(true); if (localStorage.getItem('fp-live') !== '0') { localStorage.setItem('fp-live', '1'); if (on) setLive(true); } } } } catch (_) { /* offline */ } if (!on) return; if (api.hasKey()) { try { const ok = await api.test(null); if (on) { setConnected(ok); setRateLimit(api.getRateLimit()); } } catch (_) { if (on) setConnected(false); } } } initFinnhub(); return () => { on = false; }; }, []); function saveKey(k) { api.setKey(k); setKeyState(k); } function setLiveMode(v) { const on = v && api.hasKey(); setLive(on); localStorage.setItem('fp-live', on ? '1' : '0'); } const value = { api, market, key, connected, setConnected, live: live && connected, liveRaw: live, hasKey: !!key || serverKey, rateLimit, refreshRateLimit: () => setRateLimit(api.getRateLimit()), saveKey, setLiveMode, openSettings: () => setSettingsOpen(true), closeSettings: () => setSettingsOpen(false), }; return ( {children} {settingsOpen && } ); } function SettingsModal() { const L = useLive(); const [val, setVal] = useState(L.key || ''); const [status, setStatus] = useState(L.connected ? 'ok' : 'idle'); const [statusDetail, setStatusDetail] = useState(''); const [live, setLiveLocal] = useState(L.liveRaw); async function test() { setStatus('testing'); setStatusDetail(''); try { const ok = await L.api.test(val.trim()); if (ok) { setStatus('ok'); setStatusDetail(''); L.setConnected(true); L.refreshRateLimit(); } else { setStatus('bad'); setStatusDetail('Quote returned empty — check key'); L.setConnected(false); } } catch (e) { const code = e.code || e.message; setStatus(code === 'AUTH' ? 'bad' : code === 'NETWORK' ? 'net' : code === 'NO_KEY' ? 'bad' : 'bad'); setStatusDetail(e.detail || e.message || ''); L.setConnected(false); } } function save() { L.saveKey(val.trim()); L.setLiveMode(live); L.closeSettings(); } const rl = L.rateLimit; const statusMap = { idle: { c: 'var(--text-muted)', t: 'Not tested' }, testing: { c: 'var(--accent)', t: 'Testing…' }, ok: { c: 'var(--up)', t: 'Connected · key valid' + (rl.remaining != null ? ' · ' + rl.remaining + '/' + rl.limit + ' req left' : '') }, bad: { c: 'var(--down)', t: 'Invalid key or no access' }, net: { c: 'var(--down)', t: 'Network/CORS blocked — check connection' }, }[status]; return (
e.stopPropagation()}>

Connect live data

Company Stock Price can pull live news and global data from Finnhub. Keys are stored in this browser or loaded from src/config/local.js for local dev.

{ setVal(e.target.value); setStatus('idle'); }} />
{statusMap.t}{statusDetail ? ' — ' + statusDetail : ''}
NSE quotes load automatically. Optional Finnhub key unlocks live news & global US indices.
); } function LiveBadge({ live, small }) { return ( {live ? 'LIVE' : 'DEMO'} ); } Object.assign(window, { LiveCtx, useLive, LiveProvider, SettingsModal, LiveBadge, curSym, relTime, mcapM, mapNews, buildLiveStock });