'use client'; import { useEffect, useState } from 'react'; import { Puzzle, ArrowLeft, Loader2, Eye, EyeOff } from 'lucide-react'; import { apiFetch } from '@/lib/browser-navigation'; interface ConfigField { type: 'string' | 'secret' | 'boolean' | 'number' | 'select'; label: string; description?: string; required?: boolean; default?: unknown; placeholder?: string; options?: { label: string; value: string }[]; } interface PluginConfig { [key: string]: unknown; } interface PluginInfo { id: string; name: string; description: string; version: string; author: string; type: string; permissions: string[]; enabled: boolean; configSchema?: Record; } interface Props { pluginId: string; onBack: () => void; } export function PluginConfigPanel({ pluginId, onBack }: Props) { const [plugin, setPlugin] = useState(null); const [config, setConfig] = useState({}); const [formValues, setFormValues] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [revealSecrets, setRevealSecrets] = useState>({}); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); useEffect(() => { let cancelled = false; async function fetchData() { setLoading(true); try { const [pluginsRes, configRes] = await Promise.all([ apiFetch('/api/admin/plugins'), apiFetch(`/api/admin/plugins/${encodeURIComponent(pluginId)}/config`), ]); if (cancelled) return; if (pluginsRes.ok) { const plugins: PluginInfo[] = await pluginsRes.json(); setPlugin(plugins.find(p => p.id === pluginId) || null); } if (configRes.ok) { setConfig(await configRes.json()); } } finally { if (!cancelled) setLoading(false); } } fetchData(); return () => { cancelled = true; }; }, [pluginId]); useEffect(() => { if (!plugin?.configSchema) return; const initial: Record = {}; for (const [key, field] of Object.entries(plugin.configSchema)) { const stored = config[key]; if (stored !== undefined && stored !== null) { initial[key] = String(stored); } else if (field.default !== undefined) { initial[key] = String(field.default); } else { initial[key] = ''; } } setFormValues(initial); }, [plugin, config]); async function handleSaveAll() { if (!plugin?.configSchema) return; setSaving(true); setMessage(null); for (const [key, field] of Object.entries(plugin.configSchema)) { if (field.required && !formValues[key]?.trim()) { setMessage({ type: 'error', text: `"${field.label}" is required` }); setSaving(false); return; } } try { let hasError = false; for (const [key, field] of Object.entries(plugin.configSchema)) { const newVal = formValues[key] ?? ''; const oldVal = config[key] !== undefined ? String(config[key]) : ''; if (newVal === oldVal) continue; if (field.type === 'secret' && !newVal && config[key]) continue; let value: unknown = newVal; if (field.type === 'boolean') value = newVal === 'true'; else if (field.type === 'number') value = Number(newVal); if (!newVal && !field.required) { const res = await apiFetch(`/api/admin/plugins/${encodeURIComponent(pluginId)}/config`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key }), }); if (res.ok) { setConfig(prev => { const next = { ...prev }; delete next[key]; return next; }); } else { hasError = true; } continue; } const res = await apiFetch(`/api/admin/plugins/${encodeURIComponent(pluginId)}/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, value }), }); if (res.ok) { setConfig(prev => ({ ...prev, [key]: value })); } else { hasError = true; } } setMessage(hasError ? { type: 'error', text: 'Some settings failed to save' } : { type: 'success', text: 'Configuration saved' } ); } catch { setMessage({ type: 'error', text: 'Failed to save configuration' }); } finally { setSaving(false); } } if (loading) { return (
Loading...
); } if (!plugin) { return (

Plugin not found: {pluginId}

); } const schema = plugin.configSchema; const hasSchema = schema && Object.keys(schema).length > 0; return (

{plugin.name} Configuration

v{plugin.version} by {plugin.author}

{message && (
{message.text}
)} {hasSchema ? (

Settings

{Object.entries(schema).map(([key, field]) => (
{field.description && (

{field.description}

)} {field.type === 'boolean' ? ( ) : field.type === 'select' && field.options ? ( ) : field.type === 'secret' ? (
setFormValues(prev => ({ ...prev, [key]: e.target.value }))} placeholder={config[key] ? '•••••••• (unchanged)' : (field.placeholder || '')} className="w-full h-9 px-3 pr-10 rounded-md border border-input bg-background text-sm focus:outline-none focus:ring-2 focus:ring-ring font-mono" />
) : ( setFormValues(prev => ({ ...prev, [key]: e.target.value }))} placeholder={field.placeholder || ''} className="w-full h-9 px-3 rounded-md border border-input bg-background text-sm focus:outline-none focus:ring-2 focus:ring-ring" /> )}
))}
) : (

This plugin does not declare any configuration settings.

)}
); }