feat: Features 3.2 + 3.3 — Scan UI + Bet Tracker

Scan UI (/scan):
- Leg builder with player autocomplete, stat/line/direction/book
- 2-12 legs, add/remove
- Calls POST /api/scan/parlay, displays grade results
- Color-coded grades (A/B/C/D), correlation flags, kill conditions
- Scan counter, upgrade pitch modal at limit
- New Scan / Save actions

Bet Tracker (/tracker):
- Performance cards: ROI, Win Rate, Bets with period toggle
- Quick Slip form for fast bet entry
- Bet history with status/book filters
- Inline settle modal (won/lost/push/void)
- Profit display on settled bets

Shared API client library (lib/api.ts).
Build clean: 9 static pages generated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-03-22 10:11:48 -04:00
parent bfa8345ebf
commit 850fe60e8f
3 changed files with 637 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
const NBA_SERVICE = process.env.NEXT_PUBLIC_NBA_SERVICE_URL || 'http://localhost:8000';
async function fetchAPI(path: string, options: RequestInit = {}) {
const token = typeof window !== 'undefined' ? localStorage.getItem('sb-token') : null;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string> || {}),
};
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
const err = new Error(body.error || 'Request failed');
(err as any).status = res.status;
(err as any).body = body;
throw err;
}
return res.json();
}
export async function scanParlay(legs: any[]) {
return fetchAPI('/api/scan/parlay', {
method: 'POST',
body: JSON.stringify({ legs }),
});
}
export async function searchPlayers(name: string) {
const res = await fetch(`${NBA_SERVICE}/players/search?name=${encodeURIComponent(name)}`);
if (!res.ok) return { results: [] };
return res.json();
}
export async function submitQuickslip(data: any) {
return fetchAPI('/api/bets/quickslip', {
method: 'POST',
body: JSON.stringify(data),
});
}
export async function getBets(params: Record<string, string> = {}) {
const qs = new URLSearchParams(params).toString();
return fetchAPI(`/api/bets?${qs}`);
}
export async function settleBet(betId: string, status: string) {
return fetchAPI(`/api/bets/${betId}/settle`, {
method: 'PATCH',
body: JSON.stringify({ status }),
});
}
export async function getPerformance() {
return fetchAPI('/api/bets/performance');
}