'use client'; import { useEffect, useState } from 'react'; import { getVisibleCount, getHiddenCount, type Tier } from '@/lib/tierGate'; /** * BestLinesPanel (Session 28). * * "Best lines tonight" — the prop + sportsbook offering the highest payout, * with the dollars-per-$100 a user saves by line-shopping. Reads * /api/books/:sport (cached odds, zero credits). Self-hides when empty. */ interface BestLine { player: string; stat: string; line: number | null; bestBook: string; bestOdds: number; savings: number; } export interface BestLinesPanelProps { sport: string; tier?: Tier; limit?: number; } export default function BestLinesPanel({ sport, tier = 'free', limit }: BestLinesPanelProps) { const [lines, setLines] = useState(null); useEffect(() => { let cancelled = false; async function load() { try { const res = await fetch(`/api/books/${sport}`); if (!res.ok) { if (!cancelled) setLines([]); return; } const data = await res.json(); if (!cancelled) setLines(Array.isArray(data?.bestLines) ? data.bestLines : []); } catch { if (!cancelled) setLines([]); } } load(); return () => { cancelled = true; }; }, [sport]); if (!lines || lines.length === 0) return null; const tierCount = getVisibleCount(tier, lines.length); const cap = limit && limit > 0 ? Math.min(limit, tierCount) : tierCount; const visible = lines.slice(0, cap); const hidden = limit ? lines.length - visible.length : getHiddenCount(tier, lines.length); const fmtOdds = (o: number) => (o > 0 ? `+${o}` : `${o}`); return (

💰 BEST LINES TONIGHT

{visible.map((bl) => (
{bl.player}
{bl.stat.replace(/_/g, ' ')}{bl.line != null ? ` ${bl.line}` : ''}
{bl.bestBook} {fmtOdds(bl.bestOdds)} {bl.savings > 0 && save ${bl.savings.toFixed(2)}}
))}
{hidden > 0 && ( {hidden} more — upgrade to compare every book → )}
); } const heading: React.CSSProperties = { fontSize: 12, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--text-tertiary, #6B6B7B)', margin: '0 0 10px', }; const row: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: 12, padding: '8px 10px', borderRadius: 10, background: 'var(--bg-2, #12121A)', border: '1px solid var(--border, #1A1A24)', }; const playerName: React.CSSProperties = { fontSize: 14, fontWeight: 700, color: 'var(--text-0, #F0F0F5)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', }; const propLine: React.CSSProperties = { fontSize: 12, color: 'var(--text-secondary, #8A8A9A)', textTransform: 'capitalize' }; const book: React.CSSProperties = { flex: '0 0 auto', fontSize: 12, fontWeight: 700, color: 'var(--text-0, #F0F0F5)', textTransform: 'capitalize' }; const savings: React.CSSProperties = { flex: '0 0 auto', fontSize: 11, fontWeight: 700, padding: '2px 7px', borderRadius: 6, background: 'rgba(0,212,160,0.12)', color: 'var(--grade-a, #00D4A0)', whiteSpace: 'nowrap', }; const upsell: React.CSSProperties = { display: 'inline-block', marginTop: 10, fontSize: 12, fontWeight: 600, color: 'var(--grade-a, #00D4A0)', textDecoration: 'none', };