summaryrefslogtreecommitdiff
path: root/website/src/pages/contact.astro
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2025-12-18 11:04:03 +0000
committerJoe Carstairs <me@joeac.net>2025-12-18 11:04:03 +0000
commit46c9b7731622f1f4d0f6a03c3179e747e0ce065d (patch)
tree71c60d9cf72797b06054a61990f7baa41db2e8a0 /website/src/pages/contact.astro
parent40d6c7f248c3fe506690ee3ccb3894f952e164ed (diff)
contact page
Diffstat (limited to 'website/src/pages/contact.astro')
-rw-r--r--website/src/pages/contact.astro96
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>