Files
SerpentRace/SerpentRace_Frontend/src/pages/Game/Lobby.jsx
T
2025-11-18 00:09:08 +01:00

419 lines
16 KiB
React

import React, { useEffect, useRef, useState } from "react"
import { useLocation } from "react-router-dom"
import HandleNavigate from "../../utils/HandleNavigate/HandleNavigate"
import Navbar from "../../components/Navbar/Navbar.jsx"
import Background from "../../assets/backgrounds/Background.jsx"
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
import { useGameWebSocketContext } from "../../contexts/GameWebSocketContext"
import { startGame } from "../../api/gameApi.js"
const Lobby = () => {
const [visible, setVisible] = useState(false)
const [isStarting, setIsStarting] = useState(false)
const sectionRef = useRef(null)
const { goHome, goGame } = HandleNavigate()
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')
// Use the shared WebSocket context
const {
connect,
isConnected,
gameState,
players,
isGamemaster,
gameStarted,
pendingPlayers,
approvalStatus,
approvePlayer,
rejectPlayer,
} = useGameWebSocketContext()
// Connect to WebSocket when component mounts
useEffect(() => {
if (gameToken) {
connect(gameToken)
}
}, [gameToken, connect])
const gameCode = gameCodeFromState || gameState?.gameCode || 'Loading...'
// Players list - gamemaster is separate, don't filter
// Backend should handle this correctly
const currentPlayers = players || []
// Debug logging
useEffect(() => {
if (import.meta.env.DEV) {
console.log('🎮 Lobby state update:')
console.log(' - isGamemaster:', isGamemaster)
console.log(' - gameState:', gameState)
console.log(' - players:', players)
console.log(' - currentPlayers:', currentPlayers)
console.log(' - pendingPlayers:', pendingPlayers)
}
}, [isGamemaster, gameState, players, currentPlayers, pendingPlayers])
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setVisible(true)
},
{ threshold: 0.3 }
)
if (sectionRef.current) observer.observe(sectionRef.current)
return () => observer.disconnect()
}, [])
// Auto-navigate when game starts
useEffect(() => {
if (gameStarted) {
console.log('🎮 Game started, navigating to /game')
goGame()
}
}, [gameStarted, goGame])
// Handle approval status changes
useEffect(() => {
if (approvalStatus === 'denied') {
alert('A gamemaster elutasította a csatlakozási kérelmedet.')
localStorage.removeItem('gameToken')
goHome()
} else if (approvalStatus === 'approved') {
console.log('✅ Join approved, you can now see the lobby')
}
}, [approvalStatus, goHome])
const handleExit = () => {
if (window.confirm("Biztosan ki szeretnél lépni a lobbyból?")) {
localStorage.removeItem('gameToken')
goHome()
}
}
const handleStartGame = async () => {
// Prevent double-click
if (isStarting) {
console.log('⚠️ Game start already in progress, ignoring duplicate request')
return
}
try {
setIsStarting(true)
// 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)
// Store boardData and updated game state for GameScreen
if (response.boardData) {
localStorage.setItem('boardData', JSON.stringify(response.boardData))
console.log('✅ boardData stored in localStorage')
}
// Navigate immediately after successful start (don't wait for WebSocket)
console.log('🎮 Navigating to /game...')
goGame()
} catch (error) {
console.error('Failed to start game:', error)
// Check if game already started
if (error.response?.status === 409) {
console.log('Game already started, navigating to /game...')
// Navigate anyway - game is already running
goGame()
} else {
alert(`Nem sikerült elindítani a játékot: ${error.response?.data?.error || error.message}`)
}
} finally {
setIsStarting(false)
}
}
const copyGameCode = () => {
navigator.clipboard.writeText(gameCode)
alert('Játék kód vágólapra másolva: ' + gameCode)
}
const handleApprovePlayer = (playerName) => {
if (approvePlayer(playerName)) {
console.log(`✅ Player ${playerName} approved`)
}
}
const handleRejectPlayer = (playerName) => {
const reason = prompt(`Miért utasítod el ${playerName}-t?`, 'Nincs hely a játékban')
if (reason !== null) { // User didn't cancel
if (rejectPlayer(playerName, reason)) {
console.log(`❌ Player ${playerName} rejected`)
}
}
}
const getInitials = (name) => {
return name
.split(" ")
.map((n) => n[0])
.join("")
.slice(0, 2)
.toUpperCase()
}
return (
<div className="flex flex-col min-h-screen overflow-y-auto relative">
<div className="fixed top-0 left-0 w-full h-full -z-10">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
{/* Waiting for Approval Screen (Non-gamemaster, PRIVATE games) */}
{!isGamemaster && approvalStatus === 'pending' && (
<div className="flex-1 flex items-center justify-center px-4 py-24">
<div className="bg-zinc-900/95 backdrop-blur-sm rounded-3xl p-8 max-w-md w-full border border-yellow-500/50 shadow-2xl">
<div className="text-center">
<div className="mb-6">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-yellow-900/30 border-4 border-yellow-500/50 animate-pulse">
<span className="text-4xl"></span>
</div>
</div>
<h2 className="text-3xl font-bold text-yellow-300 mb-4">
Várakozás jóváhagyásra
</h2>
<p className="text-zinc-300 text-lg mb-6">
A gamemaster még nem hagyta jóvá a csatlakozásodat.
</p>
<p className="text-zinc-400 text-sm mb-8">
Kérjük, várj türelemmel, amíg a gamemaster elfogadja a kérelmedet.
</p>
<div className="flex flex-col gap-3">
<div className="bg-zinc-800 rounded-lg p-4 border border-zinc-700">
<p className="text-zinc-400 text-xs mb-1">Játék kód:</p>
<p className="text-2xl font-mono font-bold text-green-300 tracking-widest">
{gameCode}
</p>
</div>
<button
onClick={handleExit}
className="bg-red-600 hover:bg-red-500 text-white px-6 py-3 rounded-lg font-semibold transition-all duration-200 hover:scale-105"
>
Mégse
</button>
</div>
</div>
</div>
</div>
)}
{/* Normal Lobby View (Gamemaster or approved players) */}
{(isGamemaster || approvalStatus !== 'pending') && (
<div className="flex-1 flex items-center justify-center px-4 py-24">
<section
ref={sectionRef}
className={`w-full max-w-3xl rounded-2xl p-8 md:p-10 transition-all duration-1000 ease-out backdrop-blur-md shadow-2xl ${
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
}`}
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">
Játék Lobby
</h1>
{/* 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>
{/* Pending Players Section (Gamemaster only, PRIVATE games) */}
{isGamemaster && pendingPlayers && pendingPlayers.length > 0 && (
<div className="bg-yellow-900/20 border-2 border-yellow-500/50 rounded-xl shadow-lg p-6 mb-6">
<h3 className="text-xl font-bold text-yellow-300 mb-4 flex items-center gap-2">
<span></span>
Jóváhagyásra váró játékosok ({pendingPlayers.length})
</h3>
<ul className="flex flex-col gap-3">
{pendingPlayers.map((player, index) => (
<li
key={index}
className="bg-zinc-700 py-3 px-4 rounded-xl flex items-center gap-4"
>
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold bg-yellow-900/30 text-yellow-300 border border-yellow-500/50"
>
{getInitials(player.playerName)}
</div>
<div className="flex-1">
<span className="text-white text-lg font-semibold">
{player.playerName}
</span>
{player.isAuthenticated && (
<span className="ml-2 text-xs bg-green-600/30 text-green-300 px-2 py-0.5 rounded-full border border-green-500/50">
Bejelentkezve
</span>
)}
</div>
<div className="flex gap-2">
<button
onClick={() => handleApprovePlayer(player.playerName)}
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 flex items-center gap-1"
title="Jóváhagyás"
>
<span></span>
<span className="hidden sm:inline">Jóváhagy</span>
</button>
<button
onClick={() => handleRejectPlayer(player.playerName)}
className="bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded-lg font-semibold transition-all duration-200 hover:scale-105 flex items-center gap-1"
title="Elutasítás"
>
<span></span>
<span className="hidden sm:inline">Elutasít</span>
</button>
</div>
</li>
))}
</ul>
</div>
)}
<div className="bg-zinc-800/90 rounded-xl shadow-lg p-6 mb-8">
<ul className="flex flex-col gap-4">
{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>
{/* 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 || isStarting}
className={`px-8 py-3 rounded-xl font-semibold shadow-lg transition-transform transform hover:scale-105 ${
currentPlayers.length >= 2 && !isStarting
? '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={
isStarting
? 'Játék indítása folyamatban...'
: currentPlayers.length < 2
? 'Minimum 2 játékos szükséges'
: 'Játék indítása'
}
>
{isStarting ? '⏳ Indítás...' : '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-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>
</div>
</section>
</div>
)}
</div>
)
}
export default Lobby