344b1701dd
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
58 lines
3.5 KiB
TypeScript
58 lines
3.5 KiB
TypeScript
"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 didn’t 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>
|
||
);
|
||
}
|