forked from ChelseaKR/gtfs-scorecard
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsubscribe.js
More file actions
118 lines (110 loc) · 4.11 KB
/
Copy pathsubscribe.js
File metadata and controls
118 lines (110 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// @ts-check
/**
* The opt-in alerts form. POSTs to the alerts API (infra/alerts) which stores a
* pending subscriber and emails a confirm link (double opt-in). No account, no
* client secret: abuse is bounded server-side. If the endpoint is not
* configured, the form degrades to an explanation.
*/
import { compareText } from "./locale.js";
const SUBSCRIBE_URL = /** @type {any} */ (window).SCORECARD_SUBSCRIBE_URL || null;
const DATA_BASES = [
/** @type {any} */ (window).SCORECARD_DATA_BASE,
"data/artifacts",
"../data/artifacts",
].filter(Boolean);
const form = /** @type {HTMLFormElement} */ (document.getElementById("subscribe-form"));
const status = /** @type {HTMLElement} */ (document.getElementById("form-status"));
const agencySelect = /** @type {HTMLSelectElement} */ (document.getElementById("agency"));
const emailInput = /** @type {HTMLInputElement} */ (document.getElementById("email"));
const kindInputs = /** @type {NodeListOf<HTMLInputElement>} */ (
form.querySelectorAll('input[name="kinds"]')
);
/** Fetch index.json from the first base that answers, to fill the agency list. */
async function fetchIndex() {
for (const base of DATA_BASES) {
try {
const res = await fetch(`${base}/index.json`);
if (res.ok) return res.json();
} catch {
/* try the next base */
}
}
return null;
}
async function populateAgencies() {
const index = await fetchIndex();
if (!index || !index.agencies) return;
const entries = Object.entries(index.agencies)
.map(([id, a]) => ({ id, name: /** @type {any} */ (a).name }))
.sort((x, y) => compareText(x.name, y.name));
for (const { id, name } of entries) {
const opt = document.createElement("option");
opt.value = id;
opt.textContent = name;
agencySelect.appendChild(opt);
}
}
/** @param {string} message @param {"ok"|"err"|"info"|""} kind */
function setStatus(message, kind) {
status.textContent = message;
status.className = `form-status ${kind ? `form-status-${kind}` : ""}`.trim();
}
if (!SUBSCRIBE_URL) {
setStatus(
"Alerts are not enabled on this deployment yet. Check back soon.",
"info"
);
if (form) form.querySelector("button")?.setAttribute("disabled", "true");
} else {
populateAgencies();
form.addEventListener("submit", async (event) => {
event.preventDefault();
const data = new FormData(form);
const email = String(data.get("email") || "").trim();
const agency = String(data.get("agency") || "");
const kinds = data.getAll("kinds").map(String);
emailInput.removeAttribute("aria-invalid");
for (const input of kindInputs) input.removeAttribute("aria-invalid");
if (!email || !emailInput.validity.valid) {
setStatus(
email ? "Enter a complete email address, like you@agency.gov." : "Enter your email.",
"err"
);
emailInput.setAttribute("aria-invalid", "true");
emailInput.focus();
return;
}
if (!kinds.length) {
setStatus("Choose at least one kind of alert.", "err");
for (const input of kindInputs) input.setAttribute("aria-invalid", "true");
kindInputs[0]?.focus();
return;
}
const payload = agency ? { email, agencies: [agency], kinds } : { email, all: true, kinds };
setStatus("Sending…", "");
form.querySelector("button")?.setAttribute("disabled", "true");
try {
const res = await fetch(`${SUBSCRIBE_URL.replace(/\/$/, "")}/subscribe`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const body = await res.json().catch(() => ({}));
if (res.ok) {
setStatus(
body.message || "Check your email to confirm your subscription.",
"ok"
);
form.reset();
} else if (res.status === 429) {
setStatus(body.error || "Too many requests. Try again later.", "err");
} else {
setStatus(body.error || "Something went wrong. Please try again.", "err");
}
} catch {
setStatus("Could not reach the server. Please try again.", "err");
} finally {
form.querySelector("button")?.removeAttribute("disabled");
}
});
}