Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
112 lines
3.3 KiB
Plaintext
112 lines
3.3 KiB
Plaintext
---
|
||
type Props = {
|
||
emailId?: string;
|
||
emailLabel?: string;
|
||
placeholder?: string;
|
||
submitLabel?: string;
|
||
joiningLabel?: string;
|
||
successLabel?: string;
|
||
addedLabel?: string;
|
||
errorLabel?: string;
|
||
};
|
||
|
||
const {
|
||
emailId = "waitlist-email",
|
||
emailLabel = "Email address",
|
||
placeholder = "you@company.com",
|
||
submitLabel = "Continue",
|
||
joiningLabel = "Joining…",
|
||
successLabel = "You’re in.",
|
||
addedLabel = "Added",
|
||
errorLabel = "Couldn’t add you. Try again.",
|
||
} = Astro.props;
|
||
---
|
||
|
||
<form
|
||
class="waitlist-form"
|
||
data-waitlist-form
|
||
data-endpoint="/api/waitlist"
|
||
data-joining-label={joiningLabel}
|
||
data-success-label={successLabel}
|
||
data-added-label={addedLabel}
|
||
data-error-label={errorLabel}
|
||
novalidate
|
||
>
|
||
<label class="sr-only" for={emailId}>{emailLabel}</label>
|
||
<input
|
||
id={emailId}
|
||
name="email"
|
||
type="email"
|
||
inputmode="email"
|
||
autocomplete="email"
|
||
autocapitalize="none"
|
||
spellcheck="false"
|
||
maxlength="254"
|
||
placeholder={placeholder}
|
||
required
|
||
/>
|
||
<div class="waitlist-form__trap" hidden>
|
||
<input name="contact-note" type="text" autocomplete="off" tabindex="-1" />
|
||
</div>
|
||
<button class="button button--primary button--small" type="submit" data-submit-label={submitLabel}>
|
||
{submitLabel}
|
||
</button>
|
||
<p class="waitlist-form__status" data-waitlist-status aria-live="polite"></p>
|
||
</form>
|
||
|
||
<script>
|
||
document.querySelectorAll<HTMLFormElement>("[data-waitlist-form]").forEach((form) => {
|
||
const email = form.elements.namedItem("email");
|
||
const contactNote = form.elements.namedItem("contact-note");
|
||
const submit = form.querySelector<HTMLButtonElement>("button[type='submit']");
|
||
const status = form.querySelector<HTMLElement>("[data-waitlist-status]");
|
||
|
||
if (!(email instanceof HTMLInputElement) || !(contactNote instanceof HTMLInputElement) || !submit || !status) {
|
||
return;
|
||
}
|
||
|
||
const submitLabel = submit.dataset.submitLabel || "Continue";
|
||
const joiningLabel = form.dataset.joiningLabel || "Joining…";
|
||
const successLabel = form.dataset.successLabel || "You’re in.";
|
||
const addedLabel = form.dataset.addedLabel || "Added";
|
||
const errorLabel = form.dataset.errorLabel || "Couldn’t add you. Try again.";
|
||
|
||
form.addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
status.classList.remove("is-error", "is-success");
|
||
|
||
if (!email.checkValidity()) {
|
||
email.reportValidity();
|
||
return;
|
||
}
|
||
|
||
const endpoint = form.dataset.endpoint;
|
||
if (!endpoint) return;
|
||
|
||
submit.disabled = true;
|
||
submit.textContent = joiningLabel;
|
||
|
||
try {
|
||
const response = await fetch(endpoint, {
|
||
method: "POST",
|
||
headers: { "content-type": "application/json" },
|
||
body: JSON.stringify({ email: email.value, contactNote: contactNote.value }),
|
||
});
|
||
|
||
if (!response.ok) throw new Error("Waitlist request failed");
|
||
|
||
form.reset();
|
||
status.textContent = successLabel;
|
||
status.classList.add("is-success");
|
||
submit.textContent = addedLabel;
|
||
form.dispatchEvent(new CustomEvent("waitlist:joined", { bubbles: true }));
|
||
} catch {
|
||
status.textContent = errorLabel;
|
||
status.classList.add("is-error");
|
||
submit.disabled = false;
|
||
submit.textContent = submitLabel;
|
||
}
|
||
});
|
||
});
|
||
</script>
|