2026-01-10 20:49:42 +00:00
|
|
|
|
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
2026-01-03 14:07:04 +00:00
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
|
|
import { useAuth } from '../context/AuthContext';
|
2026-01-10 20:49:42 +00:00
|
|
|
|
import { useTheme } from '../context/ThemeContext';
|
2026-01-03 14:07:04 +00:00
|
|
|
|
import { useRoom } from '../hooks/useRoom';
|
2026-01-06 20:27:50 +00:00
|
|
|
|
import { questionsApi } from '../services/api';
|
2026-01-03 14:07:04 +00:00
|
|
|
|
import QRCode from 'qrcode';
|
2026-01-10 15:51:33 +00:00
|
|
|
|
import socketService from '../services/socket';
|
2026-01-06 20:12:36 +00:00
|
|
|
|
import QRModal from '../components/QRModal';
|
2026-01-07 13:24:30 +00:00
|
|
|
|
import NameInputModal from '../components/NameInputModal';
|
2026-01-10 00:18:08 +00:00
|
|
|
|
import PasswordModal from '../components/PasswordModal';
|
2026-01-10 15:51:33 +00:00
|
|
|
|
import RoleSelectionModal from '../components/RoleSelectionModal';
|
|
|
|
|
|
import GameManagementModal from '../components/GameManagementModal';
|
2026-01-03 14:07:04 +00:00
|
|
|
|
|
|
|
|
|
|
const RoomPage = () => {
|
|
|
|
|
|
const { roomCode } = useParams();
|
|
|
|
|
|
const navigate = useNavigate();
|
2026-01-07 13:24:30 +00:00
|
|
|
|
const { user, loginAnonymous, loading: authLoading } = useAuth();
|
2026-01-10 20:49:42 +00:00
|
|
|
|
const { changeTheme } = useTheme();
|
|
|
|
|
|
|
|
|
|
|
|
// Храним предыдущий themeId комнаты для отслеживания изменений
|
|
|
|
|
|
const previousThemeIdRef = useRef(null);
|
2026-01-07 14:50:33 +00:00
|
|
|
|
|
|
|
|
|
|
// Callback для автоматической навигации при старте игры
|
|
|
|
|
|
const handleGameStartedEvent = useCallback(() => {
|
|
|
|
|
|
navigate(`/game/${roomCode}`);
|
|
|
|
|
|
}, [navigate, roomCode]);
|
|
|
|
|
|
|
2026-01-10 00:18:08 +00:00
|
|
|
|
const [password, setPassword] = useState(null);
|
2026-01-06 20:27:50 +00:00
|
|
|
|
const {
|
|
|
|
|
|
room,
|
|
|
|
|
|
participants,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
error,
|
2026-01-10 00:18:08 +00:00
|
|
|
|
requiresPassword,
|
|
|
|
|
|
fetchRoomWithPassword,
|
2026-01-06 20:27:50 +00:00
|
|
|
|
joinRoom,
|
|
|
|
|
|
startGame,
|
2026-01-10 00:18:08 +00:00
|
|
|
|
} = useRoom(roomCode, handleGameStartedEvent, password);
|
2026-01-03 14:07:04 +00:00
|
|
|
|
const [qrCode, setQrCode] = useState('');
|
|
|
|
|
|
const [joined, setJoined] = useState(false);
|
2026-01-06 20:12:36 +00:00
|
|
|
|
const [isQRModalOpen, setIsQRModalOpen] = useState(false);
|
2026-01-07 13:24:30 +00:00
|
|
|
|
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
|
2026-01-10 00:18:08 +00:00
|
|
|
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
2026-01-10 15:51:33 +00:00
|
|
|
|
const [isRoleSelectionModalOpen, setIsRoleSelectionModalOpen] = useState(false);
|
|
|
|
|
|
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false);
|
2026-01-10 00:18:08 +00:00
|
|
|
|
const [passwordError, setPasswordError] = useState(null);
|
2026-01-10 15:51:33 +00:00
|
|
|
|
const [joinError, setJoinError] = useState(null);
|
|
|
|
|
|
const [selectedRole, setSelectedRole] = useState('PLAYER');
|
2026-01-06 20:27:50 +00:00
|
|
|
|
const [questionPacks, setQuestionPacks] = useState([]);
|
2026-01-03 14:07:04 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const generateQR = async () => {
|
|
|
|
|
|
try {
|
2026-01-06 20:12:36 +00:00
|
|
|
|
// Используем абсолютный 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,
|
|
|
|
|
|
});
|
2026-01-03 14:07:04 +00:00
|
|
|
|
setQrCode(qr);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('QR generation error:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (roomCode) {
|
|
|
|
|
|
generateQR();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [roomCode]);
|
|
|
|
|
|
|
2026-01-10 00:18:08 +00:00
|
|
|
|
// Проверка пароля: показываем модальное окно, если требуется пароль
|
2026-01-10 20:49:42 +00:00
|
|
|
|
// Показываем независимо от авторизации - пароль проверяется первым
|
2026-01-10 00:18:08 +00:00
|
|
|
|
useEffect(() => {
|
2026-01-10 20:49:42 +00:00
|
|
|
|
if (requiresPassword && !isPasswordModalOpen && !loading) {
|
|
|
|
|
|
// Показывать модальное окно пароля независимо от авторизации
|
2026-01-10 00:18:08 +00:00
|
|
|
|
setIsPasswordModalOpen(true);
|
|
|
|
|
|
}
|
2026-01-10 20:49:42 +00:00
|
|
|
|
}, [requiresPassword, isPasswordModalOpen, loading]);
|
2026-01-10 00:18:08 +00:00
|
|
|
|
|
2026-01-07 13:24:30 +00:00
|
|
|
|
// Проверка авторизации и показ модального окна для ввода имени
|
2026-01-10 20:49:42 +00:00
|
|
|
|
// Показывать только если НЕТ пароля - пароль приоритетнее
|
2026-01-07 13:24:30 +00:00
|
|
|
|
useEffect(() => {
|
2026-01-10 00:18:08 +00:00
|
|
|
|
if (!authLoading && !user && room && !loading && !requiresPassword) {
|
2026-01-07 13:24:30 +00:00
|
|
|
|
setIsNameModalOpen(true);
|
|
|
|
|
|
} else if (user) {
|
|
|
|
|
|
setIsNameModalOpen(false);
|
|
|
|
|
|
}
|
2026-01-10 00:18:08 +00:00
|
|
|
|
}, [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('Ошибка при проверке пароля. Попробуйте еще раз.');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-01-07 13:24:30 +00:00
|
|
|
|
|
|
|
|
|
|
// Обработка ввода имени и авторизация
|
|
|
|
|
|
const handleNameSubmit = async (name) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await loginAnonymous(name);
|
|
|
|
|
|
setIsNameModalOpen(false);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Login error:', error);
|
|
|
|
|
|
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-10 15:51:33 +00:00
|
|
|
|
// Показываем модальное окно выбора роли, если 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, если зрители не разрешены
|
2026-01-03 14:07:04 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleJoin = async () => {
|
2026-01-10 15:51:33 +00:00
|
|
|
|
if (room && user && !joined && !isRoleSelectionModalOpen) {
|
2026-01-03 14:07:04 +00:00
|
|
|
|
const isParticipant = participants.some((p) => p.userId === user.id);
|
|
|
|
|
|
if (!isParticipant) {
|
2026-01-10 15:51:33 +00:00
|
|
|
|
// Если зрители не разрешены, присоединяемся как 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);
|
|
|
|
|
|
}
|
2026-01-03 14:07:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setJoined(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
handleJoin();
|
2026-01-10 15:51:33 +00:00
|
|
|
|
}, [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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-01-03 14:07:04 +00:00
|
|
|
|
|
2026-01-06 20:27:50 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-10 15:51:33 +00:00
|
|
|
|
// Проверяем роль участника для поддержки нескольких хостов
|
|
|
|
|
|
const currentUserParticipant = user && participants
|
|
|
|
|
|
? participants.find(p => p.userId === user.id)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
const isHost = currentUserParticipant?.role === 'HOST';
|
2026-01-06 20:27:50 +00:00
|
|
|
|
|
2026-01-10 15:51:33 +00:00
|
|
|
|
if (room && user && isHost) {
|
|
|
|
|
|
fetchPacks();
|
2026-01-06 20:27:50 +00:00
|
|
|
|
}
|
2026-01-10 15:51:33 +00:00
|
|
|
|
}, [room, user, participants]);
|
2026-01-06 20:27:50 +00:00
|
|
|
|
|
2026-01-07 14:54:32 +00:00
|
|
|
|
// Автоматически перенаправляем на страницу игры, если игра уже началась
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (room && room.status === 'PLAYING') {
|
|
|
|
|
|
navigate(`/game/${roomCode}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [room, roomCode, navigate]);
|
|
|
|
|
|
|
2026-01-10 20:49:42 +00:00
|
|
|
|
// Применяем тему из gameStateUpdated/roomUpdate
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!roomCode) return;
|
|
|
|
|
|
|
|
|
|
|
|
const handleGameStateUpdated = (state) => {
|
|
|
|
|
|
const currentThemeId = state.themeId || null;
|
2026-01-10 21:14:59 +00:00
|
|
|
|
// Применяем тему если она изменилась или если это первое присоединение (previousThemeIdRef.current === null)
|
2026-01-10 20:49:42 +00:00
|
|
|
|
if (currentThemeId !== previousThemeIdRef.current) {
|
|
|
|
|
|
previousThemeIdRef.current = currentThemeId;
|
|
|
|
|
|
if (currentThemeId) {
|
|
|
|
|
|
changeTheme(currentThemeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
socketService.on('gameStateUpdated', handleGameStateUpdated);
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
socketService.off('gameStateUpdated', handleGameStateUpdated);
|
|
|
|
|
|
};
|
2026-01-10 21:14:59 +00:00
|
|
|
|
}, [roomCode, changeTheme]);
|
|
|
|
|
|
|
|
|
|
|
|
// Применяем тему из room при первом присоединении или при изменении
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!roomCode || !room) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentThemeId = room.themeId || null;
|
|
|
|
|
|
// Применяем тему если она существует и еще не применена (первое присоединение) или изменилась
|
|
|
|
|
|
if (currentThemeId && currentThemeId !== previousThemeIdRef.current) {
|
|
|
|
|
|
previousThemeIdRef.current = currentThemeId;
|
|
|
|
|
|
changeTheme(currentThemeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [roomCode, room?.themeId, changeTheme]);
|
2026-01-10 20:49:42 +00:00
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
const handleStartGame = () => {
|
|
|
|
|
|
startGame();
|
|
|
|
|
|
navigate(`/game/${roomCode}`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-10 15:51:33 +00:00
|
|
|
|
// Получаем вопросы из 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 [];
|
|
|
|
|
|
}
|
2026-01-06 20:27:50 +00:00
|
|
|
|
}
|
2026-01-10 15:51:33 +00:00
|
|
|
|
return Array.isArray(questions) ? questions : [];
|
2026-01-06 20:27:50 +00:00
|
|
|
|
};
|
2026-01-10 15:51:33 +00:00
|
|
|
|
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]
|
|
|
|
|
|
);
|
2026-01-06 20:27:50 +00:00
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <div className="loading">Загрузка комнаты...</div>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 00:18:08 +00:00
|
|
|
|
// Не показываем ошибку, если требуется пароль - покажем модальное окно
|
|
|
|
|
|
if (error && !requiresPassword && error !== 'Room password required') {
|
2026-01-03 14:07:04 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="error-page">
|
|
|
|
|
|
<h1>Ошибка</h1>
|
|
|
|
|
|
<p>{error}</p>
|
|
|
|
|
|
<button onClick={() => navigate('/')}>На главную</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 00:18:08 +00:00
|
|
|
|
if (!room && !requiresPassword && !loading) {
|
2026-01-03 14:07:04 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="error-page">
|
|
|
|
|
|
<h1>Комната не найдена</h1>
|
|
|
|
|
|
<button onClick={() => navigate('/')}>На главную</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 15:51:33 +00:00
|
|
|
|
// Проверяем роль участника для поддержки нескольких хостов
|
|
|
|
|
|
const currentUserParticipant = user && participants
|
|
|
|
|
|
? participants.find(p => p.userId === user.id)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
const isHost = currentUserParticipant?.role === 'HOST';
|
2026-01-10 00:18:08 +00:00
|
|
|
|
|
|
|
|
|
|
// Если требуется пароль, показываем только модальное окно
|
|
|
|
|
|
if (requiresPassword && !room) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="loading">Загрузка комнаты...</div>
|
|
|
|
|
|
<PasswordModal
|
|
|
|
|
|
isOpen={isPasswordModalOpen}
|
|
|
|
|
|
onSubmit={handlePasswordSubmit}
|
|
|
|
|
|
onCancel={() => navigate('/')}
|
|
|
|
|
|
error={passwordError}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-03 14:07:04 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="room-page">
|
|
|
|
|
|
<div className="room-container">
|
|
|
|
|
|
<h1>Комната: {room.code}</h1>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="room-info">
|
|
|
|
|
|
<p>Статус: {room.status === 'WAITING' ? 'Ожидание игроков' : room.status}</p>
|
|
|
|
|
|
<p>Игроков: {participants.length}/{room.maxPlayers}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="participants-list">
|
|
|
|
|
|
<h3>Участники:</h3>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
{participants.map((participant) => (
|
|
|
|
|
|
<li key={participant.id}>
|
|
|
|
|
|
{participant.name} {participant.role === 'HOST' && '(Ведущий)'}
|
|
|
|
|
|
{participant.role === 'SPECTATOR' && '(Зритель)'}
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-10 16:16:44 +00:00
|
|
|
|
{isHost && (
|
|
|
|
|
|
<div className="button-group">
|
2026-01-10 15:51:33 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setIsQuestionsModalOpen(true)}
|
2026-01-10 16:16:44 +00:00
|
|
|
|
className="primary"
|
2026-01-10 15:51:33 +00:00
|
|
|
|
>
|
2026-01-10 16:16:44 +00:00
|
|
|
|
🎛 Настроить игру
|
2026-01-10 15:51:33 +00:00
|
|
|
|
</button>
|
2026-01-10 16:16:44 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-01-06 20:27:50 +00:00
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
<div className="button-group">
|
2026-01-06 20:12:36 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setIsQRModalOpen(true)}
|
|
|
|
|
|
className="secondary"
|
|
|
|
|
|
>
|
|
|
|
|
|
Показать QR-код
|
|
|
|
|
|
</button>
|
2026-01-07 13:59:18 +00:00
|
|
|
|
{isHost &&
|
|
|
|
|
|
(room.status === 'WAITING' ||
|
|
|
|
|
|
(room.status === 'PLAYING' &&
|
|
|
|
|
|
(!room.questionPack ||
|
|
|
|
|
|
room.questionPack.questionCount === 0 ||
|
|
|
|
|
|
room.currentQuestionIndex === 0))) && (
|
2026-01-03 14:07:04 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleStartGame}
|
|
|
|
|
|
className="primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
Начать игру
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<button onClick={() => navigate('/')}>Покинуть комнату</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-06 20:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
<QRModal
|
|
|
|
|
|
isOpen={isQRModalOpen}
|
|
|
|
|
|
onClose={() => setIsQRModalOpen(false)}
|
|
|
|
|
|
qrCode={qrCode}
|
|
|
|
|
|
roomCode={roomCode}
|
|
|
|
|
|
/>
|
2026-01-07 13:24:30 +00:00
|
|
|
|
|
|
|
|
|
|
<NameInputModal
|
|
|
|
|
|
isOpen={isNameModalOpen}
|
|
|
|
|
|
onSubmit={handleNameSubmit}
|
|
|
|
|
|
onCancel={null}
|
|
|
|
|
|
/>
|
2026-01-10 00:18:08 +00:00
|
|
|
|
|
|
|
|
|
|
<PasswordModal
|
|
|
|
|
|
isOpen={isPasswordModalOpen}
|
|
|
|
|
|
onSubmit={handlePasswordSubmit}
|
|
|
|
|
|
onCancel={() => navigate('/')}
|
|
|
|
|
|
error={passwordError}
|
|
|
|
|
|
/>
|
2026-01-10 15:51:33 +00:00
|
|
|
|
|
|
|
|
|
|
<RoleSelectionModal
|
|
|
|
|
|
isOpen={isRoleSelectionModalOpen}
|
|
|
|
|
|
onSubmit={handleRoleSubmit}
|
|
|
|
|
|
onCancel={() => navigate('/')}
|
|
|
|
|
|
allowSpectators={room?.allowSpectators}
|
|
|
|
|
|
title="Выберите роль"
|
|
|
|
|
|
description={room?.allowSpectators
|
|
|
|
|
|
? "Выберите роль для присоединения к комнате"
|
|
|
|
|
|
: "Присоединиться как игрок"}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
{isHost && room && (
|
|
|
|
|
|
<GameManagementModal
|
|
|
|
|
|
isOpen={isQuestionsModalOpen}
|
|
|
|
|
|
onClose={() => 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}
|
2026-01-10 16:16:44 +00:00
|
|
|
|
onStartGame={handleStartGame}
|
2026-01-10 15:51:33 +00:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-01-03 14:07:04 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default RoomPage;
|