"use client"; // The radio-bot pool (bot_tokens). Same table as Settings › Discord › Radio // bots: validity, server membership, invite links, lease, and the // POST /tokens/{id}/refresh re-check (server-26#213 item 2). Platform admin // only: every /tokens route is require_admin_token since server-26#204 item 6. import { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { c2api } from "@/lib/c2api"; import { useAuth } from "@/components/AuthProvider"; import { type DiscordAdminOverview, type RadioBot, parseC2Error } from "@/lib/discord"; import { PageHeader } from "@/components/ui/PageHeader"; import { LinkButton } from "@/components/ui/Button"; import { ErrorBanner } from "@/components/ui/EmptyState"; import { SkeletonCard } from "@/components/ui/Skeleton"; import { RadioBotsPanel } from "@/components/discord/RadioBotsPanel"; export default function TokensPage() { const { isAdmin, loading: authLoading } = useAuth(); const router = useRouter(); const [bots, setBots] = useState(null); const [overview, setOverview] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!authLoading && !isAdmin) router.replace("/network"); }, [authLoading, isAdmin, router]); const refresh = useCallback(async () => { try { const t = await c2api.getTokens(); setBots(t as RadioBot[]); setError(null); } catch (err) { setError(parseC2Error(err).message); setBots((b) => b ?? []); } // Server names for the membership column; the table still works without them. try { setOverview(await c2api.getDiscordAdmin()); } catch { /* membership falls back to raw server ids */ } }, []); useEffect(() => { if (isAdmin) refresh(); }, [isAdmin, refresh]); if (authLoading || !isAdmin) return null; return (
Discord settings} /> {error && } {bots === null ? : }
); }