import React, { useState } from 'react'; import { useAuth } from 'react-oidc-context'; import { useLocation, useNavigate } from 'react-router-dom'; import { useGame } from './hooks/useGame'; import { useQuizLibrary } from './hooks/useQuizLibrary'; import { useUserConfig } from './hooks/useUserConfig'; import { useUserPreferences } from './hooks/useUserPreferences'; import { Landing } from './components/Landing'; import { Lobby } from './components/Lobby'; import { GameScreen } from './components/GameScreen'; import { Scoreboard } from './components/Scoreboard'; import { Podium } from './components/Podium'; import { QuizCreator } from './components/QuizCreator'; import { RevealScreen } from './components/RevealScreen'; import { SaveQuizPrompt } from './components/SaveQuizPrompt'; import { QuizEditor } from './components/QuizEditor'; import { SaveOptionsModal } from './components/SaveOptionsModal'; import { DisconnectedScreen } from './components/DisconnectedScreen'; import { WaitingToRejoin } from './components/WaitingToRejoin'; import { HostReconnected } from './components/HostReconnected'; import { SharedQuizView } from './components/SharedQuizView'; import { UpgradePage } from './components/UpgradePage'; import { PaymentResult } from './components/PaymentResult'; import type { Quiz, GameConfig } from './types'; const seededRandom = (seed: number) => { const x = Math.sin(seed * 9999) * 10000; return x - Math.floor(x); }; const FLOATING_SHAPES = [...Array(15)].map((_, i) => ({ left: `${seededRandom(i * 1) * 100}%`, width: `${seededRandom(i * 2) * 100 + 40}px`, height: `${seededRandom(i * 3) * 100 + 40}px`, animationDuration: `${seededRandom(i * 4) * 20 + 15}s`, animationDelay: `-${seededRandom(i * 5) * 20}s`, borderRadius: seededRandom(i * 6) > 0.5 ? '50%' : '20%', background: 'rgba(255, 255, 255, 0.05)', })); const FloatingShapes = React.memo(() => { return ( <> {FLOATING_SHAPES.map((style, i) => (
))} ); }); function App() { const auth = useAuth(); const location = useLocation(); const navigate = useNavigate(); const { saveQuiz, updateQuiz, saving } = useQuizLibrary(); const { defaultConfig } = useUserConfig(); const { subscription } = useUserPreferences(); const maxPlayersLimit = (!subscription || subscription.accessType === 'none') ? 10 : 150; const [showSaveOptions, setShowSaveOptions] = useState(false); const [pendingEditedQuiz, setPendingEditedQuiz] = useState(null); const { role, gameState, quiz, players, currentQuestionIndex, timeLeft, error, gamePin, startQuizGen, startManualCreation, cancelCreation, finalizeManualQuiz, loadSavedQuiz, joinGame, startGame, handleAnswer, hasAnswered, lastPointsEarned, lastAnswerCorrect, nextQuestion, showScoreboard, currentCorrectShape, selectedOption, currentPlayerScore, currentStreak, currentPlayerId, pendingQuizToSave, dismissSavePrompt, sourceQuizId, updateQuizFromEditor, startGameFromEditor, backFromEditor, gameConfig, isReconnecting, currentPlayerName, attemptReconnect, goHomeFromDisconnected, endGame, resumeGame, presenterId, setPresenterPlayer, sendAdvance, kickPlayer, leaveGame } = useGame(defaultConfig); const handleSaveQuiz = async () => { if (!pendingQuizToSave) return; const source = pendingQuizToSave.topic ? 'ai_generated' : 'manual'; const topic = pendingQuizToSave.topic || undefined; await saveQuiz(pendingQuizToSave.quiz, source, topic); dismissSavePrompt(); }; const handleEditorSave = async (editedQuiz: Quiz) => { updateQuizFromEditor(editedQuiz); if (auth.isAuthenticated) { if (sourceQuizId) { // Quiz was loaded from library - show options modal setPendingEditedQuiz(editedQuiz); setShowSaveOptions(true); } else { // New quiz (AI-generated or manual) - save as new const source = pendingQuizToSave?.topic ? 'ai_generated' : 'manual'; const topic = pendingQuizToSave?.topic || undefined; await saveQuiz(editedQuiz, source, topic); dismissSavePrompt(); } } }; const handleOverwriteQuiz = async () => { if (!pendingEditedQuiz || !sourceQuizId) return; await updateQuiz(sourceQuizId, pendingEditedQuiz); setShowSaveOptions(false); setPendingEditedQuiz(null); dismissSavePrompt(); }; const handleSaveAsNew = async () => { if (!pendingEditedQuiz) return; await saveQuiz(pendingEditedQuiz, 'manual'); setShowSaveOptions(false); setPendingEditedQuiz(null); dismissSavePrompt(); }; const currentQ = quiz?.questions[currentQuestionIndex]; const correctOpt = currentQ?.options.find(o => { if (role === 'HOST') return o.isCorrect; return o.shape === currentCorrectShape; }); const sharedMatch = location.pathname.match(/^\/shared\/([a-zA-Z0-9_-]+)$/); const isSharedQuizRoute = !!sharedMatch && gameState === 'LANDING'; const isUpgradeRoute = location.pathname === '/upgrade' && gameState === 'LANDING'; const isPaymentSuccessRoute = location.pathname === '/payment/success' && gameState === 'LANDING'; const isPaymentCancelRoute = location.pathname === '/payment/cancel' && gameState === 'LANDING'; const navigateHome = () => { navigate('/', { replace: true }); }; if (isUpgradeRoute) { return ; } if (isPaymentSuccessRoute) { return ; } if (isPaymentCancelRoute) { return ; } if (isSharedQuizRoute) { return (
loadSavedQuiz(sharedQuiz)} shareToken={sharedMatch![1]} />
); } return (
{gameState === 'LANDING' || gameState === 'GENERATING' ? ( ) : null} {gameState === 'CREATING' ? ( ) : null} {gameState === 'EDITING' && quiz ? ( ) : null} {gameState === 'LOBBY' ? ( <> {auth.isAuthenticated && pendingQuizToSave && ( )} ) : null} {(gameState === 'COUNTDOWN' || gameState === 'QUESTION') ? ( gameState === 'COUNTDOWN' ? (
Get Ready!
{timeLeft}
) : quiz?.questions[currentQuestionIndex] ? ( ) : role === 'CLIENT' && hasAnswered ? (
🚀

Answer Sent!

Cross your fingers...

) : currentPlayerName ? ( ) : null ) : null} {gameState === 'REVEAL' ? ( correctOpt ? ( sendAdvance('SCOREBOARD')} hostParticipates={gameConfig.hostParticipates} /> ) : currentPlayerName ? ( ) : null ) : null} {gameState === 'SCOREBOARD' ? ( sendAdvance('NEXT')} /> ) : null} {gameState === 'PODIUM' ? ( ) : null} {gameState === 'DISCONNECTED' && currentPlayerName && gamePin ? ( ) : null} {gameState === 'WAITING_TO_REJOIN' && currentPlayerName ? ( ) : null} {gameState === 'HOST_RECONNECTED' && quiz ? ( p.id !== 'host').length} onResume={resumeGame} onEndGame={endGame} /> ) : null}
{ setShowSaveOptions(false); setPendingEditedQuiz(null); }} onOverwrite={handleOverwriteQuiz} onSaveNew={handleSaveAsNew} isSaving={saving} />
); } export default App;