diff options
| author | Joe Carstairs <me@joeac.net> | 2026-06-04 15:15:21 +0100 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2026-06-04 15:16:34 +0100 |
| commit | 2d6d17261cbe2043a6e59cfbecc2775146f665d2 (patch) | |
| tree | 70c1adb6557ff890cbac63263170c1bc7fccf922 /http/src | |
| parent | 668c105ccf70eaddf0b5798ecaa47c9ff9f34177 (diff) | |
http: adds contact page
Diffstat (limited to 'http/src')
| -rw-r--r-- | http/src/contact.upphtml | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/http/src/contact.upphtml b/http/src/contact.upphtml new file mode 100644 index 0000000..770ec19 --- /dev/null +++ b/http/src/contact.upphtml @@ -0,0 +1,96 @@ +#! +export TITLE="Contact" +export DESCRIPTION="Contact Joe Carstairs" +pp "${SHARE}"/components/meta.upphtml +#! + +#! +pp "${SHARE}"/components/otp-dialog.upphtml +#! + +<form class=contact-form> + <h1>Contact me</h1> + <p hidden class=error></p> + <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 type=module> + import { resendOtp } from '/scripts/contact/resend-otp.js'; + import { submitContactForm } from '/scripts/contact/submit-contact-form.js'; + import { submitOtpForm } from '/scripts/contact/submit-otp-form.js'; + + function locateOrPanic(selector, desc, root) { + const elem = (root ?? document).querySelector(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('form.contact-form', 'contact form'); + const otpDialog = locateOrPanic('dialog.otp-dialog', 'OTP dialog'); + const otpForm = locateOrPanic('form.otp-form', 'OTP form'); + const successSection = document.querySelector('section.success'); + let resendButtonInterval = undefined; + + const selectors = { + contactForm: { + emailElem: () => locateOrPanic('input[name=email]', 'email input', contactForm), + errorElem: () => contactForm.querySelector('.error'), + nameElem: () => locateOrPanic('input[name=name]', 'name input', contactForm), + messageElem: () => locateOrPanic('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('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> |
