1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
---
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 class="warn">
This contact form only accepts a very small number of submissions per day
to avoid spamming my inbox with bots. I have an idea for a workaround, but
in the meantime, please forgive me if the form refuses to accept your
submission. If you want to get in touch with me professionally, you can
message me on <a href="https://www.linkedin.com/in/joe-carstairs-0aa936277">LinkedIn</a>.
</p>
<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>
|