kaboot/components/Lobby.tsx

158 lines
8.2 KiB
TypeScript

import React, { useState } from 'react';
import { Player } from '../types';
import { motion, AnimatePresence } from 'framer-motion';
import { Sparkles, User, X, Link, Check } from 'lucide-react';
import { PlayerAvatar } from './PlayerAvatar';
import toast from 'react-hot-toast';
interface LobbyProps {
quizTitle: string;
players: Player[];
gamePin: string | null;
role: 'HOST' | 'CLIENT';
onStart: () => void;
onEndGame?: () => void;
currentPlayerId?: string | null;
hostParticipates?: boolean;
}
export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role, onStart, onEndGame, currentPlayerId, hostParticipates = false }) => {
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 copyJoinLink = async () => {
if (!gamePin) return;
const joinUrl = `${window.location.origin}/play/${gamePin}`;
await navigator.clipboard.writeText(joinUrl);
setLinkCopied(true);
toast.success('Join link copied!');
setTimeout(() => setLinkCopied(false), 2000);
};
return (
<div className="flex flex-col h-screen p-4 md:p-6 overflow-hidden">
<header className="flex flex-col md:flex-row justify-between items-center bg-white/10 p-4 md:p-6 rounded-2xl md:rounded-[2rem] backdrop-blur-md mb-4 md:mb-8 gap-3 md:gap-6 border-4 border-white/20 shadow-xl shrink-0">
<div className="flex flex-col items-center md:items-start">
<span className="text-white/80 font-bold uppercase tracking-widest text-xs md:text-sm mb-1">Game PIN</span>
<div className="flex items-center gap-2">
<div className="text-4xl md:text-6xl font-black bg-white text-theme-primary px-6 md:px-8 py-1 md:py-2 rounded-full shadow-[0_6px_0_rgba(0,0,0,0.2)] tracking-wider">
{gamePin}
</div>
{isHost && (
<button
onClick={copyJoinLink}
className="bg-white/20 hover:bg-white/30 p-3 rounded-full transition-all active:scale-95"
title="Copy join link"
>
{linkCopied ? <Check size={24} className="text-green-400" /> : <Link size={24} />}
</button>
)}
</div>
</div>
<div className="text-center hidden md:block">
<div className="text-3xl font-black font-display mb-2">{quizTitle}</div>
<div className="inline-flex items-center gap-2 bg-[#00000040] px-4 py-1 rounded-full">
<div className="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<span className="font-bold">Live Lobby</span>
</div>
</div>
<div className="bg-white/20 px-4 md:px-6 py-2 md:py-3 rounded-xl md:rounded-2xl font-black text-xl md:text-2xl flex flex-col items-center min-w-[100px] md:min-w-[120px]">
<span>{realPlayers.length}</span>
<span className="text-xs md:text-sm font-bold opacity-80 uppercase">Players</span>
</div>
</header>
<main className="flex-1 flex flex-col items-center justify-center overflow-hidden min-h-0">
{isHost ? (
<>
<div className="flex flex-wrap gap-3 md:gap-4 justify-center w-full max-w-6xl pb-24 md:pb-28 overflow-y-auto content-start">
<AnimatePresence>
{realPlayers.length === 0 && !hostParticipates && (
<div className="flex flex-col items-center opacity-60 mt-8 md:mt-12">
<div className="bg-white/10 p-4 md:p-6 rounded-full mb-4 animate-bounce">
<Sparkles size={36} className="md:w-12 md:h-12" />
</div>
<div className="text-xl md:text-3xl font-bold font-display text-center px-4">Waiting for players to join...</div>
</div>
)}
{hostParticipates && hostPlayer && (
<motion.div
key="host"
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
exit={{ scale: 0, opacity: 0 }}
className="bg-yellow-400 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-yellow-500"
>
<PlayerAvatar seed={hostPlayer.avatarSeed} size={20} />
{hostPlayer.name}
<span className="text-xs bg-black/20 px-2 py-0.5 rounded-full">HOST</span>
</motion.div>
)}
{realPlayers.map((player) => (
<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"
>
<PlayerAvatar seed={player.avatarSeed} size={20} />
{player.name}
</motion.div>
))}
</AnimatePresence>
</div>
<motion.div
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
className="fixed bottom-4 md:bottom-8 flex gap-2 md:gap-4 px-4"
>
{onEndGame && (
<button
onClick={onEndGame}
className="bg-white/20 text-white px-4 md:px-8 py-3 md:py-5 rounded-full text-base md:text-xl font-bold hover:bg-white/30 active:scale-95 transition-all flex items-center gap-2"
>
<X size={20} className="md:w-6 md:h-6" />
<span className="hidden md:inline">End Game</span>
<span className="md:hidden">End</span>
</button>
)}
<button
onClick={onStart}
disabled={realPlayers.length === 0}
className="bg-white text-theme-primary px-8 md:px-16 py-3 md:py-5 rounded-full text-xl md:text-3xl font-black hover:scale-105 active:scale-95 transition-all shadow-[0_8px_0_rgba(0,0,0,0.2)] disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none disabled:translate-y-2"
>
Start
</button>
</motion.div>
</>
) : (
<div className="flex flex-col items-center justify-center flex-1 text-center p-4 md:p-8">
<motion.div
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"
>
{currentPlayer ? (
<PlayerAvatar seed={currentPlayer.avatarSeed} size={60} className="md:w-20 md:h-20" />
) : (
<User size={60} strokeWidth={2.5} className="text-theme-primary md:w-20 md:h-20" />
)}
</motion.div>
<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>
</div>
)}
</main>
</div>
);
};