Add presenter role for game flow control
This commit is contained in:
parent
99977bc8e6
commit
9ef8f7343d
10 changed files with 1412 additions and 17 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Player } from '../types';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Sparkles, User, X, Link, Check, QrCode } from 'lucide-react';
|
||||
import { Sparkles, User, X, Link, Check, QrCode, Crown } from 'lucide-react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { PlayerAvatar } from './PlayerAvatar';
|
||||
import toast from 'react-hot-toast';
|
||||
|
|
@ -15,15 +15,19 @@ interface LobbyProps {
|
|||
onEndGame?: () => void;
|
||||
currentPlayerId?: string | null;
|
||||
hostParticipates?: boolean;
|
||||
presenterId?: string | null;
|
||||
onSetPresenter?: (playerId: string | null) => void;
|
||||
}
|
||||
|
||||
export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role, onStart, onEndGame, currentPlayerId, hostParticipates = false }) => {
|
||||
export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role, onStart, onEndGame, currentPlayerId, hostParticipates = false, presenterId, onSetPresenter }) => {
|
||||
const isHost = role === 'HOST';
|
||||
const hostPlayer = players.find(p => p.id === 'host');
|
||||
const realPlayers = players.filter(p => p.id !== 'host');
|
||||
const currentPlayer = currentPlayerId ? players.find(p => p.id === currentPlayerId) : null;
|
||||
const [linkCopied, setLinkCopied] = useState(false);
|
||||
const [isQrModalOpen, setIsQrModalOpen] = useState(false);
|
||||
const isPresenter = currentPlayerId === presenterId;
|
||||
const canSelectPresenter = isHost && !hostParticipates && onSetPresenter;
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
|
|
@ -165,6 +169,12 @@ export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role,
|
|||
<div className="text-xl md:text-3xl font-bold font-display text-center px-4">Waiting for players to join...</div>
|
||||
</div>
|
||||
)}
|
||||
{realPlayers.length > 0 && !hostParticipates && canSelectPresenter && (
|
||||
<div className="w-full text-center text-white/60 text-sm mb-2">
|
||||
<Crown size={14} className="inline mr-1 text-yellow-400" />
|
||||
Click a player to make them presenter (can advance screens)
|
||||
</div>
|
||||
)}
|
||||
{hostParticipates && hostPlayer && (
|
||||
<motion.div
|
||||
key="host"
|
||||
|
|
@ -178,18 +188,30 @@ export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role,
|
|||
<span className="text-xs bg-black/20 px-2 py-0.5 rounded-full">HOST</span>
|
||||
</motion.div>
|
||||
)}
|
||||
{realPlayers.map((player) => (
|
||||
{realPlayers.map((player) => {
|
||||
const isPlayerPresenter = player.id === presenterId;
|
||||
return (
|
||||
<motion.div
|
||||
key={player.id}
|
||||
initial={{ scale: 0, rotate: -10 }}
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
exit={{ scale: 0, opacity: 0 }}
|
||||
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"
|
||||
onClick={() => canSelectPresenter && onSetPresenter(player.id)}
|
||||
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 ${
|
||||
isPlayerPresenter ? 'border-yellow-400 ring-2 ring-yellow-400' : 'border-gray-200'
|
||||
} ${canSelectPresenter ? 'cursor-pointer hover:scale-105 transition-transform' : ''}`}
|
||||
>
|
||||
{isPlayerPresenter && (
|
||||
<Crown size={18} className="text-yellow-500 -ml-1" />
|
||||
)}
|
||||
<PlayerAvatar seed={player.avatarSeed} size={20} />
|
||||
{player.name}
|
||||
{isPlayerPresenter && (
|
||||
<span className="text-xs bg-yellow-400 text-yellow-900 px-2 py-0.5 rounded-full">PRESENTER</span>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
|
|
@ -223,8 +245,13 @@ export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role,
|
|||
initial={{ scale: 0.5 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', bounce: 0.6 }}
|
||||
className="bg-white p-6 md:p-8 rounded-2xl md:rounded-[2rem] shadow-[0_10px_0_rgba(0,0,0,0.1)] mb-4 md:mb-8"
|
||||
className={`bg-white p-6 md:p-8 rounded-2xl md:rounded-[2rem] shadow-[0_10px_0_rgba(0,0,0,0.1)] mb-4 md:mb-8 relative ${isPresenter ? 'ring-4 ring-yellow-400' : ''}`}
|
||||
>
|
||||
{isPresenter && (
|
||||
<div className="absolute -top-3 -right-3 bg-yellow-400 p-2 rounded-full shadow-lg">
|
||||
<Crown size={24} className="text-yellow-900" />
|
||||
</div>
|
||||
)}
|
||||
{currentPlayer ? (
|
||||
<PlayerAvatar seed={currentPlayer.avatarSeed} size={60} className="md:w-20 md:h-20" />
|
||||
) : (
|
||||
|
|
@ -234,7 +261,17 @@ export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role,
|
|||
<h2 className="text-3xl md:text-5xl font-black mb-2 md:mb-4 font-display">
|
||||
{currentPlayer?.name || "You're in!"}
|
||||
</h2>
|
||||
<p className="text-lg md:text-2xl font-bold opacity-80">Waiting for the host to start...</p>
|
||||
{isPresenter ? (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<span className="bg-yellow-400 text-yellow-900 px-4 py-1 rounded-full font-bold text-sm flex items-center gap-2">
|
||||
<Crown size={16} />
|
||||
You are the Presenter
|
||||
</span>
|
||||
<p className="text-lg md:text-xl font-bold opacity-80">You can advance screens during the game</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-lg md:text-2xl font-bold opacity-80">Waiting for the host to start...</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue