From affbbdaa46704b83bd0daf209588bfef1b058965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gjermund=20H=C3=B8s=C3=B8ien=20Wiggen?= Date: Tue, 9 Jun 2026 12:49:45 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20add=20template=20delete=20=E2=80=94=20b?= =?UTF-8?q?ackend=20DELETE=20route,=20frontend=20trash=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DELETE /templates/:id — backend route - deleteTemplate() API client function - Trash icon on each template list item (shows on hover) - Confirms inline, no dialog needed - Resets builder if the deleted template was being edited Co-Authored-By: Claude Opus 4.8 --- src/routes/templates.ts | 15 ++++++++++++++ web/src/app/admin/page-content.tsx | 33 ++++++++++++++++++++++++++---- web/src/lib/api.ts | 4 ++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/routes/templates.ts b/src/routes/templates.ts index a123c4d..71e2d38 100644 --- a/src/routes/templates.ts +++ b/src/routes/templates.ts @@ -151,6 +151,21 @@ export function createTemplatesRouter(db: Db): Hono { return c.json(updated); }); + router.delete('/:id', async (c) => { + const id = c.req.param('id'); + + const existing = await db.query.templates.findFirst({ + where: eq(templates.id, id), + }); + + if (!existing) { + throw new HTTPException(404, { message: 'Template not found' }); + } + + await db.delete(templates).where(eq(templates.id, id)); + return c.json({ ok: true }); + }); + router.post('/preview', async (c) => { const body = await c.req.json(); const subjectTemplate = String(body.subject_template ?? ''); diff --git a/web/src/app/admin/page-content.tsx b/web/src/app/admin/page-content.tsx index 02a4b28..d0286a7 100644 --- a/web/src/app/admin/page-content.tsx +++ b/web/src/app/admin/page-content.tsx @@ -10,6 +10,7 @@ import { PlusIcon, Settings2Icon, SlidersHorizontalIcon, + Trash2Icon, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -52,6 +53,7 @@ import { createTemplate, updateTemplate, previewTemplate, + deleteTemplate, getCustomFields, getQueueCustomFields, assignQueueCustomField, @@ -1699,6 +1701,7 @@ Location: {{custom_fields.location}}`); const [previewError, setPreviewError] = useState(null); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); + const [deletingId, setDeletingId] = useState(null); const fetchTemplates = useCallback(async () => { setLoading(true); @@ -1741,6 +1744,14 @@ Location: {{custom_fields.location}}`; setSaveError(null); }; + const handleDeleteTemplate = async (templateId: string) => { + setDeletingId(templateId); + await deleteTemplate(templateId); + if (editingId === templateId) resetBuilder(); + await fetchTemplates(); + setDeletingId(null); + }; + const selectTemplate = (template: Template) => { setEditingId(template.id); setName(template.name); @@ -1835,7 +1846,7 @@ Location: {{custom_fields.location}}`; type="button" onClick={() => selectTemplate(template)} className={cn( - "min-w-0 max-w-full overflow-hidden rounded-md border p-3 text-left transition hover:border-primary/45 hover:bg-accent/45", + "group min-w-0 max-w-full overflow-hidden rounded-md border p-3 text-left transition hover:border-primary/45 hover:bg-accent/45", editingId === template.id ? "border-primary bg-primary/10" : "border-border bg-card" )} > @@ -1844,9 +1855,23 @@ Location: {{custom_fields.location}}`;
{template.name}
{queueName(template.queue_id)}
- - {template.queue_id ? "Queue" : "Global"} - +
+ + {template.queue_id ? "Queue" : "Global"} + + +
{template.subject_template} diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 70b7e2c..f9da707 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -169,6 +169,10 @@ export async function previewTemplate(data: { return request("/templates/preview", { method: "POST", body: JSON.stringify(data) }); } +export async function deleteTemplate(id: string): Promise<{ data: { ok: boolean } | null; error: string | null }> { + return request<{ ok: boolean }>(`/templates/${id}`, { method: "DELETE" }); +} + export async function getLifecycles(): Promise<{ data: Lifecycle[] | null; error: string | null }> { return request("/lifecycles"); }