'use client'; import React, { useState, useEffect } from 'react'; import { ServerFunction } from '@/lib/vfs/types'; import { Plus, Loader2, AlertCircle, Wrench, MoreVertical, Pencil, Trash2, ToggleLeft, ToggleRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { ServerFunctionEditor } from './server-function-editor'; import { cn } from '@/lib/utils'; import type { ServerFunctionsDataProvider } from './data-providers'; interface ServerFunctionsManagerProps { deploymentId?: string; dataProvider?: ServerFunctionsDataProvider; workspaceId?: string; } export function ServerFunctionsManager({ deploymentId, dataProvider, workspaceId }: ServerFunctionsManagerProps) { const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api'; const [functions, setFunctions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editingFunction, setEditingFunction] = useState(null); const [isCreating, setIsCreating] = useState(false); useEffect(() => { loadFunctions(); }, [deploymentId, dataProvider]); const loadFunctions = async () => { try { setLoading(true); setError(null); if (dataProvider) { setFunctions(await dataProvider.list()); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/server-functions`); if (!res.ok) { const data = await res.json(); throw new Error(data.error || 'Failed to load server functions'); } const data = await res.json(); setFunctions(data.functions); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load server functions'); } finally { setLoading(false); } }; const toggleEnabled = async (fn: ServerFunction) => { try { if (dataProvider) { await dataProvider.toggle(fn.id, !fn.enabled); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/server-functions/${fn.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !fn.enabled }), }); if (!res.ok) throw new Error('Failed to update server function'); } else { return; } await loadFunctions(); } catch (err) { console.error('Failed to toggle server function:', err); } }; const deleteFunction = async (fn: ServerFunction) => { if (!confirm(`Delete server function "${fn.name}"? This cannot be undone.`)) return; try { if (dataProvider) { await dataProvider.remove(fn.id); } else if (deploymentId) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/server-functions/${fn.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error('Failed to delete server function'); } else { return; } await loadFunctions(); } catch (err) { console.error('Failed to delete server function:', err); } }; const handleSave = async (data: Partial) => { try { if (dataProvider) { await dataProvider.save(editingFunction?.id || null, data); } else if (!deploymentId) { throw new Error('No deployment ID available'); } else if (editingFunction) { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/server-functions/${editingFunction.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to update server function'); } } else { const res = await fetch(`${apiBase}/admin/deployments/${deploymentId}/server-functions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to create server function'); } } setEditingFunction(null); setIsCreating(false); await loadFunctions(); } catch (err) { throw err; } }; if (loading) { return (
); } if (error) { return (

{error}

); } return (

Server Functions (Helpers)

{functions.length === 0 ? (

No server functions yet

Create reusable helpers for your edge functions

) : (
{functions.map(fn => (
{fn.name} {!fn.enabled && ( disabled )}
{fn.description && (

{fn.description}

)}
server.{fn.name}(args)
setEditingFunction(fn)}> Edit toggleEnabled(fn)}> {fn.enabled ? ( <> Disable ) : ( <> Enable )} deleteFunction(fn)} className="text-destructive" > Delete
))}
)}
{/* Server Function Editor Dialog */} {(isCreating || editingFunction) && ( { setIsCreating(false); setEditingFunction(null); }} onSave={handleSave} /> )}
); }