delete-migration-button.tsx89 lines · main
1'use client';
2
3import { useRouter } from 'next/navigation';
4import { useState } from 'react';
5
6interface Props {
7 requestId: string;
8}
9
10interface ErrorBody {
11 code?: string;
12 message?: string;
13}
14
15/**
16 * Two-click delete for a migration request. Same pattern as the auth
17 * sdk-key revoke: first click arms the confirm state, second sends the
18 * DELETE. Hard delete — the api cascades audit-event rows.
19 */
20export function DeleteMigrationButton({ requestId }: Props) {
21 const router = useRouter();
22 const [confirming, setConfirming] = useState(false);
23 const [pending, setPending] = useState(false);
24 const [errMsg, setErrMsg] = useState<string | null>(null);
25
26 async function del(): Promise<void> {
27 setPending(true);
28 setErrMsg(null);
29 try {
30 const res = await fetch(`/api/v1/migration-requests/${requestId}`, {
31 method: 'DELETE',
32 credentials: 'include',
33 });
34 if (!res.ok) {
35 const body = (await res.json().catch(() => ({}))) as ErrorBody;
36 throw new Error(body.message ?? body.code ?? `http ${res.status}`);
37 }
38 router.replace('/dashboard/migrations');
39 } catch (err) {
40 setErrMsg(err instanceof Error ? err.message : 'delete failed');
41 setPending(false);
42 setConfirming(false);
43 }
44 }
45
46 if (!confirming) {
47 return (
48 <div className="flex flex-col gap-1">
49 <button
50 type="button"
51 onClick={() => setConfirming(true)}
52 className="self-start rounded-md border border-[var(--color-border)] px-3 py-1.5 font-mono text-xs text-[var(--color-text-muted)] hover:border-[var(--color-error)] hover:text-[var(--color-error)]"
53 >
54 delete migration request
55 </button>
56 {errMsg ? (
57 <p className="font-mono text-[10px] text-[var(--color-error)]">{errMsg}</p>
58 ) : null}
59 </div>
60 );
61 }
62
63 return (
64 <div className="flex flex-col gap-2 rounded-md border border-[var(--color-error)] bg-[var(--color-bg)] p-3">
65 <p className="font-mono text-xs text-[var(--color-text)]">
66 permanently delete this migration request? this also deletes its audit
67 history.
68 </p>
69 <div className="flex gap-2">
70 <button
71 type="button"
72 onClick={() => void del()}
73 disabled={pending}
74 className="rounded-md bg-[var(--color-error)] px-3 py-1.5 font-mono text-xs text-[var(--color-text-inverse)] disabled:opacity-50"
75 >
76 {pending ? 'deleting…' : 'yes, delete'}
77 </button>
78 <button
79 type="button"
80 onClick={() => setConfirming(false)}
81 disabled={pending}
82 className="rounded-md border border-[var(--color-border)] px-3 py-1.5 font-mono text-xs text-[var(--color-text-muted)] hover:border-[var(--color-primary)] hover:text-[var(--color-primary)]"
83 >
84 cancel
85 </button>
86 </div>
87 </div>
88 );
89}