import React, { useState } from 'react'; import { useGame } from '../context/GameContext'; import { GamePhase } from '../types'; import type { Question } from '../types'; import { Soundboard } from './Soundboard'; import { Play, SkipForward, CheckCircle, XCircle, Users, Library, Sparkles, Plus, Trash2, Edit, ArrowLeft, Upload, RefreshCw, Image as ImageIcon, List, Trophy, RotateCcw, QrCode } from 'lucide-react'; import { generateQuestions } from '../services/geminiService'; export const HostView: React.FC = () => { const { gameState, activeGameName, players, teams, buzzQueue, questions, games, joinUrl, setJoinUrl, approvePlayer, startGame, startCountdown, openBuzzers, resolveBuzz, rectifyBuzz, skipQuestion, nextPhase, resetGame, createGame, updateGame, deleteGame, loadGameToLive } = useGame(); const [activeTab, setActiveTab] = useState<'GAME' | 'PLAYERS' | 'LIBRARY'>('GAME'); // Library State const [editingGameId, setEditingGameId] = useState(null); const [newGameName, setNewGameName] = useState(''); // Editor State const [aiTopic, setAiTopic] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [isAddingQuestion, setIsAddingQuestion] = useState(false); const [manualQ, setManualQ] = useState({ text: '', answer: '', points: 10, category: '', mediaUrl: '' }); const currentQ = questions[gameState.currentQuestionIndex]; const isPreGame = gameState.currentQuestionIndex === -1; const isGameOver = gameState.currentQuestionIndex >= questions.length; // Correctly identify if we have finished all questions and are just lingering on the last leaderboard const isLastQuestionPhase = gameState.currentQuestionIndex === questions.length - 1; // --- Handlers --- const handleCreateGame = () => { if (newGameName.trim()) { createGame(newGameName); setNewGameName(''); } }; const handleAiGenerate = async (gameId: string) => { if (!aiTopic) return; setIsGenerating(true); const newQuestions = await generateQuestions(aiTopic); if (newQuestions.length > 0) { const game = games.find(g => g.id === gameId); if (game) { updateGame(gameId, { questions: [...game.questions, ...newQuestions] }); } alert(`Added ${newQuestions.length} questions!`); setAiTopic(''); } else { alert("Failed to generate. Check API Key or try again."); } setIsGenerating(false); }; const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { setManualQ(prev => ({ ...prev, mediaUrl: reader.result as string })); }; reader.readAsDataURL(file); } }; const handleAddManualQuestion = (gameId: string) => { if (!manualQ.text || !manualQ.answer) return; const game = games.find(g => g.id === gameId); if (game) { // Detect Media Type let mediaType: 'image' | 'video' | undefined = undefined; if (manualQ.mediaUrl) { if (manualQ.mediaUrl.startsWith('data:image') || manualQ.mediaUrl.match(/\.(jpeg|jpg|gif|png)$/i)) { mediaType = 'image'; } else if (manualQ.mediaUrl.includes('youtube') || manualQ.mediaUrl.includes('youtu.be')) { mediaType = 'video'; } else { // Fallback assumption for uploads vs links mediaType = 'image'; } } const newQuestion: Question = { id: crypto.randomUUID(), text: manualQ.text, answer: manualQ.answer, points: Number(manualQ.points), category: manualQ.category || 'General', mediaUrl: manualQ.mediaUrl, mediaType }; updateGame(gameId, { questions: [...game.questions, newQuestion] }); setManualQ({ text: '', answer: '', points: 10, category: '', mediaUrl: '' }); setIsAddingQuestion(false); } }; const handleDeleteQuestion = (gameId: string, qId: string) => { const game = games.find(g => g.id === gameId); if (game) { updateGame(gameId, { questions: game.questions.filter(q => q.id !== qId) }); } }; const handleLoadGame = (gameId: string) => { // Removed confirm() as it can block execution or be annoying loadGameToLive(gameId); setActiveTab('GAME'); }; // Find the winner of the current round (if any) const currentWinnerBuzz = buzzQueue.find(b => b.status === 'CORRECT'); const currentWinner = currentWinnerBuzz ? players.find(p => p.id === currentWinnerBuzz.playerId) : null; return (
{/* Top Bar */}
QM

Host Dashboard

{/* Active Game Display */}
ACTIVE GAME: {activeGameName}
{isGameOver && (
GAME OVER
)}
STATUS: {gameState.phase}
{/* Left Sidebar - Navigation & Soundboard */} {/* Main Content Area */}
{/* GAME CONTROL TAB */} {activeTab === 'GAME' && (
{/* Session Settings */}
setJoinUrl(e.target.value)} className="w-full font-mono text-sm border-b border-slate-300 focus:border-indigo-600 outline-none bg-transparent py-1 text-slate-800" placeholder="https://..." />
{/* LAST QUESTION WARNING */} {isLastQuestionPhase && !isGameOver && (
⚠️ THIS IS THE LAST QUESTION!
)} {/* Current Question Card */}

{isPreGame ? 'Ready to Start' : isGameOver ? 'Game Finished' : `Current Question (${gameState.currentQuestionIndex + 1}/${questions.length})` }

{isPreGame && ( {questions.length} Questions Queued )}
{currentQ ? ( <>
{currentQ.text}
{currentQ.mediaUrl && (
Media Attached
{currentQ.mediaUrl.substring(0, 50)}...
{currentQ.mediaType === 'image' && ( Preview )}
)}
ANSWER: {currentQ.answer}
) : (
{isPreGame ? "Game initialized. Waiting to start first question." : "Game Over. Check the final results."}
)} {/* Controls based on Phase */}
{gameState.phase === GamePhase.LOBBY && ( )} {/* Button logic for moving to next question OR finishing game */} {(gameState.phase === GamePhase.LEADERBOARD || gameState.phase === GamePhase.LOBBY) && !isGameOver && ( isLastQuestionPhase && gameState.phase === GamePhase.LEADERBOARD ? ( ) : ( ) )} {gameState.phase === GamePhase.QUESTION_DISPLAY && ( )} {/* Skip/Reveal Button */} {(gameState.phase === GamePhase.QUESTION_DISPLAY || gameState.phase === GamePhase.BUZZER_OPEN || gameState.phase === GamePhase.ADJUDICATION) && ( )} {gameState.phase === GamePhase.ANSWER_REVEAL && (
{/* UNDO BUTTON */} {currentWinner && ( )}
)}
{/* QUESTION QUEUE PREVIEW (Added for better Host visibility) */} {isPreGame && questions.length > 0 && (

Loaded Queue

    {questions.map((q, idx) => (
  • {idx+1}. {q.text}
  • ))}
)} {/* Buzzer Queue Adjudication */} {buzzQueue.length > 0 && (

Buzzer Queue

{buzzQueue.length} Buzzes
{buzzQueue.map((buzz, idx) => { const player = players.find(p => p.id === buzz.playerId); const isCurrent = idx === 0 && buzz.status === 'PENDING'; return (
#{idx + 1}
{player?.name}
{(buzz.timestamp % 10000)}ms
{buzz.status === 'PENDING' ? ( isCurrent ? ( <> ) : ( WAITING ) ) : (
{buzz.status} {/* Correction Button for Wrong Answers */} {buzz.status === 'WRONG' && ( )}
)}
); })}
)}
)} {/* PLAYERS TAB */} {activeTab === 'PLAYERS' && (
{players.map(p => ( ))}
Name Team Score Status
{p.name} {teams.find(t => t.id === p.teamId)?.name || '-'} {p.score} {p.isApproved ? ( JOINED ) : ( )}
)} {/* GAME LIBRARY TAB */} {activeTab === 'LIBRARY' && (
{!editingGameId ? ( /* MODE: LIST GAMES */ <>

Your Games

setNewGameName(e.target.value)} placeholder="New Game Name..." className="px-4 py-2 rounded border border-slate-300 text-sm text-black bg-white" />
{games.map(game => (

{game.name}

{game.questions.length} Questions

))}
) : ( /* MODE: EDIT GAME */ (() => { const game = games.find(g => g.id === editingGameId); if (!game) return null; return (
updateGame(game.id, { name: e.target.value })} className="text-2xl font-bold bg-transparent border-b border-transparent hover:border-slate-300 focus:border-indigo-600 focus:outline-none px-2 py-1 text-slate-900" /> {game.questions.length} Questions
{/* AI Generator for this game */}

AI Question Generator

setAiTopic(e.target.value)} placeholder="e.g. 80s Movies, World Capitals, Pokemon" className="flex-1 px-4 py-2 rounded text-slate-900 bg-white outline-none" />

Questions

{/* Add Manual Form */} {isAddingQuestion && (
setManualQ({...manualQ, text: e.target.value})} />
setManualQ({...manualQ, answer: e.target.value})} />
setManualQ({...manualQ, points: Number(e.target.value)})} />
setManualQ({...manualQ, category: e.target.value})} />
setManualQ({...manualQ, mediaUrl: e.target.value})} />

Supports YouTube links or Image Uploads

)}
    {game.questions.length === 0 && (
  • No questions yet. Add some manually or use AI!
  • )} {game.questions.map((q, i) => (
  • Q{i+1} {q.category} {q.points}pts
    {q.mediaUrl && } {q.text}
    A: {q.answer}
  • ))}
); })() )}
)}
); };