feat: add template delete — backend DELETE route, frontend trash button
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 ?? '');
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [deletingId, setDeletingId] = useState<string | null>(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}}`;
|
||||
<div className="truncate text-sm font-semibold text-foreground">{template.name}</div>
|
||||
<div className="mt-1 truncate text-xs text-muted-foreground">{queueName(template.queue_id)}</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="shrink-0 rounded">
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<Badge variant="outline" className="rounded">
|
||||
{template.queue_id ? "Queue" : "Global"}
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void handleDeleteTemplate(template.id);
|
||||
}}
|
||||
disabled={deletingId === template.id}
|
||||
className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground/60 opacity-0 transition-all hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100 disabled:opacity-50"
|
||||
title="Delete template"
|
||||
>
|
||||
<Trash2Icon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 truncate font-mono text-[11px] text-muted-foreground">
|
||||
{template.subject_template}
|
||||
|
||||
@@ -169,6 +169,10 @@ export async function previewTemplate(data: {
|
||||
return request<TemplatePreview>("/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<Lifecycle[]>("/lifecycles");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user