From 0f6bfbb7df6594d1ea2a0759a54eaac789a6adf9 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Sun, 24 May 2026 10:05:17 -0500 Subject: [PATCH] feat(pages): pass user explicitly + null-guard leaky page seeds (Brief 2 of fix-layout-default-user) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the page-side half of P0 #7 from .convoys/ship-readiness.md. Brief 1 (commit ddf8fd2) handled the Layout-side fix. Per the architect's per-page bucket table (Decision B in .convoys/fix-layout-default-user.md), 7 pages needed code changes; the other 10 of 17 Layout-importing pages already pass `user` correctly. Pass user={user} to Layout (4 pages, 11 call sites): - pages/scanner.js (1 call) - pages/decks.js (3 calls) - pages/deck-builder.js (4 calls) - pages/deck/[id].js (3 calls) (All four still import useAuth from lib/auth-context.js — that's intentional and stays as-is until the single-auth-provider convoy collapses the three parallel auth surfaces.) Replace leaky page-level seed values with useState(null) + null guards (2 pages, R2 mitigation): - pages/profile.js: useState({email: 'me@...', role: 'user', ...}) → useState(null) + ?. on every sync user.* read + early-return guards in getDisplayName/getInitials + conditional render around the "Member since" block so formatDate(undefined) never runs - pages/settings.js: same pattern (single user.email reader guarded) Replace hardcoded const with useAuth from lib/use-auth.js (1 page): - pages/card/[id].js: const user = {email: 'me@...'} → const { user } = useAuth() (called unconditionally at the top of the component; rules-of-hooks safe) Verification: - grep 'me@randallstillwell.com' pages/ → 0 hits - 21/21 vitest tests pass (16 pre-existing + 5 from Brief 1) - npm run lint matches baseline (128 problems pre, 128 post; verified via git stash before/after) - Manual static read-through of every diff; ReadLints clean on the 7 files - Dev-server smoke: /cards anonymous returned HTTP 200 with 0 'me@randallstillwell' matches before the user's shared dev server became unresponsive mid-session (same dev-server-shared-by-user constraint flagged in Brief 1); interactive logged-in smoke is parent/operator gated Flagged-but-deferred (untouched per scope): - 4 pages still import useAuth from lib/auth-context.js → single-auth-provider (queued P1 #9) - components/MobileNavigation.js still receives dead user prop → cleanup-mobile-nav-dead-props (or fold into god-component-split) addresses: P0 #7 from .convoys/ship-readiness.md (last P0 ship-blocker) Co-authored-by: Cursor --- pages/card/[id].js | 7 ++--- pages/deck-builder.js | 8 +++--- pages/deck/[id].js | 6 ++-- pages/decks.js | 6 ++-- pages/profile.js | 66 ++++++++++++++++++++----------------------- pages/scanner.js | 2 +- pages/settings.js | 7 ++--- 7 files changed, 45 insertions(+), 57 deletions(-) diff --git a/pages/card/[id].js b/pages/card/[id].js index acde0bb..e7d92e3 100644 --- a/pages/card/[id].js +++ b/pages/card/[id].js @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Layout from '../../components/Layout'; import { useIsAdmin } from '../../lib/admin-auth'; +import { useAuth } from '../../lib/use-auth'; import CollectionSelectionModal from '../../components/CollectionSelectionModal'; import { ManaCost, ColorIdentity, AdvancedManaCost } from '../../components/ManaSymbols'; import ManaSymbolSettings from '../../components/ManaSymbolSettings'; @@ -9,11 +10,7 @@ import ManaSymbolSettings from '../../components/ManaSymbolSettings'; export default function CardDetail() { const router = useRouter(); const { id } = router.query; - - const user = { - email: 'me@randallstillwell.com', - role: 'user' - }; + const { user } = useAuth(); const [card, setCard] = useState(null); const [loading, setLoading] = useState(true); diff --git a/pages/deck-builder.js b/pages/deck-builder.js index 97b927b..02328ab 100644 --- a/pages/deck-builder.js +++ b/pages/deck-builder.js @@ -259,7 +259,7 @@ export default function DeckBuilder() { if (!user) { return ( - +

Please log in to use the deck builder

