159 lines
7.9 KiB
TypeScript
159 lines
7.9 KiB
TypeScript
"use client";
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { getSocket } from "../lib/socket-client";
|
|
import { writeSession } from "../lib/store";
|
|
|
|
const AVATARS = ["🦄","🦊","🐼","🐸","🐱","🐶","🦁","🐯","🐰","🐻","🐨","🦝"];
|
|
|
|
export default function CreateRoomPage() {
|
|
const router = useRouter();
|
|
const [nickname, setNickname] = useState("");
|
|
const [avatar, setAvatar] = useState("🦊");
|
|
const [mode, setMode] = useState<"skribbl"|"gartic"|"color">("skribbl");
|
|
const [rounds, setRounds] = useState(4);
|
|
const [drawTime, setDrawTime] = useState(80);
|
|
const [canvasType, setCanvasType] = useState<"blank"|"template">("blank");
|
|
const [templateId, setTemplateId] = useState("mandala");
|
|
const [busy, setBusy] = useState(false);
|
|
const [err, setErr] = useState("");
|
|
|
|
const submit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setErr("");
|
|
if (!nickname.trim()) { setErr("Pick a nickname"); return; }
|
|
setBusy(true);
|
|
const settings: any = {};
|
|
if (mode === "skribbl") { settings.rounds = rounds; settings.drawTimeSec = drawTime; settings.language = "en"; }
|
|
if (mode === "color") { settings.canvasType = canvasType; settings.templateId = templateId; }
|
|
|
|
const socket = getSocket();
|
|
const finish = () => {
|
|
socket.emit("room:create", { nickname: nickname.trim(), avatar, mode, settings }, (resp: any) => {
|
|
if (!resp || !resp.ok) { setErr(resp?.error || "failed"); 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="/join" className="btn btn-ghost" style={{padding:"10px 18px",fontSize:14}}>Join Instead</Link>
|
|
</header>
|
|
|
|
<main className="max-w-[760px] mx-auto px-6 pb-16">
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-4xl font-bold">Create your room</h1>
|
|
<p className="font-medium mt-2" style={{color:"rgba(45,45,45,0.7)"}}>Pick a mode and customise the rules</p>
|
|
</div>
|
|
<form className="panel flex flex-col gap-6" onSubmit={submit}>
|
|
<div>
|
|
<label className="field-label">Your nickname</label>
|
|
<input data-testid="nickname-input" className="input-text" maxLength={20} value={nickname} onChange={(e)=>setNickname(e.target.value)} placeholder="Doodle Master"/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="field-label">Pick 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)"}}
|
|
data-testid={`avatar-${a}`}
|
|
>{a}</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="field-label">Choose game mode</label>
|
|
<div className="grid grid-cols-3 gap-3 max-[640px]:grid-cols-1">
|
|
{([
|
|
{id:"skribbl", label:"Skribbl Race", desc:"Draw & guess", color:"var(--coral)", testid:"mode-skribbl"},
|
|
{id:"gartic", label:"Gartic Phone", desc:"Pass it on", color:"var(--mint)", testid:"mode-gartic"},
|
|
{id:"color", label:"Color Together", desc:"Chill paint", color:"var(--lavender)", testid:"mode-color"},
|
|
] as const).map(m => (
|
|
<button type="button" key={m.id} onClick={()=>setMode(m.id as any)} data-testid={m.testid}
|
|
className="rounded-2xl border-[3px] p-4 text-left flex flex-col gap-1 transition-transform hover:-translate-y-1"
|
|
style={{
|
|
background: mode===m.id ? m.color : "var(--cream)",
|
|
color: mode===m.id ? "white" : "var(--dark)",
|
|
borderColor: mode===m.id ? "var(--dark)" : "rgba(45,45,45,0.2)",
|
|
boxShadow: mode===m.id ? "0 5px 0 rgba(0,0,0,0.18)" : "none",
|
|
}}>
|
|
<div className="font-bold text-lg">{m.label}</div>
|
|
<div className="text-sm opacity-90 font-medium">{m.desc}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{mode === "skribbl" && (
|
|
<div className="grid grid-cols-2 gap-4 max-[640px]:grid-cols-1">
|
|
<div>
|
|
<label className="field-label">Rounds</label>
|
|
<div className="flex gap-2">
|
|
{[2,4,8].map(r => (
|
|
<button type="button" key={r} onClick={()=>setRounds(r)}
|
|
data-testid={`rounds-${r}`}
|
|
className="flex-1 py-3 rounded-xl border-[3px] font-bold"
|
|
style={{background: rounds===r ? "var(--yellow)" : "var(--cream)", borderColor:"var(--dark)"}}>{r}</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="field-label">Draw time</label>
|
|
<div className="flex gap-2">
|
|
{[60,80,120].map(t => (
|
|
<button type="button" key={t} onClick={()=>setDrawTime(t)}
|
|
data-testid={`time-${t}`}
|
|
className="flex-1 py-3 rounded-xl border-[3px] font-bold"
|
|
style={{background: drawTime===t ? "var(--mint)" : "var(--cream)", borderColor:"var(--dark)"}}>{t}s</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{mode === "color" && (
|
|
<div className="grid grid-cols-2 gap-4 max-[640px]:grid-cols-1">
|
|
<div>
|
|
<label className="field-label">Canvas</label>
|
|
<div className="flex gap-2">
|
|
<button type="button" onClick={()=>setCanvasType("blank")} className="flex-1 py-3 rounded-xl border-[3px] font-bold" style={{background: canvasType==="blank"?"var(--yellow)":"var(--cream)",borderColor:"var(--dark)"}}>Blank</button>
|
|
<button type="button" onClick={()=>setCanvasType("template")} className="flex-1 py-3 rounded-xl border-[3px] font-bold" style={{background: canvasType==="template"?"var(--yellow)":"var(--cream)",borderColor:"var(--dark)"}}>Template</button>
|
|
</div>
|
|
</div>
|
|
{canvasType === "template" && (
|
|
<div>
|
|
<label className="field-label">Template</label>
|
|
<select className="input-text" value={templateId} onChange={(e)=>setTemplateId(e.target.value)}>
|
|
{["mandala","cat","tree","fish","butterfly","house","flower","sun"].map(t => <option key={t} value={t}>{t}</option>)}
|
|
</select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{err && <div className="text-coral font-bold text-sm">{err}</div>}
|
|
|
|
<button type="submit" disabled={busy} data-testid="create-submit" className="btn btn-primary self-end" style={{padding:"16px 32px", fontSize:18}}>
|
|
{busy ? "Creating..." : "Create Room"}
|
|
</button>
|
|
</form>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|