import { useState } from 'react'; import { useWatchlists, useWatchlistMembers, useCreateWatchlist, useRateLimits } from '../api/hooks'; import type { Company } from '../api/hooks'; import { LoadingSpinner, Card } from '../components/ui'; import { AlertTriangle } from 'lucide-react'; export function WatchlistsPage() { const { data: watchlists, isLoading } = useWatchlists(); const { data: rateLimits } = useRateLimits(); const [selected, setSelected] = useState(null); const [showCreate, setShowCreate] = useState(false); if (isLoading) return ; return (

Watchlists

{showCreate && setShowCreate(false)} />}
{(watchlists ?? []).map((wl) => ( ))}
{selected && }
); } interface RateLimitInfo { polygon_global_limit: number; market_api: { rate_per_minute: number; cadence_seconds: number; max_tickers_per_cycle: number; active_sources: number; }; } function WatchlistMembers({ watchlistId, rateLimits }: { watchlistId: string; rateLimits?: RateLimitInfo }) { const { data: members, isLoading } = useWatchlistMembers(watchlistId); if (isLoading) return ; if (!members?.length) return

No members in this watchlist

; const maxTickers = rateLimits?.market_api.max_tickers_per_cycle; const activeSources = rateLimits?.market_api.active_sources ?? 0; const cadenceSec = rateLimits?.market_api.cadence_seconds ?? 300; const cadenceMin = Math.round(cadenceSec / 60); const overCapacity = maxTickers != null && activeSources > maxTickers; return (

Members

{overCapacity && ( )}
{members.map((m: Company) => ( {m.ticker} — {m.legal_name} ))}
); } function RateLimitWarning({ activeSources, maxTickers, cadenceMin }: { activeSources: number; maxTickers: number; cadenceMin: number }) { const cycleMinutes = Math.ceil(activeSources / (maxTickers / cadenceMin)); return (
); } function CreateWatchlistForm({ onClose }: { onClose: () => void }) { const [name, setName] = useState(''); const [desc, setDesc] = useState(''); const mutation = useCreateWatchlist(); return (
{ e.preventDefault(); if (name.trim()) mutation.mutate({ name: name.trim(), description: desc || undefined }, { onSuccess: onClose }); }} > setName(e.target.value)} required className="rounded-md border border-surface-700 bg-surface-900 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 focus:border-brand-500 focus:outline-none" aria-label="Watchlist name" /> setDesc(e.target.value)} className="flex-1 rounded-md border border-surface-700 bg-surface-900 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 focus:border-brand-500 focus:outline-none" aria-label="Watchlist description" />
); }