96 lines
4.3 KiB
TypeScript
96 lines
4.3 KiB
TypeScript
"use client";
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { getSocket } from "../lib/socket-client";
|
|
import { writeSession } from "../lib/store";
|
|
|
|
const AVATARS = ["🦄","🦊","🐼","🐸","🐱","🐶","🦁","🐯","🐰","🐻","🐨","🦝"];
|
|
|
|
export default function JoinRoomPage() {
|
|
return (
|
|
<Suspense fallback={<div className="p-10 text-center font-bold">Loading...</div>}>
|
|
<JoinRoomInner />
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
function JoinRoomInner() {
|
|
const router = useRouter();
|
|
const search = useSearchParams();
|
|
const [code, setCode] = useState("");
|
|
const [nickname, setNickname] = useState("");
|
|
const [avatar, setAvatar] = useState("🐱");
|
|
const [busy, setBusy] = useState(false);
|
|
const [err, setErr] = useState("");
|
|
|
|
useEffect(() => {
|
|
const c = search?.get("code");
|
|
if (c) setCode(c.toUpperCase());
|
|
}, [search]);
|
|
|
|
const submit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setErr("");
|
|
const cleanCode = code.trim().toUpperCase();
|
|
if (!cleanCode) { setErr("Enter a room code"); return; }
|
|
if (!nickname.trim()) { setErr("Pick a nickname"); return; }
|
|
setBusy(true);
|
|
const socket = getSocket();
|
|
const finish = () => {
|
|
socket.emit("room:join", { code: cleanCode, nickname: nickname.trim(), avatar }, (resp: any) => {
|
|
if (!resp || !resp.ok) { setErr(resp?.error || "couldn't join"); setBusy(false); return; }
|
|
writeSession(resp.code, { token: resp.sessionToken, nickname: nickname.trim(), avatar, playerId: resp.playerId });
|
|
router.push(`/room/${resp.code}`);
|
|
});
|
|
};
|
|
if (socket.connected) finish();
|
|
else socket.once("connect", finish);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<header className="flex items-center justify-between max-w-[1280px] mx-auto px-7 py-5">
|
|
<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>
|
|
<Link href="/create" className="btn btn-primary" style={{padding:"10px 18px",fontSize:14}}>Create Room</Link>
|
|
</header>
|
|
|
|
<main className="max-w-[520px] mx-auto px-6 pb-16">
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-4xl font-bold">Join a room</h1>
|
|
<p className="font-medium mt-2" style={{color:"rgba(45,45,45,0.7)"}}>Got a 6-character code? Drop it in.</p>
|
|
</div>
|
|
<form className="panel flex flex-col gap-5" onSubmit={submit}>
|
|
<div>
|
|
<label className="field-label">Room code</label>
|
|
<input data-testid="room-code-input" maxLength={6} value={code} onChange={(e)=>setCode(e.target.value.toUpperCase())} className="input-text font-bold tracking-[8px] text-center text-2xl uppercase" placeholder="ABC123"/>
|
|
</div>
|
|
<div>
|
|
<label className="field-label">Nickname</label>
|
|
<input data-testid="nickname-input" maxLength={20} value={nickname} onChange={(e)=>setNickname(e.target.value)} className="input-text" placeholder="Your name"/>
|
|
</div>
|
|
<div>
|
|
<label className="field-label">Avatar</label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{AVATARS.map(a => (
|
|
<button type="button" key={a} onClick={()=>setAvatar(a)}
|
|
className="w-12 h-12 rounded-xl border-[3px] grid place-items-center text-2xl shadow-chunky transition-transform hover:-translate-y-0.5"
|
|
style={{background: avatar===a?"var(--yellow)":"var(--cream)", borderColor: avatar===a?"var(--dark)":"rgba(45,45,45,0.3)"}}>{a}</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{err && <div className="text-coral font-bold text-sm">{err}</div>}
|
|
<button type="submit" disabled={busy} data-testid="join-submit" className="btn btn-secondary self-stretch" style={{padding:"16px 32px",fontSize:18}}>
|
|
{busy ? "Joining..." : "Join Room"}
|
|
</button>
|
|
</form>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|