lxc-keyreg/public/pedido.html
2026-08-24 15:21:24 +01:00

165 lines
5.6 KiB
HTML

<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#131E1B">
<meta name="robots" content="noindex, nofollow">
<title>Pedido de acesso</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans+Condensed:wght@700&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="req-page">
<div class="req-top">
<div class="inner">
<p class="eyebrow" id="brand"></p>
<h1 id="title"></h1>
<p id="intro"></p>
</div>
</div>
<div class="req-wrap" id="wrap">
<div class="req-msg bad" id="msg" role="alert" aria-live="polite"></div>
<div class="req-field">
<span class="lab" id="l_name"></span>
<input type="text" id="f_name" autocomplete="name" maxlength="80">
</div>
<div class="req-field">
<span class="lab" id="l_dept"></span>
<input type="text" id="f_dept" autocomplete="organization" maxlength="80">
</div>
<div class="req-field">
<span class="lab" id="l_contact"></span>
<input type="text" id="f_contact" autocomplete="tel" maxlength="80">
</div>
<div class="req-field">
<span class="lab" id="l_areas"></span>
<div class="areas" id="areas"></div>
</div>
<div class="req-field">
<span class="lab" id="l_detail"></span>
<textarea id="f_detail" maxlength="600"></textarea>
</div>
<div class="req-field" id="pinbox" style="display:none">
<span class="lab" id="l_pin"></span>
<input type="text" id="f_pin" inputmode="numeric" autocomplete="off" maxlength="24">
<p class="req-hint" id="h_pin"></p>
</div>
<!-- Bots fill every field they can find; a person never sees this one. -->
<div class="hp" aria-hidden="true">
<label>Website<input type="text" id="f_website" tabindex="-1" autocomplete="off"></label>
</div>
<button class="btn btn-p" id="send" style="width:100%"></button>
<p class="req-foot" id="foot"></p>
</div>
</div>
<script src="/i18n.js"></script>
<script>
setLang("__LANG__".replace(/^__.*__$/, "pt"));
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
$("brand").textContent = T.brand;
$("title").textContent = T.reqTitle;
$("intro").textContent = T.reqIntro;
$("l_name").textContent = T.reqName;
$("l_dept").textContent = T.reqDept;
$("l_contact").textContent = T.reqContact;
$("l_areas").textContent = T.reqAreas;
$("l_detail").textContent = T.reqDetail;
$("f_detail").placeholder = T.reqDetailPh;
$("l_pin").textContent = T.reqPin;
$("h_pin").textContent = T.reqPinHelp;
$("send").textContent = T.reqSend;
$("foot").textContent = T.reqFoot;
document.title = T.reqTitle;
/* Areas, not doors. The endpoint returns building names only — this page is
the one an outsider might reach, and a door list is a map of the building. */
/* kiosk=1 means the keyholder handed their own phone over: no PIN prompt,
and the confirmation offers a fresh blank form rather than a dead end. */
const KIOSK = new URLSearchParams(location.search).get("kiosk") === "1";
(async () => {
try {
const r = await fetch("/api/request/areas").then((x) => x.json());
$("areas").innerHTML = (r.areas || []).map((a, n) =>
`<label class="area"><input type="checkbox" value="${esc(a)}" id="a${n}"><span>${esc(a)}</span></label>`
).join("");
if (r.pin) $("pinbox").style.display = "";
else if (KIOSK) $("foot").textContent = T.reqNoPinStaff;
} catch (e) { /* leave the section empty; the free-text field still works */ }
})();
function fail(m) {
const el = $("msg");
el.textContent = m;
el.classList.add("on");
if (el.scrollIntoView) el.scrollIntoView({ behavior: "smooth", block: "center" });
}
let busy = false;
$("send").addEventListener("click", async () => {
if (busy) return;
$("msg").classList.remove("on");
const name = $("f_name").value.trim();
if (!name) return fail(T.reqNeedName);
const areas = [...document.querySelectorAll("#areas input:checked")].map((i) => i.value);
if (!areas.length && !$("f_detail").value.trim()) return fail(T.reqNeedArea);
busy = true;
$("send").textContent = T.reqSending;
$("send").disabled = true;
try {
const res = await fetch("/api/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name,
dept: $("f_dept").value.trim(),
contact: $("f_contact").value.trim(),
areas,
detail: $("f_detail").value.trim(),
pin: $("f_pin").value.trim(),
website: $("f_website").value,
}),
});
if (res.ok) {
$("wrap").innerHTML = `<div class="req-done">
<div class="tickmark">✓</div>
<h2>${esc(T.reqOk)}</h2>
<p>${esc(KIOSK ? T.reqKioskDone : T.reqOkMore)}</p>
<button class="btn btn-g" onclick="location.reload()">${
esc(KIOSK ? T.reqKioskNext : T.reqAnother)}</button>
</div>`;
return;
}
const b = await res.json().catch(() => ({}));
fail(res.status === 429 ? T.reqTooMany
: res.status === 401 ? T.reqWrongPin
: (T.errors && T.errors[b.error]) || b.error || T.noServer);
} catch (e) {
fail(T.noServer);
}
busy = false;
$("send").textContent = T.reqSend;
$("send").disabled = false;
});
</script>
</body>
</html>