contact page
This commit is contained in:
30
website/src/scripts/contact/post-error-message.ts
Normal file
30
website/src/scripts/contact/post-error-message.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { Selectors } from "./selectors";
|
||||
|
||||
export function postErrorMessageOnContactForm(
|
||||
{ contactForm }: Selectors,
|
||||
errorMsg: string,
|
||||
) {
|
||||
const errorElem = contactForm.errorElem();
|
||||
if (errorElem) {
|
||||
errorElem.textContent = errorMsg;
|
||||
errorElem.removeAttribute("hidden");
|
||||
} else {
|
||||
alert(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
export function postErrorMessageOnOtpForm(
|
||||
{ otpDialog }: Selectors,
|
||||
errorMsg: string,
|
||||
) {
|
||||
const errorElem = otpDialog.errorElem();
|
||||
if (errorElem) {
|
||||
errorElem.textContent = errorMsg;
|
||||
errorElem.removeAttribute("hidden");
|
||||
} else {
|
||||
alert(errorMsg);
|
||||
}
|
||||
for (const input of otpDialog.allOtpInputs()) {
|
||||
(input as HTMLInputElement).value = "";
|
||||
}
|
||||
}
|
||||
29
website/src/scripts/contact/resend-otp.ts
Normal file
29
website/src/scripts/contact/resend-otp.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { actions } from "astro:actions";
|
||||
import { resetResendButton } from "./reset-resend-button";
|
||||
import { postErrorMessageOnOtpForm } from "./post-error-message";
|
||||
import type { Selectors } from "./selectors";
|
||||
|
||||
const fallbackErrorMsg =
|
||||
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
|
||||
|
||||
export async function resendOtp(
|
||||
selectors: Selectors,
|
||||
resetButtonInterval: NodeJS.Timeout | undefined,
|
||||
): Promise<Result> {
|
||||
const result = resetResendButton(selectors, resetButtonInterval);
|
||||
const name = selectors.contactForm.nameElem()?.value;
|
||||
const email = selectors.contactForm.emailElem().value;
|
||||
|
||||
const sendOtpResult = await actions.otp.send({ type: "email", name, email });
|
||||
if (sendOtpResult.error) {
|
||||
const errorMsg = sendOtpResult.error?.toString() ?? fallbackErrorMsg;
|
||||
postErrorMessageOnOtpForm(selectors, errorMsg);
|
||||
throw sendOtpResult.error;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
type Result = {
|
||||
resendButtonInterval: NodeJS.Timeout | undefined;
|
||||
};
|
||||
32
website/src/scripts/contact/reset-resend-button.ts
Normal file
32
website/src/scripts/contact/reset-resend-button.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Selectors } from "./selectors";
|
||||
|
||||
export function resetResendButton(
|
||||
{ otpDialog }: Selectors,
|
||||
resendButtonInterval: NodeJS.Timeout | undefined,
|
||||
): Result {
|
||||
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 };
|
||||
}
|
||||
|
||||
type Result = {
|
||||
resendButtonInterval: NodeJS.Timeout | undefined;
|
||||
};
|
||||
27
website/src/scripts/contact/selectors.ts
Normal file
27
website/src/scripts/contact/selectors.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export type Selectors = {
|
||||
contactForm: {
|
||||
emailElem: () => HTMLInputElement;
|
||||
errorElem: () => Element | null;
|
||||
nameElem: () => HTMLInputElement;
|
||||
messageElem: () => HTMLTextAreaElement;
|
||||
self: () => HTMLFormElement | null;
|
||||
submitButton: () => HTMLInputElement | null;
|
||||
};
|
||||
otpDialog: {
|
||||
allOtpInputs: () => NodeListOf<HTMLInputElement>;
|
||||
errorElem: () => Element | null;
|
||||
firstOtpInput: () => HTMLInputElement | null;
|
||||
otpForm: () => HTMLFormElement | null;
|
||||
otpRecipient: () => Element | null;
|
||||
otpValidUntil: () => Element | null;
|
||||
resendButton: () => HTMLButtonElement | null;
|
||||
self: () => HTMLDialogElement;
|
||||
submitButton: () => HTMLInputElement | null;
|
||||
};
|
||||
successSection: {
|
||||
email: () => Element | null;
|
||||
message: () => Element | null;
|
||||
name: () => Element | null;
|
||||
self: () => Element | null;
|
||||
};
|
||||
};
|
||||
53
website/src/scripts/contact/submit-contact-form.ts
Normal file
53
website/src/scripts/contact/submit-contact-form.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { actions } from "astro:actions";
|
||||
import { postErrorMessageOnContactForm } from "./post-error-message";
|
||||
import { resetResendButton } from "./reset-resend-button";
|
||||
import type { Selectors } from "./selectors";
|
||||
|
||||
const fallbackErrorMsg =
|
||||
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
|
||||
|
||||
export async function submitContactForm(
|
||||
selectors: Selectors,
|
||||
resendButtonInterval: NodeJS.Timeout | undefined,
|
||||
): Promise<Result> {
|
||||
const { contactForm, otpDialog } = selectors;
|
||||
|
||||
const name = contactForm.nameElem()?.value;
|
||||
const email = contactForm.emailElem().value;
|
||||
|
||||
contactForm.submitButton()?.setAttribute("disabled", "");
|
||||
const sendOtpResult = await actions.otp.send({ type: "email", name, email });
|
||||
if (sendOtpResult.error) {
|
||||
const errorMsg = sendOtpResult.error?.toString() ?? fallbackErrorMsg;
|
||||
postErrorMessageOnContactForm(selectors, errorMsg);
|
||||
throw sendOtpResult.error;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
type Result = {
|
||||
resendButtonInterval: NodeJS.Timeout | undefined;
|
||||
};
|
||||
72
website/src/scripts/contact/submit-otp-form.ts
Normal file
72
website/src/scripts/contact/submit-otp-form.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { actions } from "astro:actions";
|
||||
import type { Selectors } from "./selectors";
|
||||
import {
|
||||
postErrorMessageOnContactForm,
|
||||
postErrorMessageOnOtpForm,
|
||||
} from "./post-error-message";
|
||||
|
||||
const fallbackErrorMsg =
|
||||
"No can do. I'm afraid joeac.net is a bit broken right now - sorry about that.";
|
||||
|
||||
export async function submitOtpForm(selectors: 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;
|
||||
|
||||
const verifyResult = await actions.otp.verify({ guess, userId: email });
|
||||
if (verifyResult.error) {
|
||||
otpDialog.submitButton()?.removeAttribute("disabled");
|
||||
postErrorMessageOnOtpForm(
|
||||
selectors,
|
||||
verifyResult.error?.toString() ?? fallbackErrorMsg,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!verifyResult.data) {
|
||||
otpDialog.submitButton()?.removeAttribute("disabled");
|
||||
postErrorMessageOnOtpForm(selectors, "Incorrect OTP. Check your email?");
|
||||
otpDialog.firstOtpInput()?.focus();
|
||||
return;
|
||||
}
|
||||
const sendmailToken = verifyResult.data;
|
||||
|
||||
const sendmailResult = await actions.sendmail({
|
||||
email,
|
||||
message: message,
|
||||
name: name,
|
||||
userId: email,
|
||||
token: sendmailToken,
|
||||
});
|
||||
if (sendmailResult.error) {
|
||||
const errorMsg = sendmailResult.error?.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();
|
||||
}
|
||||
Reference in New Issue
Block a user