Initial commit: Своя Игра - multiplayer quiz game

This commit is contained in:
Cosmo
2026-03-21 05:00:06 +00:00
commit 1d46ad8b06
80 changed files with 9215 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
import { useState, useRef, useEffect } from 'react'
function QuestionModal({ question, timer, onSubmitAnswer, answeringTeamName, isHost, onBuzzIn, canBuzzIn, onCloseQuestion, gameCode, socket }) {
const [showAnswer, setShowAnswer] = useState(false)
const [mediaEnded, setMediaEnded] = useState(false)
const audioRef = useRef(null)
const videoRef = useRef(null)
if (!question) return null
// Обработчик окончания проигрывания медиа
const handleMediaEnded = () => {
console.log('🎬 Media playback ended');
setMediaEnded(true);
// Отправляем событие на сервер, чтобы начался таймер
if (socket && gameCode) {
socket.emit('media-ended', { gameCode });
}
}
// Сбрасываем флаг при смене вопроса
useEffect(() => {
setMediaEnded(false);
}, [question])
const getMediaUrl = (path) => {
// Если определена переменная окружения, используем её
if (import.meta.env.VITE_SERVER_URL) {
return `${import.meta.env.VITE_SERVER_URL}${path}`
}
// В production используем текущий хост (Caddy проксирует на сервер)
if (import.meta.env.PROD) {
return `${window.location.protocol}//${window.location.host}${path}`
}
// В development используем localhost
return `http://localhost:3001${path}`
}
const renderQuestionContent = () => {
switch (question.type) {
case 'image':
return (
<div className="bg-blue-900 p-4 md:p-8 rounded-lg mb-4 md:mb-6">
<img
src={getMediaUrl(question.question)}
alt="Question"
className="max-w-full max-h-64 md:max-h-96 mx-auto rounded-lg mb-4"
/>
{question.questionText && (
<p className="text-white text-base md:text-2xl text-center mt-4">
{question.questionText}
</p>
)}
</div>
)
case 'audio':
return (
<div className="bg-blue-900 p-4 md:p-8 rounded-lg mb-4 md:mb-6">
<audio
ref={audioRef}
controls
autoPlay
src={getMediaUrl(question.question)}
className="w-full mb-4"
onEnded={handleMediaEnded}
>
Ваш браузер не поддерживает аудио элемент.
</audio>
{question.questionText && (
<p className="text-white text-base md:text-2xl text-center mt-4">
{question.questionText}
</p>
)}
{!mediaEnded && (
<p className="text-yellow-400 text-sm md:text-base text-center mt-2">
Таймер начнется после прослушивания
</p>
)}
</div>
)
case 'video':
return (
<div className="bg-blue-900 p-4 md:p-8 rounded-lg mb-4 md:mb-6">
<video
ref={videoRef}
controls
autoPlay
src={getMediaUrl(question.question)}
className="max-w-full max-h-64 md:max-h-96 mx-auto rounded-lg mb-4"
onEnded={handleMediaEnded}
>
Ваш браузер не поддерживает видео элемент.
</video>
{question.questionText && (
<p className="text-white text-base md:text-2xl text-center mt-4">
{question.questionText}
</p>
)}
{!mediaEnded && (
<p className="text-yellow-400 text-sm md:text-base text-center mt-2">
Таймер начнется после просмотра
</p>
)}
</div>
)
case 'text':
default:
return (
<div className="bg-blue-900 p-4 md:p-8 rounded-lg mb-4 md:mb-6">
<p className="text-white text-base md:text-2xl text-center leading-relaxed">
{question.question}
</p>
</div>
)
}
}
return (
<div className="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 p-4">
<div className="bg-gray-800 p-4 md:p-8 lg:p-12 rounded-lg max-w-4xl w-full max-h-screen overflow-y-auto">
<div className="text-center mb-4 md:mb-6">
<p className="text-game-gold text-base md:text-xl mb-2">{question.category}</p>
<p className="text-game-gold text-xl md:text-3xl font-bold mb-3 md:mb-4">{question.points} баллов</p>
{timer !== null && (
<p className="text-3xl md:text-5xl font-bold text-white mb-4 md:mb-6">{timer}с</p>
)}
</div>
{renderQuestionContent()}
{/* Кнопка "Показать ответ" для ведущего */}
{isHost && (
<div className="mb-4 md:mb-6">
{!showAnswer ? (
<button
onClick={() => setShowAnswer(true)}
className="w-full bg-yellow-600 text-white font-bold py-3 md:py-4 px-6 md:px-8 rounded-lg text-base md:text-xl hover:bg-yellow-700 transition"
>
Показать ответ
</button>
) : (
<div className="bg-green-900 p-4 md:p-6 rounded-lg">
<p className="text-white text-lg md:text-2xl font-bold text-center">
Правильный ответ:
</p>
<p className="text-game-gold text-xl md:text-3xl text-center mt-2 md:mt-3">
{question.answer}
</p>
</div>
)}
</div>
)}
{/* Кнопка "Ответить" для команд (до того как кто-то нажал buzz-in) */}
{canBuzzIn && (
<div className="mb-4 md:mb-6">
<button
onClick={onBuzzIn}
className="w-full bg-red-600 text-white font-bold py-4 md:py-6 px-8 md:px-12 rounded-lg text-lg md:text-2xl hover:bg-red-700 transition animate-pulse"
>
ОТВЕТИТЬ
</button>
{timer && (
<p className="text-2xl md:text-3xl text-game-gold mt-3 md:mt-4 font-bold text-center">
{timer}с
</p>
)}
</div>
)}
{/* Для ведущего: показать кто отвечает + кнопки управления */}
{isHost && answeringTeamName && (
<div className="mb-4 md:mb-6">
<div className="bg-blue-900 p-4 md:p-6 rounded-lg mb-3 md:mb-4">
<p className="text-white text-lg md:text-2xl font-bold text-center">
Отвечает команда:
</p>
<p className="text-game-gold text-xl md:text-3xl text-center mt-2">
{answeringTeamName}
</p>
</div>
<div className="flex gap-2 md:gap-4 justify-center">
<button
onClick={() => onSubmitAnswer(true)}
className="flex-1 md:flex-none bg-green-600 text-white font-bold py-3 md:py-4 px-6 md:px-8 rounded-lg text-base md:text-xl hover:bg-green-700 transition"
>
Верно
</button>
<button
onClick={() => onSubmitAnswer(false)}
className="flex-1 md:flex-none bg-red-600 text-white font-bold py-3 md:py-4 px-6 md:px-8 rounded-lg text-base md:text-xl hover:bg-red-700 transition"
>
Неверно
</button>
</div>
</div>
)}
{/* Для ведущего: кнопка завершить досрочно (если никто не отвечает) */}
{isHost && !answeringTeamName && (
<div className="mb-4 md:mb-6">
<button
onClick={onCloseQuestion}
className="w-full bg-orange-600 text-white font-bold py-2 md:py-3 px-4 md:px-6 rounded-lg text-sm md:text-lg hover:bg-orange-700 transition"
>
Завершить вопрос досрочно
</button>
</div>
)}
{/* Сообщение для команд */}
{!isHost && answeringTeamName && (
<div className="mb-4 md:mb-6">
<p className="text-center text-white text-base md:text-xl">
Отвечает команда: <span className="text-game-gold font-bold">{answeringTeamName}</span>
</p>
</div>
)}
{/* Сообщение ожидания для команд */}
{!isHost && !canBuzzIn && !answeringTeamName && (
<p className="text-center text-gray-400 text-sm md:text-lg">
Ожидание...
</p>
)}
</div>
</div>
)
}
export default QuestionModal