deckhearth/pages/games/index.js

174 lines
5.9 KiB
JavaScript
Raw Permalink Normal View History

import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import Layout from '../../components/Layout';
import { Button, Input } from '../../components/ui';
import { useAuth } from '../../lib/use-auth';
export default function MyGames() {
const router = useRouter();
const { user, loading: authLoading } = useAuth();
const [games, setGames] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [creating, setCreating] = useState(false);
useEffect(() => {
if (!authLoading && !user) {
router.push('/login');
}
}, [authLoading, user, router]);
useEffect(() => {
if (!user) return undefined;
const loadGames = async () => {
try {
const token = localStorage.getItem('auth_token');
const response = await fetch('/api/custom-games', {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (response.ok) {
const data = await response.json();
setGames(data.games);
} else {
setError('Failed to load your games.');
}
} catch {
setError('Failed to load your games.');
} finally {
setLoading(false);
}
};
loadGames();
return undefined;
}, [user]);
const handleCreate = async (e) => {
e.preventDefault();
if (!name.trim()) return;
setCreating(true);
setError(null);
try {
const token = localStorage.getItem('auth_token');
const response = await fetch('/api/custom-games', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
body: JSON.stringify({ name, description }),
});
const data = await response.json();
if (response.ok) {
setGames((prev) =>
[...prev, data.game].sort((a, b) => a.name.localeCompare(b.name))
);
setName('');
setDescription('');
} else {
setError(data.error || 'Could not create the game.');
}
} catch {
setError('Could not create the game.');
} finally {
setCreating(false);
}
};
if (loading) {
return (
<Layout user={user}>
<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(--accent-ember)' }} />
</div>
</Layout>
);
}
return (
<Layout user={user}>
<div className="p-4 sm:p-6 max-w-[1500px] mx-auto space-y-6">
<div className="pt-2">
<h1 className="text-2xl sm:text-3xl font-bold mb-1" style={{ color: 'var(--text-primary)' }}>
My Games
</h1>
<p className="text-base" style={{ color: 'var(--text-secondary)' }}>
Custom game systems you&apos;ve created private to you.
</p>
</div>
{error && (
<div className="glass-panel rounded-xl px-4 py-3 text-sm" style={{ color: '#f87171' }} role="alert">
{error}
</div>
)}
{/* Create form */}
<form onSubmit={handleCreate} className="glass-panel rounded-2xl p-5">
<h2 className="text-sm font-semibold uppercase tracking-wide mb-3" style={{ color: 'var(--text-secondary)' }}>
New Game
</h2>
<div className="flex flex-col sm:flex-row gap-3">
<Input
className="flex-1"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Game name (e.g. Aetherfall)"
maxLength={100}
required
/>
<Input
className="flex-1"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Short description (optional)"
maxLength={300}
/>
<Button variant="primary" type="submit" disabled={creating || !name.trim()}>
{creating ? 'Creating…' : 'Create Game'}
</Button>
</div>
</form>
{/* Games grid */}
{games.length === 0 ? (
<div className="text-center py-16">
<h3 className="text-lg font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
No custom games yet
</h3>
<p style={{ color: 'var(--text-secondary)' }}>
Create your first game above, then target designs toward it in the{' '}
<Link href="/designer" style={{ color: 'var(--accent-ember)' }}>Card Designer</Link>.
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
{games.map((game) => (
<div
key={game.id}
className="glass-panel rounded-2xl p-5 cursor-pointer transition-all duration-200 hover:shadow-lg"
onClick={() => router.push(`/games/${game.id}`)}
role="link"
tabIndex={0}
onKeyDown={(e) => e.key === 'Enter' && router.push(`/games/${game.id}`)}
>
<h3 className="text-lg font-bold mb-1 truncate" style={{ color: 'var(--text-primary)' }}>
{game.name}
</h3>
<p className="text-sm mb-3 line-clamp-2" style={{ color: 'var(--text-secondary)' }}>
{game.description || 'No description'}
</p>
<span className="text-xs font-semibold" style={{ color: 'var(--accent-ember)' }}>
{game.card_count} card{game.card_count === 1 ? '' : 's'}
</span>
</div>
))}
</div>
)}
</div>
</Layout>
);
}