'use client'; import { useState } from 'react'; import Link from 'next/link'; /** * PropRow — single prop line in the Slate (Session 13). * * Three visual states: * 1. Ungraded — player | stat | line | book | [Read] * 2. Grading — player | stat | line | book | […] (busy) * 3. Graded — player | stat | line | book | grade | ▸ (expandable) * * Pure presentational. The parent owns the grading API call (one * shared call site = consistent rate-limit + error handling). PropRow * just emits onRead() and reads the supplied state. */ export type PropDirection = 'over' | 'under'; export type Tier = 'free' | 'africa' | 'analyst' | 'desk'; export interface PropRowProp { player: string; stat_type: string; line: number; direction: PropDirection; book?: string; // A1 S3 — the FULL per-book rows for this player+stat (the grouped // odds shape from Express). Feeds best-price detection downstream; // optional so legacy flat callers stay valid. books?: Array<{ book?: string; line?: number; over_odds?: number | null; under_odds?: number | null }>; // Stable key used by the parent to look up grade results. key?: string; } export interface KillCondition { code: string; reason: string; locked?: boolean; } export interface PropRowResult { grade: string; // 'A', 'B', etc. confidence?: number; edge_pct?: number; reasoning?: { summary?: string; steps?: unknown; locked?: boolean }; kill_conditions_triggered?: KillCondition[]; tier_gated?: boolean; upgrade_hint?: string; } export interface PropRowProps { prop: PropRowProp; result?: PropRowResult | null; loading?: boolean; error?: string | null; tier?: Tier; onRead: (prop: PropRowProp) => void; onUpgrade?: () => void; } const STAT_LABELS: Record = { goals: 'Goals', assists: 'Assists', shots_on_target: 'SoT', shots: 'Shots', tackles: 'Tackles', cards: 'Cards', corners: 'Corners', saves: 'Saves', passes: 'Passes', clean_sheet: 'Clean Sheet', points: 'Pts', rebounds: 'Reb', threes: '3PT', blocks: 'Blk', steals: 'Stl', pra: 'P+R+A', turnovers: 'TO', strikeouts: 'K', hits: 'H', home_runs: 'HR', rbi: 'RBI', runs: 'R', total_bases: 'TB', earned_runs: 'ER', innings_pitched: 'IP', }; const BOOK_COLORS: Record = { draftkings: '#53D337', fanduel: '#1493FF', betmgm: '#BB9959', caesars: '#C8A35F', pointsbet: '#E2231A', }; const BOOK_LABELS: Record = { draftkings: 'DK', fanduel: 'FD', betmgm: 'MGM', caesars: 'CSR', pointsbet: 'PB', }; function gradeColor(grade?: string): string { const g = (grade || '').trim().toUpperCase().charAt(0); if (g === 'A') return 'var(--grade-a, #00D4A0)'; if (g === 'B') return 'var(--grade-b, #4ECDC4)'; if (g === 'C') return 'var(--grade-c, #FFD93D)'; return 'var(--grade-d, #FF6B6B)'; } export default function PropRow(props: PropRowProps) { const { prop, result, loading, error, tier = 'free', onRead, onUpgrade } = props; const [expanded, setExpanded] = useState(false); const isGraded = !!result; const isLocked = !!(result?.tier_gated || result?.reasoning?.locked); return (
  • {prop.player} {STAT_LABELS[prop.stat_type] || prop.stat_type} {prop.direction === 'under' ? 'u' : 'o'}{prop.line.toFixed(1)} {prop.book && ( {BOOK_LABELS[prop.book] || prop.book.slice(0, 3).toUpperCase()} )}
    {!isGraded && !loading && ( )} {loading && ( )} {isGraded && result && ( )}
    {error && (
    {error}
    )} {expanded && result && (
    {isLocked ? (
    Recent form: 28.4 over last 5 · Opp defense: top-5 vs PG · Pace: +3.1 possessions · Usage: 31% · Trap composite: 0.18

    {result.upgrade_hint || 'Unlock the reasoning — factor analysis, kill conditions, and trap score.'}

    {tier === 'free' ? ( ) : ( Upgrade plan → )}
    ) : ( <> {result.reasoning?.summary && (

    {result.reasoning.summary}

    )} {Array.isArray(result.kill_conditions_triggered) && result.kill_conditions_triggered.length > 0 && (

    Kill conditions ({result.kill_conditions_triggered.length})

      {result.kill_conditions_triggered.map((k, i) => (
    • {k.code} {k.reason}
    • ))}
    )} )}
    )}
  • ); } // Stable cache key for the parent's gradedProps map. Exported so the // Slate and tests build the same string. export function propRowKey(prop: PropRowProp): string { return `${prop.player}|${prop.stat_type}|${prop.line}|${prop.direction}|${prop.book || ''}`; }