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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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'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's on us.
|
||||
|
||||
Reference in New Issue
Block a user