Session 49: Complete onboarding flow + name micro-fixes (2185 tests)
Name micro-fixes (close the normalization arc): - collapseInitials merges "J C Escarra" -> "JC Escarra" (display + key); both playerName.js copies. Added mickey:michael nickname. Onboarding flow (end-to-end, complete): - Storage: Supabase user_metadata.preferences (no migration). - API: src/routes/preferences.js GET/POST (requireAuth, admin getUserById/ updateUserById, partial merge + sanitize) + Next /api/preferences proxy. - Page: web onboarding/page.tsx — 3 steps (sports >=1 / books skip / bankroll presets+custom+skip) -> SIGNAL ACTIVE -> POST onboarding_complete:true -> 2s -> /dashboard. Redirects to login when unauthenticated. - Redirect: dashboard fetches /api/preferences fresh; new+incomplete users (created_at >= cutoff) -> /onboarding; never while auth loading; existing users exempt. - Personalization: Slate default tab = prefs.sports[0]; preferred books glow in the card lines grid (lib/books isPreferredBook, threaded dash->Slate->GameCard). - Settings: PREFERENCES section loads + edits + saves sports/books/limit. Backend 2156 -> 2185 tests (+29), 184 suites. Web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,7 +75,7 @@ const SPORT_COLOR: Record<Sport, string> = {
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const { user, tier, scansRemaining, loading: authLoading } = useAuth();
|
||||
const { user, session, tier, scansRemaining, loading: authLoading } = useAuth();
|
||||
const { addLeg, open } = useParlay();
|
||||
|
||||
const [sport, setSport] = useState<Sport>('NBA');
|
||||
@@ -83,12 +83,40 @@ export default function DashboardPage() {
|
||||
const [topGrades, setTopGrades] = useState<TopGrade[] | null>(null);
|
||||
const [mostParlayed, setMostParlayed] = useState<ParlayLegStat[] | null>(null);
|
||||
const [recentScans, setRecentScans] = useState<RecentScan[] | null>(null);
|
||||
// Session 49 — the user's primary sport tab + preferred books from prefs.
|
||||
const [primaryTab, setPrimaryTab] = useState<'all' | 'nba' | 'mlb' | 'wnba' | 'soccer'>('all');
|
||||
const [prefBooks, setPrefBooks] = useState<string[]>([]);
|
||||
|
||||
// Gate
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) router.replace('/login?next=/dashboard');
|
||||
}, [authLoading, user, router]);
|
||||
|
||||
// Session 49 — onboarding gate + dashboard personalization. New users
|
||||
// (created after onboarding shipped) who haven't completed it are sent to
|
||||
// /onboarding; everyone else's saved preferences set the default sport tab.
|
||||
// Never fires while auth is loading (would bounce unauthenticated users).
|
||||
useEffect(() => {
|
||||
if (authLoading || !user || !session?.access_token) return;
|
||||
let active = true;
|
||||
fetch('/api/preferences', { headers: { Authorization: `Bearer ${session.access_token}` } })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((prefs) => {
|
||||
if (!active || !prefs) return;
|
||||
const createdAt = session.user?.created_at ? new Date(session.user.created_at).getTime() : 0;
|
||||
const ONBOARDING_CUTOFF = new Date('2026-06-19T00:00:00Z').getTime();
|
||||
const isNewUser = createdAt >= ONBOARDING_CUTOFF;
|
||||
if (prefs.onboarding_complete !== true && isNewUser) {
|
||||
router.replace('/onboarding');
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(prefs.sports) && prefs.sports[0]) setPrimaryTab(prefs.sports[0]);
|
||||
if (Array.isArray(prefs.books)) setPrefBooks(prefs.books);
|
||||
})
|
||||
.catch(() => { /* prefs are best-effort — never block the dashboard */ });
|
||||
return () => { active = false; };
|
||||
}, [authLoading, user, session, router]);
|
||||
|
||||
// Fetch slate when sport changes
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -198,7 +226,7 @@ export default function DashboardPage() {
|
||||
search, and inline grading. Renders ABOVE the existing
|
||||
intelligence sections (Top Graded / Most Parlayed / Recent
|
||||
Reads) which serve as supplementary surfaces. */}
|
||||
<Slate tier={tier} />
|
||||
<Slate tier={tier} initialTab={primaryTab} preferredBooks={prefBooks} key={primaryTab} />
|
||||
|
||||
{/* Legacy sport tabs — supplementary, kept for the existing
|
||||
Top Graded / Most Parlayed flows below. */}
|
||||
|
||||
Reference in New Issue
Block a user