1
0
Fork 0
polaris-task-force/src/components/frontend/minigames/radio/RadioArena.tsx
Z8MB1E 911fc31354 feat(minigames): hide UGC entry buttons until custom content unlocks
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-09 00:03:29 -04:00

101 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useCallback, useEffect, useState } from "react";
import type { RadioStats } from "@/app/(frontend)/minigames/radio/actions";
import {
isRadioUnlocked,
SPRINT_ACCURACY_REQUIREMENT,
WATCH_ACCURACY_REQUIREMENT,
} from "@/lib/minigames/custom/radio";
import { useRealtimePresence } from "@/hooks/useRealtimePresence";
import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice";
import { UgcLinkButton } from "@/components/frontend/minigames/UgcLinkButton";
import { RadioLeaderboard } from "./RadioLeaderboard";
import { RadioTraffic } from "./RadioTraffic";
export function RadioArena({
initialStats,
currentUserId,
}: {
initialStats: RadioStats;
currentUserId: number | string;
}) {
const [refreshKey, setRefreshKey] = useState(0);
const [playing, setPlaying] = useState(false);
const [mode, setMode] = useState<"sprint" | "watch">("sprint");
const [modeHydrated, setModeHydrated] = useState(false);
const activeOperators = useRealtimePresence("radio", playing, true);
const onPhaseChange = useCallback((phase: "ready" | "playing" | "over") => {
setPlaying(phase === "playing");
}, []);
const customUnlocked = isRadioUnlocked({
sprintBestAccuracy: initialStats.sprintBestAccuracy,
watchBestAccuracy: initialStats.watchBestAccuracy,
});
const sprintPercent = Math.round(initialStats.sprintBestAccuracy * 100);
const watchPercent = Math.round(initialStats.watchBestAccuracy * 100);
useEffect(() => {
const savedMode = window.localStorage.getItem("radio-traffic-mode");
if (savedMode === "sprint" || savedMode === "watch") setMode(savedMode);
setModeHydrated(true);
}, []);
useEffect(() => {
if (modeHydrated) window.localStorage.setItem("radio-traffic-mode", mode);
}, [mode, modeHydrated]);
return (
<div className="flex flex-col items-center gap-6">
{customUnlocked && (
<UgcLinkButton
href="/minigames/radio/custom"
title="UGC Radio Tests"
subtitle="Play and create custom radio tests you can share with other unit members! Runs are unranked, so it is a great place to practice before an official transmission."
unlocked={customUnlocked}
className="max-w-[520px]"
/>
)}
{!customUnlocked && (
<UnlockNotice
title="custom radio tests"
requirement={`your best accuracy reaches ${Math.round(SPRINT_ACCURACY_REQUIREMENT * 100)}% in sprint and ${Math.round(WATCH_ACCURACY_REQUIREMENT * 100)}% in watch`}
progress={`Sprint ${sprintPercent}%, Watch ${watchPercent}%`}
/>
)}
<RadioTraffic
initialStats={initialStats}
mode={mode}
onModeChangeAction={setMode}
onGameRecordedAction={() => setRefreshKey((key) => key + 1)}
onPhaseChangeAction={onPhaseChange}
/>
<div className="w-full max-w-[520px]">
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">How scoring works</h2>
<ul className="mt-1.5 flex flex-col gap-0.5 text-xs leading-relaxed text-muted-foreground/80">
<li>
Accuracy tracks every keystroke; each wrong character or backspace counts against it. Each message scores accuracy × speed multiplier, up to ~120 points for a fast perfect message.
</li>
<li>
<span className="font-medium text-foreground/80">Sprint:</span> clear exactly 10 messages as fast as possible; maximum score is 1200.
</li>
<li>
<span className="font-medium text-foreground/80">Watch:</span> relay as many messages as possible in 60 seconds; score is unbounded in practice.
</li>
<li>
<span className="font-medium text-foreground/80">XP:</span> earn 0.1 per perfectly-typed message.
</li>
<li>The lifetime leaderboard ranks Top Score, Top Accuracy, Fastest Sprint, and Most Messages.</li>
</ul>
</div>
<RadioLeaderboard
refreshKey={refreshKey}
mode={mode}
modeHydrated={modeHydrated}
currentUserId={currentUserId}
activeOperators={activeOperators}
/>
</div>
);
}