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