112 lines
3.8 KiB
JavaScript
112 lines
3.8 KiB
JavaScript
|
|
/* eslint-disable @next/next/no-img-element -- Artwork/symbols come from the MinIO CDN; next/image is out of scope for the designer canvas. */
|
||
|
|
|
||
|
|
import { useEffect, useState } from 'react';
|
||
|
|
import { useRouter } from 'next/router';
|
||
|
|
import Layout from '../../../components/Layout';
|
||
|
|
import CardPreview from '../../../components/designer/CardFrame';
|
||
|
|
import { Button } from '../../../components/ui';
|
||
|
|
import { useAuth } from '../../../lib/use-auth';
|
||
|
|
|
||
|
|
/** Read-only public view of a community-shared custom game. */
|
||
|
|
export default function PublicGameSpace() {
|
||
|
|
const router = useRouter();
|
||
|
|
const { id } = router.query;
|
||
|
|
const { user } = useAuth();
|
||
|
|
|
||
|
|
const [game, setGame] = useState(null);
|
||
|
|
const [designs, setDesigns] = useState([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [error, setError] = useState(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!id) return undefined;
|
||
|
|
|
||
|
|
const loadGame = async () => {
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/public/games/${id}`);
|
||
|
|
const data = await response.json();
|
||
|
|
if (response.ok) {
|
||
|
|
setGame(data.game);
|
||
|
|
setDesigns(data.designs);
|
||
|
|
} else {
|
||
|
|
setError(data.error || 'Game not found.');
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
setError('Failed to load the game.');
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
loadGame();
|
||
|
|
return undefined;
|
||
|
|
}, [id]);
|
||
|
|
|
||
|
|
const symbolsMap = {};
|
||
|
|
// Symbol icons render from per-user libraries; public views fall back
|
||
|
|
// to text pips unless codes are embedded later.
|
||
|
|
|
||
|
|
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>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error && !game) {
|
||
|
|
return (
|
||
|
|
<Layout user={user}>
|
||
|
|
<div className="p-6 max-w-2xl mx-auto text-center py-20">
|
||
|
|
<h1 className="text-xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>{error}</h1>
|
||
|
|
<Button variant="primary" onClick={() => router.push('/community/games')}>
|
||
|
|
Back to Community Games
|
||
|
|
</Button>
|
||
|
|
</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)' }}>
|
||
|
|
{game.name}
|
||
|
|
</h1>
|
||
|
|
<p className="text-base" style={{ color: 'var(--text-secondary)' }}>
|
||
|
|
{game.description || 'Custom game system'} · by {game.author} · {designs.length} card{designs.length === 1 ? '' : 's'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{designs.length === 0 ? (
|
||
|
|
<div className="text-center py-16">
|
||
|
|
<h3 className="text-lg font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||
|
|
No cards in this game yet
|
||
|
|
</h3>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5">
|
||
|
|
{designs.map((design) => (
|
||
|
|
<div key={design.id} className="glass-panel rounded-2xl p-4 flex flex-col gap-3">
|
||
|
|
<CardPreview design={design} maxWidth={280} symbols={symbolsMap} />
|
||
|
|
<div className="min-w-0">
|
||
|
|
<p className="font-semibold truncate" style={{ color: 'var(--text-primary)' }}>
|
||
|
|
{design.name}
|
||
|
|
</p>
|
||
|
|
<p className="text-xs truncate" style={{ color: 'var(--text-secondary)' }}>
|
||
|
|
{design.card_type || 'No type'} {design.mana_cost ? `· ${design.mana_cost}` : ''}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</Layout>
|
||
|
|
);
|
||
|
|
}
|