import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { useBotStore, type Bot } from '../stores/bot.ts'; import { useServerStore } from '../stores/server.ts'; import { useChannelStore } from '../stores/channel.ts'; // ponytail: config fields per bot type, add new types here const BOT_TYPE_CONFIGS: Record = { steamfree: { label: 'Steam Free Games', fields: [ { key: 'channel_id', label: 'CHANNEL', type: 'channel', placeholder: 'select channel' }, { key: 'poll_minutes', label: 'POLL INTERVAL (min)', type: 'number', placeholder: '30' }, ], }, }; export function BotManager() { const bots = useBotStore((s) => s.bots); const loading = useBotStore((s) => s.loading); const error = useBotStore((s) => s.error); const fetchBots = useBotStore((s) => s.fetchBots); const createBot = useBotStore((s) => s.createBot); const updateBot = useBotStore((s) => s.updateBot); const deleteBot = useBotStore((s) => s.deleteBot); const addToServer = useBotStore((s) => s.addToServer); const regenerateToken = useBotStore((s) => s.regenerateToken); const servers = useServerStore((s) => s.servers); const fetchServers = useServerStore((s) => s.fetchServers); const channelsByServer = useChannelStore((s) => s.channelsByServer); const fetchChannels = useChannelStore((s) => s.fetchChannels); const [showCreate, setShowCreate] = useState(false); const [createName, setCreateName] = useState(''); const [createDesc, setCreateDesc] = useState(''); const [createType, setCreateType] = useState(''); const [createConfig, setCreateConfig] = useState>({}); const [channelServerId, setChannelServerId] = useState(''); const [editingId, setEditingId] = useState(null); const [editName, setEditName] = useState(''); const [editDesc, setEditDesc] = useState(''); const [tokenDisplay, setTokenDisplay] = useState<{ botId: string; token: string } | null>(null); const [addToServerBotId, setAddToServerBotId] = useState(null); const [selectedServer, setSelectedServer] = useState(''); const [copied, setCopied] = useState(false); useEffect(() => { fetchBots(); fetchServers(); }, [fetchBots, fetchServers]); const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); if (!createName.trim()) return; try { // Build config object with proper types const cfg: Record = {}; for (const [k, v] of Object.entries(createConfig)) { if (v === '') continue; const fieldDef = BOT_TYPE_CONFIGS[createType]?.fields.find((f) => f.key === k); cfg[k] = fieldDef?.type === 'number' ? parseInt(v, 10) : v; } const result = await createBot(createName.trim(), createDesc.trim(), createType || undefined, Object.keys(cfg).length ? cfg : undefined); setTokenDisplay({ botId: result.id, token: result.token }); setCreateName(''); setCreateDesc(''); setCreateType(''); setCreateConfig({}); setChannelServerId(''); setShowCreate(false); } catch { // error handled in store } }; const handleUpdate = async (e: React.FormEvent) => { e.preventDefault(); if (!editingId) return; try { await updateBot(editingId, { name: editName.trim(), description: editDesc.trim() }); setEditingId(null); } catch { // error handled in store } }; const handleDelete = async (id: string, name: string) => { if (!window.confirm(`Delete bot "${name}"? This cannot be undone.`)) return; try { await deleteBot(id); } catch { // error handled in store } }; const handleRegenerate = async (id: string) => { if (!window.confirm('Regenerate token? The old token will be invalidated.')) return; try { const result = await regenerateToken(id); setTokenDisplay({ botId: id, token: result.token }); } catch { // error handled in store } }; const handleAddToServer = async () => { if (!addToServerBotId || !selectedServer) return; try { await addToServer(addToServerBotId, selectedServer); setAddToServerBotId(null); setSelectedServer(''); } catch { // error handled in store } }; const handleCopyToken = () => { if (tokenDisplay) { navigator.clipboard.writeText(tokenDisplay.token); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const startEdit = (bot: Bot) => { setEditingId(bot.id); setEditName(bot.name); setEditDesc(bot.description); }; const typeConfig = createType ? BOT_TYPE_CONFIGS[createType] : null; return (
            {'┌──────────────────────────────────┐\n'}
            {'│      === BOT MANAGER ===         │\n'}
            {'└──────────────────────────────────┘'}
          
{error && (

ERR: {error}

)} {/* Token display overlay */} {tokenDisplay && (

{'>'} TOKEN GENERATED — copy it now, it won't be shown again:

{tokenDisplay.token}
)} {/* Create bot form */}
{!showCreate ? ( ) : (

{'>'} NEW BOT

setCreateName(e.target.value.slice(0, 32))} maxLength={32} className="terminal-input w-full" placeholder="my-cool-bot" autoFocus />
setCreateDesc(e.target.value.slice(0, 256))} maxLength={256} className="terminal-input w-full" placeholder="what does this bot do?" />
{typeConfig && typeConfig.fields.map((field) => (
{field.type === 'channel' ? (
) : ( setCreateConfig((prev) => ({ ...prev, [field.key]: e.target.value }))} placeholder={field.placeholder} className="terminal-input w-full" /> )}
))}
)}
{/* Bot list */} {loading && bots.length === 0 && (

[loading bots...]

)} {!loading && bots.length === 0 && (

[no bots created yet]

)}
{bots.map((bot) => (
{editingId === bot.id ? (
setEditName(e.target.value.slice(0, 32))} maxLength={32} className="terminal-input w-full" />
setEditDesc(e.target.value.slice(0, 256))} maxLength={256} className="terminal-input w-full" />
) : ( <>

{bot.avatar && ( )} [{bot.name}] {bot.bot_type && ( ({BOT_TYPE_CONFIGS[bot.bot_type]?.label || bot.bot_type}) )}

{bot.description && (

{bot.description}

)}
ID:{bot.id.slice(0, 8)}
[COMMANDS]
)}
))}
{/* Add to server modal */} {addToServerBotId && (

{'>'} ADD BOT TO SERVER

)} {/* Footer */}
{'<'} [BACK TO STORE]
); }