Bonus
Type-to-confirm delete dialog
Destructive action gated by an exact match
Destructive action gated by an exact match
Build a destructive confirmation dialog with Tailwind CSS. A fixed inset-0 overlay with a black/50 backdrop centring a max-w-md white card with rounded-2xl corners. Inside: a red-tinted warning tile with an exclamation glyph beside a title reading "Delete acme/payments-api", a paragraph explaining the action cannot be undone, and a bordered list of exactly what disappears — 3 environments, 412 build records, 8 API tokens — each row with a small red dot. Then an instruction line asking the person to type the repository name to confirm, with the name shown in a monospaced pill they can copy, a text input under it, a footer with a Cancel button and a red Delete button that starts disabled and faded, and a small line noting the action is logged in the audit trail. Then add JavaScript that compares the input against the required name on every keystroke, enabling the delete button only on an exact match, adding a red ring to the input once the typing diverges from the target, allowing Enter to submit when it matches, closing on Cancel, Escape or a backdrop click, and putting the dialog into a "Deleting…" state with the button disabled when it fires. <div id="dangerModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-6">
<div id="dangerCard" class="w-full max-w-md rounded-2xl bg-white p-6 shadow-2xl">
<div class="flex items-start gap-3">
<span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-red-50 text-lg text-red-600">!</span>
<div>
<h2 class="font-semibold text-neutral-900">Delete acme/payments-api</h2>
<p class="mt-1 text-sm leading-relaxed text-neutral-500">This removes the repository and everything attached to it. It cannot be undone.</p>
</div>
</div>
<ul class="mt-5 space-y-2 rounded-xl border border-neutral-200 bg-neutral-50 p-4 text-sm text-neutral-600">
<li class="flex items-center gap-2"><span class="h-1.5 w-1.5 rounded-full bg-red-500"></span> 3 environments, including production</li>
<li class="flex items-center gap-2"><span class="h-1.5 w-1.5 rounded-full bg-red-500"></span> 412 build records and their logs</li>
<li class="flex items-center gap-2"><span class="h-1.5 w-1.5 rounded-full bg-red-500"></span> 8 API tokens scoped to this repo</li>
</ul>
<label for="dangerInput" class="mt-5 block text-sm text-neutral-600">
Type <span class="rounded-md bg-neutral-100 px-1.5 py-0.5 font-mono text-xs text-neutral-900">acme/payments-api</span> to confirm
</label>
<input id="dangerInput" autocomplete="off" placeholder="acme/payments-api"
class="mt-2 w-full rounded-xl border border-neutral-200 px-3.5 py-2.5 font-mono text-sm outline-none focus:border-neutral-900 focus:ring-4 focus:ring-neutral-900/10" />
<div class="mt-6 flex items-center justify-end gap-3">
<button id="dangerCancel" class="rounded-xl border border-neutral-200 px-4 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50">Cancel</button>
<button id="dangerConfirm" disabled class="cursor-not-allowed rounded-xl bg-red-600 px-4 py-2.5 text-sm font-medium text-white opacity-40 transition hover:bg-red-700">Delete repository</button>
</div>
<p class="mt-4 text-center text-xs text-neutral-400">This action is recorded in the audit log with your account and IP.</p>
</div>
</div> var TARGET = "acme/payments-api";
var modal = document.getElementById("dangerModal");
var card = document.getElementById("dangerCard");
var input = document.getElementById("dangerInput");
var confirm = document.getElementById("dangerConfirm");
var cancel = document.getElementById("dangerCancel");
function matches() {
return input.value.trim() === TARGET;
}
function sync() {
var ok = matches();
confirm.disabled = !ok;
confirm.classList.toggle("opacity-40", !ok);
confirm.classList.toggle("cursor-not-allowed", !ok);
// Only complain once what they typed can no longer become the target.
var diverged = input.value.length > 0 && TARGET.indexOf(input.value.trim()) !== 0;
input.classList.toggle("border-red-400", diverged);
input.classList.toggle("ring-4", diverged);
input.classList.toggle("ring-red-500/10", diverged);
}
function close() {
modal.classList.add("hidden");
}
input.addEventListener("input", sync);
input.addEventListener("keydown", function (e) {
if (e.key === "Enter" && matches()) confirm.click();
});
confirm.addEventListener("click", function () {
confirm.disabled = true;
confirm.textContent = "Deleting…";
confirm.classList.add("opacity-60", "cursor-not-allowed");
// Fire the real request here; close the dialog when it resolves.
});
cancel.addEventListener("click", close);
modal.addEventListener("click", function (e) { if (!card.contains(e.target)) close(); });
document.addEventListener("keydown", function (e) { if (e.key === "Escape") close(); });
input.focus();
sync();