Files
threadcount-community/app/ops/(console)/facilities/[id]/Danger.tsx
T
ThreadCount 1bc2de655a ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
2026-09-13 08:45:19 +10:00

58 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
/* The one thing on the console that cannot be undone. Owner only (the server checks too), the
* facility's exact name typed, and a live code from the operator's own second factor. */
export default function Danger({ facilityId, name, owner, totpEnabled }: { facilityId: string; name: string; owner: boolean; totpEnabled: boolean }) {
const [open, setOpen] = useState(false);
const [confirm, setConfirm] = useState("");
const [code, setCode] = useState("");
const [err, setErr] = useState("");
const [busy, setBusy] = useState(false);
async function submit(e: React.FormEvent) {
e.preventDefault();
setBusy(true); setErr("");
try {
const r = await fetch("/api/ops/controls", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "facility.delete", facilityId, confirm, code }) });
const j = await r.json().catch(() => ({}));
if (!r.ok) { setErr(j.error || "That didnt work."); return; }
window.location.assign("/ops/facilities?deleted=1");
} catch {
setErr("No connection — check the network and try again.");
} finally {
setBusy(false);
}
}
const label: React.CSSProperties = { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--color-neutral-600)", marginBottom: 4 };
if (!owner) return <div className="tc-panel-body" style={{ fontSize: 13, color: "var(--color-neutral-700)" }}>Only an owner can delete a facility.</div>;
if (!totpEnabled) return <div className="tc-panel-body" style={{ fontSize: 13, color: "var(--color-neutral-700)" }}>Enrol a second factor under <a href="/ops/security">Your sign-in</a> before deleting anything.</div>;
if (!open) {
return (
<div className="tc-panel-body" style={{ fontSize: 13, display: "flex", alignItems: "center", gap: 12 }}>
<div style={{ flex: 1, color: "var(--color-neutral-700)" }}>Deletes every record, image and account belonging to this facility. There is no undo beyond the database dumps on the box.</div>
<button type="button" className="btn" onClick={() => setOpen(true)}>Delete facility</button>
</div>
);
}
return (
<form onSubmit={submit} className="tc-panel-body" style={{ fontSize: 13, display: "grid", gap: 10 }}>
<div>
<label htmlFor="danger-confirm" style={label}>Type the facility name exactly</label>
<input id="danger-confirm" className="input" value={confirm} onChange={(e) => setConfirm(e.target.value)} placeholder={name} autoComplete="off" autoFocus />
</div>
<div>
<label htmlFor="danger-code" style={label}>Code from your authenticator</label>
<input id="danger-code" className="input" inputMode="numeric" autoComplete="one-time-code" maxLength={7} value={code} onChange={(e) => setCode(e.target.value)} style={{ maxWidth: 160 }} />
</div>
{err && <div role="alert" style={{ border: "2px solid var(--color-accent)", padding: "8px 12px", fontWeight: 600, color: "var(--color-accent-700)" }}>{err}</div>}
<div style={{ display: "flex", gap: 8 }}>
<button type="submit" className="btn btn-primary" disabled={busy || confirm.trim() !== name || code.replace(/\s+/g, "").length < 6}>{busy ? "Deleting…" : "Delete this facility"}</button>
<button type="button" className="btn" onClick={() => { setOpen(false); setConfirm(""); setCode(""); setErr(""); }} disabled={busy}>Cancel</button>
</div>
</form>
);
}