http: adds scripts
This commit is contained in:
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
class OtpForm extends HTMLElement {
|
||||
/** @type MutationObserver? */
|
||||
observer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.observer = new MutationObserver(() => {
|
||||
this.clearInputs();
|
||||
this.configureInputs();
|
||||
});
|
||||
this.observer.observe(this, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.observer?.disconnect();
|
||||
}
|
||||
|
||||
clearInputs() {
|
||||
console.log("clearing all inputs");
|
||||
/** @type NodeListOf<HTMLInputElement> */
|
||||
const inputs = this.querySelectorAll('input:not([type="submit"])');
|
||||
for (const input of inputs) {
|
||||
input.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
configureInputs() {
|
||||
this.observer?.disconnect();
|
||||
/** @type NodeListOf<HTMLInputElement> */
|
||||
const inputs = this.querySelectorAll('input:not([type="submit"])');
|
||||
/** @type NodeListOf<HTMLFormElement> */
|
||||
const form = this.querySelector("form");
|
||||
|
||||
for (const input of inputs) {
|
||||
input.addEventListener("focus", () => {
|
||||
input.select();
|
||||
});
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
if (input.value.length > 0) {
|
||||
input.value = input.value.slice(0, 1).toLocaleUpperCase();
|
||||
/** @type HTMLInputElement */
|
||||
const nextInput = input.nextElementSibling;
|
||||
if (nextInput) {
|
||||
nextInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type HTMLInputElement */
|
||||
const submitButton = form.querySelector('input[type="submit"]');
|
||||
submitButton.focus();
|
||||
let areAllInputsEntered = true;
|
||||
inputs.forEach((input) => {
|
||||
areAllInputsEntered = areAllInputsEntered && input.value.length > 0;
|
||||
});
|
||||
if (areAllInputsEntered) {
|
||||
form.requestSubmit(submitButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("otp-form", OtpForm);
|
||||
Reference in New Issue
Block a user