diff --git a/backend/src/game/game.gateway.ts b/backend/src/game/game.gateway.ts index 5c8b820..d92c68e 100644 --- a/backend/src/game/game.gateway.ts +++ b/backend/src/game/game.gateway.ts @@ -305,7 +305,14 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On const isHost = await this.isHost(payload.roomId, payload.userId); const isCurrentPlayer = room.currentPlayerId === payload.participantId; - if (!isHost && !isCurrentPlayer) { + // Только хост может открывать ответы + if (payload.action === 'revealAnswer' && !isHost) { + client.emit('error', { message: 'Only the host can reveal answers' }); + return; + } + + // Для других действий проверяем права (хост или текущий игрок) + if (payload.action !== 'revealAnswer' && !isHost && !isCurrentPlayer) { client.emit('error', { message: 'Not your turn!' }); return; } diff --git a/backend/src/rooms/rooms.service.ts b/backend/src/rooms/rooms.service.ts index 12c33ec..b695f25 100644 --- a/backend/src/rooms/rooms.service.ts +++ b/backend/src/rooms/rooms.service.ts @@ -72,6 +72,8 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -133,12 +135,14 @@ export class RoomsService { }, }); - // Получаем обновленную комнату со всеми участниками + // Получаем обновленную комнату со всеми активными участниками const updatedRoom = await this.prisma.room.findUnique({ where: { id: roomId }, include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -221,12 +225,14 @@ export class RoomsService { }, }); - // Получаем обновленную комнату со всеми участниками + // Получаем обновленную комнату со всеми активными участниками const updatedRoom = await this.prisma.room.findUnique({ where: { id: roomId }, include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -291,6 +297,8 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -320,6 +328,8 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -367,6 +377,8 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -404,9 +416,13 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, + roomPack: true, + theme: true, }, }); @@ -424,9 +440,13 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, + roomPack: true, + theme: true, }, }); @@ -445,6 +465,8 @@ export class RoomsService { include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, @@ -537,12 +559,14 @@ export class RoomsService { data: { role: newRole }, }); - // Получаем обновленную комнату со всеми участниками + // Получаем обновленную комнату со всеми активными участниками const room = await this.prisma.room.findUnique({ where: { id: roomId }, include: { host: true, participants: { + where: { isActive: true }, + orderBy: { joinedAt: 'asc' }, include: { user: true }, }, questionPack: true, diff --git a/src/components/Question.css b/src/components/Question.css index cc46297..ec74d6a 100644 --- a/src/components/Question.css +++ b/src/components/Question.css @@ -3,8 +3,8 @@ flex: 1; display: flex; flex-direction: column; - overflow: hidden; min-height: 0; + overflow: visible; } .question-box { @@ -130,8 +130,9 @@ grid-auto-rows: minmax(auto, clamp(120px, 18vh, 200px)); column-gap: clamp(6px, 1.2vw, 12px); row-gap: clamp(6px, 0.8vh, 12px); - flex: 1 1 0; + flex: 0 1 auto; min-height: 0; + max-height: 100%; overflow-y: auto; align-content: start; /* Scrollbar styling */ diff --git a/src/pages/GamePage.jsx b/src/pages/GamePage.jsx index ec04d5c..43f7b5a 100644 --- a/src/pages/GamePage.jsx +++ b/src/pages/GamePage.jsx @@ -175,20 +175,13 @@ const GamePage = () => { // === Handlers для действий игрока === const handleAnswerClick = (answerId, points) => { - if (!gameState.roomId || !user || !canPerformActions) return; + if (!gameState.roomId || !user) return; const myParticipant = gameState.participants.find(p => p.userId === user.id); if (!myParticipant) return; - // Зрители не могут отвечать на вопросы - if (isSpectator) { - alert('Зрители не могут отвечать на вопросы'); - return; - } - - // Проверка очереди (только для не-хостов) - if (!isHost && gameState.currentPlayerId !== myParticipant.id) { - alert('Сейчас не ваша очередь!'); + // Только хост может кликать на ответы + if (!isHost) { return; } @@ -550,7 +543,7 @@ const GamePage = () => { revealedAnswers={revealedForCurrentQ} playerScores={playerScores} currentPlayerId={gameState.currentPlayerId} - onAnswerClick={canPerformActions ? handleAnswerClick : null} + onAnswerClick={isHost ? handleAnswerClick : null} onPreviousQuestion={isHost && canGoPrev ? handlePrevQuestion : null} onNextQuestion={isHost && canGoNext ? handleNextQuestion : null} onSelectPlayer={isHost ? handleSelectPlayer : null} diff --git a/src/pages/RoomPage.jsx b/src/pages/RoomPage.jsx index 7cf06a5..60bb876 100644 --- a/src/pages/RoomPage.jsx +++ b/src/pages/RoomPage.jsx @@ -49,6 +49,9 @@ const RoomPage = () => { const [joinError, setJoinError] = useState(null); const [selectedRole, setSelectedRole] = useState('PLAYER'); const [questionPacks, setQuestionPacks] = useState([]); + + // Ref для отслеживания попытки присоединения (защита от двойного запроса) + const joinAttemptedRef = useRef(false); useEffect(() => { const generateQR = async () => { @@ -131,19 +134,25 @@ const RoomPage = () => { const isParticipant = participants.some((p) => p.userId === user.id); if (isParticipant) { setJoined(true); + joinAttemptedRef.current = true; return; } + // Защита от повторной попытки присоединения + if (joinAttemptedRef.current) return; + // Если зрители разрешены, показываем модальное окно выбора роли if (room.allowSpectators) { if (!isRoleSelectionModalOpen) { setIsRoleSelectionModalOpen(true); + joinAttemptedRef.current = true; } return; } // Если зрители не разрешены, автоматически присоединяемся как PLAYER // Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED) + joinAttemptedRef.current = true; try { setJoinError(null); await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER'); @@ -154,6 +163,8 @@ const RoomPage = () => { const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате'; setJoinError(errorMessage); alert(errorMessage); + // Сбрасываем флаг при ошибке, чтобы можно было попробовать снова + joinAttemptedRef.current = false; } }; @@ -177,7 +188,8 @@ const RoomPage = () => { const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате'; setJoinError(errorMessage); alert(errorMessage); - // Открываем модальное окно снова при ошибке + // Сбрасываем флаг и открываем модальное окно снова при ошибке + joinAttemptedRef.current = false; setIsRoleSelectionModalOpen(true); } };