Files
skribbl-gartic-color/app/join/page.tsx
T

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>
);
}