🎨 Redesign UI + 🐛 Fix media answer modal auto-close
This commit is contained in:
@@ -8,226 +8,175 @@ function QuestionModal({ question, timer, onSubmitAnswer, answeringTeamName, isH
|
||||
|
||||
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
|
||||
if (import.meta.env.VITE_SERVER_URL) return `${import.meta.env.VITE_SERVER_URL}${path}`
|
||||
if (import.meta.env.PROD) return `${window.location.protocol}//${window.location.host}${path}`
|
||||
return `http://localhost:3001${path}`
|
||||
}
|
||||
|
||||
const timerColor = timer !== null
|
||||
? timer <= 5 ? 'text-red-400' : timer <= 10 ? 'text-amber-400' : 'text-white'
|
||||
: 'text-white'
|
||||
|
||||
const timerBg = timer !== null && timer <= 5 ? 'animate-pulse' : ''
|
||||
|
||||
const renderQuestionContent = () => {
|
||||
const wrapperClass = "bg-gradient-to-br from-indigo-900/40 to-purple-900/40 border border-indigo-400/20 p-4 md:p-8 rounded-xl mb-4 md:mb-6 backdrop-blur-sm"
|
||||
|
||||
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"
|
||||
/>
|
||||
<div className={wrapperClass}>
|
||||
<img src={getMediaUrl(question.question)} alt="Question"
|
||||
className="max-w-full max-h-64 md:max-h-96 mx-auto rounded-xl mb-4 shadow-lg" />
|
||||
{question.questionText && (
|
||||
<p className="text-white text-base md:text-2xl text-center mt-4">
|
||||
{question.questionText}
|
||||
</p>
|
||||
<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}
|
||||
>
|
||||
<div className={wrapperClass}>
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<div className="text-5xl md:text-7xl animate-pulse-slow">🎵</div>
|
||||
</div>
|
||||
<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>
|
||||
<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 className="text-amber-300/80 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}
|
||||
>
|
||||
<div className={wrapperClass}>
|
||||
<video ref={videoRef} controls autoPlay src={getMediaUrl(question.question)}
|
||||
className="max-w-full max-h-64 md:max-h-96 mx-auto rounded-xl mb-4 shadow-lg" onEnded={handleMediaEnded}>
|
||||
Ваш браузер не поддерживает видео элемент.
|
||||
</video>
|
||||
{question.questionText && (
|
||||
<p className="text-white text-base md:text-2xl text-center mt-4">
|
||||
{question.questionText}
|
||||
</p>
|
||||
<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 className="text-amber-300/80 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 className={wrapperClass}>
|
||||
<p className="text-white text-lg 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="fixed inset-0 bg-black/90 backdrop-blur-sm flex items-center justify-center z-50 p-3 md:p-4 animate-scale-in">
|
||||
<div className="glass-strong p-4 md:p-8 lg:p-10 rounded-2xl max-w-4xl w-full max-h-[95vh] overflow-y-auto shadow-2xl">
|
||||
{/* Header */}
|
||||
<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>
|
||||
<p className="text-indigo-300 text-sm md:text-lg mb-1 font-medium">{question.category}</p>
|
||||
<p className="font-display text-gradient-gold text-2xl md:text-4xl 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 className={`inline-flex items-center justify-center w-16 h-16 md:w-20 md:h-20 rounded-full glass border-2 ${timer <= 5 ? 'border-red-400/50' : timer <= 10 ? 'border-amber-400/30' : 'border-white/20'} ${timerBg}`}>
|
||||
<p className={`font-display text-3xl md:text-4xl font-bold ${timerColor}`}>{timer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{renderQuestionContent()}
|
||||
|
||||
{/* Кнопка "Показать ответ" для ведущего */}
|
||||
{/* Show answer button for host */}
|
||||
{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 onClick={() => setShowAnswer(true)}
|
||||
className="w-full bg-gradient-to-r from-amber-600/80 to-yellow-600/80 hover:from-amber-500 hover:to-yellow-500 text-white font-bold py-3 md:py-4 rounded-xl text-base md:text-lg transition-all duration-300 border border-amber-400/30 hover:scale-[1.01] active:scale-[0.99]">
|
||||
👁️ Показать ответ
|
||||
</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 className="bg-gradient-to-r from-emerald-900/40 to-green-900/40 border border-emerald-400/30 p-4 md:p-6 rounded-xl animate-scale-in">
|
||||
<p className="text-emerald-300 text-sm md:text-lg font-medium text-center">Правильный ответ:</p>
|
||||
<p className="text-gradient-gold font-display text-xl md:text-3xl text-center mt-2 font-bold">{question.answer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Кнопка "Ответить" для команд (до того как кто-то нажал buzz-in) */}
|
||||
{/* Buzz-in button for teams */}
|
||||
{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 onClick={onBuzzIn}
|
||||
className="w-full bg-gradient-to-r from-red-600 to-rose-600 hover:from-red-500 hover:to-rose-500 text-white font-black py-5 md:py-7 rounded-2xl text-xl md:text-3xl transition-all duration-200 shadow-lg shadow-red-500/30 hover:shadow-red-500/50 animate-pulse-slow hover:animate-none hover:scale-[1.03] active:scale-95 active:animate-buzz border border-red-400/30">
|
||||
🔔 ОТВЕТИТЬ
|
||||
</button>
|
||||
{timer && (
|
||||
<p className="text-2xl md:text-3xl text-game-gold mt-3 md:mt-4 font-bold text-center">
|
||||
{timer}с
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Для ведущего: показать кто отвечает + кнопки управления */}
|
||||
{/* Host: who is answering + correct/incorrect buttons */}
|
||||
{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 className="mb-4 md:mb-6 animate-scale-in">
|
||||
<div className="bg-gradient-to-r from-blue-600/20 to-indigo-600/20 border border-blue-400/30 p-4 md:p-5 rounded-xl mb-3 md:mb-4 text-center">
|
||||
<p className="text-gray-300 text-sm md:text-lg">Отвечает команда:</p>
|
||||
<p className="text-white font-display text-xl md:text-2xl font-bold mt-1">{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"
|
||||
>
|
||||
Верно
|
||||
<div className="flex gap-3 md:gap-4 justify-center">
|
||||
<button onClick={() => onSubmitAnswer(true)}
|
||||
className="flex-1 md:flex-none bg-gradient-to-r from-emerald-600 to-green-600 hover:from-emerald-500 hover:to-green-500 text-white font-bold py-3 md:py-4 px-6 md:px-10 rounded-xl text-base md:text-xl transition-all duration-300 hover:scale-[1.03] active:scale-95 border border-emerald-400/30">
|
||||
✅ Верно
|
||||
</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 onClick={() => onSubmitAnswer(false)}
|
||||
className="flex-1 md:flex-none bg-gradient-to-r from-red-600 to-rose-600 hover:from-red-500 hover:to-rose-500 text-white font-bold py-3 md:py-4 px-6 md:px-10 rounded-xl text-base md:text-xl transition-all duration-300 hover:scale-[1.03] active:scale-95 border border-red-400/30">
|
||||
❌ Неверно
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Для ведущего: кнопка завершить досрочно (если никто не отвечает) */}
|
||||
{/* Host: close question early */}
|
||||
{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 onClick={onCloseQuestion}
|
||||
className="w-full glass hover:bg-white/10 text-gray-300 hover:text-white font-bold py-2.5 md:py-3 rounded-xl text-sm md:text-base transition-all duration-300">
|
||||
⏭️ Завершить вопрос досрочно
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Сообщение для команд */}
|
||||
{/* Team view: who is answering */}
|
||||
{!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>
|
||||
<div className="mb-4 md:mb-6 text-center">
|
||||
<p className="text-gray-300 text-base md:text-lg">
|
||||
Отвечает: <span className="text-blue-300 font-bold">{answeringTeamName}</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Сообщение ожидания для команд */}
|
||||
{!isHost && !canBuzzIn && !answeringTeamName && (
|
||||
<p className="text-center text-gray-400 text-sm md:text-lg">
|
||||
Ожидание...
|
||||
</p>
|
||||
<p className="text-center text-gray-500 text-sm md:text-base">Ожидание...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user