@@ -274,7 +274,7 @@ export default function DeckBuilder() { if (loading) { return ( - +
@@ -284,7 +284,7 @@ export default function DeckBuilder() { if (!deck) { return ( - +

Deck not found

@@ -300,7 +300,7 @@ export default function DeckBuilder() { const stats = getDeckStats(); return ( - +
{/* Header */}
diff --git a/pages/deck/[id].js b/pages/deck/[id].js index e205830..2c975b3 100644 --- a/pages/deck/[id].js +++ b/pages/deck/[id].js @@ -129,7 +129,7 @@ export default function DeckDetail() { if (loading) { return ( - +
@@ -139,7 +139,7 @@ export default function DeckDetail() { if (!deck) { return ( - +

Deck not found

@@ -157,7 +157,7 @@ export default function DeckDetail() { const isOwner = user && deck.user_id === user.userId; return ( - +
{/* Header */}
diff --git a/pages/decks.js b/pages/decks.js index b98faaa..33aedf4 100644 --- a/pages/decks.js +++ b/pages/decks.js @@ -158,7 +158,7 @@ export default function Decks() { if (!user) { return ( - +

Please log in to view your decks

@@ -173,7 +173,7 @@ export default function Decks() { if (loading) { return ( - +
@@ -182,7 +182,7 @@ export default function Decks() { } return ( - +
{/* Header */}
diff --git a/pages/profile.js b/pages/profile.js index 54f15f6..2227f61 100644 --- a/pages/profile.js +++ b/pages/profile.js @@ -7,17 +7,7 @@ export default function Profile() { const fileInputRef = useRef(null); // User state - const [user, setUser] = useState({ - email: 'me@randallstillwell.com', - role: 'admin', - first_name: '', - last_name: '', - username: '', - bio: '', - avatar_url: '', - favorite_games: ['MTG'], - created_at: new Date().toISOString() - }); + const [user, setUser] = useState(null); // UI state const [loading, setLoading] = useState(true); @@ -236,6 +226,7 @@ export default function Profile() { }; const getDisplayName = () => { + if (!user) return ''; if (user.first_name || user.last_name) { return `${user.first_name} ${user.last_name}`.trim(); } @@ -243,6 +234,7 @@ export default function Profile() { }; const getInitials = () => { + if (!user) return ''; if (user.first_name || user.last_name) { return `${user.first_name?.charAt(0) || ''}${user.last_name?.charAt(0) || ''}`.toUpperCase(); } @@ -296,7 +288,7 @@ export default function Profile() { {/* Avatar */}
- {user.avatar_url ? ( + {user?.avatar_url ? ( Profile {getDisplayName()} - {user.username && ( + {user?.username && (

@{user.username}

)}

- {user.email} + {user?.email}

- {user.role?.toUpperCase()} + {user?.role?.toUpperCase()}
{/* Bio */} - {user.bio && ( + {user?.bio && (

{user.bio} @@ -389,11 +381,13 @@ export default function Profile() { )} {/* Member Since */} -

-

- Member since {formatDate(user.created_at)} -

-
+ {user?.created_at && ( +
+

+ Member since {formatDate(user.created_at)} +

+
+ )}
{/* Stats Card */} @@ -468,7 +462,7 @@ export default function Profile() { /> ) : (

- {user.first_name || 'Not set'} + {user?.first_name || 'Not set'}

)}
@@ -486,7 +480,7 @@ export default function Profile() { /> ) : (

- {user.last_name || 'Not set'} + {user?.last_name || 'Not set'}

)}
@@ -507,7 +501,7 @@ export default function Profile() { /> ) : (

- {user.username || 'Not set'} + {user?.username || 'Not set'}

)}
@@ -527,7 +521,7 @@ export default function Profile() { /> ) : (

- {user.bio || 'No bio set'} + {user?.bio || 'No bio set'}

)}
@@ -546,19 +540,19 @@ export default function Profile() { className={`px-4 py-2 rounded-xl font-medium transition-all duration-200 flex items-center gap-2 ${ editMode ? 'cursor-pointer hover:shadow-md' : 'cursor-default' } ${ - (editMode ? formData.favorite_games : user.favorite_games)?.includes(game.value) + (editMode ? formData.favorite_games : user?.favorite_games)?.includes(game.value) ? 'shadow-lg' : 'hover:shadow-md' }`} style={{ - backgroundColor: (editMode ? formData.favorite_games : user.favorite_games)?.includes(game.value) + backgroundColor: (editMode ? formData.favorite_games : user?.favorite_games)?.includes(game.value) ? 'var(--accent-ember)' : 'var(--bg-tertiary)', - color: (editMode ? formData.favorite_games : user.favorite_games)?.includes(game.value) + color: (editMode ? formData.favorite_games : user?.favorite_games)?.includes(game.value) ? 'white' : 'var(--text-primary)', border: `1px solid ${ - (editMode ? formData.favorite_games : user.favorite_games)?.includes(game.value) + (editMode ? formData.favorite_games : user?.favorite_games)?.includes(game.value) ? 'var(--accent-ember)' : 'var(--border)' }` @@ -578,7 +572,7 @@ export default function Profile() {

- {user.email} + {user?.email}

Verified @@ -596,11 +590,11 @@ export default function Profile() { onClick={() => { setEditMode(false); setFormData({ - first_name: user.first_name || '', - last_name: user.last_name || '', - username: user.username || '', - bio: user.bio || '', - favorite_games: user.favorite_games || [] + first_name: user?.first_name || '', + last_name: user?.last_name || '', + username: user?.username || '', + bio: user?.bio || '', + favorite_games: user?.favorite_games || [] }); setMessage({ type: '', text: '' }); }} diff --git a/pages/scanner.js b/pages/scanner.js index c180fed..4fa22b2 100644 --- a/pages/scanner.js +++ b/pages/scanner.js @@ -330,7 +330,7 @@ export default function Scanner() { } return ( - +
{/* Header */}
diff --git a/pages/settings.js b/pages/settings.js index 9d4e721..5869f67 100644 --- a/pages/settings.js +++ b/pages/settings.js @@ -6,10 +6,7 @@ export default function Settings() { const router = useRouter(); // User state - const [user, setUser] = useState({ - email: 'me@randallstillwell.com', - role: 'admin' - }); + const [user, setUser] = useState(null); // Settings state const [settings, setSettings] = useState({ @@ -303,7 +300,7 @@ export default function Settings() {