import React, { useEffect, useState, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { useRoom } from '../hooks/useRoom'; import { questionsApi } from '../services/api'; import QRCode from 'qrcode'; import socketService from '../services/socket'; import QRModal from '../components/QRModal'; import NameInputModal from '../components/NameInputModal'; import PasswordModal from '../components/PasswordModal'; import RoleSelectionModal from '../components/RoleSelectionModal'; import GameManagementModal from '../components/GameManagementModal'; const RoomPage = () => { const { roomCode } = useParams(); const navigate = useNavigate(); const { user, loginAnonymous, loading: authLoading } = useAuth(); // Callback для автоматической навигации при старте игры const handleGameStartedEvent = useCallback(() => { navigate(`/game/${roomCode}`); }, [navigate, roomCode]); const [password, setPassword] = useState(null); const { room, participants, loading, error, requiresPassword, fetchRoomWithPassword, joinRoom, startGame, } = useRoom(roomCode, handleGameStartedEvent, password); const [qrCode, setQrCode] = useState(''); const [joined, setJoined] = useState(false); const [isQRModalOpen, setIsQRModalOpen] = useState(false); const [isNameModalOpen, setIsNameModalOpen] = useState(false); const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false); const [isRoleSelectionModalOpen, setIsRoleSelectionModalOpen] = useState(false); const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false); const [passwordError, setPasswordError] = useState(null); const [joinError, setJoinError] = useState(null); const [selectedRole, setSelectedRole] = useState('PLAYER'); const [questionPacks, setQuestionPacks] = useState([]); useEffect(() => { const generateQR = async () => { try { // Используем абсолютный URL с протоколом для работы из любого приложения const origin = window.location.origin || `${window.location.protocol}//${window.location.host}`; const url = `${origin}/join-room?code=${roomCode}`; const qr = await QRCode.toDataURL(url, { errorCorrectionLevel: 'M', type: 'image/png', quality: 0.92, margin: 1, }); setQrCode(qr); } catch (err) { console.error('QR generation error:', err); } }; if (roomCode) { generateQR(); } }, [roomCode]); // Проверка пароля: показываем модальное окно, если требуется пароль // Хост не должен видеть модальное окно пароля (проверяется на бэкенде) useEffect(() => { if (requiresPassword && !isPasswordModalOpen && !loading && user) { // Проверяем, не является ли пользователь хостом // Если это хост, то requiresPassword не должно быть true (бэкенд должен разрешить доступ) setIsPasswordModalOpen(true); } else if (requiresPassword && !isPasswordModalOpen && !loading && !user) { // Если пользователь не авторизован, все равно показываем модальное окно // После авторизации проверим, является ли он хостом setIsPasswordModalOpen(true); } }, [requiresPassword, isPasswordModalOpen, loading, user]); // Проверка авторизации и показ модального окна для ввода имени useEffect(() => { if (!authLoading && !user && room && !loading && !requiresPassword) { setIsNameModalOpen(true); } else if (user) { setIsNameModalOpen(false); } }, [authLoading, user, room, loading, requiresPassword]); // Обработка ввода пароля const handlePasswordSubmit = async (enteredPassword) => { try { setPasswordError(null); await fetchRoomWithPassword(enteredPassword); setPassword(enteredPassword); setIsPasswordModalOpen(false); } catch (error) { console.error('Password error:', error); if (error.response?.status === 401) { setPasswordError('Неверный пароль. Попробуйте еще раз.'); } else { setPasswordError('Ошибка при проверке пароля. Попробуйте еще раз.'); } } }; // Обработка ввода имени и авторизация const handleNameSubmit = async (name) => { try { await loginAnonymous(name); setIsNameModalOpen(false); } catch (error) { console.error('Login error:', error); alert('Ошибка при авторизации. Попробуйте еще раз.'); } }; // Показываем модальное окно выбора роли, если allowSpectators === true и пользователь авторизован useEffect(() => { if ( room && user && !joined && !isRoleSelectionModalOpen && room.allowSpectators && !participants.some((p) => p.userId === user.id) ) { setIsRoleSelectionModalOpen(true); } }, [room, user, joined, participants, isRoleSelectionModalOpen]); // Автоматическое присоединение как PLAYER, если зрители не разрешены useEffect(() => { const handleJoin = async () => { if (room && user && !joined && !isRoleSelectionModalOpen) { const isParticipant = participants.some((p) => p.userId === user.id); if (!isParticipant) { // Если зрители не разрешены, присоединяемся как PLAYER автоматически if (!room.allowSpectators) { try { setJoinError(null); await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER'); setJoined(true); } catch (error) { console.error('Join error:', error); const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате'; setJoinError(errorMessage); alert(errorMessage); } } } else { setJoined(true); } } }; handleJoin(); }, [room, user, participants, joined, joinRoom, isRoleSelectionModalOpen]); // Обработка выбора роли const handleRoleSubmit = async (role) => { if (!room || !user) return; try { setJoinError(null); setIsRoleSelectionModalOpen(false); await joinRoom(room.id, user.id, user.name || 'Гость', role); setSelectedRole(role); setJoined(true); } catch (error) { console.error('Join error:', error); const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате'; setJoinError(errorMessage); alert(errorMessage); // Открываем модальное окно снова при ошибке setIsRoleSelectionModalOpen(true); } }; useEffect(() => { const fetchPacks = async () => { if (user) { try { const response = await questionsApi.getPacks(user.id); setQuestionPacks(response.data); } catch (error) { console.error('Error fetching question packs:', error); } } }; // Проверяем роль участника для поддержки нескольких хостов const currentUserParticipant = user && participants ? participants.find(p => p.userId === user.id) : null; const isHost = currentUserParticipant?.role === 'HOST'; if (room && user && isHost) { fetchPacks(); } }, [room, user, participants]); // Автоматически перенаправляем на страницу игры, если игра уже началась useEffect(() => { if (room && room.status === 'PLAYING') { navigate(`/game/${roomCode}`); } }, [room, roomCode, navigate]); const handleStartGame = () => { startGame(); navigate(`/game/${roomCode}`); }; // Получаем вопросы из roomPack (может быть JSON строкой или массивом) const getRoomQuestions = () => { if (!room?.roomPack?.questions) return []; const questions = room.roomPack.questions; if (typeof questions === 'string') { try { return JSON.parse(questions); } catch { return []; } } return Array.isArray(questions) ? questions : []; }; const roomQuestions = getRoomQuestions(); // Обновление вопросов через WebSocket const handleUpdateRoomQuestions = useCallback( async (newQuestions) => { if (!room?.id || !user) return; try { socketService.updateRoomPack( room.id, room.code, user.id, newQuestions ); } catch (error) { console.error('Error updating room questions:', error); alert('Ошибка при сохранении вопросов'); } }, [room, user] ); // Изменение роли участника через WebSocket const handleChangeParticipantRole = useCallback( (participantId, newRole) => { if (!room?.id || !user) return; try { socketService.changeParticipantRole( room.id, room.code, user.id, participantId, newRole ); } catch (error) { console.error('Error changing participant role:', error); alert('Ошибка при изменении роли участника'); } }, [room, user] ); if (loading) { return
Загрузка комнаты...
; } // Не показываем ошибку, если требуется пароль - покажем модальное окно if (error && !requiresPassword && error !== 'Room password required') { return (

Ошибка

{error}

); } if (!room && !requiresPassword && !loading) { return (

Комната не найдена

); } // Проверяем роль участника для поддержки нескольких хостов const currentUserParticipant = user && participants ? participants.find(p => p.userId === user.id) : null; const isHost = currentUserParticipant?.role === 'HOST'; // Если требуется пароль, показываем только модальное окно if (requiresPassword && !room) { return ( <>
Загрузка комнаты...
navigate('/')} error={passwordError} /> ); } return (

Комната: {room.code}

Статус: {room.status === 'WAITING' ? 'Ожидание игроков' : room.status}

Игроков: {participants.length}/{room.maxPlayers}

Участники:

    {participants.map((participant) => (
  • {participant.name} {participant.role === 'HOST' && '(Ведущий)'} {participant.role === 'SPECTATOR' && '(Зритель)'}
  • ))}
{isHost && (
)}
{isHost && (room.status === 'WAITING' || (room.status === 'PLAYING' && (!room.questionPack || room.questionPack.questionCount === 0 || room.currentQuestionIndex === 0))) && ( )}
setIsQRModalOpen(false)} qrCode={qrCode} roomCode={roomCode} /> navigate('/')} error={passwordError} /> navigate('/')} allowSpectators={room?.allowSpectators} title="Выберите роль" description={room?.allowSpectators ? "Выберите роль для присоединения к комнате" : "Присоединиться как игрок"} /> {isHost && room && ( setIsQuestionsModalOpen(false)} initialTab="questions" room={{ id: room.id, code: room.code, status: room.status, hostId: room.hostId, }} participants={participants} currentQuestion={null} currentQuestionIndex={0} totalQuestions={roomQuestions.length} revealedAnswers={[]} questions={roomQuestions} onUpdateQuestions={handleUpdateRoomQuestions} availablePacks={questionPacks} onChangeParticipantRole={handleChangeParticipantRole} onStartGame={handleStartGame} /> )}
); }; export default RoomPage;