"use client"; import { useCallback, useEffect, useState } from "react"; import { Trash2 } from "lucide-react"; import { toast } from "sonner"; import { toastError } from "@/lib/format"; import { getServiceEntryXrpcs, updateServiceEntry, removeServiceEntryXrpcs, addServiceEntryXrpcs, deleteServiceEntry, type ServiceEntry, } from "@/lib/api"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; interface ServiceEntrySheetProps { entry: ServiceEntry; open: boolean; onOpenChange: (open: boolean) => void; onSaved: () => void; } export function ServiceEntrySheet({ entry, open, onOpenChange, onSaved, }: ServiceEntrySheetProps) { const [accessMode, setAccessMode] = useState(entry.access_mode); const [xrpcs, setXrpcs] = useState([]); const [selected, setSelected] = useState>(new Set()); const [newXrpc, setNewXrpc] = useState(""); const [adding, setAdding] = useState(false); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); const loadXrpcs = useCallback(async () => { if (accessMode !== "specific") return; try { const list = await getServiceEntryXrpcs(entry.id); setXrpcs(list); } catch (e: unknown) { toastError("Failed to load XRPC list", e); } }, [entry.id, accessMode]); useEffect(() => { if (open) { setAccessMode(entry.access_mode); setSelected(new Set()); } }, [open, entry]); useEffect(() => { if (open) { loadXrpcs(); } }, [open, loadXrpcs]); function toggleSelectAll() { if (selected.size === xrpcs.length) { setSelected(new Set()); } else { setSelected(new Set(xrpcs)); } } function toggleSelect(xrpc: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(xrpc)) { next.delete(xrpc); } else { next.add(xrpc); } return next; }); } async function handleRemoveSelected() { if (selected.size === 0) return; try { await removeServiceEntryXrpcs(entry.id, Array.from(selected)); toast.success(`Removed ${selected.size} XRPC${selected.size > 1 ? "s" : ""}`); setSelected(new Set()); await loadXrpcs(); } catch (e: unknown) { toastError("Failed to remove XRPCs", e); } } async function handleAddXrpc() { const value = newXrpc.trim(); if (!value) return; setAdding(true); try { await addServiceEntryXrpcs(entry.id, [value]); toast.success(`Added ${value}`); setNewXrpc(""); await loadXrpcs(); } catch (e: unknown) { toastError("Failed to add XRPC", e); } finally { setAdding(false); } } async function handleSave() { setSaving(true); try { await updateServiceEntry(entry.id, { access_mode: accessMode }); toast.success("Service entry updated"); onSaved(); onOpenChange(false); } catch (e: unknown) { toastError("Failed to update service entry", e); } finally { setSaving(false); } } async function handleDelete() { setDeleting(true); try { await deleteServiceEntry(entry.id); toast.success(`Deleted ${entry.fragment_id}`); onSaved(); onOpenChange(false); } catch (e: unknown) { toastError("Failed to delete service entry", e); } finally { setDeleting(false); } } const allSelected = xrpcs.length > 0 && selected.size === xrpcs.length; const someSelected = selected.size > 0 && selected.size < xrpcs.length; return ( {entry.fragment_id} {entry.service_type}

XRPC Access

{accessMode === "specific" && (

Allowed XRPCs

{selected.size > 0 && ( )}
XRPC {xrpcs.length === 0 && ( No XRPCs configured. Add methods below that this service entry can access. )} {xrpcs.map((xrpc) => ( toggleSelect(xrpc)} aria-label={`Select ${xrpc}`} /> {xrpc} ))}
setNewXrpc(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleAddXrpc(); }} disabled={adding} />
)}
Delete {entry.fragment_id}? This will permanently remove the service entry and its XRPC configuration. The change will take effect in the DID document after your next PLC sync. Cancel {deleting ? "Deleting…" : "Delete"}
); }