Delete Business
CI / ci (push) Canceled after 0s

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:
Stefan Lazic committed 2026-09-05 17:41:38 +01:00
1 parent 2e739c9a26
commit ba46334179
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>
);
}