Improve MineGeo interface and functionality
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
import { useI18n } from "@/components/I18nProvider";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { PixelTrophy, PlayerAvatar } from "./PixelArt";
|
||||
import {
|
||||
formatScore,
|
||||
type LeaderboardData,
|
||||
type LeaderboardEntry,
|
||||
} from "@/shared/leaderboard";
|
||||
@@ -14,6 +15,7 @@ export function LeaderboardEmpty({
|
||||
unavailable?: boolean;
|
||||
search?: boolean;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div
|
||||
className="leaderboard-empty"
|
||||
@@ -22,24 +24,24 @@ export function LeaderboardEmpty({
|
||||
<PixelTrophy />
|
||||
<h2>
|
||||
{unavailable
|
||||
? "The scoreboard is taking a break."
|
||||
? t("The scoreboard is taking a break.")
|
||||
: search
|
||||
? "No explorers found."
|
||||
: "The first place is still open."}
|
||||
? t("No explorers found.")
|
||||
: t("The first place is still open.")}
|
||||
</h2>
|
||||
<p>
|
||||
{unavailable
|
||||
? "We couldn’t load the scores. Try again in a moment."
|
||||
? t("We couldn’t load the scores. Try again in a moment.")
|
||||
: search
|
||||
? "Try another player name or clear your search."
|
||||
: "Log in and finish five rounds to put your name on the board."}
|
||||
? t("Try another player name or clear your search.")
|
||||
: t("Log in and finish five rounds to put your name on the board.")}
|
||||
</p>
|
||||
{!search && (
|
||||
<Link
|
||||
href={unavailable ? "/leaderboard" : "/servers"}
|
||||
className="button primary"
|
||||
>
|
||||
{unavailable ? "Reload scores" : "Play an expedition"}{" "}
|
||||
{unavailable ? t("Reload scores") : t("Play an expedition")}{" "}
|
||||
<ArrowRight size={16} />
|
||||
</Link>
|
||||
)}
|
||||
@@ -56,31 +58,33 @@ export function LeaderboardTable({
|
||||
compact?: boolean;
|
||||
playerId?: string;
|
||||
}) {
|
||||
const { t, formatNumber } = useI18n();
|
||||
return (
|
||||
<div className={`leaderboard-table-wrap${compact ? " compact" : ""}`}>
|
||||
<table className="leaderboard-table">
|
||||
<caption className="sr-only">
|
||||
Players ranked by best five-round score. Ties are broken by average
|
||||
score, then games played.
|
||||
{t(
|
||||
"Players ranked by best five-round score. Ties are broken by average score, then games played.",
|
||||
)}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="rank-column">
|
||||
Rank
|
||||
{t("Rank")}
|
||||
</th>
|
||||
<th scope="col">Explorer</th>
|
||||
<th scope="col">{t("Explorer")}</th>
|
||||
{!compact && (
|
||||
<th scope="col" className="games-column">
|
||||
Games
|
||||
{t("Games")}
|
||||
</th>
|
||||
)}
|
||||
{!compact && (
|
||||
<th scope="col" className="average-column">
|
||||
Average
|
||||
{t("Average")}
|
||||
</th>
|
||||
)}
|
||||
<th scope="col" className="score-column">
|
||||
Best score
|
||||
{t("Best score")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -103,24 +107,24 @@ export function LeaderboardTable({
|
||||
<span className="player-name">
|
||||
{entry.display_name}
|
||||
{entry.player_id === playerId && (
|
||||
<span className="you-label">You</span>
|
||||
<span className="you-label">{t("You")}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
{!compact && (
|
||||
<td className="games-column">
|
||||
{formatScore(entry.games_played)}
|
||||
{formatNumber(entry.games_played)}
|
||||
</td>
|
||||
)}
|
||||
{!compact && (
|
||||
<td className="average-column">
|
||||
{formatScore(entry.average_score)}
|
||||
{formatNumber(entry.average_score)}
|
||||
</td>
|
||||
)}
|
||||
<td className="score-column">
|
||||
<strong>{formatScore(entry.best_score)}</strong>
|
||||
<span className="score-unit"> pts</span>
|
||||
<strong>{formatNumber(entry.best_score)}</strong>
|
||||
<span className="score-unit"> {t("pts")}</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -131,8 +135,9 @@ export function LeaderboardTable({
|
||||
}
|
||||
|
||||
export function WinnersPodium({ leaders }: { leaders: LeaderboardEntry[] }) {
|
||||
const { t, formatNumber } = useI18n();
|
||||
return (
|
||||
<section className="winners-podium" aria-label="Top three explorers">
|
||||
<section className="winners-podium" aria-label={t("Top three explorers")}>
|
||||
{leaders.map((entry, index) => (
|
||||
<article
|
||||
className={`podium-place podium-${index + 1}`}
|
||||
@@ -140,12 +145,16 @@ export function WinnersPodium({ leaders }: { leaders: LeaderboardEntry[] }) {
|
||||
>
|
||||
<div className="podium-player">
|
||||
<span className="podium-label">
|
||||
{index === 0 ? "TOP EXPLORER" : `PLACE 0${index + 1}`}
|
||||
{index === 0
|
||||
? t("TOP EXPLORER")
|
||||
: t("Place {place}", {
|
||||
place: String(index + 1).padStart(2, "0"),
|
||||
})}
|
||||
</span>
|
||||
<PlayerAvatar id={entry.player_id} />
|
||||
<h2>{entry.display_name}</h2>
|
||||
<p>
|
||||
{formatScore(entry.best_score)} <span>/ 25,000</span>
|
||||
{formatNumber(entry.best_score)} <span>/ 25,000</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="podium-block" aria-hidden="true">
|
||||
@@ -159,6 +168,7 @@ export function WinnersPodium({ leaders }: { leaders: LeaderboardEntry[] }) {
|
||||
}
|
||||
|
||||
export function LeaderboardPreview({ data }: { data: LeaderboardData | null }) {
|
||||
const { t, formatNumber } = useI18n();
|
||||
return (
|
||||
<section
|
||||
className="section home-leaderboard"
|
||||
@@ -167,24 +177,24 @@ export function LeaderboardPreview({ data }: { data: LeaderboardData | null }) {
|
||||
<div className="leaderboard-preview-copy">
|
||||
<div className="leaderboard-kicker">
|
||||
<PixelTrophy />
|
||||
<span className="eyebrow">THE EXPLORERS’ BOARD</span>
|
||||
<span className="eyebrow">{t("THE EXPLORERS’ BOARD")}</span>
|
||||
</div>
|
||||
<h2 id="home-leaderboard-heading">
|
||||
Good eye.
|
||||
<br /> <span>Great guess.</span>
|
||||
{t("Good eye.")}
|
||||
<br /> <span>{t("Great guess.")}</span>
|
||||
</h2>
|
||||
<p>
|
||||
Five rounds to find your way.
|
||||
<br /> One score to make your mark.
|
||||
{t("Five rounds to find your way.")}
|
||||
<br /> {t("One score to make your mark.")}
|
||||
</p>
|
||||
<Link className="text-link" href="/leaderboard">
|
||||
See the full leaderboard <ArrowRight size={16} />
|
||||
{t("See the full leaderboard")} <ArrowRight size={16} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="leaderboard-preview-board">
|
||||
<div className="board-heading">
|
||||
<span>Top explorers</span>
|
||||
<span>All difficulties · All time</span>
|
||||
<span>{t("Top explorers")}</span>
|
||||
<span>{t("All difficulties · All time")}</span>
|
||||
</div>
|
||||
{data?.entries.length ? (
|
||||
<LeaderboardTable entries={data.entries} compact />
|
||||
@@ -192,10 +202,12 @@ export function LeaderboardPreview({ data }: { data: LeaderboardData | null }) {
|
||||
<LeaderboardEmpty unavailable={!data} />
|
||||
)}
|
||||
<div className="board-footnote">
|
||||
Best expedition score ·{" "}
|
||||
{t("Best expedition score ·")}{" "}
|
||||
{data
|
||||
? `${formatScore(data.total_players)} ranked ${data.total_players === 1 ? "explorer" : "explorers"}`
|
||||
: "Scores temporarily unavailable"}
|
||||
? t("Ranked explorers: {count}", {
|
||||
count: formatNumber(data.total_players),
|
||||
})
|
||||
: t("Scores temporarily unavailable")}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user