Fix lobby jank

This commit is contained in:
Joey Yakimowich-Payne 2026-01-16 10:24:47 -07:00
commit c98e262fd0
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
2 changed files with 108 additions and 45 deletions

View file

@ -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 } from 'lucide-react';
import { Sparkles, User, X, Link, Check, QrCode } from 'lucide-react';
import { QRCodeSVG } from 'qrcode.react';
import { PlayerAvatar } from './PlayerAvatar';
import toast from 'react-hot-toast';
@ -44,51 +44,111 @@ export const Lobby: React.FC<LobbyProps> = ({ quizTitle, players, gamePin, role,
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 items-center gap-4">
{isHost && gamePin && (
<div
<header className="w-full max-w-[90rem] mx-auto mb-4 md:mb-8 shrink-0 z-20">
<div className="md:hidden flex flex-col gap-3 w-full">
<div className="bg-white/10 backdrop-blur-md p-4 rounded-2xl border-2 border-white/20 text-center shadow-lg relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-white/50 to-transparent opacity-50"></div>
<h1 className="text-xl font-black font-display leading-tight text-white drop-shadow-md line-clamp-2">{quizTitle}</h1>
<div className="inline-flex items-center gap-1.5 mt-2 bg-black/20 px-3 py-0.5 rounded-full">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse shadow-[0_0_8px_rgba(74,222,128,0.6)]" />
<span className="text-[10px] font-bold text-white/90 uppercase tracking-widest">Live</span>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="col-span-2 bg-white/10 backdrop-blur-md p-4 rounded-2xl border-2 border-white/20 shadow-lg flex flex-col items-center justify-center">
<span className="text-white/60 font-bold uppercase tracking-widest text-[10px] mb-1">Game PIN</span>
<div className="text-5xl font-black bg-white text-theme-primary px-4 py-2 rounded-xl shadow-[0_4px_0_rgba(0,0,0,0.1)] tracking-[0.15em] w-full text-center">
{gamePin}
</div>
</div>
<div className={`bg-white/10 backdrop-blur-md p-3 rounded-2xl border-2 border-white/20 shadow-lg flex flex-col items-center justify-center gap-1 ${!isHost ? 'col-span-2' : ''}`}>
<span className="text-white/60 font-bold uppercase tracking-widest text-[10px]">Players</span>
<div className="flex items-center gap-2">
<User size={20} className="text-white/90" />
<span className="text-2xl font-black text-white">{realPlayers.length}</span>
</div>
</div>
{isHost && (
<div className="flex flex-col gap-2 h-full">
<button
onClick={() => setIsQrModalOpen(true)}
className="bg-white p-2 rounded-xl shadow-lg hidden md:block cursor-pointer hover:scale-105 transition-transform"
title="Click to expand"
>
<QRCodeSVG
value={`${window.location.origin}/play/${gamePin}`}
size={80}
level="M"
/>
</div>
)}
<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}
className="flex-1 bg-white/20 hover:bg-white/30 border-2 border-white/10 rounded-xl flex items-center justify-center gap-2 transition-all active:scale-95 py-2"
>
<QrCode size={16} />
<span className="font-bold text-xs">QR Code</span>
</button>
<button
onClick={copyJoinLink}
className="flex-1 bg-white/20 hover:bg-white/30 border-2 border-white/10 rounded-xl flex items-center justify-center gap-2 transition-all active:scale-95 py-2"
>
{linkCopied ? <Check size={16} className="text-green-400"/> : <Link size={16}/>}
<span className="font-bold text-xs">{linkCopied ? 'Copied' : 'Link'}</span>
</button>
</div>
)}
</div>
</div>
<div className="hidden md:flex justify-between items-center bg-white/10 p-5 rounded-[2.5rem] backdrop-blur-md border-4 border-white/20 shadow-2xl relative overflow-hidden min-h-[140px]">
<div className="flex-1 flex items-center gap-6">
{isHost && gamePin && (
<button
onClick={() => setIsQrModalOpen(true)}
className="bg-white p-3 rounded-2xl shadow-lg hover:scale-105 hover:shadow-xl transition-all group relative cursor-pointer active:scale-95 shrink-0"
title="Show Big QR Code"
>
<QRCodeSVG
value={`${window.location.origin}/play/${gamePin}`}
size={70}
level="M"
/>
<div className="absolute inset-0 bg-black/60 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<QrCode className="text-white w-8 h-8" />
</div>
</button>
)}
<div className="flex flex-col justify-center min-w-0">
<span className="text-white/60 font-bold uppercase tracking-widest text-xs mb-1.5 ml-1 truncate">Join at kaboot.com</span>
<div className="flex items-center gap-3">
<div className="text-6xl font-black bg-white text-theme-primary px-8 py-2 rounded-2xl shadow-[0_6px_0_rgba(0,0,0,0.15)] tracking-widest whitespace-nowrap">
{gamePin}
</div>
{isHost && (
<button
onClick={copyJoinLink}
className="bg-white/10 hover:bg-white/20 border-2 border-white/20 p-4 rounded-2xl transition-all active:scale-95 group shrink-0"
title="Copy join link"
>
{linkCopied ?
<Check size={28} className="text-green-400" /> :
<Link size={28} className="text-white group-hover:text-white/100 opacity-80 group-hover:opacity-100" />
}
</button>
)}
</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>
<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 className="flex-[1.5] flex flex-col items-center justify-center text-center px-4 relative z-10 min-w-0">
<h1 className="text-3xl lg:text-4xl xl:text-5xl font-black font-display mb-3 drop-shadow-md leading-tight">
{quizTitle}
</h1>
<div className="inline-flex items-center gap-2.5 bg-black/30 px-5 py-1.5 rounded-full backdrop-blur-sm border border-white/10">
<div className="w-3 h-3 bg-green-400 rounded-full animate-pulse shadow-[0_0_12px_rgba(74,222,128,0.8)]"></div>
<span className="font-bold text-sm tracking-widest text-white/90 uppercase">Live Lobby</span>
</div>
</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 className="flex-1 flex justify-end items-center">
<div className="bg-black/20 hover:bg-black/30 transition-colors px-8 py-4 rounded-[2rem] border-2 border-white/10 backdrop-blur-sm flex flex-col items-center min-w-[140px]">
<span className="text-4xl font-black text-white drop-shadow-sm">{realPlayers.length}</span>
<span className="text-xs font-bold text-white/60 uppercase tracking-widest mt-1">Players</span>
</div>
</div>
</div>
</header>

View file

@ -99,13 +99,15 @@ describe('Lobby', () => {
it('displays game PIN', () => {
render(<Lobby {...defaultProps} />);
expect(screen.getByText('ABC123')).toBeInTheDocument();
const pins = screen.getAllByText('ABC123');
expect(pins.length).toBeGreaterThan(0);
});
it('displays quiz title on larger screens', () => {
it('displays quiz title on all screen sizes', () => {
render(<Lobby {...defaultProps} />);
expect(screen.getByText('Test Quiz')).toBeInTheDocument();
const titles = screen.getAllByText('Test Quiz');
expect(titles.length).toBeGreaterThan(0);
});
it('displays player count', () => {
@ -115,7 +117,8 @@ describe('Lobby', () => {
];
render(<Lobby {...defaultProps} players={players} />);
expect(screen.getByText('2')).toBeInTheDocument();
const counts = screen.getAllByText('2');
expect(counts.length).toBeGreaterThan(0);
});
it('shows waiting message when no players have joined', () => {