http: adds scripts

This commit is contained in:
2026-06-04 15:15:02 +01:00
parent fac1199cd6
commit 813836fbba
7 changed files with 320 additions and 0 deletions
@@ -0,0 +1,34 @@
/** @typedef {import("./selectors.js").Selectors} Selectors */
/**
* @param {{contactForm: Selectors}} contactForm
* @param {string} errorMsg
*/
export function postErrorMessageOnContactForm({ contactForm }, errorMsg) {
const errorElem = contactForm.errorElem();
if (errorElem) {
errorElem.textContent = errorMsg;
errorElem.removeAttribute("hidden");
} else {
alert(errorMsg);
}
}
/**
* @param {{otpDialog: Selectors}} otpDialog
* @param {string} errorMsg
*/
export function postErrorMessageOnOtpForm({ otpDialog }, errorMsg) {
const errorElem = otpDialog.errorElem();
if (errorElem) {
errorElem.textContent = errorMsg;
errorElem.removeAttribute("hidden");
} else {
alert(errorMsg);
}
/** @type NodeListOf<HTMLInputElement> */
const inputs = otpDialog.allOtpInputs();
for (const input of inputs) {
input.value = "";
}
}
+31
View File
@@ -0,0 +1,31 @@
/** @typedef {import("./selectors.js").Selectors} Selectors */
import { actions } from "./actions.js";
import { resetResendButton } from "./reset-resend-button.js";
import { postErrorMessageOnOtpForm } from "./post-error-message.js";
/** @typedef {{ resendButtonInterval: NodeJS.Timeout | undefined }} Result */
const fallbackErrorMsg =
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
/**
* @param {Selectors} selectors
* @param {NodeJS.Timeout | undefined} resetButtonInterval
* @returns {Promise<Result>}
*/
export async function resendOtp(selectors, resetButtonInterval) {
const result = resetResendButton(selectors, resetButtonInterval);
const name = selectors.contactForm.nameElem()?.value;
const email = selectors.contactForm.emailElem().value;
try {
await actions.sendOtp({ type: "email", name, email });
} catch (sendOtpError) {
const errorMsg = sendOtpError?.toString() ?? fallbackErrorMsg;
postErrorMessageOnOtpForm(selectors, errorMsg);
throw sendOtpError;
}
return result;
}
@@ -0,0 +1,32 @@
/** @typedef {import("./selectors.js").Selectors} Selectors */
/** @typedef {{ resendButtonInterval: NodeJS.Timeout | undefined }} Result */
/**
* @param {Selectors} selectors
* @param {NodeJS.Timeout | undefined} resetButtonInterval
* @returns {Result}
*/
export function resetResendButton({ otpDialog }, resendButtonInterval) {
clearInterval(resendButtonInterval);
const resendButton = otpDialog.resendButton();
if (resendButton) {
resendButton.setAttribute("data-countdown", "60");
resendButton.setAttribute("disabled", "");
resendButtonInterval = setInterval(() => {
const countdown = +(resendButton.getAttribute("data-countdown") ?? 1) - 1;
resendButton.setAttribute("data-countdown", countdown.toString());
resendButton.textContent = `Resend (${countdown}s)`;
}, 1000);
setTimeout(() => {
clearInterval(resendButtonInterval);
resendButton.textContent = "Resend";
resendButton.removeAttribute("disabled");
}, 1000 * 60);
}
return { resendButtonInterval };
}
+25
View File
@@ -0,0 +1,25 @@
/**
* @typedef {Object} Selectors
* @property {Object} contactForm
* @property {() => HTMLInputElement} contactForm.emailElem
* @property {() => Element | null} contactForm.errorElem
* @property {() => HTMLInputElement} contactForm.nameElem
* @property {() => HTMLTextAreaElement} contactForm.messageElem
* @property {() => HTMLFormElement | null} contactForm.self
* @property {() => HTMLInputElement | null} contactForm.submitButton
* @property {Object} otpDialog
* @property {() => NodeListOf<HTMLInputElement>} otpDialog.allOtpInputs
* @property {() => Element | null} otpDialog.errorElem
* @property {() => HTMLInputElement | null} otpDialog.firstOtpInput
* @property {() => HTMLFormElement | null} otpDialog.otpForm
* @property {() => Element | null} otpDialog.otpRecipient
* @property {() => Element | null} otpDialog.otpValidUntil
* @property {() => HTMLButtonElement | null} otpDialog.resendButton
* @property {() => HTMLDialogElement} otpDialog.self
* @property {() => HTMLInputElement | null} otpDialog.submitButton
* @property {Object} successSection
* @property {() => Element | null} successSection.email
* @property {() => Element | null} successSection.message
* @property {() => Element | null} successSection.name
* @property {() => Element | null} successSection.self
*/
@@ -0,0 +1,53 @@
import { actions } from "./actions.js";
import { postErrorMessageOnContactForm } from "./post-error-message.js";
import { resetResendButton } from "./reset-resend-button.js";
/** @typedef {import("./selectors.js").Selectors} Selectors */
const fallbackErrorMsg =
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
/**
* @param {Selectors} selectors
* @param {NodeJS.Timeout | undefined} resetButtonInterval
* @returns {Promise<Result>}
*/
export async function submitContactForm(selectors, resendButtonInterval) {
const { contactForm, otpDialog } = selectors;
const name = contactForm.nameElem()?.value;
const email = contactForm.emailElem().value;
contactForm.submitButton()?.setAttribute("disabled", "");
try {
await actions.sendOtp({ type: "email", name, email });
} catch (sendOtpError) {
const errorMsg = sendOtpError?.toString() ?? fallbackErrorMsg;
postErrorMessageOnContactForm(selectors, errorMsg);
throw sendOtpError;
}
const otpRecipient = otpDialog.otpRecipient();
email && otpRecipient && (otpRecipient.textContent = `<${email}>`);
const otpValidUntil = otpDialog.otpValidUntil();
const validUntil = new Date(Date.now() + 1000 * 60 * 5);
otpValidUntil &&
(otpValidUntil.textContent = `until ${validUntil.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`);
const dialog = otpDialog.self();
dialog.showModal();
contactForm.submitButton()?.removeAttribute("disabled");
dialog.addEventListener("click", function (event) {
const rect = dialog.getBoundingClientRect();
const isInDialog =
rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width;
if (!isInDialog) {
dialog.close();
}
});
return resetResendButton(selectors, resendButtonInterval);
}
@@ -0,0 +1,77 @@
import { actions } from "./actions.js";
import { postErrorMessageOnOtpForm } from "./post-error-message.js";
/** @typedef {import("./selectors.js").Selectors} Selectors */
const fallbackErrorMsg =
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
/**
* @param {Selectors} selectors
*/
export async function submitOtpForm(selectors) {
const { contactForm, otpDialog, successSection } = selectors;
const otpForm = otpDialog.otpForm();
otpDialog.submitButton()?.setAttribute("disabled", "");
const otpFormData = new FormData(otpForm ?? undefined);
const guess = [
otpFormData.get("1"),
otpFormData.get("2"),
otpFormData.get("3"),
otpFormData.get("4"),
otpFormData.get("5"),
otpFormData.get("6"),
].join("");
const name = contactForm.nameElem()?.value;
const email = contactForm.emailElem().value;
const message = contactForm.messageElem()?.value;
let verifyResult;
try {
verifyResult = await actions.verifyOtp({ guess, userId: email });
} catch (verifyError) {
otpDialog.submitButton()?.removeAttribute("disabled");
postErrorMessageOnOtpForm(
selectors,
verifyError?.toString() ?? fallbackErrorMsg,
);
return;
}
if (verifyResult === false) {
otpDialog.submitButton()?.removeAttribute("disabled");
postErrorMessageOnOtpForm(selectors, "Incorrect OTP. Check your email?");
otpDialog.firstOtpInput()?.focus();
return;
}
const sendmailToken = verifyResult;
try {
await actions.sendEmail({
email,
message: message,
name: name,
userId: email,
token: sendmailToken,
});
} catch (sendEmailError) {
const errorMsg = sendEmailError?.toString() ?? fallbackErrorMsg;
postErrorMessageOnOtpForm(selectors, errorMsg);
otpDialog.submitButton()?.removeAttribute("disabled");
return;
}
const sentName = successSection.name();
const sentEmail = successSection.email();
const sentMessage = successSection.message();
sentName && (sentName.textContent = name ?? "???");
sentEmail && (sentEmail.textContent = email ?? "???");
sentMessage && (sentMessage.textContent = message ?? "???");
contactForm.self()?.remove();
successSection.self()?.removeAttribute("hidden");
otpDialog.submitButton()?.removeAttribute("disabled");
otpDialog.self().close();
}