import { useCallback, useEffect, useRef, useState } from 'react' import { Loading, ErrorState } from '../../../components/PageState.jsx' import { api } from '../../../api/client.js' // Discord bot control panel (Phase 1). The bot token is write-only over this // API — stored encrypted in the DB, never returned — same convention as the // Google/Discord login-SSO secrets on the Authentication page. Saving pushes // the config straight to the bot process, so Enabled takes effect immediately // with no redeploy. function Toggle({ checked, onChange, label }) { return ( ) } const STATUS_COLOR = { connected: '#7fd0a4', connecting: '#e0b070', error: '#d98b84', disconnected: 'var(--muted)', } function StatusPanel({ config }) { const color = STATUS_COLOR[config.status] || 'var(--muted)' return (
{config.status || 'disconnected'}
{config.statusDetail && (

{config.statusDetail}

)} {config.lastConnectedAt && (

Last connected: {new Date(config.lastConnectedAt).toLocaleString()}

)}
) } export default function DiscordBotAdmin() { const [config, setConfig] = useState(null) const [error, setError] = useState('') const [guildId, setGuildId] = useState('') const [token, setToken] = useState('') const [enabled, setEnabled] = useState(false) const [busy, setBusy] = useState(false) const [msg, setMsg] = useState('') const [saveError, setSaveError] = useState('') const pollRef = useRef(null) // Only the very first load seeds the editable fields (guildId/enabled). // Every subsequent poll tick updates `config` (status/hasToken/etc.) so the // live-status panel stays fresh, but must NOT touch the form state — doing // so would silently overwrite whatever the admin is mid-typing/toggling // before they get a chance to hit Save. const initializedRef = useRef(false) const load = useCallback(async () => { try { const c = await api.admin.getDiscordBotConfig() setConfig(c) if (!initializedRef.current) { setGuildId(c.guildId || '') setEnabled(c.enabled) initializedRef.current = true } } catch { setError('Could not load Discord bot config.') } }, []) useEffect(() => { load() pollRef.current = setInterval(load, 5000) return () => clearInterval(pollRef.current) }, [load]) async function save() { setBusy(true) setMsg('') setSaveError('') try { const body = { guildId, enabled } if (token) body.token = token // only send a new token when entered const saved = await api.admin.saveDiscordBotConfig(body) setConfig(saved) setToken('') setMsg('Saved.') } catch (err) { setSaveError(err.message || 'Could not save.') } finally { setBusy(false) } } if (error) return if (!config) return return (

Discord Bot

{msg && {msg}} {saveError && {saveError}}
) }