Merge pull request 'csatlakozas-mukodesdemodemodemo (HIVJ FEL DONAT EMIATT)' (#94) from zsola into main
Reviewed-on: #94
This commit was merged in pull request #94.
This commit is contained in:
Generated
+128
-1
@@ -16,6 +16,7 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwindcss": "^4.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1259,6 +1260,11 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.7.tgz",
|
||||
@@ -1957,6 +1963,42 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@@ -3097,7 +3139,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
@@ -3478,6 +3519,64 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -3729,6 +3828,34 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwindcss": "^4.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -14,6 +14,7 @@ import CompanyHub from "./pages/Contacts/Contacts"
|
||||
import About from "./pages/About/About"
|
||||
import ScrollToTop from "./components/ScrollToTop"
|
||||
import GameScreen from "./pages/Game/GameScreen"
|
||||
import GameTest from "./pages/Game/GameTest"
|
||||
import Reports from "./pages/Report/Reports"
|
||||
import Lobby from "./pages/Game/Lobby"
|
||||
import ProfileCard from "./components/Userdetails/Userdetails"
|
||||
@@ -68,6 +69,7 @@ function App() {
|
||||
<Route path="/deck-creator" element={<DeckCreator />} />
|
||||
<Route path="/deck-creator/:deckId" element={<DeckCreator />} />
|
||||
<Route path="/game" element={<GameScreen />} />
|
||||
<Route path="/game-test" element={<GameTest />} />
|
||||
{/* <Route path="/contacts" element={<CompanyHub />} /> */}
|
||||
<Route path="/report" element={<Reports />} />
|
||||
<Route path="/choosedeck" element={<ChooseDeck />} />
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { apiClient } from './userApi';
|
||||
|
||||
/**
|
||||
* Create a new game
|
||||
* @param {Object} gameData - Game creation data
|
||||
* @param {string[]} gameData.deckids - Array of deck UUIDs
|
||||
* @param {number} gameData.maxplayers - Maximum players (2-8)
|
||||
* @param {number} gameData.logintype - 0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION
|
||||
* @returns {Promise<Object>} Game data with gameCode
|
||||
*/
|
||||
export const createGame = async (gameData) => {
|
||||
try {
|
||||
const response = await apiClient.post('/games/start', gameData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating game:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Join an existing game
|
||||
* @param {Object} joinData - Join game data
|
||||
* @param {string} joinData.gameCode - 6-character game code
|
||||
* @param {string} [joinData.playerName] - Player name (required for public games)
|
||||
* @returns {Promise<Object>} Game data with gameToken
|
||||
*/
|
||||
export const joinGame = async (joinData) => {
|
||||
try {
|
||||
const response = await apiClient.post('/games/join', joinData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error joining game:', error);
|
||||
console.error('Join game error response:', error.response?.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the game (gamemaster only)
|
||||
* @param {string} gameId - Game UUID
|
||||
* @returns {Promise<Object>} Game data with board
|
||||
*/
|
||||
export const startGame = async (gameId) => {
|
||||
try {
|
||||
const response = await apiClient.post(`/games/${gameId}/start`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error starting game:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get user's games
|
||||
* @returns {Promise<Array>} Array of games
|
||||
*/
|
||||
export const getMyGames = async () => {
|
||||
try {
|
||||
const response = await apiClient.get('/games/my-games');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching games:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get active public games
|
||||
* @returns {Promise<Array>} Array of active games
|
||||
*/
|
||||
export const getActiveGames = async () => {
|
||||
try {
|
||||
const response = await apiClient.get('/games/active');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching active games:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,268 @@
|
||||
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { io } from 'socket.io-client';
|
||||
import { API_CONFIG } from '../api/userApi';
|
||||
|
||||
const isDev = import.meta.env.DEV;
|
||||
const log = (...args) => isDev && console.log(...args);
|
||||
const warn = (...args) => isDev && console.warn(...args);
|
||||
const error = (...args) => console.error(...args);
|
||||
|
||||
/**
|
||||
* Optimized WebSocket hook for game connection
|
||||
* @param {string} gameToken - JWT token from game join
|
||||
* @returns {Object} WebSocket state and methods
|
||||
*/
|
||||
export const useGameWebSocket = (gameToken) => {
|
||||
const socketRef = useRef(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [gameState, setGameState] = useState(null);
|
||||
const [boardData, setBoardData] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [isGamemaster, setIsGamemaster] = useState(false);
|
||||
const [gameStarted, setGameStarted] = useState(false);
|
||||
const eventListenersRef = useRef(new Map());
|
||||
|
||||
// Memoized derived values - no extra state needed
|
||||
const players = useMemo(() => {
|
||||
// Backend sends different player fields depending on game state
|
||||
// connectedPlayers: array of player names (strings) who are connected via WebSocket
|
||||
// players: full player objects with game data (positions, etc.)
|
||||
const connectedPlayers = gameState?.connectedPlayers || [];
|
||||
const gamePlayers = gameState?.players || [];
|
||||
const currentPlayers = gameState?.currentPlayers || [];
|
||||
|
||||
// If we have full player objects, use those
|
||||
if (currentPlayers.length > 0) return currentPlayers;
|
||||
if (gamePlayers.length > 0) return gamePlayers;
|
||||
|
||||
// Otherwise, map connected player names to basic player objects
|
||||
return connectedPlayers.map((name, index) => ({
|
||||
id: `player-${index}`,
|
||||
name: typeof name === 'string' ? name : name.playerName || `Player ${index + 1}`,
|
||||
isOnline: true,
|
||||
isReady: gameState?.readyPlayers?.includes(name) || false,
|
||||
}));
|
||||
}, [gameState?.connectedPlayers, gameState?.players, gameState?.currentPlayers, gameState?.readyPlayers]);
|
||||
const currentTurn = useMemo(() => gameState?.currentPlayer || null, [gameState?.currentPlayer]);
|
||||
|
||||
// Connect to game WebSocket - only once per token
|
||||
useEffect(() => {
|
||||
if (!gameToken) return;
|
||||
|
||||
log('🔌 Connecting to game WebSocket...');
|
||||
|
||||
// Connect to /game namespace
|
||||
socketRef.current = io(`${API_CONFIG.wsURL}/game`, {
|
||||
transports: ['websocket'],
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
const socket = socketRef.current;
|
||||
|
||||
// Connection handlers
|
||||
const handleConnect = () => {
|
||||
log('✅ Connected to game WebSocket');
|
||||
setIsConnected(true);
|
||||
setError(null);
|
||||
socket.emit('game:join', { gameToken });
|
||||
};
|
||||
|
||||
const handleConnectError = (err) => {
|
||||
error('❌ Connection error:', err);
|
||||
setIsConnected(false);
|
||||
setError(err.message);
|
||||
};
|
||||
|
||||
const handleDisconnect = (reason) => {
|
||||
log('🔌 Disconnected:', reason);
|
||||
setIsConnected(false);
|
||||
};
|
||||
|
||||
// Game state handlers - batch updates
|
||||
const handleGameState = (state) => {
|
||||
log('📊 Game state:', state);
|
||||
setGameState(state);
|
||||
};
|
||||
|
||||
const handleGameJoined = (data) => {
|
||||
log('✅ Joined game:', data);
|
||||
// Store if this user is the gamemaster
|
||||
if (data.isGamemaster !== undefined) {
|
||||
setIsGamemaster(data.isGamemaster);
|
||||
}
|
||||
// Backend will send game:state next
|
||||
};
|
||||
|
||||
const handlePlayerJoined = (data) => {
|
||||
log('👤 Player joined:', data.playerName);
|
||||
// Update game state to add the new player to connectedPlayers
|
||||
setGameState(prev => {
|
||||
if (!prev) return prev;
|
||||
|
||||
const currentConnected = prev.connectedPlayers || [];
|
||||
// Only add if not already in the list
|
||||
if (!currentConnected.includes(data.playerName)) {
|
||||
return {
|
||||
...prev,
|
||||
connectedPlayers: [...currentConnected, data.playerName]
|
||||
};
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleGameStarted = (data) => {
|
||||
log('🎮 Game started:', data);
|
||||
// Batch state updates
|
||||
if (data.boardData) setBoardData(data.boardData);
|
||||
if (data.gameState) setGameState(data.gameState);
|
||||
// Signal that game has started
|
||||
setGameStarted(true);
|
||||
};
|
||||
|
||||
const handlePlayerMoved = (moveData) => {
|
||||
log('🏃 Player moved:', moveData.playerName);
|
||||
// Update only the moved player
|
||||
setGameState(prev => {
|
||||
if (!prev?.currentPlayers) return prev;
|
||||
return {
|
||||
...prev,
|
||||
currentPlayers: prev.currentPlayers.map(p =>
|
||||
p.playerId === moveData.playerId
|
||||
? { ...p, boardPosition: moveData.newPosition }
|
||||
: p
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleTurnChanged = (data) => {
|
||||
log('🔄 Turn changed to:', data.currentPlayerName);
|
||||
setGameState(prev => prev ? { ...prev, currentPlayer: data.currentPlayer } : prev);
|
||||
};
|
||||
|
||||
const handleError = (err) => {
|
||||
error('❌ Game error:', err);
|
||||
setError(err.message);
|
||||
};
|
||||
|
||||
// Register all handlers
|
||||
socket.on('connect', handleConnect);
|
||||
socket.on('connect_error', handleConnectError);
|
||||
socket.on('disconnect', handleDisconnect);
|
||||
socket.on('game:state', handleGameState);
|
||||
socket.on('game:state-update', handleGameState);
|
||||
socket.on('game:joined', handleGameJoined);
|
||||
socket.on('game:player-joined', handlePlayerJoined);
|
||||
socket.on('game:started', handleGameStarted);
|
||||
socket.on('game:player-moved', handlePlayerMoved);
|
||||
socket.on('game:turn-changed', handleTurnChanged);
|
||||
socket.on('game:error', handleError);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
log('🧹 Cleaning up WebSocket connection');
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [gameToken]);
|
||||
|
||||
// Optimized event listener management
|
||||
const addEventListener = useCallback((event, handler) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket) return;
|
||||
|
||||
socket.on(event, handler);
|
||||
eventListenersRef.current.set(event, handler);
|
||||
}, []);
|
||||
|
||||
const removeEventListener = useCallback((event) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket) return;
|
||||
|
||||
const handler = eventListenersRef.current.get(event);
|
||||
if (handler) {
|
||||
socket.off(event, handler);
|
||||
eventListenersRef.current.delete(event);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Memoized action methods - stable references
|
||||
const rollDice = useCallback((diceValue) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot roll dice: not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
log('🎲 Rolling dice:', diceValue);
|
||||
socket.emit('game:dice-roll', {
|
||||
gameCode: gameState?.gameCode,
|
||||
diceValue,
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const sendMessage = useCallback((message) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot send message: not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.emit('game:chat', {
|
||||
gameCode: gameState?.gameCode,
|
||||
message,
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const setReady = useCallback((ready = true) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot set ready: not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.emit('game:ready', {
|
||||
gameCode: gameState?.gameCode,
|
||||
ready,
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
const leaveGame = useCallback(() => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot leave game: not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.emit('game:leave', {
|
||||
gameCode: gameState?.gameCode,
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
|
||||
return {
|
||||
socket: socketRef.current,
|
||||
isConnected,
|
||||
gameState,
|
||||
players,
|
||||
boardData,
|
||||
currentTurn,
|
||||
error,
|
||||
isGamemaster,
|
||||
gameStarted,
|
||||
// Methods
|
||||
rollDice,
|
||||
sendMessage,
|
||||
setReady,
|
||||
leaveGame,
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
};
|
||||
};
|
||||
@@ -1,88 +1,189 @@
|
||||
import React, { useState } from "react"
|
||||
import React, { useState, useEffect, useMemo, useCallback } from "react"
|
||||
import { getVerticalOffset } from "../../utils/randomUtils"
|
||||
import Dice from "../../utils/dice/Dice"
|
||||
import { useGameWebSocket } from "../../hooks/useGameWebSocket"
|
||||
|
||||
// Constants - outside component to prevent recreation
|
||||
const PLAYER_STYLES = [
|
||||
{ color: "bg-blue-600", emoji: "🐍" },
|
||||
{ color: "bg-green-600", emoji: "🐢" },
|
||||
{ color: "bg-purple-600", emoji: "🐇" },
|
||||
{ color: "bg-yellow-600", emoji: "🦊" },
|
||||
{ color: "bg-red-600", emoji: "🦁" },
|
||||
{ color: "bg-pink-600", emoji: "🐷" },
|
||||
{ color: "bg-orange-600", emoji: "🐯" },
|
||||
{ color: "bg-indigo-600", emoji: "🐺" },
|
||||
]
|
||||
|
||||
const BOARD_CONFIG = {
|
||||
rows: 5,
|
||||
cols: 20,
|
||||
cellSize: 40,
|
||||
cellMargin: 2.5,
|
||||
rowSpacing: 70,
|
||||
}
|
||||
|
||||
// Helper functions outside component
|
||||
const mapFieldType = (backendType) => {
|
||||
switch (backendType) {
|
||||
case 'positive': return 'good'
|
||||
case 'negative': return 'bad'
|
||||
case 'luck': return 'clover'
|
||||
default: return 'regular'
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultFieldType = (count) => {
|
||||
if (count % 17 === 0) return "clover"
|
||||
if (count % 13 === 0) return "bad"
|
||||
if ((count + 5) % 13 === 0) return "good"
|
||||
return "regular"
|
||||
}
|
||||
|
||||
const GameScreen = () => {
|
||||
const boardRows = 5
|
||||
const boardCols = 20
|
||||
const totalCells = boardRows * boardCols
|
||||
const cellSize = 40
|
||||
const cellMargin = 2.5
|
||||
const rowSpacing = 70 // Extra spacing between rows
|
||||
const topOffset = rowSpacing * 0.5 // Increase topOffset for more spacing
|
||||
const bottomOffset = rowSpacing * 0.5 // Increase bottomOffset for more spacing
|
||||
const boardWidthPx = boardCols * (cellSize + cellMargin * 2)
|
||||
const boardHeightPx =
|
||||
boardRows * (cellSize + cellMargin * 2 + rowSpacing) + topOffset + bottomOffset - rowSpacing
|
||||
// WebSocket connection
|
||||
const gameToken = localStorage.getItem('gameToken')
|
||||
const {
|
||||
isConnected,
|
||||
gameState,
|
||||
players: backendPlayers,
|
||||
boardData,
|
||||
currentTurn,
|
||||
error,
|
||||
rollDice,
|
||||
addEventListener,
|
||||
removeEventListener
|
||||
} = useGameWebSocket(gameToken)
|
||||
|
||||
const [path, setPath] = useState([])
|
||||
const [players, setPlayers] = useState([])
|
||||
|
||||
// Generate a snake-like path with vertical spacing and vertical offsets
|
||||
const generateWindingPath = () => {
|
||||
// Memoized board dimensions
|
||||
const { rows, cols, totalCells, cellSize, cellMargin, rowSpacing, topOffset, width, height } = useMemo(() => {
|
||||
const { rows, cols, cellSize, cellMargin, rowSpacing } = BOARD_CONFIG
|
||||
const topOffset = rowSpacing * 0.5
|
||||
const bottomOffset = rowSpacing * 0.5
|
||||
const totalCells = rows * cols
|
||||
|
||||
return {
|
||||
rows,
|
||||
cols,
|
||||
totalCells,
|
||||
cellSize,
|
||||
cellMargin,
|
||||
rowSpacing,
|
||||
topOffset,
|
||||
bottomOffset,
|
||||
width: cols * (cellSize + cellMargin * 2),
|
||||
height: rows * (cellSize + cellMargin * 2 + rowSpacing) + topOffset + bottomOffset - rowSpacing,
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Memoized path generator - Snake pattern with proper turn handling
|
||||
const generateWindingPath = useCallback((backendFields = null) => {
|
||||
const path = []
|
||||
const hasBackendData = backendFields && Array.isArray(backendFields)
|
||||
|
||||
let currentNum = 1
|
||||
|
||||
for (let row = 0; row < boardRows && currentNum <= totalCells; row++) {
|
||||
// Calculate the y position with extra row spacing
|
||||
const baseYPosition = topOffset + row * (cellSize + cellMargin * 2 + rowSpacing)
|
||||
|
||||
// If row number is even, go right; if odd, go left
|
||||
if (row % 2 === 0) {
|
||||
// Left to right
|
||||
for (let col = 0; col < boardCols && currentNum <= totalCells; col++) {
|
||||
path.push({
|
||||
number: currentNum++,
|
||||
x: col * (cellSize + cellMargin * 2),
|
||||
y: baseYPosition + getVerticalOffset(currentNum - 1),
|
||||
type: getFieldType(currentNum - 1),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Right to left
|
||||
for (let col = boardCols - 1; col >= 0 && currentNum <= totalCells; col--) {
|
||||
path.push({
|
||||
number: currentNum++,
|
||||
x: col * (cellSize + cellMargin * 2),
|
||||
y: baseYPosition + getVerticalOffset(currentNum - 1),
|
||||
type: getFieldType(currentNum - 1),
|
||||
})
|
||||
}
|
||||
|
||||
// Generate all 100 positions
|
||||
while (currentNum <= totalCells) {
|
||||
const row = Math.floor((currentNum - 1) / cols)
|
||||
const posInRow = (currentNum - 1) % cols
|
||||
const isLeftToRight = row % 2 === 0
|
||||
|
||||
// Calculate column based on direction
|
||||
const col = isLeftToRight ? posInRow : (cols - 1 - posInRow)
|
||||
|
||||
// Base Y position for this row
|
||||
let baseYPosition = topOffset + row * (cellSize + cellMargin * 2 + rowSpacing)
|
||||
|
||||
// Apply vertical offset for wave effect
|
||||
let yOffset = getVerticalOffset(currentNum - 1)
|
||||
|
||||
// Special handling for turn positions (21, 41, 61, 81)
|
||||
// These should be positioned between rows to show the turn
|
||||
if (currentNum % cols === 1 && currentNum > 1) {
|
||||
// This is the first element of a new row (21, 41, 61, 81)
|
||||
// Position it halfway between the previous row and current row
|
||||
baseYPosition = topOffset + (row - 0.5) * (cellSize + cellMargin * 2 + rowSpacing)
|
||||
yOffset = 0 // Reset wave offset for turn positions
|
||||
}
|
||||
|
||||
const backendField = hasBackendData ? backendFields.find(f => f.position === currentNum) : null
|
||||
|
||||
path.push({
|
||||
number: currentNum,
|
||||
x: col * (cellSize + cellMargin * 2),
|
||||
y: baseYPosition + yOffset,
|
||||
type: backendField ? mapFieldType(backendField.type) : getDefaultFieldType(currentNum - 1),
|
||||
stepValue: backendField?.stepValue || 0,
|
||||
})
|
||||
|
||||
currentNum++
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}, [rows, cols, totalCells, cellSize, cellMargin, rowSpacing, topOffset])
|
||||
|
||||
const getFieldType = (count) => {
|
||||
if (count % 17 === 0) return "clover"
|
||||
if (count % 13 === 0) return "bad"
|
||||
if ((count + 5) % 13 === 0) return "good"
|
||||
return "regular"
|
||||
}
|
||||
// Update path when boardData changes
|
||||
useEffect(() => {
|
||||
if (boardData?.fields) {
|
||||
setPath(generateWindingPath(boardData.fields))
|
||||
} else if (path.length === 0) {
|
||||
setPath(generateWindingPath())
|
||||
}
|
||||
}, [boardData, generateWindingPath])
|
||||
|
||||
const [path, setPath] = useState(generateWindingPath())
|
||||
const [players, setPlayers] = useState([
|
||||
{ id: 1, name: "Béla", position: 34, score: 25, color: "bg-blue-600", emoji: "🐍" },
|
||||
{ id: 2, name: "Juci", position: 50, score: 30, color: "bg-green-600", emoji: "🐢" },
|
||||
{ id: 3, name: "Kati", position: 70, score: 15, color: "bg-purple-600", emoji: "🐇" },
|
||||
{ id: 3, name: "Fürtös", position: 68, score: 14, color: "bg-yellow-600", emoji: "😂" },
|
||||
])
|
||||
// Update players from backend - memoized mapping
|
||||
useEffect(() => {
|
||||
if (!backendPlayers?.length) return
|
||||
|
||||
// New: selected dice value from dropdown (null = none)
|
||||
const [selectedDice, setSelectedDice] = useState(null)
|
||||
const mappedPlayers = backendPlayers.map((player, index) => ({
|
||||
id: player.playerId || player.id || index,
|
||||
name: player.playerName || player.name || `Player ${index + 1}`,
|
||||
position: player.boardPosition || 0,
|
||||
score: player.score || 0,
|
||||
color: PLAYER_STYLES[index % PLAYER_STYLES.length].color,
|
||||
emoji: PLAYER_STYLES[index % PLAYER_STYLES.length].emoji,
|
||||
isOnline: player.isOnline !== undefined ? player.isOnline : true,
|
||||
isReady: player.isReady || false,
|
||||
}))
|
||||
|
||||
setPlayers(mappedPlayers)
|
||||
}, [backendPlayers])
|
||||
|
||||
// Sort players by position in descending order
|
||||
const sortedPlayers = [...players].sort((a, b) => b.position - a.position)
|
||||
// Listen to player movement - optimized to update only moved player
|
||||
useEffect(() => {
|
||||
if (!addEventListener) return
|
||||
|
||||
// Handle dice roll completion
|
||||
const handleDiceRoll = (value) => {
|
||||
console.log("Rolled:", value)
|
||||
// reset dropdown selection after roll
|
||||
setSelectedDice(null)
|
||||
// You can add logic here to move the current player based on the dice value
|
||||
}
|
||||
const handlePlayerMoved = (moveData) => {
|
||||
setPlayers(prev =>
|
||||
prev.map(p =>
|
||||
p.id === moveData.playerId
|
||||
? { ...p, position: moveData.newPosition }
|
||||
: p
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
console.log("Generated path length:", path.length)
|
||||
addEventListener('game:player-moved', handlePlayerMoved)
|
||||
return () => removeEventListener('game:player-moved')
|
||||
}, [addEventListener, removeEventListener])
|
||||
|
||||
const getFieldStyle = (type) => {
|
||||
// Sorted players - memoized
|
||||
const sortedPlayers = useMemo(
|
||||
() => [...players].sort((a, b) => b.position - a.position),
|
||||
[players]
|
||||
)
|
||||
|
||||
// Handle dice roll
|
||||
const handleDiceRoll = useCallback((value) => {
|
||||
rollDice(value)
|
||||
}, [rollDice])
|
||||
|
||||
// Get field style - memoized
|
||||
const getFieldStyle = useCallback((type) => {
|
||||
switch (type) {
|
||||
case "clover":
|
||||
return "bg-teal-700 border-teal-500 shadow-teal-800"
|
||||
@@ -93,15 +194,16 @@ const GameScreen = () => {
|
||||
default:
|
||||
return "bg-gray-800 border-gray-600 shadow-gray-900"
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getPlayerPosition = (playerPosition) => {
|
||||
// Get player position - memoized
|
||||
const getPlayerPosition = useCallback((playerPosition) => {
|
||||
const field = path.find((p) => p.number === playerPosition)
|
||||
return field ? { top: `${field.y}px`, left: `${field.x}px` } : { top: 0, left: 0 }
|
||||
}
|
||||
}, [path])
|
||||
|
||||
// Function to get medal style based on rank
|
||||
const getMedalStyle = (rank) => {
|
||||
// Get medal style - memoized
|
||||
const getMedalStyle = useCallback((rank) => {
|
||||
switch (rank) {
|
||||
case 1:
|
||||
return "bg-yellow-400 text-yellow-900 border-yellow-500 shadow-yellow-600"
|
||||
@@ -112,20 +214,57 @@ const GameScreen = () => {
|
||||
default:
|
||||
return "bg-gray-700 text-gray-300 border-gray-600 shadow-gray-800"
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-gradient-to-br from-gray-900 via-gray-800 to-teal-900 min-h-screen flex items-center justify-center">
|
||||
<div className="w-full">
|
||||
{/* Connection Status Indicator */}
|
||||
<div className="fixed top-4 right-4 z-50">
|
||||
<div className={`px-4 py-2 rounded-lg shadow-lg flex items-center gap-2 ${
|
||||
isConnected
|
||||
? 'bg-green-600 text-white'
|
||||
: 'bg-red-600 text-white'
|
||||
}`}>
|
||||
<div className={`w-3 h-3 rounded-full ${
|
||||
isConnected ? 'bg-green-300 animate-pulse' : 'bg-red-300'
|
||||
}`}></div>
|
||||
<span className="text-sm font-medium">
|
||||
{isConnected ? '🟢 Csatlakozva' : '🔴 Kapcsolódás...'}
|
||||
</span>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="mt-2 px-4 py-2 rounded-lg shadow-lg bg-red-600 text-white text-xs">
|
||||
⚠️ {error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Game Info Bar */}
|
||||
{gameState && (
|
||||
<div className="fixed top-4 left-4 z-50">
|
||||
<div className="bg-gray-800 border border-teal-700 px-4 py-2 rounded-lg shadow-lg">
|
||||
<div className="text-teal-300 text-sm font-medium">
|
||||
🎮 Játék kód: <span className="font-bold text-white">{gameState.gameCode || 'N/A'}</span>
|
||||
</div>
|
||||
{currentTurn && (
|
||||
<div className="text-gray-400 text-xs mt-1">
|
||||
🎯 Köron: <span className="text-white">{players.find(p => p.id === currentTurn)?.name || 'Betöltés...'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-6 justify-center">
|
||||
{/* Game Board */}
|
||||
<div className="relative bg-gray-800 p-6 rounded-2xl shadow-xl border border-teal-700 flex flex-col items-center justify-center overflow-hidden">
|
||||
{/* Háttér */}
|
||||
{/* Background decoration */}
|
||||
<div className="absolute w-full h-full opacity-10 pointer-events-none overflow-hidden">
|
||||
{[...Array(35)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute rounded-full bg-teal-600 animate-pulse8"
|
||||
className="absolute rounded-full bg-teal-600 animate-pulse"
|
||||
style={{
|
||||
width: Math.random() * 120 + 40 + "px",
|
||||
height: Math.random() * 120 + 40 + "px",
|
||||
@@ -136,8 +275,9 @@ const GameScreen = () => {
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div className="relative" style={{ height: `${boardHeightPx}px`, width: `${boardWidthPx}px` }}>
|
||||
{/* Mezők */}
|
||||
|
||||
<div className="relative" style={{ height: `${height}px`, width: `${width}px` }}>
|
||||
{/* Fields */}
|
||||
{path.map((field) => (
|
||||
<div
|
||||
key={field.number}
|
||||
@@ -163,44 +303,65 @@ const GameScreen = () => {
|
||||
className={`absolute w-6 h-6 ${player.color} rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white text-xs font-bold z-10 animate-bounce`}
|
||||
style={{
|
||||
...getPlayerPosition(player.position),
|
||||
transform: "translate(18px, 18px)",
|
||||
transform: "translate(17px, 17px)",
|
||||
}}
|
||||
>
|
||||
{player.emoji}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Game information */}
|
||||
{/* <div className="bg-white rounded-xl p-2 shadow-lg border border-indigo-100 max-w-3xl mx-auto mt-4 z-10">
|
||||
<p className="text-gray-600 text-sm text-center">
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-white border border-gray-300 rounded-full mr-1"></span> Sima</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-green-200 border border-green-500 rounded-full mr-1"></span> Lóhere</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-red-200 border border-red-500 rounded-full mr-1"></span> Rossz</span>
|
||||
<span className="inline-flex items-center mx-2"><span className="w-3 h-3 bg-blue-200 border border-blue-500 rounded-full mr-1"></span> Jó</span>
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{/* Right sidebar */}
|
||||
<div className="flex-1 max-w-md">
|
||||
<div className="bg-gray-800 rounded-xl p-4 shadow-lg mb-4 border border-teal-700">
|
||||
<h2 className="text-xl font-semibold mb-3 text-teal-300">Játékosok</h2>
|
||||
|
||||
{/* Empty state */}
|
||||
{players.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<div className="text-4xl mb-2">👥</div>
|
||||
<p className="text-sm">Várakozás játékosokra...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Players list */}
|
||||
{sortedPlayers.map((player, index) => (
|
||||
<div
|
||||
key={player.id}
|
||||
className="flex items-center mb-3 p-2 bg-gray-900 rounded-lg hover:bg-gray-700 transition-colors"
|
||||
className="flex items-center mb-3 p-2 bg-gray-900 rounded-lg hover:bg-gray-700 transition-colors relative"
|
||||
>
|
||||
{/* Online indicator */}
|
||||
{player.isOnline && (
|
||||
<div className="absolute top-1 right-1 w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`w-8 h-8 ${player.color} rounded-full mr-3 flex items-center justify-center text-white text-sm font-bold shadow-md`}
|
||||
>
|
||||
{player.emoji}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm text-gray-300 flex items-center">
|
||||
<div className="font-medium text-sm text-gray-300 flex items-center gap-2 flex-wrap">
|
||||
{player.name}
|
||||
|
||||
{/* Ready indicator */}
|
||||
{player.isReady && (
|
||||
<span className="px-2 py-0.5 bg-green-600 text-white text-xs rounded-full">
|
||||
✓ Kész
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Current turn indicator */}
|
||||
{currentTurn === player.id && (
|
||||
<span className="px-2 py-0.5 bg-yellow-500 text-gray-900 text-xs rounded-full font-bold animate-pulse">
|
||||
▶ Köre
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Rank medal */}
|
||||
<span
|
||||
className={`ml-2 px-2 py-1 rounded-full border text-xs font-bold shadow-md ${getMedalStyle(
|
||||
className={`ml-auto px-2 py-1 rounded-full border text-xs font-bold shadow-md ${getMedalStyle(
|
||||
index + 1
|
||||
)}`}
|
||||
>
|
||||
@@ -225,31 +386,33 @@ const GameScreen = () => {
|
||||
<div className="bg-gray-800 rounded-xl p-4 shadow-lg border border-teal-700 text-center">
|
||||
<h2 className="text-xl font-semibold mb-3 text-teal-300">Dobókocka</h2>
|
||||
<p className="text-gray-300 text-sm mb-4">
|
||||
Kattints a kockára dobáshoz vagy válassz egy számot az alábbiból!
|
||||
Kattints a kockára dobáshoz!
|
||||
</p>
|
||||
|
||||
{/* Dropdown to select number 1-6 (triggers animated roll to that number) */}
|
||||
<div className="mb-3">
|
||||
<select
|
||||
value={selectedDice ?? ""}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value ? Number(e.target.value) : null
|
||||
setSelectedDice(v)
|
||||
}}
|
||||
className="bg-gray-900 text-gray-200 rounded-md p-2 border border-gray-700"
|
||||
>
|
||||
<option value="">Válassz számot...</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Dice onRoll={handleDiceRoll} selectedValue={selectedDice} />
|
||||
<Dice onRoll={handleDiceRoll} />
|
||||
|
||||
{/* Connection warning */}
|
||||
{!isConnected && (
|
||||
<div className="mt-3 text-xs text-red-400">
|
||||
⚠️ Nincs kapcsolat a szerverrel
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Debug Info Panel (Development only) */}
|
||||
{import.meta.env.DEV && (
|
||||
<div className="bg-gray-900 rounded-xl p-4 shadow-lg border border-gray-700 text-left mt-4">
|
||||
<h3 className="text-sm font-semibold mb-2 text-gray-400">🔧 Debug Info</h3>
|
||||
<div className="text-xs text-gray-500 space-y-1">
|
||||
<div>📡 Connected: {isConnected ? '✅' : '❌'}</div>
|
||||
<div>🎮 Game Code: {gameState?.gameCode || 'N/A'}</div>
|
||||
<div>👥 Players: {backendPlayers?.length || 0}</div>
|
||||
<div>🎲 Board Fields: {boardData?.fields?.length || 0}</div>
|
||||
<div>🏁 Current Turn: {currentTurn || 'N/A'}</div>
|
||||
<div>🔑 Token: {gameToken ? '✅' : '❌'}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { createGame, joinGame } from '../../api/gameApi';
|
||||
|
||||
const GameTest = () => {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [gameCode, setGameCode] = useState('');
|
||||
const [createdGameCode, setCreatedGameCode] = useState('');
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
|
||||
const handleCreateGame = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setShowSuccess(false);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
setError('Please login first at /login');
|
||||
return;
|
||||
}
|
||||
|
||||
const gameData = {
|
||||
deckids: ['99333c9a-5928-4788-b852-fa482d34ce56'], // Test deck ID as array
|
||||
maxplayers: 4,
|
||||
logintype: 0, // 0=PUBLIC
|
||||
};
|
||||
|
||||
const response = await createGame(gameData);
|
||||
console.log('Game created:', response);
|
||||
|
||||
// Backend returns game object directly
|
||||
const code = response.gamecode || response.gameCode;
|
||||
if (code) {
|
||||
setCreatedGameCode(code);
|
||||
setShowSuccess(true);
|
||||
}
|
||||
|
||||
// Store game token if provided
|
||||
if (response.gameToken) {
|
||||
localStorage.setItem('gameToken', response.gameToken);
|
||||
}
|
||||
|
||||
// Wait 3 seconds to show code, then navigate
|
||||
setTimeout(() => {
|
||||
navigate('/lobby', { state: { gameCode: code } });
|
||||
}, 3000);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || 'Failed to create game');
|
||||
console.error('Create game error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoinGame = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
setError('Kérlek jelentkezz be először a /login oldalon');
|
||||
return;
|
||||
}
|
||||
|
||||
const joinData = {
|
||||
gameCode: gameCode.toUpperCase(),
|
||||
playerName: localStorage.getItem('username') || 'Test Player',
|
||||
};
|
||||
|
||||
const response = await joinGame(joinData);
|
||||
console.log('Joined game:', response);
|
||||
|
||||
// Store game token
|
||||
if (response.data?.gameToken) {
|
||||
localStorage.setItem('gameToken', response.data.gameToken);
|
||||
navigate('/lobby', { state: { gameCode: gameCode.toUpperCase() } });
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || 'Nem sikerült csatlakozni a játékhoz');
|
||||
console.error('Join game error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-8">
|
||||
<div className="bg-gray-800 rounded-lg p-8 max-w-md w-full">
|
||||
<h1 className="text-3xl font-bold mb-6 text-center">Game Test</h1>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/20 border border-red-500 rounded p-3 mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSuccess && createdGameCode && (
|
||||
<div className="bg-green-500/20 border border-green-500 rounded p-4 mb-4">
|
||||
<p className="font-bold text-lg mb-2">Game Created!</p>
|
||||
<p className="text-2xl font-mono tracking-wider text-green-400 mb-2">
|
||||
{createdGameCode}
|
||||
</p>
|
||||
<p className="text-sm text-gray-300">
|
||||
Share this code with other players so they can join!
|
||||
</p>
|
||||
<p className="text-sm text-gray-400 mt-2">
|
||||
Redirecting to game in 3 seconds...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={handleCreateGame}
|
||||
disabled={loading}
|
||||
className="w-full bg-green-600 hover:bg-green-700 disabled:bg-gray-600 text-white font-bold py-3 px-4 rounded transition"
|
||||
>
|
||||
{loading ? 'Creating...' : 'Create New Game'}
|
||||
</button>
|
||||
|
||||
<div className="text-center text-gray-400">OR</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={gameCode}
|
||||
onChange={(e) => setGameCode(e.target.value)}
|
||||
placeholder="Enter Game Code"
|
||||
className="w-full bg-gray-700 text-white px-4 py-2 rounded mb-2"
|
||||
/>
|
||||
<button
|
||||
onClick={handleJoinGame}
|
||||
disabled={loading || !gameCode}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 text-white font-bold py-3 px-4 rounded transition"
|
||||
>
|
||||
{loading ? 'Joining...' : 'Join Game'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-gray-700">
|
||||
<p className="text-sm text-gray-400 mb-2">Quick Access (Dev Only):</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
localStorage.setItem('gameToken', 'test-token-123');
|
||||
navigate('/game');
|
||||
}}
|
||||
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded text-sm"
|
||||
>
|
||||
Go to Game (with test token)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GameTest;
|
||||
@@ -3,6 +3,8 @@ import { useNavigate, useLocation } from "react-router-dom"
|
||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
|
||||
import { useGameWebSocket } from "../../hooks/useGameWebSocket.js"
|
||||
import { startGame } from "../../api/gameApi.js"
|
||||
|
||||
const Lobby = () => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
@@ -11,6 +13,30 @@ const Lobby = () => {
|
||||
const location = useLocation()
|
||||
|
||||
const [user, setUser] = useRequireAuth()
|
||||
|
||||
// Get game code from location state or WebSocket
|
||||
const gameCodeFromState = location.state?.gameCode
|
||||
const gameToken = localStorage.getItem('gameToken')
|
||||
|
||||
const {
|
||||
isConnected,
|
||||
gameState,
|
||||
players,
|
||||
isGamemaster,
|
||||
gameStarted,
|
||||
} = useGameWebSocket(gameToken)
|
||||
|
||||
const gameCode = gameCodeFromState || gameState?.gameCode || 'Loading...'
|
||||
|
||||
// Filter out gamemaster from player list - gamemaster is NOT a player
|
||||
const currentPlayers = (players || []).filter(p => {
|
||||
// If we have userId info, filter by that
|
||||
if (p.userId) {
|
||||
return p.userId !== gameState?.createdBy
|
||||
}
|
||||
// Otherwise filter by name (less reliable but works for now)
|
||||
return true
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
@@ -23,12 +49,48 @@ const Lobby = () => {
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
// Auto-navigate when game starts
|
||||
useEffect(() => {
|
||||
if (gameStarted) {
|
||||
console.log('🎮 Game started, navigating to /game')
|
||||
navigate("/game")
|
||||
}
|
||||
}, [gameStarted, navigate])
|
||||
|
||||
const handleExit = () => {
|
||||
if (window.confirm("Biztosan ki szeretnél lépni a lobbyból?")) {
|
||||
localStorage.removeItem('gameToken')
|
||||
navigate("/home")
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartGame = async () => {
|
||||
try {
|
||||
// Get gameId from gameState
|
||||
const gameId = gameState?.gameId
|
||||
if (!gameId) {
|
||||
alert('Hiba: Játék azonosító nem található')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Starting game with ID:', gameId)
|
||||
const response = await startGame(gameId)
|
||||
console.log('Game start response:', response)
|
||||
|
||||
// Backend will broadcast game:started event to all players
|
||||
// Navigate to game page
|
||||
navigate("/game")
|
||||
} catch (error) {
|
||||
console.error('Failed to start game:', error)
|
||||
alert(`Nem sikerült elindítani a játékot: ${error.response?.data?.error || error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const copyGameCode = () => {
|
||||
navigator.clipboard.writeText(gameCode)
|
||||
alert('Játék kód vágólapra másolva: ' + gameCode)
|
||||
}
|
||||
|
||||
const getInitials = (name) => {
|
||||
return name
|
||||
.split(" ")
|
||||
@@ -57,31 +119,121 @@ const Lobby = () => {
|
||||
style={{ background: "rgba(0,0,0,0.25)" }}
|
||||
>
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold text-green-300 mb-4 text-center tracking-wide drop-shadow-lg">
|
||||
{user} Lobby-ja
|
||||
Játék Lobby
|
||||
</h1>
|
||||
|
||||
<p className="text-lg text-zinc-300 mb-8 text-center">
|
||||
Játékosok, akik csatlakoztak ehhez a szobához:
|
||||
{/* Game Code Display */}
|
||||
<div className="bg-gradient-to-r from-green-600/20 to-teal-600/20 rounded-xl p-6 mb-6 border-2 border-green-400/50">
|
||||
<p className="text-lg text-zinc-300 mb-2 text-center font-semibold">
|
||||
Játék Kód:
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<p className="text-5xl font-mono font-extrabold text-green-300 tracking-widest drop-shadow-lg">
|
||||
{gameCode}
|
||||
</p>
|
||||
<button
|
||||
onClick={copyGameCode}
|
||||
className="bg-green-600 hover:bg-green-500 text-white px-4 py-2 rounded-lg font-semibold transition-all duration-200 hover:scale-105"
|
||||
title="Másolás vágólapra"
|
||||
>
|
||||
📋 Másolás
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-zinc-400 mt-3 text-center">
|
||||
Oszd meg ezt a kódot másokkal, hogy csatlakozhassanak a játékhoz!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Connection Status */}
|
||||
<div className="mb-4 text-center">
|
||||
<span className={`inline-block px-4 py-2 rounded-full text-sm font-semibold ${
|
||||
isConnected
|
||||
? 'bg-green-600/20 text-green-300 border border-green-400'
|
||||
: 'bg-red-600/20 text-red-300 border border-red-400'
|
||||
}`}>
|
||||
{isConnected ? '🟢 Kapcsolódva' : '🔴 Kapcsolat megszakadt'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-lg text-zinc-300 mb-6 text-center">
|
||||
Játékosok ({currentPlayers.length}):
|
||||
</p>
|
||||
|
||||
<div className="bg-zinc-800/90 rounded-xl shadow-lg p-6 mb-8">
|
||||
<ul className="flex flex-col gap-4">
|
||||
<li className="bg-zinc-700 py-3 px-4 rounded-xl text-green-400 font-semibold flex items-center gap-4 shadow hover:shadow-green-500/20 transition">
|
||||
<div
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
|
||||
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
|
||||
>
|
||||
{getInitials(user)}
|
||||
</div>
|
||||
<span className="text-white text-lg">{user}</span>
|
||||
</li>
|
||||
{currentPlayers.length === 0 ? (
|
||||
<li className="text-center text-zinc-400 py-4">
|
||||
Várakozás játékosokra...
|
||||
</li>
|
||||
) : (
|
||||
currentPlayers.map((player, index) => (
|
||||
<li
|
||||
key={player.id || index}
|
||||
className="bg-zinc-700 py-3 px-4 rounded-xl text-green-400 font-semibold flex items-center gap-4 shadow hover:shadow-green-500/20 transition"
|
||||
>
|
||||
<div
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
|
||||
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
|
||||
>
|
||||
{getInitials(player.name || `Player ${index + 1}`)}
|
||||
</div>
|
||||
<span className="text-white text-lg flex-1">
|
||||
{player.name || `Player ${index + 1}`}
|
||||
</span>
|
||||
{player.isReady && (
|
||||
<span className="bg-green-600 text-white text-xs px-2 py-1 rounded-full">
|
||||
Kész
|
||||
</span>
|
||||
)}
|
||||
{player.isOnline && (
|
||||
<span className="text-green-400 text-xs">🟢</span>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
{/* Role indicator */}
|
||||
<div className="mb-6 text-center">
|
||||
{isGamemaster ? (
|
||||
<div className="bg-yellow-600/20 text-yellow-300 px-4 py-3 rounded-lg border border-yellow-400/50">
|
||||
<p className="font-semibold">👑 Te vagy a Gamemaster!</p>
|
||||
<p className="text-sm mt-1">Te nem játszol, csak indítod és moderálod a játékot.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-blue-600/20 text-blue-300 px-4 py-3 rounded-lg border border-blue-400/50">
|
||||
<p className="font-semibold">🎮 Te vagy egy Játékos!</p>
|
||||
<p className="text-sm mt-1">Várj, amíg a gamemaster elindítja a játékot.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-4">
|
||||
{isGamemaster ? (
|
||||
/* Gamemaster view - can start game */
|
||||
<button
|
||||
onClick={handleStartGame}
|
||||
disabled={currentPlayers.length < 2}
|
||||
className={`px-8 py-3 rounded-xl font-semibold shadow-lg transition-transform transform hover:scale-105 ${
|
||||
currentPlayers.length >= 2
|
||||
? 'bg-gradient-to-r from-green-700 to-green-500 hover:from-green-600 hover:to-green-400 text-white hover:shadow-green-400/30'
|
||||
: 'bg-gray-600 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
title={currentPlayers.length < 2 ? 'Minimum 2 játékos szükséges' : 'Játék indítása'}
|
||||
>
|
||||
Játék Indítása
|
||||
</button>
|
||||
) : (
|
||||
/* Player view - cannot start game, just wait */
|
||||
<div className="text-center text-zinc-400">
|
||||
<p className="text-lg">Várakozás a gamemaster-re...</p>
|
||||
<p className="text-sm mt-2">Csak a gamemaster indíthatja el a játékot</p>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={handleExit}
|
||||
className="bg-gradient-to-r from-green-700 to-green-500 hover:from-green-600 hover:to-green-400 text-white px-8 py-3 rounded-xl font-semibold shadow-lg hover:shadow-green-400/30 transition-transform transform hover:scale-105"
|
||||
className="bg-gradient-to-r from-red-700 to-red-500 hover:from-red-600 hover:to-red-400 text-white px-8 py-3 rounded-xl font-semibold shadow-lg hover:shadow-red-400/30 transition-transform transform hover:scale-105"
|
||||
>
|
||||
Kilépés
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react"
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { useNavigate, useLocation } from "react-router-dom"
|
||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
@@ -6,6 +6,7 @@ import Footer from "../../components/Footer/Footer.jsx"
|
||||
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
|
||||
import ButtonGreen from "../../components/Buttons/ButtonGreen.jsx"
|
||||
import { motion } from "framer-motion"
|
||||
import { createGame, joinGame } from "../../api/gameApi.js"
|
||||
|
||||
const GameLobbySetup = () => {
|
||||
const [username] = useRequireAuth({ key: "username", redirectTo: "/login" })
|
||||
@@ -16,19 +17,82 @@ const GameLobbySetup = () => {
|
||||
|
||||
const [maxPlayers, setMaxPlayers] = useState(4)
|
||||
const [isPublic, setIsPublic] = useState(true)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const [createdGameCode, setCreatedGameCode] = useState('')
|
||||
const [showSuccess, setShowSuccess] = useState(false)
|
||||
|
||||
const handleCreateLobby = () => {
|
||||
console.log({
|
||||
deckIds,
|
||||
maxPlayers,
|
||||
isPublic,
|
||||
})
|
||||
// Itt küldd el az API-nak a lobby létrehozását
|
||||
// navigate("/game-lobby", { state: { lobbyId: response.lobbyId } })
|
||||
const handleCreateLobby = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const username = localStorage.getItem('username')
|
||||
|
||||
console.log('Creating game - username:', username)
|
||||
|
||||
if (!username) {
|
||||
setError('Kérlek jelentkezz be először!')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Backend expects deckids (array), maxplayers (number), logintype (0=PUBLIC, 1=PRIVATE)
|
||||
const gameData = {
|
||||
deckids: deckIds, // Array of deck UUIDs
|
||||
maxplayers: maxPlayers, // Number
|
||||
logintype: isPublic ? 0 : 1, // 0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION
|
||||
}
|
||||
|
||||
console.log('Creating game with data:', gameData)
|
||||
const response = await createGame(gameData)
|
||||
console.log('Game created:', response)
|
||||
|
||||
// Verify localStorage still has username
|
||||
console.log('After create - username:', localStorage.getItem('username'))
|
||||
|
||||
// Backend returns game object directly
|
||||
const code = response.gamecode || response.gameCode
|
||||
if (code) {
|
||||
setCreatedGameCode(code)
|
||||
setShowSuccess(true)
|
||||
}
|
||||
|
||||
// Creator needs to join their own game to get a gameToken
|
||||
// This allows the WebSocket to recognize them as the gamemaster
|
||||
try {
|
||||
const username = localStorage.getItem('username')
|
||||
const joinResponse = await joinGame({
|
||||
gameCode: code,
|
||||
playerName: username
|
||||
})
|
||||
|
||||
if (joinResponse.gameToken) {
|
||||
localStorage.setItem('gameToken', joinResponse.gameToken)
|
||||
console.log('Creator joined game as gamemaster, token stored')
|
||||
}
|
||||
} catch (joinError) {
|
||||
console.error('Failed to join game as creator:', joinError)
|
||||
// Continue anyway - the creator can still try to join manually
|
||||
}
|
||||
|
||||
// Wait 3 seconds to show code, then navigate to lobby
|
||||
setTimeout(() => {
|
||||
console.log('Navigating to lobby with code:', code)
|
||||
navigate('/lobby', { state: { gameCode: code } })
|
||||
}, 3000)
|
||||
} catch (err) {
|
||||
console.error('Create game error:', err)
|
||||
console.error('Error response:', err.response?.data)
|
||||
console.error('Error status:', err.response?.status)
|
||||
setError(err.response?.data?.message || err.response?.data?.error || 'Nem sikerült létrehozni a játékot')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (deckIds.length === 0) {
|
||||
navigate("/choose-deck")
|
||||
navigate("/choosedeck")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -67,6 +131,27 @@ const GameLobbySetup = () => {
|
||||
{deckIds.length} pakli kiválasztva. Add meg a játék részleteit.
|
||||
</motion.p>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/20 border border-red-500 rounded-lg p-4 mb-6">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{createdGameCode && (
|
||||
<div className="bg-green-500/20 border border-green-500 rounded-lg p-6 mb-6">
|
||||
<p className="font-bold text-xl mb-2">Játék Létrehozva! 🎉</p>
|
||||
<p className="text-3xl font-mono tracking-wider text-green-400 mb-2">
|
||||
{createdGameCode}
|
||||
</p>
|
||||
<p className="text-sm text-gray-300">
|
||||
Oszd meg ezt a kódot más játékosokkal, hogy csatlakozhassanak!
|
||||
</p>
|
||||
<p className="text-sm text-gray-400 mt-2">
|
||||
Átirányítás a lobby-hoz 3 másodperc múlva...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl p-8 shadow-lg space-y-6">
|
||||
{/* Max Players */}
|
||||
<div>
|
||||
@@ -115,11 +200,17 @@ const GameLobbySetup = () => {
|
||||
<div className="flex justify-center gap-4 mt-8">
|
||||
<ButtonGreen
|
||||
text="Vissza"
|
||||
onClick={() => navigate("/choose-deck")}
|
||||
onClick={() => navigate("/choosedeck")}
|
||||
width="w-auto px-8"
|
||||
className="bg-gray-600 hover:bg-gray-700"
|
||||
disabled={loading}
|
||||
/>
|
||||
<ButtonGreen
|
||||
text={loading ? "Létrehozás..." : "Lobby Létrehozása"}
|
||||
onClick={handleCreateLobby}
|
||||
width="w-auto px-8"
|
||||
disabled={loading}
|
||||
/>
|
||||
<ButtonGreen text="Lobby Létrehozása" onClick={handleCreateLobby} width="w-auto px-8" />
|
||||
</div>
|
||||
</motion.section>
|
||||
</main>
|
||||
|
||||
@@ -1,24 +1,75 @@
|
||||
// src/pages/Home/Home.jsx
|
||||
// Régi PlayMenu-s oldal, "Home" néven
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import useRequireAuth from "../../hooks/useRequireAuth"
|
||||
import Navbar from "../../components/Navbar/Navbar"
|
||||
import Footer from "../../components/Footer/Footer.jsx"
|
||||
import Background from "../../assets/backgrounds/Background.jsx"
|
||||
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
|
||||
import { joinGame } from "../../api/gameApi.js"
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate()
|
||||
// a hook inicializálja a user-t a localStorage-ból és visszaadja a state-et + settert
|
||||
const [user, setUser] = useRequireAuth({ redirect: false }) // no redirect on unauthenticated visitors
|
||||
const [isJoining, setIsJoining] = useState(false)
|
||||
|
||||
// Dummy callbackok és user példa
|
||||
const handleJoinGame = (code) => {
|
||||
alert(`Csatlakozás játékhoz: ${code}`)
|
||||
// Join game handler - csatlakozás játékhoz kóddal
|
||||
const handleJoinGame = async (code) => {
|
||||
if (!user) {
|
||||
alert('Kérlek először jelentkezz be!')
|
||||
navigate('/login')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('=== JOIN GAME DEBUG ===')
|
||||
console.log('Current user:', user)
|
||||
console.log('Game code:', code)
|
||||
console.log('LocalStorage username:', localStorage.getItem('username'))
|
||||
console.log('LocalStorage authLevel:', localStorage.getItem('authLevel'))
|
||||
console.log('======================')
|
||||
|
||||
setIsJoining(true)
|
||||
try {
|
||||
const joinData = {
|
||||
gameCode: code.toUpperCase(),
|
||||
playerName: user || 'Player',
|
||||
}
|
||||
|
||||
console.log('Sending join request with:', joinData)
|
||||
const response = await joinGame(joinData)
|
||||
console.log('Joined game:', response)
|
||||
|
||||
// Backend returns game object directly
|
||||
if (response.gameToken) {
|
||||
localStorage.setItem('gameToken', response.gameToken)
|
||||
}
|
||||
|
||||
navigate('/lobby', { state: { gameCode: code.toUpperCase() } })
|
||||
} catch (err) {
|
||||
const errorMsg = err.response?.data?.error || err.response?.data?.message || 'Nem sikerült csatlakozni a játékhoz'
|
||||
alert(errorMsg)
|
||||
console.error('Join game error:', err)
|
||||
console.error('Error details:', err.response?.data)
|
||||
} finally {
|
||||
setIsJoining(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Create game handler - új játék létrehozása
|
||||
const handleCreateGame = () => {
|
||||
alert("Új játék létrehozása")
|
||||
if (!user) {
|
||||
alert('Kérlek először jelentkezz be!')
|
||||
navigate('/login')
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate to choose deck page to start game creation flow
|
||||
navigate('/choosedeck')
|
||||
}
|
||||
|
||||
const userObj = { name: user }
|
||||
|
||||
// ha szükséges a user módosítása máshol: setUser("újnév") automatikusan menti localStorage-be
|
||||
|
||||
@@ -13,6 +13,17 @@ export default defineConfig({
|
||||
},
|
||||
hmr: {
|
||||
clientPort: 5173,
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://backend:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/socket.io': {
|
||||
target: 'http://backend:3000',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
|
||||
Reference in New Issue
Block a user