diff options
| author | Joe Carstairs <me@joeac.net> | 2025-12-18 11:04:03 +0000 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2025-12-18 11:04:03 +0000 |
| commit | 46c9b7731622f1f4d0f6a03c3179e747e0ce065d (patch) | |
| tree | 71c60d9cf72797b06054a61990f7baa41db2e8a0 /website/src/pages/contact.astro | |
| parent | 40d6c7f248c3fe506690ee3ccb3894f952e164ed (diff) | |
contact page
Diffstat (limited to 'website/src/pages/contact.astro')
| -rw-r--r-- | website/src/pages/contact.astro | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/website/src/pages/contact.astro b/website/src/pages/contact.astro new file mode 100644 index 0000000..b03436c --- /dev/null +++ b/website/src/pages/contact.astro @@ -0,0 +1,96 @@ +--- +import OtpDialog from "../components/OtpDialog.astro"; +import Page from "../layouts/Page.astro"; +--- + +<Page title="Contact" description="Contact Joe Carstairs"> + <OtpDialog /> + + <form class="contact-form"> + <h1>Contact me</h1> + <p hidden class="error"/> + <label for="name">Name</label> + <input id="name" name="name" type="text" required> + <label for="email">Email</label> + <input id="email" name="email" type="email" required> + <label for="message">Message</label> + <textarea id="message" name="message" required></textarea> + <input type="submit" value="Send"> + </form> + + <section class="success" hidden="true"> + <h1>You sent me a message!</h1> + <p>Thanks for that. I may be in touch.</p> + <p>In case you forgot, your message was this:</p> + <hr> + <dl> + <dt>Name</dt> <dd class="sentname">???</dd> + <dt>Email</dt> <dd class="sentemail">???</dd> + <dt>Message</dt> <dd class="sentmessage">???</dd> + </dl> + </section> + + <script> + import { resendOtp } from '../scripts/contact/resend-otp'; + import { submitContactForm } from '../scripts/contact/submit-contact-form'; + import { submitOtpForm } from '../scripts/contact/submit-otp-form'; + import type { Selectors } from '../scripts/contact/selectors'; + + function locateOrPanic<T extends Element>(selector: string, desc: string, root?: Element): T { + const elem = (root ?? document).querySelector<T>(selector); + if (!elem) { + alert(`Technical error: could not locate ${desc}. Please let Joe know if you have another means of contacting him.`); + throw new Error(`Could not locate ${desc}`); + } + return elem; + } + + const contactForm = locateOrPanic<HTMLFormElement>('form.contact-form', 'contact form'); + const otpDialog = locateOrPanic<HTMLDialogElement>('dialog.otp-dialog', 'OTP dialog'); + const otpForm = locateOrPanic<HTMLFormElement>('form.otp-form', 'OTP form'); + const successSection = document.querySelector('section.success'); + let resendButtonInterval: undefined | NodeJS.Timeout = undefined; + + const selectors: Selectors = { + contactForm: { + emailElem: () => locateOrPanic<HTMLInputElement>('input[name="email"]', 'email input', contactForm), + errorElem: () => contactForm.querySelector('.error'), + nameElem: () => locateOrPanic<HTMLInputElement>('input[name="name"]', 'name input', contactForm), + messageElem: () => locateOrPanic<HTMLTextAreaElement>('textarea[name="message"]', 'message textarea', contactForm), + self: () => contactForm, + submitButton: () => contactForm.querySelector('input[type="submit"]'), + }, + otpDialog: { + allOtpInputs: () => otpForm.querySelectorAll('input:not([type="submit"])'), + errorElem: () => otpDialog.querySelector('.error'), + firstOtpInput: () => otpForm.querySelector('input:not([type="submit"]):first-child'), + otpForm: () => otpForm, + otpRecipient: () => otpDialog.querySelector('.otp-recipient'), + otpValidUntil: () => otpDialog.querySelector('.otp-valid-until'), + resendButton: () => otpDialog.querySelector<HTMLButtonElement>('button.resend-button'), + self: () => otpDialog, + submitButton: () => otpForm.querySelector('input[type="submit"]'), + }, + successSection: { + email: () => successSection?.querySelector('.sentemail') ?? null, + name: () => successSection?.querySelector('.sentname') ?? null, + message: () => successSection?.querySelector('.sentmessage') ?? null, + self: () => successSection, + } + }; + + contactForm.addEventListener('submit', async (event) => { + event.preventDefault(); + ({ resendButtonInterval } = await submitContactForm(selectors, resendButtonInterval)); + }); + + selectors.otpDialog.resendButton()?.addEventListener('click', async () => { + ({ resendButtonInterval } = await resendOtp(selectors, resendButtonInterval)); + }); + + otpForm.addEventListener('submit', async (event) => { + event.preventDefault(); + await submitOtpForm(selectors); + }); + </script> +</Page> |
