148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
"use client";
|
|
import { useI18n } from "@/components/I18nProvider";
|
|
|
|
import { useRef, useState } from "react";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { ArrowRight, Timer, ZoomIn, Users } from "lucide-react";
|
|
import { difficulties, type Difficulty } from "@/shared/protocol";
|
|
import { api } from "@/lib/client-api";
|
|
export { api } from "@/lib/client-api";
|
|
export function StartGame({
|
|
datasetId,
|
|
hostName,
|
|
}: {
|
|
datasetId: string;
|
|
hostName: string | null;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const [difficulty, setDifficulty] = useState<Difficulty>("normal"),
|
|
[mode, setMode] = useState<"solo" | "multiplayer">("solo"),
|
|
[playerLimit, setPlayerLimit] = useState(4),
|
|
[loading, setLoading] = useState(false),
|
|
[error, setError] = useState("");
|
|
const router = useRouter();
|
|
const starting = useRef(false);
|
|
async function start() {
|
|
if (starting.current) return;
|
|
starting.current = true;
|
|
setLoading(true);
|
|
setError("");
|
|
try {
|
|
const session = await api<{ id: string }>(
|
|
mode === "solo" ? "sessions" : "rooms",
|
|
{
|
|
datasetId,
|
|
difficulty,
|
|
...(mode === "multiplayer" ? { playerLimit } : {}),
|
|
},
|
|
);
|
|
router.push((mode === "solo" ? "/play/" : "/multiplayer/") + session.id);
|
|
} catch (e) {
|
|
starting.current = false;
|
|
setError((e as Error).message);
|
|
setLoading(false);
|
|
}
|
|
}
|
|
return (
|
|
<div className="start-panel">
|
|
<span className="eyebrow">{t("YOUR EXPEDITION")}</span>
|
|
<h2>{t("Choose your challenge.")}</h2>
|
|
<div className="tabs" aria-label={t("Game mode")}>
|
|
<button
|
|
aria-pressed={mode === "solo"}
|
|
className={mode === "solo" ? "active" : ""}
|
|
disabled={loading}
|
|
onClick={() => setMode("solo")}
|
|
>
|
|
{t("Solo")}
|
|
</button>
|
|
<button
|
|
aria-pressed={mode === "multiplayer"}
|
|
className={mode === "multiplayer" ? "active" : ""}
|
|
disabled={loading}
|
|
onClick={() => setMode("multiplayer")}
|
|
>
|
|
<Users size={15} /> {t("Multiplayer")}
|
|
</button>
|
|
</div>
|
|
<div className="difficulty-options">
|
|
{Object.entries(difficulties).map(([key, value]) => (
|
|
<button
|
|
key={key}
|
|
aria-pressed={difficulty === key}
|
|
className={difficulty === key ? "selected" : ""}
|
|
disabled={loading}
|
|
onClick={() => setDifficulty(key as Difficulty)}
|
|
>
|
|
<strong>{t(value.label)}</strong>
|
|
<span>
|
|
<Timer size={13} />
|
|
{value.seconds}
|
|
{t("s per round")}
|
|
</span>
|
|
<span>
|
|
<ZoomIn size={13} />
|
|
{value.zoom ? t("Zoom allowed") : t("No zoom")}
|
|
</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<p className="muted">
|
|
{t("5 rounds · 25,000 possible points · No movement")}
|
|
</p>
|
|
{mode === "multiplayer" && (
|
|
<div className="multiplayer-settings">
|
|
{hostName ? (
|
|
<p>
|
|
{t("Host display name")}: <strong>{hostName}</strong>
|
|
</p>
|
|
) : (
|
|
<p className="muted">
|
|
{t("Log in to create a multiplayer room")}{" "}
|
|
<Link href="/log-in?next=/servers">{t("Log in")}</Link>
|
|
</p>
|
|
)}
|
|
<label htmlFor="room-player-limit">
|
|
{t("Player limit (including you)")}
|
|
</label>
|
|
<select
|
|
id="room-player-limit"
|
|
value={playerLimit}
|
|
onChange={(event) => setPlayerLimit(Number(event.target.value))}
|
|
disabled={loading}
|
|
>
|
|
{Array.from({ length: 15 }, (_, i) => i + 2).map((count) => (
|
|
<option key={count} value={count}>
|
|
{count}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<p className="muted">
|
|
{t(
|
|
"Create a room, share the invite link, and start when your friends arrive.",
|
|
)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
<button
|
|
className="button primary full"
|
|
onClick={start}
|
|
disabled={loading || (mode === "multiplayer" && !hostName)}
|
|
>
|
|
{loading
|
|
? t("Preparing your world…")
|
|
: mode === "multiplayer"
|
|
? t("Create multiplayer room")
|
|
: t("Start exploring")}
|
|
<ArrowRight size={17} />
|
|
</button>
|
|
{error && (
|
|
<p role="alert" className="error-message">
|
|
{t(error)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|