Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/site/platform/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ 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',
interestNews: 'Transit news',
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',
Expand Down
62 changes: 55 additions & 7 deletions apps/site/public/scripts/join-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]');
Expand All @@ -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]');
Expand All @@ -40,7 +84,9 @@
backButton,
email,
givenName,
phone,
consent,
errorSummary,
basicHeading,
interestsHeading,
intro,
Expand All @@ -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;
Expand All @@ -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(
Expand Down
10 changes: 9 additions & 1 deletion apps/site/src/components/join/JoinForm.astro
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const { action } = Astro.props;
<TextField
name="email"
label={t('join.emailLabel')}
hint={t('join.emailHint')}
type="email"
autocomplete="email"
required
Expand All @@ -52,11 +53,17 @@ const { action } = Astro.props;
<TextField
name="given_name"
label={t('join.givenNameLabel')}
hint={t('join.givenNameHint')}
autocomplete="given-name"
required
error={t('join.givenNameError')}
/>
<TextField name="family_name" label={t('join.familyNameLabel')} autocomplete="family-name" />
<TextField
name="family_name"
label={t('join.familyNameLabel')}
hint={t('join.familyNameHint')}
autocomplete="family-name"
/>
</div>

<TextField
Expand Down Expand Up @@ -97,6 +104,7 @@ const { action } = Astro.props;
<ChoiceGroup
name="interests"
legend={t('join.interestsHeading')}
hint={t('join.interestsHint')}
type="checkbox"
cards
options={[
Expand Down
9 changes: 5 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading