Session 24: Connect everything — Slate wired to all sources, copy fixed, nav fixed, startup prefetch, language button removed (1571 tests)

This commit is contained in:
Kev
2026-06-12 15:45:19 -04:00
parent 0538205fab
commit 433e827103
15 changed files with 586 additions and 99 deletions
+31
View File
@@ -0,0 +1,31 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
/**
* /account (Session 24).
*
* Paid users see "Account" in the nav instead of "Pricing". Rather than
* duplicate the subscription UI, this route forwards to /profile, which
* already renders the current plan, usage, founder pricing, and the
* cancel/manage-subscription controls. Keeping one canonical surface
* avoids two screens drifting out of sync.
*/
export default function AccountPage() {
const router = useRouter();
useEffect(() => {
router.replace('/profile');
}, [router]);
return (
<section style={{ minHeight: '60vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<p
className="mono"
style={{ color: 'var(--text-tertiary)', fontSize: 13, letterSpacing: '0.08em', textTransform: 'uppercase' }}
>
Opening your account
</p>
</section>
);
}
+37 -4
View File
@@ -24,6 +24,16 @@ interface Game {
injury_note?: string;
}
// Session 24 — shape returned by the free ESPN schedule endpoint
// (/api/schedule/:sport), used as a fallback when the odds slate is empty.
interface ScheduleApiGame {
id: string;
homeTeam?: { name?: string | null; abbreviation?: string | null };
awayTeam?: { name?: string | null; abbreviation?: string | null };
gameTime?: string | null;
status?: 'pre' | 'in' | 'post' | null;
}
interface TopGrade {
player: string;
stat: string;
@@ -88,9 +98,30 @@ export default function DashboardPage() {
Promise.all([
fetch(`/api/games/tonight?sport=${sport}`).then((r) => r.json()).catch(() => ({ games: [] })),
fetch(`/api/props/top-graded?sport=${sport}`).then((r) => r.json()).catch(() => ({ props: [] })),
]).then(([gamesData, gradesData]) => {
]).then(async ([gamesData, gradesData]) => {
if (cancelled) return;
setGames(Array.isArray(gamesData?.games) ? gamesData.games : []);
let list: Game[] = Array.isArray(gamesData?.games) ? gamesData.games : [];
// Session 24 — when the odds-backed slate is empty (off-day or
// odds-api quota exhausted), fall back to the FREE ESPN schedule so
// the dashboard still shows today's matchups instead of "NO SLATE".
if (list.length === 0) {
try {
const sched = await fetch(`/api/schedule/${sport.toLowerCase()}`).then((r) => r.json());
const schedGames = Array.isArray(sched?.games) ? sched.games : [];
list = schedGames.map((sg: ScheduleApiGame) => ({
id: sg.id,
away: sg.awayTeam?.name || sg.awayTeam?.abbreviation || 'Away',
home: sg.homeTeam?.name || sg.homeTeam?.abbreviation || 'Home',
start_time: sg.gameTime || '',
sport,
status: sg.status === 'in' ? 'live' : sg.status === 'post' ? 'final' : 'scheduled',
}));
} catch { /* schedule unavailable too — leave list empty */ }
}
if (cancelled) return;
setGames(list);
setTopGrades(Array.isArray(gradesData?.props) ? gradesData.props.slice(0, 10) : []);
});
@@ -255,7 +286,7 @@ export default function DashboardPage() {
</Section>
{/* Tonight's games */}
<Section title={`Tonight's ${sport} games`} subtitle={slateEmpty ? null : `${games?.length ?? 0} games tipping`}>
<Section title={`Today's ${sport} games`} subtitle={slateEmpty ? null : `${games?.length ?? 0} game${games?.length === 1 ? '' : 's'} today`}>
{games === null ? (
<SkeletonRow stacked />
) : games.length === 0 ? (
@@ -378,7 +409,9 @@ export default function DashboardPage() {
WELCOME TO THE LEDGER
</p>
<h3 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>
Tonight&apos;s slate is loaded. {games?.length ?? 0} {games?.length === 1 ? 'game' : 'games'} across 3 sports.
{(games?.length ?? 0) > 0
? `Today's slate is loaded. ${games?.length} ${games?.length === 1 ? 'game' : 'games'} on the ${sport} board.`
: 'Your ledger starts here.'}
</h3>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginBottom: 20 }}>
Pick a game and read your first prop it&apos;s on us.