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 (