🎨 Redesign UI + 🐛 Fix media answer modal auto-close

This commit is contained in:
Cosmo
2026-03-21 05:22:17 +00:00
parent 1d46ad8b06
commit c3b8415725
9 changed files with 451 additions and 532 deletions

View File

@@ -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>