"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { c2api } from "@/lib/c2api"; import { useAuth } from "@/components/AuthProvider"; interface TokenRecord { token_id: string; name: string; token: string; // masked server-side in_use: boolean; assigned_node_id: string | null; assigned_at: string | null; } export default function TokensPage() { const { isAdmin, loading: authLoading } = useAuth(); const router = useRouter(); const [tokens, setTokens] = useState([]); const [loading, setLoading] = useState(true); const [showAdd, setShowAdd] = useState(false); const [name, setName] = useState(""); const [token, setToken] = useState(""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!authLoading && !isAdmin) router.replace("/dashboard"); }, [authLoading, isAdmin, router]); const refresh = useCallback(async () => { try { const data = await c2api.getTokens(); setTokens(data as TokenRecord[]); } finally { setLoading(false); } }, []); useEffect(() => { refresh(); }, [refresh]); async function handleAdd(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); try { await c2api.addToken({ name, token }); setName(""); setToken(""); setShowAdd(false); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to add token."); } finally { setSaving(false); } } async function handleDelete(id: string) { if (!confirm("Remove this token from the pool?")) return; try { await c2api.deleteToken(id); await refresh(); } catch (err) { alert(err instanceof Error ? err.message : "Delete failed."); } } if (authLoading || !isAdmin) return null; return (

Bot Token Pool

Discord bot tokens assigned to nodes when they join a voice channel.

{showAdd && (

Add Token

setName(e.target.value)} required className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" placeholder="DRB Bot 1" />
setToken(e.target.value)} required type="password" className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm font-mono focus:outline-none focus:border-indigo-500" placeholder="MTxxxxxxxxxx.Gxxxxx.xxxxxxxxxx" />
{error &&

{error}

}
)} {loading ? (

Loading…

) : tokens.length === 0 ? (

No tokens in the pool. Add one to enable Discord voice streaming.

) : (
{tokens.map((t) => ( ))}
Label Token Status Node
{t.name} {t.token} {t.in_use ? ( In use ) : ( Free )} {t.assigned_node_id ?? "—"}
)}
); }