From 6fb67c502675ff2dd6cefe87f53917bcb05a1799 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 16 Apr 2026 18:01:31 -0600 Subject: [PATCH] feat(chess): add lobby UI for create/join rooms (P4.11) --- packages/chess/src/app/App.tsx | 11 +- packages/chess/src/ui/Lobby.tsx | 187 ++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 packages/chess/src/ui/Lobby.tsx diff --git a/packages/chess/src/app/App.tsx b/packages/chess/src/app/App.tsx index 6ee56c1..c9ac41c 100644 --- a/packages/chess/src/app/App.tsx +++ b/packages/chess/src/app/App.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react' import { Routes, Route, useNavigate } from 'react-router-dom' +import { Lobby } from '../ui/Lobby' import { GameView } from '../ui/GameView' import { RulesView } from '../ui/RulesView' import { SavePanel } from '../ui/SavePanel' @@ -26,7 +27,7 @@ export function App() { return (
- } /> + } /> } /> } /> } /> @@ -66,11 +67,3 @@ function SaveWrapper({ chessState }: { chessState: ReturnType ) } - -function Home() { - return ( -
-

Home

-
- ) -} \ No newline at end of file diff --git a/packages/chess/src/ui/Lobby.tsx b/packages/chess/src/ui/Lobby.tsx new file mode 100644 index 0000000..b84dd2a --- /dev/null +++ b/packages/chess/src/ui/Lobby.tsx @@ -0,0 +1,187 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { GameClient } from '../net/client.js'; + +const WS_URL = (import.meta as { env?: Record }).env?.["VITE_WS_URL"] ?? "ws://localhost:7357/ws"; + +export function Lobby() { + const [codeInput, setCodeInput] = useState(''); + const [roomCode, setRoomCode] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleCreate = async () => { + setLoading(true); + setError(null); + const client = new GameClient(WS_URL); + + client.on("room.created", (e) => { + setRoomCode(e.payload.code); + sessionStorage.setItem("room-code", e.payload.code); + sessionStorage.setItem("room-token", e.payload.token); + sessionStorage.setItem("player-color", e.payload.color); + setLoading(false); + }); + + client.on("error", (e) => { + setError(e.payload.message); + setLoading(false); + }); + + try { + if (typeof client.connectAndCreate === 'function') { + await client.connectAndCreate(); + } else { + // Fallback approach if connectAndCreate doesn't exist + const ws = new WebSocket(WS_URL); + ws.onopen = () => { + ws.send(JSON.stringify({ v: 1, seq: 1, ts: Date.now(), type: "room.create", payload: {} })); + }; + ws.onmessage = (e) => { + const msg = JSON.parse(e.data); + if (msg.type === "room.created") { + setRoomCode(msg.payload.code); + sessionStorage.setItem("room-code", msg.payload.code); + sessionStorage.setItem("room-token", msg.payload.token); + sessionStorage.setItem("player-color", msg.payload.color); + setLoading(false); + } + if (msg.type === "error") { + setError(msg.payload.message); + setLoading(false); + } + }; + ws.onerror = (e) => { + console.error("WebSocket error:", e); + setError("Could not connect to server"); + setLoading(false); + }; + ws.onclose = (e) => { + console.error("WebSocket closed:", e.code, e.reason); + if (loading) { + setError("Could not connect to server"); + setLoading(false); + } + }; + } + } catch { + setError("Could not connect to server"); + setLoading(false); + } + }; + + const handleJoin = async () => { + if (!codeInput.trim()) { + setError("Enter a room code"); + return; + } + setLoading(true); + setError(null); + + const client = new GameClient(WS_URL); + client.on("room.joined", (e) => { + sessionStorage.setItem("room-code", e.payload.code); + sessionStorage.setItem("room-token", e.payload.token); + sessionStorage.setItem("player-color", e.payload.color); + navigate("/game"); + }); + + client.on("error", (e) => { + setError(e.payload.message || "Invalid room code"); + setLoading(false); + }); + + try { + await client.connect(codeInput.trim().toUpperCase(), ""); + } catch { + setError("Could not connect"); + setLoading(false); + } + }; + + return ( +
+
+
+

Chess

+

Play realtime multiplayer

+
+ +
+ {/* Create Room Section */} +
+

New Game

+
+ {!roomCode ? ( + + ) : ( +
+
+ Room code: + + {roomCode} + +
+ +
+ )} +
+
+ +
+
+
+
+
+ or +
+
+ + {/* Join Room Section */} +
+

Join Game

+
+ setCodeInput(e.target.value.toUpperCase())} + placeholder="Enter 6-letter code" + maxLength={6} + className="w-full font-mono text-center text-lg py-2.5 px-4 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent placeholder:text-slate-400" + /> + +
+
+
+ + {/* Error Display */} + {error && ( +
+

{error}

+
+ )} +
+
+ ); +}