diff --git a/apps/site/platform/messages/en.ts b/apps/site/platform/messages/en.ts
index 15b48136..645d8129 100644
--- a/apps/site/platform/messages/en.ts
+++ b/apps/site/platform/messages/en.ts
@@ -12,12 +12,16 @@ export const en = {
intro:
"Members get LVBT's updates, invitations to events and a say in what we work on. It's free.",
emailLabel: 'Email address',
+ emailHint: "We'll send your welcome email and LVBT updates here, about 1–2 times a week.",
givenNameLabel: 'First name',
+ givenNameHint: "We'll use this to greet you in your welcome email.",
givenNameError: 'Enter your first name',
familyNameLabel: 'Last name (optional)',
+ familyNameHint: 'Your account will show this with your first name.',
phoneLabel: 'Phone number (optional)',
phoneHint: 'Only for questions about your membership. LVBT updates come by email.',
interestsHeading: 'What are you interested in? (optional)',
+ interestsHint: "We'll use your choices to suggest events and ways to help.",
interestEvents: 'Coming to events',
interestMeetings: 'Speaking up at public meetings',
interestVolunteering: 'Volunteering on a team',
@@ -25,7 +29,7 @@ export const en = {
discordUsernameLabel: 'Discord username (optional)',
discordUsernameHint: 'We can use this later to give you the right roles in our server.',
transportHeading: 'What do you own for getting around? (optional)',
- transportHint: 'Select all that apply, or leave this blank.',
+ transportHint: 'This helps us understand how members get around. You can skip it.',
transportCar: 'Car',
micromobilityBike: 'Bike (traditional)',
micromobilityEbike: 'E-bike',
diff --git a/apps/site/public/scripts/join-steps.js b/apps/site/public/scripts/join-steps.js
index 9ee8cb23..44d133ae 100644
--- a/apps/site/public/scripts/join-steps.js
+++ b/apps/site/public/scripts/join-steps.js
@@ -16,6 +16,48 @@
});
}
+ function validateBasicFields({ form, summary, email, givenName, phone, consent }) {
+ function setFieldError(field, invalid) {
+ const error = form.querySelector(`[data-error-for="${field.id}"]`);
+ const item = summary.querySelector(`[data-summary-for="${field.id}"]`);
+ if (!error || !item) return;
+ error.hidden = !invalid;
+ item.hidden = !invalid;
+ field.toggleAttribute('aria-invalid', invalid);
+ const describedBy = (field.getAttribute('aria-describedby') || '')
+ .split(/\s+/)
+ .filter((id) => id && id !== error.id);
+ if (invalid) describedBy.unshift(error.id);
+ if (describedBy.length) field.setAttribute('aria-describedby', describedBy.join(' '));
+ else field.removeAttribute('aria-describedby');
+ summary.hidden = !summary.querySelector('[data-summary-for]:not([hidden])');
+ }
+
+ for (const field of [email, givenName, phone, consent]) {
+ field.addEventListener(field === consent ? 'change' : 'input', () =>
+ setFieldError(field, false),
+ );
+ }
+
+ return () => {
+ const digits = phone.value.replace(/\D/g, '');
+ const invalid = [
+ [email, !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim())],
+ [givenName, !givenName.value.trim()],
+ [
+ phone,
+ Boolean(phone.value.trim()) &&
+ !(digits.length === 10 || (digits.length === 11 && digits.startsWith('1'))),
+ ],
+ [consent, !consent.checked],
+ ];
+ invalid.forEach(([field, failed]) => setFieldError(field, failed));
+ if (!invalid.some(([, failed]) => failed)) return true;
+ summary.focus();
+ return false;
+ };
+ }
+
function run() {
const form = document.querySelector('form[action="/join/member/"][data-enhance]');
const progress = document.querySelector('[data-join-progress]');
@@ -27,7 +69,9 @@
const backButton = form.querySelector('[data-join-back]');
const email = form.querySelector('[name="email"]');
const givenName = form.querySelector('[name="given_name"]');
+ const phone = form.querySelector('[name="phone"]');
const consent = form.querySelector('[name="consent"]');
+ const errorSummary = document.querySelector('[data-error-summary]');
const items = progress.querySelectorAll('li');
const basicHeading = document.querySelector('[data-join-heading-basic]');
const interestsHeading = document.querySelector('[data-join-heading-interests]');
@@ -40,7 +84,9 @@
backButton,
email,
givenName,
+ phone,
consent,
+ errorSummary,
basicHeading,
interestsHeading,
intro,
@@ -52,6 +98,14 @@
progress.style.display = '';
continueButton.style.display = '';
backButton.style.display = '';
+ const basicFieldsValid = validateBasicFields({
+ form,
+ summary: errorSummary,
+ email,
+ givenName,
+ phone,
+ consent,
+ });
function show(step) {
first.hidden = step !== 1;
@@ -67,16 +121,10 @@
}
function continueToInterests() {
- if (!email.reportValidity()) return;
- if (!givenName.value.trim()) givenName.setCustomValidity('Enter your first name');
- else givenName.setCustomValidity('');
- if (!givenName.reportValidity()) return;
- if (!consent.reportValidity()) return;
- show(2);
+ if (basicFieldsValid()) show(2);
}
second.hidden = true;
- givenName.addEventListener('input', () => givenName.setCustomValidity(''));
continueButton.addEventListener('click', continueToInterests);
backButton.addEventListener('click', () => show(1));
form.addEventListener(
diff --git a/apps/site/src/components/join/JoinForm.astro b/apps/site/src/components/join/JoinForm.astro
index 97d94ea6..ef5d7b55 100644
--- a/apps/site/src/components/join/JoinForm.astro
+++ b/apps/site/src/components/join/JoinForm.astro
@@ -42,6 +42,7 @@ const { action } = Astro.props;
-
+
=8'}
- devalue@5.8.1:
- resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==}
+ devalue@5.9.4:
+ resolution: {integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -7798,7 +7799,7 @@ snapshots:
clsx: 2.1.1
common-ancestor-path: 2.0.0
cookie: 2.0.1
- devalue: 5.8.1
+ devalue: 5.9.4
diff: 9.0.0
dset: 3.1.4
es-module-lexer: 2.1.0
@@ -8430,7 +8431,7 @@ snapshots:
detect-libc@2.1.2: {}
- devalue@5.8.1: {}
+ devalue@5.9.4: {}
devlop@1.1.0:
dependencies:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 68a6eea2..5814738e 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -24,6 +24,7 @@ packageExtensions:
# Temporary transitive pins for published security fixes not yet selected by
# their direct dependents.
overrides:
+ devalue: 5.9.4
sharp: 0.35.4
smol-toml: 1.8.0
svgo: 4.1.0