feat(panel): implement GameLobby and GameRoom pages
Some checks failed
Deploy to Production / test (push) Failing after 34s
Some checks failed
Deploy to Production / test (push) Failing after 34s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,118 @@
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useGameRoom } from "../lib/useGameRoom";
|
||||
import { gameUIRegistry } from "./registry";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import "./chess";
|
||||
|
||||
export function GameRoom({ userId }: { userId: string }) {
|
||||
return <div className="text-text-tertiary">Game Room — loading...</div>;
|
||||
const { gameSlug, roomId } = useParams<{ gameSlug: string; roomId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
gameState, players, spectators, roomStatus,
|
||||
isSpectator, gameOver, error, sendAction, leaveRoom,
|
||||
} = useGameRoom(roomId!, userId);
|
||||
|
||||
const plugin = gameSlug ? gameUIRegistry.get(gameSlug) : undefined;
|
||||
|
||||
if (!plugin) {
|
||||
return (
|
||||
<div className="text-center py-16">
|
||||
<div className="text-lg font-display font-semibold mb-2">Unknown Game</div>
|
||||
<p className="text-sm text-text-tertiary mb-4">The game type "{gameSlug}" doesn't exist.</p>
|
||||
<button onClick={() => navigate("/games")} className="text-sm text-primary hover:underline">
|
||||
Back to Games
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (roomStatus === "not_found") {
|
||||
return (
|
||||
<div className="text-center py-16">
|
||||
<div className="text-lg font-display font-semibold mb-2">Room Not Found</div>
|
||||
<p className="text-sm text-text-tertiary mb-4">This room no longer exists or has expired.</p>
|
||||
<button onClick={() => navigate("/games")} className="text-sm text-primary hover:underline">
|
||||
Back to Games
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (roomStatus === "connecting") {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-16">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-text-tertiary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const GameComponent = plugin.component;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xl">{plugin.icon}</span>
|
||||
<div>
|
||||
<h1 className="font-display text-base font-semibold">{plugin.name}</h1>
|
||||
<div className="flex items-center gap-2 text-xs text-text-tertiary">
|
||||
<span className={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold ${
|
||||
roomStatus === "waiting" ? "bg-warning/15 text-warning"
|
||||
: roomStatus === "playing" ? "bg-success/15 text-success"
|
||||
: "bg-card text-text-tertiary"
|
||||
}`}>
|
||||
{roomStatus === "waiting" ? "Waiting" : roomStatus === "playing" ? "Playing" : "Finished"}
|
||||
</span>
|
||||
{isSpectator && <span className="text-text-disabled">Spectating</span>}
|
||||
<span>👁 {spectators.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { leaveRoom(); navigate("/games"); }}
|
||||
className="rounded-md px-3 py-1.5 text-sm font-medium bg-card border border-border text-text-tertiary hover:text-foreground transition-colors"
|
||||
>
|
||||
Leave
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 rounded-lg border border-destructive/30 bg-destructive/10 px-4 py-2 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gameOver && (
|
||||
<div className="mb-4 rounded-lg border border-primary/30 bg-primary/10 px-4 py-3">
|
||||
<div className="text-sm font-semibold text-primary">
|
||||
{gameOver.winner
|
||||
? `Winner: ${players.find(p => p.discordId === gameOver.winner)?.username ?? gameOver.winner}`
|
||||
: "Draw!"}
|
||||
</div>
|
||||
<div className="text-xs text-text-tertiary mt-1">Reason: {gameOver.reason}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{roomStatus === "waiting" && (
|
||||
<div className="bg-card rounded-lg border border-border p-8 text-center">
|
||||
<div className="text-sm text-text-tertiary mb-2">
|
||||
Waiting for players ({players.length}/2)
|
||||
</div>
|
||||
<div className="text-xs text-text-disabled">
|
||||
Share this URL to invite: <span className="font-mono bg-surface px-2 py-0.5 rounded select-all">{window.location.href}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(roomStatus === "playing" || roomStatus === "finished") && gameState && (
|
||||
<GameComponent
|
||||
state={gameState}
|
||||
myPlayerId={userId}
|
||||
isSpectator={isSpectator}
|
||||
onAction={sendAction}
|
||||
players={players}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user