2026-01-03 14:07:04 +00:00
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
|
|
import { useAuth } from '../context/AuthContext';
|
|
|
|
|
|
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-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-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-06 20:27:50 +00:00
|
|
|
|
const {
|
|
|
|
|
|
room,
|
|
|
|
|
|
participants,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
error,
|
|
|
|
|
|
joinRoom,
|
|
|
|
|
|
startGame,
|
|
|
|
|
|
updateQuestionPack,
|
|
|
|
|
|
} = useRoom(roomCode);
|
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-06 20:27:50 +00:00
|
|
|
|
const [questionPacks, setQuestionPacks] = useState([]);
|
|
|
|
|
|
const [selectedPackId, setSelectedPackId] = useState('');
|
|
|
|
|
|
const [loadingPacks, setLoadingPacks] = useState(false);
|
|
|
|
|
|
const [updatingPack, setUpdatingPack] = useState(false);
|
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-07 13:24:30 +00:00
|
|
|
|
// Проверка авторизации и показ модального окна для ввода имени
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!authLoading && !user && room && !loading) {
|
|
|
|
|
|
setIsNameModalOpen(true);
|
|
|
|
|
|
} else if (user) {
|
|
|
|
|
|
setIsNameModalOpen(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [authLoading, user, room, loading]);
|
|
|
|
|
|
|
|
|
|
|
|
// Обработка ввода имени и авторизация
|
|
|
|
|
|
const handleNameSubmit = async (name) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await loginAnonymous(name);
|
|
|
|
|
|
setIsNameModalOpen(false);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Login error:', error);
|
|
|
|
|
|
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleJoin = async () => {
|
|
|
|
|
|
if (room && user && !joined) {
|
|
|
|
|
|
const isParticipant = participants.some((p) => p.userId === user.id);
|
|
|
|
|
|
if (!isParticipant) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER');
|
|
|
|
|
|
setJoined(true);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Join error:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setJoined(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
handleJoin();
|
|
|
|
|
|
}, [room, user, participants, joined, joinRoom]);
|
|
|
|
|
|
|
2026-01-06 20:27:50 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const fetchPacks = async () => {
|
|
|
|
|
|
if (user) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoadingPacks(true);
|
|
|
|
|
|
const response = await questionsApi.getPacks(user.id);
|
|
|
|
|
|
setQuestionPacks(response.data);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching question packs:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoadingPacks(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (room && user && room.hostId === user.id) {
|
|
|
|
|
|
fetchPacks();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [room, user]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (room && room.questionPackId) {
|
|
|
|
|
|
setSelectedPackId(room.questionPackId);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setSelectedPackId('');
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [room]);
|
|
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
const handleStartGame = () => {
|
|
|
|
|
|
startGame();
|
|
|
|
|
|
navigate(`/game/${roomCode}`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-06 20:27:50 +00:00
|
|
|
|
const handleUpdateQuestionPack = async () => {
|
|
|
|
|
|
if (!selectedPackId) {
|
|
|
|
|
|
alert('Выберите пак вопросов');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setUpdatingPack(true);
|
|
|
|
|
|
await updateQuestionPack(selectedPackId);
|
|
|
|
|
|
alert('Пак вопросов успешно добавлен');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error updating question pack:', error);
|
|
|
|
|
|
alert('Ошибка при обновлении пака вопросов');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setUpdatingPack(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-03 14:07:04 +00:00
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <div className="loading">Загрузка комнаты...</div>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="error-page">
|
|
|
|
|
|
<h1>Ошибка</h1>
|
|
|
|
|
|
<p>{error}</p>
|
|
|
|
|
|
<button onClick={() => navigate('/')}>На главную</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!room) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="error-page">
|
|
|
|
|
|
<h1>Комната не найдена</h1>
|
|
|
|
|
|
<button onClick={() => navigate('/')}>На главную</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isHost = user && room.hostId === user.id;
|
|
|
|
|
|
|
|
|
|
|
|
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-06 20:27:50 +00:00
|
|
|
|
<div className="question-pack-section">
|
|
|
|
|
|
<h3>Пак вопросов:</h3>
|
|
|
|
|
|
{room.questionPack ? (
|
|
|
|
|
|
<div className="pack-info">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>{room.questionPack.name}</strong> (
|
|
|
|
|
|
{room.questionPack.questionCount || 0} вопросов)
|
|
|
|
|
|
</p>
|
2026-01-06 20:46:39 +00:00
|
|
|
|
{isHost && (
|
2026-01-06 20:27:50 +00:00
|
|
|
|
<p className="pack-hint">
|
2026-01-06 20:46:39 +00:00
|
|
|
|
Можете изменить пак вопросов в любой момент
|
2026-01-06 20:27:50 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="pack-info">
|
2026-01-06 20:46:39 +00:00
|
|
|
|
<p className="pack-hint">
|
|
|
|
|
|
Пак вопросов не выбран. Вы можете начать игру без пака и
|
|
|
|
|
|
добавить его позже.
|
2026-01-06 20:27:50 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-01-06 20:46:39 +00:00
|
|
|
|
{isHost && (
|
2026-01-06 20:27:50 +00:00
|
|
|
|
<div className="pack-selector">
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={selectedPackId}
|
|
|
|
|
|
onChange={(e) => setSelectedPackId(e.target.value)}
|
|
|
|
|
|
disabled={loadingPacks || updatingPack}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Выберите пак вопросов</option>
|
|
|
|
|
|
{questionPacks.map((pack) => (
|
|
|
|
|
|
<option key={pack.id} value={pack.id}>
|
|
|
|
|
|
{pack.name} ({pack.questionCount} вопросов)
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleUpdateQuestionPack}
|
|
|
|
|
|
disabled={
|
|
|
|
|
|
!selectedPackId ||
|
|
|
|
|
|
selectedPackId === room.questionPackId ||
|
|
|
|
|
|
updatingPack ||
|
|
|
|
|
|
loadingPacks
|
|
|
|
|
|
}
|
|
|
|
|
|
className="secondary"
|
|
|
|
|
|
>
|
|
|
|
|
|
{updatingPack ? 'Сохранение...' : 'Сохранить пак'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
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-03 14:07:04 +00:00
|
|
|
|
{isHost && room.status === 'WAITING' && (
|
|
|
|
|
|
<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-03 14:07:04 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default RoomPage;
|