From 6cc7ae01f7281d5e7e60d296492b63fd16a8e0ba Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 21 Sep 2026 14:17:55 +0200 Subject: [PATCH] feat(admin): delete button on request link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The request-links panel now has a trash button at the far right of each row. Click it, the link is gone — cascadeDelete on intake_link_submissions.link removes the daily counters in the same transaction, and revalidatePath re-renders the panel without the row. Hard delete, no confirmation: matches the existing Pause/Resume pattern (no dialog there either). The icon is a Trash2 from lucide-react; the title attribute is the only label, by design owner-only surface. Phase 9 shape: action takes a scope so revalidation hits the client admin page the panel is mounted on. --- src/app/actions/intake-links.ts | 16 ++++++++++++++++ src/components/admin/intake-links-panel.tsx | 21 ++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/app/actions/intake-links.ts b/src/app/actions/intake-links.ts index 20d3490..b9f4c48 100644 --- a/src/app/actions/intake-links.ts +++ b/src/app/actions/intake-links.ts @@ -114,6 +114,22 @@ export async function setIntakeLinkNotesAction(input: { revalidatePath(adminPathForScope(input.scope)); } +/** + * Hard-delete a request link. Cascades to `intake_link_submissions` rows + * (those use `cascadeDelete: true` on their `link` field, so they go + * with it). The token stops working immediately because there's no row + * left to resolve against. + */ +export async function deleteIntakeLinkAction(input: { + linkId: string; + scope: IntakeLinkScope; +}): Promise { + await requireOwner(); + const pb = await getAdminPb(); + await pb.collection("intake_links").delete(input.linkId); + revalidatePath(adminPathForScope(input.scope)); +} + function intakeUrlFor(token: string): string { const base = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; return `${base}/intake/${token}`; diff --git a/src/components/admin/intake-links-panel.tsx b/src/components/admin/intake-links-panel.tsx index 7e5e520..f9a9a67 100644 --- a/src/components/admin/intake-links-panel.tsx +++ b/src/components/admin/intake-links-panel.tsx @@ -1,10 +1,11 @@ "use client"; import { useState, useTransition } from "react"; -import { Copy, RefreshCw, Power, PowerOff, Plus } from "lucide-react"; +import { Copy, RefreshCw, Power, PowerOff, Plus, Trash2 } from "lucide-react"; import { createIntakeLinkAction, + deleteIntakeLinkAction, rotateIntakeTokenAction, setIntakeLinkActiveAction, setIntakeLinkNotesAction, @@ -100,6 +101,15 @@ export function IntakeLinksPanel({ }); } + function remove(link: IntakeLinkRecord) { + startTransition(async () => { + await deleteIntakeLinkAction({ + linkId: link.id, + scope, + }); + }); + } + const field = "w-full rounded-lg border border-border-subtle bg-surface px-3 py-2 text-xs text-text-secondary"; @@ -220,6 +230,15 @@ export function IntakeLinksPanel({ {link.active ? : } {link.active ? "Revoke" : "Re-activate"} +