Initial commit

This commit is contained in:
2026-03-13 16:01:39 -04:00
commit 27e8c9a6fe
22 changed files with 8681 additions and 0 deletions

143
client/src/App.jsx Normal file
View File

@@ -0,0 +1,143 @@
import React, { useState, useEffect, useCallback } from 'react';
import socket from './socket';
import Lobby from './components/Lobby';
import GameBoard from './components/GameBoard';
const SCREENS = { NAME: 'name', LOBBY: 'lobby', ROOM: 'room', GAME: 'game' };
export default function App() {
const [screen, setScreen] = useState(SCREENS.NAME);
const [playerName, setPlayerName] = useState('');
const [playerId, setPlayerId] = useState(null);
const [rooms, setRooms] = useState([]);
const [currentRoom, setCurrentRoom] = useState(null);
const [gameState, setGameState] = useState(null);
const [gameId, setGameId] = useState(null);
const [error, setError] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
socket.connect();
socket.on('connect', () => {
setConnected(true);
setPlayerId(socket.id);
});
socket.on('disconnect', () => setConnected(false));
socket.on('nameSet', (data) => {
setPlayerId(data.id);
setScreen(SCREENS.LOBBY);
});
socket.on('roomsUpdated', (data) => setRooms(data));
socket.on('roomCreated', (room) => {
setCurrentRoom(room);
setScreen(SCREENS.ROOM);
});
socket.on('roomJoined', (room) => {
setCurrentRoom(room);
setScreen(SCREENS.ROOM);
});
socket.on('roomUpdated', (room) => setCurrentRoom(room));
socket.on('gameStarted', (data) => {
setGameId(data.gameId);
setGameState(data.state);
setScreen(SCREENS.GAME);
});
socket.on('gameStateUpdated', (state) => setGameState(state));
socket.on('error', (data) => {
setError(data.message);
setTimeout(() => setError(null), 3000);
});
socket.on('actionError', (data) => {
setError(data.message);
setTimeout(() => setError(null), 3000);
});
socket.on('playerDisconnected', (data) => {
setError(`${data.playerName} disconnected`);
setTimeout(() => setError(null), 3000);
});
socket.on('needsResponse', () => {});
return () => {
socket.removeAllListeners();
socket.disconnect();
};
}, []);
const handleSetName = useCallback((e) => {
e.preventDefault();
if (!playerName.trim()) return;
socket.emit('setName', playerName.trim());
}, [playerName]);
const handleBackToLobby = useCallback(() => {
socket.emit('leaveRoom');
setCurrentRoom(null);
setGameState(null);
setGameId(null);
setScreen(SCREENS.LOBBY);
socket.emit('getRooms');
}, []);
if (screen === SCREENS.NAME) {
return (
<div className="app">
<div className="title-screen">
<h1 className="game-title">Arcane Duels</h1>
<p className="game-subtitle">A game of strategy, mana, and might</p>
<form onSubmit={handleSetName} className="name-form">
<input
type="text"
value={playerName}
onChange={(e) => setPlayerName(e.target.value)}
placeholder="Enter your name..."
className="name-input"
maxLength={20}
autoFocus
/>
<button type="submit" className="btn btn-primary" disabled={!connected}>
{connected ? 'Enter the Arena' : 'Connecting...'}
</button>
</form>
</div>
{error && <div className="error-toast">{error}</div>}
</div>
);
}
if (screen === SCREENS.GAME && gameState) {
return (
<div className="app">
<GameBoard
gameState={gameState}
playerId={playerId}
onLeave={handleBackToLobby}
/>
{error && <div className="error-toast">{error}</div>}
</div>
);
}
return (
<div className="app">
<Lobby
playerId={playerId}
playerName={playerName}
rooms={rooms}
currentRoom={currentRoom}
screen={screen}
onBackToLobby={handleBackToLobby}
/>
{error && <div className="error-toast">{error}</div>}
</div>
);
}