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:
parent
53423509f0
commit
3478932c3c
4 changed files with 66 additions and 25 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,18 +191,21 @@ export default function CardEditor() {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<AdminProtected>
|
<AdminProtected>
|
||||||
<Layout user={user}>
|
{(user) => (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<Layout user={user}>
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
</div>
|
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
|
||||||
</Layout>
|
</div>
|
||||||
|
</Layout>
|
||||||
|
)}
|
||||||
</AdminProtected>
|
</AdminProtected>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminProtected>
|
<AdminProtected>
|
||||||
<Layout user={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 */}
|
||||||
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||||||
|
|
@ -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 });
|
||||||
|
|
@ -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,14 +101,10 @@ export default function CardImport() {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = {
|
|
||||||
email: 'admin@tcgvault.com',
|
|
||||||
role: 'admin'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminProtected>
|
<AdminProtected>
|
||||||
<Layout user={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 */}
|
||||||
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||||||
|
|
@ -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 });
|
||||||
Loading…
Reference in a new issue