Files
skribbl-gartic-color/app/room/[code]/page.tsx
T

144 lines
7.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter, useParams } from "next/navigation";
import RoomConnector from "../../components/RoomConnector";
import PlayerList from "../../components/PlayerList";
import ChatBox from "../../components/ChatBox";
import { getSocket } from "../../lib/socket-client";
import { useGame } from "../../lib/store";
export default function LobbyPage() {
const params = useParams<{ code: string }>();
const code = String(params.code || "").toUpperCase();
const router = useRouter();
const room = useGame((s) => s.room);
const myId = useGame((s) => s.myId);
const [copied, setCopied] = useState(false);
const [err, setErr] = useState("");
useEffect(() => {
if (room && room.phase === "playing") router.push(`/room/${code}/play`);
if (room && room.phase === "results") router.push(`/room/${code}/results`);
}, [room?.phase, code, router]);
const isHost = room && myId && room.hostId === myId;
const shareUrl = typeof window !== "undefined" ? `${window.location.origin}/join?code=${code}` : "";
const playerCount = room?.players.filter(p => p.connected).length || 0;
const garticNeedsMore = room?.mode === "gartic" && playerCount < 3;
const start = () => {
setErr("");
getSocket().emit("game:start", null, (resp: any) => {
if (!resp?.ok) setErr(resp?.error || "could not start");
});
};
const copy = async () => {
try { await navigator.clipboard.writeText(shareUrl); setCopied(true); setTimeout(()=>setCopied(false), 1500); } catch {}
};
return (
<div>
<RoomConnector code={code} />
<header className="flex items-center justify-between max-w-[1400px] mx-auto px-7 py-4">
<Link href="/" className="flex items-center gap-2.5 font-bold text-2xl no-underline text-dark">
<span className="logo-mark sm">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5" strokeLinecap="round"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/></svg>
</span>
DrawTogether
</Link>
<div className="pill" data-testid="room-pill">
<span className="w-2 h-2 rounded-full bg-mint"/> Room <strong>{code}</strong>
</div>
</header>
<main className="max-w-[1400px] mx-auto px-7 pb-20 grid gap-6 max-[900px]:grid-cols-1" style={{gridTemplateColumns: "1fr 1.2fr"}}>
<section className="panel" data-testid="panel-players">
<div className="flex items-center justify-between mb-4">
<h2 className="font-bold text-lg flex items-center gap-2.5">
<span className="w-8 h-8 rounded-lg border-2 border-dark grid place-items-center" style={{background:"var(--mint)"}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
</span>
Players
</h2>
<span className="pill" style={{padding:"4px 10px", fontSize:12}}>{room?.players.length || 0} / 12</span>
</div>
<PlayerList />
</section>
<div className="flex flex-col gap-6">
<section className="panel">
<h2 className="font-bold text-lg mb-3">Invite friends</h2>
<div className="flex gap-2.5 items-center p-2 pl-4 rounded-2xl border-[3px] border-dark" style={{background:"var(--cream)"}}>
<span className="flex-1 font-semibold text-sm overflow-hidden text-ellipsis whitespace-nowrap min-w-0" data-testid="share-url">{shareUrl}</span>
<button onClick={copy} data-testid="copy-link" className="px-3.5 py-2 rounded-xl font-bold text-xs text-white border-2 border-dark shadow-chunky flex items-center gap-1.5" style={{background:"var(--coral)"}}>
{copied ? "Copied!" : (
<>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
Copy
</>
)}
</button>
</div>
<div className="text-center p-4 mt-3 rounded-2xl border-[3px] border-dashed border-dark" style={{background:"var(--cream)"}}>
<div className="text-xs font-bold uppercase tracking-widest" style={{color:"rgba(45,45,45,0.6)"}}>Room code</div>
<div className="text-3xl font-bold tracking-[8px] mt-1" data-testid="room-code">{code}</div>
</div>
</section>
<section className="panel">
<h2 className="font-bold text-lg mb-3">Game settings</h2>
<div className="text-sm font-medium" style={{color:"rgba(45,45,45,0.7)"}}>
Mode: <strong className="text-dark uppercase">{room?.mode || "—"}</strong>
</div>
<div className="grid grid-cols-2 gap-3 mt-3 max-[640px]:grid-cols-1">
{room?.mode === "skribbl" && <>
<SettingTile label="Rounds" value={String(room?.settings?.rounds || 4)}/>
<SettingTile label="Draw Time" value={`${room?.settings?.drawTimeSec || 80}s`}/>
</>}
{room?.mode === "gartic" && <>
<SettingTile label="Players" value={String(room?.players.length || 0)}/>
<SettingTile label="Time / round" value="60s"/>
</>}
{room?.mode === "color" && <>
<SettingTile label="Canvas" value={room?.settings?.canvasType || "blank"}/>
{room?.settings?.canvasType === "template" && <SettingTile label="Template" value={room?.settings?.templateId || "mandala"}/>}
</>}
</div>
{garticNeedsMore && (
<div data-testid="gartic-min-players" className="mt-4 p-3 rounded-2xl border-[3px] border-dashed border-dark text-center text-sm font-semibold" style={{background:"var(--cream)", color:"rgba(45,45,45,0.75)"}}>
Gartic Phone is way more fun with friends! Grab at least <strong>3 players</strong> to start. ({playerCount}/3)
</div>
)}
{isHost ? (
<button onClick={start} disabled={!!garticNeedsMore} data-testid="start-game" className="btn btn-primary w-full mt-4" style={{padding:"16px",fontSize:18, opacity: garticNeedsMore ? 0.5 : 1, cursor: garticNeedsMore ? "not-allowed" : undefined}}>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round"><polygon points="6 4 20 12 6 20 6 4"/></svg>
Start Game
</button>
) : (
<div className="text-center mt-4 font-semibold text-sm" style={{color:"rgba(45,45,45,0.6)"}}>Waiting for host to start...</div>
)}
{err && <div className="text-coral font-bold text-sm mt-2 text-center">{err}</div>}
</section>
<section className="panel">
<h2 className="font-bold text-lg mb-3">Lobby chat</h2>
<ChatBox placeholder="Say hi to your crew..."/>
</section>
</div>
</main>
</div>
);
}
function SettingTile({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-xl p-3 px-3.5 border-2" style={{background:"var(--cream)", borderColor:"rgba(45,45,45,0.1)"}}>
<div className="text-xs font-semibold uppercase" style={{color:"rgba(45,45,45,0.6)"}}>{label}</div>
<div className="font-bold text-base capitalize">{value}</div>
</div>
);
}