A Delete button now sits next to Edit on each business's detail page, styled in red to distinguish it from the safe actions. It opens a dedicated confirmation page (matching the Add/Edit page pattern) that clearly lists exactly what gets destroyed — container + media volume, the database by name, the Caddy site/domain, and the stack directory — and requires typing the business's exact slug before the Delete button even becomes clickable. This is a stronger guard than a plain "are you sure?" popup, given what we just learned about how easy it is to lose data here. deleteBusiness() in provisioning.ts reuses the same primitives as create/edit (composeDown, reloadCaddy, the shared MySQL root connection), and is deliberately best-effort past the first step — if a piece is already gone (matching exactly the kind of half-broken state we just fixed for blake-heritage), it still finishes removing the rest and reports what it couldn't clean up automatically, rather than getting stuck. Verified live: provisioned a real test business, confirmed its container/database/user/volume/Caddy site all existed, deleted it, and confirmed every single one was actually gone — including checking Caddy's running config (not just the file on disk) to prove the reload took effect. Also confirmed the confirm-button correctly stays disabled for a wrong slug and only enables on an exact match. Your real production database was never touched throughout.
This commit is contained in:
7 files changed
+245
-18
No files matched your search
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState, useState } from "react";
|
||||
import { deleteBusinessAction, type DeleteBusinessState } from "./actions";
|
||||
|
||||
const initialState: DeleteBusinessState = { error: null };
|
||||
|
||||
export function DeleteBusinessForm({ slug }: { slug: string }) {
|
||||
const boundAction = deleteBusinessAction.bind(null, slug);
|
||||
const [state, formAction, isPending] = useActionState(boundAction, initialState);
|
||||
const [confirmation, setConfirmation] = useState("");
|
||||
const canDelete = confirmation === slug;
|
||||
|
||||
return (
|
||||
<form action={formAction} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="confirmation" className="block text-sm font-medium text-muted">
|
||||
Type <span className="font-mono font-semibold text-ink">{slug}</span> to confirm
|
||||
</label>
|
||||
<input
|
||||
id="confirmation"
|
||||
name="confirmation"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
value={confirmation}
|
||||
onChange={(e) => setConfirmation(e.target.value)}
|
||||
className="mt-1 w-full rounded-md border border-line px-3 py-2 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
{state.error && (
|
||||
<p role="alert" className="text-sm font-medium text-danger">
|
||||
{state.error}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!canDelete || isPending}
|
||||
className="w-full rounded-md bg-danger py-2 text-sm font-semibold text-white disabled:opacity-40"
|
||||
>
|
||||
{isPending ? "Deleting…" : "Delete Business"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user