137 lines
7.0 KiB
TypeScript
137 lines
7.0 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 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>
|
|
|
|
{isHost ? (
|
|
<button onClick={start} data-testid="start-game" className="btn btn-primary w-full mt-4" style={{padding:"16px",fontSize:18}}>
|
|
<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>
|
|
);
|
|
}
|