119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
"use client";
|
||
import { useI18n } from "@/components/I18nProvider";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { useRouter } from "next/navigation";
|
||
import { api } from "./StartGame";
|
||
import { GuessMap } from "./GuessMap";
|
||
import type { SessionView } from "./Game";
|
||
export function Results({ id }: { id: string }) {
|
||
const { t, formatNumber } = useI18n();
|
||
const [session, setSession] = useState<SessionView | null>(null),
|
||
[selected, setSelected] = useState(0),
|
||
[error, setError] = useState("");
|
||
const router = useRouter();
|
||
useEffect(() => {
|
||
let disposed = false;
|
||
setSession(null);
|
||
setSelected(0);
|
||
setError("");
|
||
api<SessionView>("sessions/" + id)
|
||
.then((s) => {
|
||
if (disposed) return;
|
||
if (s.round < 5) router.replace("/play/" + id);
|
||
else setSession(s);
|
||
})
|
||
.catch((e) => {
|
||
if (!disposed) setError(e.message);
|
||
});
|
||
return () => {
|
||
disposed = true;
|
||
};
|
||
}, [id, router]);
|
||
if (error) return <p role="alert">{t(error)}</p>;
|
||
if (!session)
|
||
return <p className="muted">{t("Unpacking your expedition…")}</p>;
|
||
const total = session.rounds.reduce((n, r) => n + (r.result?.score || 0), 0);
|
||
const current = session.rounds[selected].result;
|
||
return (
|
||
<>
|
||
<span className="eyebrow">
|
||
{t("EXPEDITION COMPLETE ·")}{" "}
|
||
{t(session.difficulty[0].toUpperCase() + session.difficulty.slice(1))}
|
||
</span>
|
||
<h1>{t("Every guess is a story.")}</h1>
|
||
<p className="lead">
|
||
{session.name} {t("· Here’s where your instincts took you.")}
|
||
</p>
|
||
<div className="results-total">
|
||
{formatNumber(total)}
|
||
<small> / 25,000</small>
|
||
</div>
|
||
<div className="results-grid">
|
||
<div className="result-map">
|
||
<GuessMap
|
||
bounds={session.bounds}
|
||
image={session.map}
|
||
pin={current?.guess}
|
||
actual={current?.actual}
|
||
disabled
|
||
/>
|
||
</div>
|
||
<div className="result-summary">
|
||
<h2>{t("Your five-round journey")}</h2>
|
||
<div className="tabs">
|
||
{session.rounds.map((r, i) => (
|
||
<button
|
||
key={i}
|
||
className={i === selected ? "active" : ""}
|
||
aria-pressed={i === selected}
|
||
onClick={() => setSelected(i)}
|
||
>
|
||
{t("Round")} {i + 1}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<table className="history-table">
|
||
<thead>
|
||
<tr>
|
||
<th>{t("ROUND")}</th>
|
||
<th>{t("DISTANCE")}</th>
|
||
<th>{t("POINTS")}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{session.rounds.map((r, i) => (
|
||
<tr key={i}>
|
||
<td>{i + 1}</td>
|
||
<td>
|
||
{r.result?.timedOut
|
||
? t("Timed out")
|
||
: t("{count} blocks", {
|
||
count: formatNumber(
|
||
Math.round(r.result?.distance || 0),
|
||
),
|
||
})}
|
||
</td>
|
||
<td>{r.result ? formatNumber(r.result.score) : "—"}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
<div className="inline-actions">
|
||
<Link className="button primary" href="/servers">
|
||
{t("Another adventure →")}
|
||
</Link>
|
||
<Link className="button" href="/dashboard">
|
||
{t("My maps")}
|
||
</Link>
|
||
<Link className="text-link" href="/leaderboard">
|
||
{t("View leaderboard →")}
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|