diff --git a/App.tsx b/App.tsx index 8f0e380..1bcfac8 100644 --- a/App.tsx +++ b/App.tsx @@ -137,10 +137,10 @@ function App() { }); return ( -
+
-
+
{gameState === 'LANDING' || gameState === 'GENERATING' ? ( {auth.isAuthenticated && pendingQuizToSave && ( = ({ onGoHome, }) => { return ( -
+
= ({ onChange={(v) => update({ hostParticipates: v })} /> + } + iconActive={config.randomNamesEnabled} + label="Random Names" + description="Assign fun two-word names to players" + checked={config.randomNamesEnabled} + onChange={(v) => update({ randomNamesEnabled: v })} + tooltip="Players will be assigned random names like 'Brave Falcon' or 'Swift Tiger' instead of choosing their own nicknames." + /> + } iconActive={config.streakBonusEnabled} diff --git a/components/GameScreen.tsx b/components/GameScreen.tsx index 060de98..d68279d 100644 --- a/components/GameScreen.tsx +++ b/components/GameScreen.tsx @@ -40,35 +40,34 @@ export const GameScreen: React.FC = ({ return (
{/* Header */} -
-
+
+
{currentQuestionIndex + 1} / {totalQuestions}
- {/* Whimsical Timer */}
-
-
- {timeLeftSeconds} -
+
+
+ {timeLeftSeconds} +
-
+
{isClient ? 'Player' : isSpectator ? 'Spectator' : 'Host'}
{/* Question Area */} -
+
{question && ( -

+

{question.text}

@@ -76,7 +75,7 @@ export const GameScreen: React.FC = ({
{/* Answer Grid */} -
+
{displayOptions.map((option, idx) => { const ShapeIcon = SHAPES[option.shape]; const colorClass = COLORS[option.color]; diff --git a/components/HostReconnected.tsx b/components/HostReconnected.tsx index 3069ac0..4783fef 100644 --- a/components/HostReconnected.tsx +++ b/components/HostReconnected.tsx @@ -20,7 +20,7 @@ export const HostReconnected: React.FC = ({ onEndGame, }) => { return ( -
+
= ({ onGenerate, onCreateManual, on const [editingDefaultConfig, setEditingDefaultConfig] = useState(null); const [preferencesOpen, setPreferencesOpen] = useState(false); const [accountSettingsOpen, setAccountSettingsOpen] = useState(false); + const [gameInfo, setGameInfo] = useState<{ randomNamesEnabled: boolean; quizTitle: string } | null>(null); + const [checkingPin, setCheckingPin] = useState(false); const hasImageFile = selectedFiles.some(f => f.type.startsWith('image/')); const hasDocumentFile = selectedFiles.some(f => !f.type.startsWith('image/') && !['application/pdf', 'text/plain', 'text/markdown', 'text/csv', 'text/html'].includes(f.type)); @@ -67,6 +69,30 @@ export const Landing: React.FC = ({ onGenerate, onCreateManual, on } }, [libraryOpen, auth.isAuthenticated, fetchQuizzes]); + useEffect(() => { + const checkGamePin = async () => { + if (pin.trim().length === 6) { + setCheckingPin(true); + try { + const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3001'; + const response = await fetch(`${backendUrl}/api/games/${pin.trim()}`); + if (response.ok) { + const data = await response.json(); + setGameInfo({ randomNamesEnabled: data.randomNamesEnabled, quizTitle: data.quizTitle }); + } else { + setGameInfo(null); + } + } catch { + setGameInfo(null); + } + setCheckingPin(false); + } else { + setGameInfo(null); + } + }; + checkGamePin(); + }, [pin]); + const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const newFiles = Array.from(e.target.files); @@ -128,7 +154,9 @@ export const Landing: React.FC = ({ onGenerate, onCreateManual, on const handleJoinSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (pin.trim() && name.trim()) onJoin(pin, name); + if (pin.trim() && (gameInfo?.randomNamesEnabled || name.trim())) { + onJoin(pin, name.trim() || 'Player'); + } }; const handleLoadQuiz = async (id: string) => { @@ -145,7 +173,7 @@ export const Landing: React.FC = ({ onGenerate, onCreateManual, on }; return ( -
+
{auth.isAuthenticated && ( <> @@ -174,17 +202,17 @@ export const Landing: React.FC = ({ onGenerate, onCreateManual, on initial={{ scale: 0.8, opacity: 0, rotate: -2 }} animate={{ scale: 1, opacity: 1, rotate: 0 }} transition={{ type: "spring", bounce: 0.5 }} - className="bg-white text-gray-900 p-8 rounded-[2rem] shadow-[0_10px_0_rgba(0,0,0,0.1)] max-w-md w-full border-4 border-white/50" + className="bg-white text-gray-900 p-6 md:p-8 rounded-[2rem] shadow-[0_10px_0_rgba(0,0,0,0.1)] max-w-md w-full border-4 border-white/50 max-h-[calc(100vh-2rem)] overflow-y-auto" > -
-
- +
+
+
-

Kaboot

-

The AI Quiz Party

+

Kaboot

+

The AI Quiz Party

-
+
)} diff --git a/components/Lobby.tsx b/components/Lobby.tsx index 972f783..e3c518f 100644 --- a/components/Lobby.tsx +++ b/components/Lobby.tsx @@ -11,23 +11,25 @@ interface LobbyProps { role: 'HOST' | 'CLIENT'; onStart: () => void; onEndGame?: () => void; + currentPlayerId?: string | null; } -export const Lobby: React.FC = ({ quizTitle, players, gamePin, role, onStart, onEndGame }) => { +export const Lobby: React.FC = ({ quizTitle, players, gamePin, role, onStart, onEndGame, currentPlayerId }) => { const isHost = role === 'HOST'; const realPlayers = players.filter(p => p.id !== 'host'); + const currentPlayer = currentPlayerId ? players.find(p => p.id === currentPlayerId) : null; return ( -
-
+
+
- Game PIN -
+ Game PIN +
{gamePin}
-
+
{quizTitle}
@@ -35,23 +37,23 @@ export const Lobby: React.FC = ({ quizTitle, players, gamePin, role,
-
+
{realPlayers.length} - Players + Players
-
+
{isHost ? ( <> -
+
{realPlayers.length === 0 && ( -
-
- +
+
+
-
Waiting for players to join...
+
Waiting for players to join...
)} {realPlayers.map((player) => ( @@ -60,7 +62,7 @@ export const Lobby: React.FC = ({ quizTitle, players, gamePin, role, initial={{ scale: 0, rotate: -10 }} animate={{ scale: 1, rotate: 0 }} exit={{ scale: 0, opacity: 0 }} - className="bg-white text-black px-6 py-3 rounded-full font-black text-xl shadow-[0_4px_0_rgba(0,0,0,0.2)] flex items-center gap-3 border-b-4 border-gray-200" + className="bg-white text-black px-4 md:px-6 py-2 md:py-3 rounded-full font-black text-base md:text-xl shadow-[0_4px_0_rgba(0,0,0,0.2)] flex items-center gap-2 md:gap-3 border-b-4 border-gray-200" > {player.name} @@ -72,38 +74,45 @@ export const Lobby: React.FC = ({ quizTitle, players, gamePin, role, {onEndGame && ( )} ) : ( -
+
- + {currentPlayer ? ( + + ) : ( + + )} -

You're in!

-

See your name on the big screen?

+

+ {currentPlayer?.name || "You're in!"} +

+

Waiting for the host to start...

)}
diff --git a/components/Podium.tsx b/components/Podium.tsx index 31526be..0030697 100644 --- a/components/Podium.tsx +++ b/components/Podium.tsx @@ -34,10 +34,10 @@ export const Podium: React.FC = ({ players, onRestart }) => { }, []); return ( -
-

Podium

+
+

Podium

-
+
{/* Second Place */} {second && ( = ({ onFinalize, onCancel } }; return ( -
-
+
+

Create Quiz

@@ -79,7 +79,7 @@ export const QuizCreator: React.FC = ({ onFinalize, onCancel }
-
+
= ({ }; return ( -
-
+
+
-
+

Questions