"use client"; // Settings → Organization → "Radio bot per system" (server-26#225, owner // ruling Q2). The org owner picks which of the org's OWN radio bots plays each // of the org's systems in Discord. C2 tries that bot first for every new // radio channel of the system (discord_streams.start_stream); when it can't // take the stream (busy in that server, no permission, not invited) another // free bot is used. Shared systems and DRB pool bots stay platform-admin only, // so they are not offered here. Backend: GET /tokens/org/preferred (list), // PUT /tokens/{token_id}/prefer/{system_id} (set; "_none" clears). import { useCallback, useEffect, useState } from "react"; import { c2api } from "@/lib/c2api"; import { parseC2Error, type OrgPreferredBots } from "@/lib/discordPicker"; import { Card, CardHeader } from "@/components/ui/Card"; const SELECT = "bg-page border border-line rounded-lg px-2.5 py-1.5 text-ink text-sm focus:outline-none focus:border-accent disabled:opacity-50 max-w-full"; export function PreferredBotsCard({ canEdit }: { canEdit: boolean }) { const [data, setData] = useState(null); const [loadError, setLoadError] = useState(null); const [saving, setSaving] = useState(null); const [rowError, setRowError] = useState<{ id: string; message: string } | null>(null); // A platform admin with no org of their own gets a 403 here: nothing to show // (shared systems are set on /systems), so the card hides instead of erroring. const [hidden, setHidden] = useState(false); const load = useCallback(async () => { try { setData(await c2api.getOrgPreferredBots()); setLoadError(null); } catch (err) { const e = parseC2Error(err); if (e.status === 403) setHidden(true); // A server error's raw body ("C2 API error 500: ...") means nothing to an owner. else setLoadError(e.status !== null && e.status >= 500 ? "DRB couldn't load them just now." : e.message); } }, []); useEffect(() => { if (canEdit) load(); }, [canEdit, load]); async function choose(systemId: string, tokenId: string, currentId: string | null) { if (tokenId === (currentId ?? "")) return; setSaving(systemId); setRowError(null); try { if (tokenId) await c2api.setPreferredToken(tokenId, systemId); else if (currentId) await c2api.setPreferredToken(currentId, "_none"); await load(); } catch (err) { setRowError({ id: systemId, message: parseC2Error(err).message }); } finally { setSaving(null); } } if (!canEdit || hidden) return null; const bots = data?.bots ?? []; const botsById = new Map(bots.map((b) => [b.token_id, b])); return ( {loadError ? (

Couldn't load radio bots: {loadError}{" "}

) : !data ? (

Loading…

) : data.systems.length === 0 ? (

Your organization has no radio systems yet.

) : bots.length === 0 ? (

Your organization has no radio bots of its own, so DRB picks a free DRB radio bot for each channel. Adding your own bot isn't self-serve yet: DRB support adds it for you, so ask whoever set up your DRB account.

) : (
{data.systems.map((s) => { const current = s.preferred_token_id ? botsById.get(s.preferred_token_id) : undefined; const err = rowError?.id === s.system_id ? rowError.message : null; const usedElsewhere = (tokenId: string) => { const other = botsById.get(tokenId)?.preferred_for_system_id; return other && other !== s.system_id ? data.systems.find((x) => x.system_id === other)?.name ?? "another system" : null; }; return (
{s.name}
{current ? `${current.name} is tried first.` : s.preferred_is_platform_bot ? "A DRB radio bot chosen by DRB support is tried first. Pick one of yours to replace it." : "No preference — any free radio bot."} {current?.valid === false && " Discord rejected this bot's token — DRB support has to replace it."} {current && current.server_count === 0 && " It isn't in any Discord server yet — invite it first."}
{current?.invite_url && ( Invite {current.name} to a Discord server )} {err &&
{err}
}
); })}
)}
); }