"use client"; import { useCallback, useEffect, useState } from "react"; import { pluginRegistry } from "@/lib/plugins/registry"; import type { PluginDefinition, PluginConfigField } from "@/lib/plugins/types"; interface PluginWithState extends PluginDefinition { enabled: boolean; config: Record; missingDeps: string[]; allDepIds: string[]; } export function PluginsClient() { const [plugins, setPlugins] = useState([]); const [editing, setEditing] = useState(null); const [toggling, setToggling] = useState(null); const load = useCallback(async () => { try { const res = await fetch("/api/admin/plugins"); const data = await res.json(); const allDepIds = new Set(); for (const p of data.plugins ?? []) { if (p.dependencies) { for (const d of p.dependencies) allDepIds.add(d); } } const items: PluginWithState[] = (data.plugins ?? []).map( (p: PluginDefinition & { enabled: boolean; config: Record }) => { const missing = p.dependencies?.filter( (d: string) => !allDepIds.has(d), ) ?? []; return { ...p, missingDeps: missing, allDepIds: Array.from(allDepIds), }; }, ); setPlugins(items); } catch {} }, []); useEffect(() => { load(); }, [load]); const toggleEnabled = async (id: string, current: boolean) => { setToggling(id); try { await fetch(`/api/admin/plugins`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id, enabled: !current }), }); load(); } catch {} setToggling(null); }; const saveConfig = async (id: string, config: Record) => { try { await fetch(`/api/admin/plugins`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id, config }), }); setEditing(null); load(); } catch {} }; const hasDependencyIssues = (p: PluginWithState) => p.dependencies?.length && p.missingDeps.length > 0; return (
{plugins.length === 0 && (
No plugins registered.
)} {plugins.map((plugin) => (

{plugin.name}

v{plugin.version} {!plugin.enabled && ( Disabled )} {hasDependencyIssues(plugin) && ( Missing deps )}

{plugin.description}

ID: {plugin.id} {plugin.author && Author: {plugin.author}} {plugin.license && License: {plugin.license}} {plugin.dependencies && plugin.dependencies.length > 0 && ( Deps: {plugin.dependencies.join(", ")} )}
{plugin.configFields && plugin.configFields.length > 0 && (
{editing === plugin.id ? ( saveConfig(plugin.id, cfg)} onCancel={() => setEditing(null)} /> ) : ( )}
)}
))}
); } function ConfigForm({ fields, initial, onSave, onCancel, }: { fields: PluginConfigField[]; initial: Record; onSave: (config: Record) => void; onCancel: () => void; }) { const [values, setValues] = useState>(initial); const update = (key: string, value: string | boolean) => { setValues((prev) => ({ ...prev, [key]: value })); }; return (
{fields.map((field) => (
{field.type === "text" && ( update(field.key, e.target.value)} className="w-full px-2 py-1 text-xs rounded-md border border-[var(--admin-border)] bg-transparent text-[var(--admin-text)]" /> )} {field.type === "boolean" && (
); }