'use client'; import React, { useState } from 'react'; import { ChevronLeft, Search, Activity, Shield, Crosshair, DollarSign, Newspaper } from 'lucide-react'; import { useDataKeys } from '@/hooks/useDataStore'; import type { DashboardData, StockTicker } from '@/types/dashboard'; function formatVolume(vol: number): string { if (!vol || vol <= 0) return ''; if (vol >= 1_000_000) return `$${(vol / 1_000_000).toFixed(1)}M`; if (vol >= 1_000) return `$${(vol / 1_000).toFixed(0)}K`; return `$${vol.toFixed(0)}`; } function formatEndDate(iso: string | null | undefined): string { if (!iso) return ''; try { const d = new Date(iso); const now = new Date(); const days = Math.floor((d.getTime() - now.getTime()) / 86400000); if (days < 0) return 'EXPIRED'; if (days === 0) return 'TODAY'; if (days === 1) return '1d'; if (days < 30) return `${days}d`; if (days < 365) return `${Math.floor(days / 30)}mo`; return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); } catch { return ''; } } const CATEGORY_CONFIG: Record = { POLITICS: { color: 'text-blue-400', icon: Shield }, CONFLICT: { color: 'text-red-400', icon: Crosshair }, FINANCE: { color: 'text-emerald-400', icon: DollarSign }, CRYPTO: { color: 'text-amber-400', icon: DollarSign }, NEWS: { color: 'text-cyan-400', icon: Newspaper }, }; type Category = 'ALL' | 'POLITICS' | 'CONFLICT' | 'FINANCE' | 'CRYPTO' | 'NEWS'; interface MarketViewProps { onBack: () => void; } type DataSlice = Pick; const DATA_KEYS = ['trending_markets', 'stocks'] as const; export default function MarketView({ onBack }: MarketViewProps) { const [category, setCategory] = useState('ALL'); const [searchInput, setSearchInput] = useState(''); const data = useDataKeys(DATA_KEYS) as DataSlice; const markets = data?.trending_markets || []; const stocks = data?.stocks; const filteredMarkets = markets.filter(m => { const matchesCat = category === 'ALL' || m.category === category; const matchesSearch = !searchInput || m.title.toLowerCase().includes(searchInput.toLowerCase()); return matchesCat && matchesSearch; }); const CATEGORIES: Category[] = ['ALL', 'POLITICS', 'CONFLICT', 'FINANCE', 'CRYPTO', 'NEWS']; // Build ticker from real stocks data const tickerItems: string[] = []; if (stocks) { const entries = Object.entries(stocks as Record).filter(([k]) => !['last_updated', 'source'].includes(k)); for (const [symbol, val] of entries) { if (val && val.change_percent != null) { const up = val.change_percent >= 0; tickerItems.push(`${symbol.toUpperCase()} ${up ? '▲' : '▼'} ${Math.abs(val.change_percent).toFixed(1)}%`); } } } return (
{/* Header */}

PREDICTION MARKETS

Live Polymarket + Kalshi feeds. {markets.length} active markets tracked.

{/* Categories */}
{CATEGORIES.map(cat => ( ))}
{filteredMarkets.length} RESULTS
{/* Search Bar */}
setSearchInput(e.target.value)} placeholder="Search prediction markets..." className="bg-transparent border-none outline-none text-white w-full text-sm placeholder-gray-700" spellCheck={false} />
{/* Markets List */}
{filteredMarkets.length > 0 ? filteredMarkets.map((market, i) => { const pct = market.consensus_pct ?? market.polymarket_pct ?? market.kalshi_pct ?? 0; const catConfig = CATEGORY_CONFIG[market.category] || { color: 'text-gray-400' }; const vol = formatVolume(market.volume); const vol24 = formatVolume(market.volume_24h); // Runtime-optional fields the backend may send but aren't in the strict TS type const raw = market as Record; const endDate = formatEndDate(typeof raw.end_date === 'string' ? raw.end_date : null); const outcomes = market.outcomes && market.outcomes.length > 0 ? market.outcomes : null; const consensus = raw.consensus as { total_picks: number; total_staked: number } | undefined; return (
{/* Title + Category */}
{market.title}
{market.category} {vol && VOL: {vol}} {vol24 && 24H: {vol24}} {endDate && CLOSES: {endDate}}
{outcomes && outcomes.length > 0 ? ( <>
{outcomes[0].pct}%
{outcomes[0].name}
) : ( <>
{pct}%
CONSENSUS
)}
{/* Probability bar */} {outcomes && outcomes.length > 0 ? (
{outcomes[0].name}
{outcomes[0].pct}%
) : (
YES
NO
)} {/* Source badges */}
{market.sources?.map((s, si) => ( {s.name} {s.pct}% ))} {consensus && consensus.total_picks > 0 && ( {consensus.total_picks} pick{consensus.total_picks !== 1 ? 's' : ''} {consensus.total_staked > 0 ? ` · ${consensus.total_staked.toFixed(1)} REP` : ''} )}
{/* Delta indicator */} {market.delta_pct != null && market.delta_pct !== 0 && ( 0 ? 'text-green-400' : 'text-red-400'}`}> {market.delta_pct > 0 ? '▲' : '▼'} {Math.abs(market.delta_pct).toFixed(1)}% )}
{/* Multi-choice outcomes */} {outcomes && outcomes.length > 0 && (
{outcomes.slice(0, 5).map((outcome, oi) => (
{outcome.name}
{outcome.pct}%
))}
)}
); }) : (

No markets found{searchInput ? ` for "${searchInput}"` : ''}.

)}
{/* Ticker */} {tickerItems.length > 0 && (
{Array(10).fill(tickerItems.join(' | ')).join(' | ').split(' | ').map((item, i) => { const isUp = item.includes('▲'); return ( {item.replace(/[▲▼]/, '')} {isUp ? '▲' : '▼'} ); })}
)}
); }