Fixed SSR and null user issues for production deployment

- Made admin pages client-side only using dynamic imports to prevent SSR issues
- Added proper null checks in Layout component to handle guest users
- Updated AdminProtected to support render prop pattern for user data
- Fixed card-editor and card-import pages to use proper authentication flow
- Eliminated hardcoded user data that was causing build failures
- All pages now build successfully and handle null user states gracefully
- Production deployment should now work without SSR errors
This commit is contained in:
Randall Stillwell 2025-07-24 17:04:06 -05:00
parent 53423509f0
commit 3478932c3c
4 changed files with 66 additions and 25 deletions

View file

@ -99,5 +99,6 @@ export default function AdminProtected({ children }) {
); );
} }
return children; // Support both render prop and children patterns
return typeof children === 'function' ? children(user) : children;
} }

View file

@ -180,12 +180,16 @@ export default function Layout({ children, user = { email: 'me@randallstillwell.
</button> </button>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="text-right"> <div className="text-right">
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>{user.email}</p> <p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>{user.role}</p> {user?.email || 'Guest'}
</p>
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
{user?.role || 'visitor'}
</p>
</div> </div>
<div className="h-10 w-10 logo-container"> <div className="h-10 w-10 logo-container">
<span className="text-white text-sm font-medium"> <span className="text-white text-sm font-medium">
{user.email.charAt(0).toUpperCase()} {user?.email?.charAt(0).toUpperCase() || 'G'}
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,16 +1,29 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
import Layout from '../../components/Layout'; import Layout from '../../components/Layout';
import AdminProtected from '../../components/AdminProtected'; import AdminProtected from '../../components/AdminProtected';
export default function CardEditor() { // Make this component client-side only to avoid SSR issues
const CardEditor = () => {
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;
const user = { const [mounted, setMounted] = useState(false);
email: 'admin@tcgvault.com',
role: 'admin' useEffect(() => {
}; setMounted(true);
}, []);
if (!mounted) {
return (
<Layout user={null}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
</div>
</Layout>
);
}
const [card, setCard] = useState(null); const [card, setCard] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -178,17 +191,20 @@ export default function CardEditor() {
if (loading) { if (loading) {
return ( return (
<AdminProtected> <AdminProtected>
{(user) => (
<Layout user={user}> <Layout user={user}>
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div> <div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
</div> </div>
</Layout> </Layout>
)}
</AdminProtected> </AdminProtected>
); );
} }
return ( return (
<AdminProtected> <AdminProtected>
{(user) => (
<Layout user={user}> <Layout user={user}>
<div className="container mx-auto px-6 py-8"> <div className="container mx-auto px-6 py-8">
{/* Admin Navigation */} {/* Admin Navigation */}
@ -753,6 +769,10 @@ export default function CardEditor() {
)} )}
</div> </div>
</Layout> </Layout>
)}
</AdminProtected> </AdminProtected>
); );
} };
// Export with dynamic import to disable SSR
export default dynamic(() => Promise.resolve(CardEditor), { ssr: false });

View file

@ -1,9 +1,25 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
import Layout from '../../components/Layout'; import Layout from '../../components/Layout';
import AdminProtected from '../../components/AdminProtected'; import AdminProtected from '../../components/AdminProtected';
export default function CardImport() { const CardImport = () => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<Layout user={null}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
</div>
</Layout>
);
}
const router = useRouter(); const router = useRouter();
const [importType, setImportType] = useState('mtg'); const [importType, setImportType] = useState('mtg');
const [setCode, setSetCode] = useState(''); const [setCode, setSetCode] = useState('');
@ -85,13 +101,9 @@ export default function CardImport() {
] ]
}; };
const user = {
email: 'admin@tcgvault.com',
role: 'admin'
};
return ( return (
<AdminProtected> <AdminProtected>
{(user) => (
<Layout user={user}> <Layout user={user}>
<div className="container mx-auto px-6 py-8"> <div className="container mx-auto px-6 py-8">
{/* Admin Navigation */} {/* Admin Navigation */}
@ -288,6 +300,10 @@ export default function CardImport() {
</div> </div>
</div> </div>
</Layout> </Layout>
)}
</AdminProtected> </AdminProtected>
); );
} };
// Export with dynamic import to disable SSR
export default dynamic(() => Promise.resolve(CardImport), { ssr: false });