Skip to content

feat: volunteer self-registration with email confirmation - #955

Open
nadavosa wants to merge 3 commits into
developfrom
nadavosa/954-volunteer-registration
Open

feat: volunteer self-registration with email confirmation#955
nadavosa wants to merge 3 commits into
developfrom
nadavosa/954-volunteer-registration

Conversation

@nadavosa

Copy link
Copy Markdown
Collaborator

Description

Adds a public "Become a volunteer" registration page, mirroring the existing NGO/agent registration screen (/register/agent) but for volunteers. Same account fields (first name, last name, email, password, confirm password, phone, consent), headline "Become a volunteer" instead of "Register your organisation".

No backend or SDK change was needed:

  • POST /user already accepts role: UserRole.VOLUNTEER and self-registers it freely (its pre-handler comment: "Volunteers and users self-register freely", unlike AGENT which requires a matching org email domain).
  • It already sends the verification email for any role.
  • verify-email/[token]/page.tsx already redirects any non-"agent" role straight to /dashboard after verification, so no extra profile-completion step is needed for volunteers.
  • The consent checkboxes' linked pages (Privacy, Need4Deed Guidelines, Voluntary Participation Agreement) already address volunteers directly in their copy, so the same three links apply unchanged.

Related Issues

Closes #954

Changes

  • New route /register/volunteerVolunteerRegistration component, POSTs role: UserRole.VOLUNTEER.
  • Generalized AccountStep and validateStep (under AgentRegistration/) with an optional namespace param (defaults to "agentRegistration", so the existing agent flow is unchanged) instead of duplicating the ~150 lines of identical field markup/validation for a second flow.
  • Reused AgentRegistration's styled.ts primitives and added a volunteerRegistration i18n namespace (en/de) for the copy that does differ (headline, subtitle, success screen).

Verification

  • tsc --noEmit and yarn lint pass.
  • Ran the dev server and confirmed /en/register/volunteer renders the headline, all fields, and the consent links correctly; verified client-side validation triggers on empty submit. Did not exercise the live POST /user call (no backend running locally), that endpoint itself is unchanged.

Checklist

  • WITHIN THE SCOPE OF AN ISSUE; No unnecessary files included
  • Tests added/updated
  • Documentation updated
  • CI passes

Mirrors the NGO/agent registration screen but for volunteers, headline
"Become a volunteer" instead of "Register your organisation". Same
account fields, POSTs role: UserRole.VOLUNTEER to the existing POST
/user endpoint, which already self-registers volunteers freely and
sends the verification email (no BE/SDK change needed).

Generalizes AccountStep/validateStep with a translation-namespace
parameter (default unchanged, so the agent flow is unaffected) instead
of duplicating the step markup and validation logic.
@arturasmckwcz

Copy link
Copy Markdown
Collaborator

Review findings

1. Consent checkbox still shows agent-only legal copy to volunteers (src/components/AgentRegistration/steps/AccountStep.tsx:96)

AccountStep was generalized with a namespace prop and every other label switched to t(\${namespace}...`), but the consent line's guidelines link still uses the non-namespaced homepage.footer.legal.guidelineskey andSubpage.RAC_GUIDELINES— both written for accommodation centers. A volunteer signing up at/register/volunteer` has to check a required legal-consent box reading "...RAC Guidelines..." linking to content addressed to accommodation centers ("request matching", "volunteer screening"), not to volunteers.

2. PageWrapper override duplicated instead of shared (src/components/VolunteerRegistration/VolunteerRegistration.tsx:31)

The min-height: 0; flex: 1 override (and its explanatory comment, which literally says "see the same override in AgentRegistration.tsx") is copy-pasted verbatim rather than extracted into the shared styled.ts next to Wrapper/Card. A future fix to how PageLayout composes with Wrapper now has to be applied in two places and can silently drift.

3. No dedupe check against an existing Volunteer/Person by email (src/components/VolunteerRegistration/VolunteerRegistration.tsx:57)

handleSubmit POSTs straight to /user with no person.id and no prior lookup. On the backend, POST /user (src/server/routes/user.ts ~lines 318-350) only looks up an existing Person when person.id is explicitly passed — otherwise it unconditionally creates a new one, even when a Person with that exact email already exists (e.g. via an existing Volunteer row from the legacy public form). Someone who already submitted the "Become a Volunteer" form and now self-registers here with the same email ends up with a second, disconnected Person/User pair instead of being linked to their existing volunteer profile — silently, with no error surfaced to them or a coordinator.

This third one is a pre-existing backend gap, not something this PR introduces, so I filed it as a standalone follow-up rather than blocking this PR on it: need4deed-org/be#923 (tracked under the epic need4deed-org/be#922).

import { AccountRegistrationData, defaultAccountRegistrationData } from "@/components/AgentRegistration/types";
import Link from "next/link";

const NAMESPACE = "volunteerRegistration";

@DarrellRoberts DarrellRoberts Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems pointless to me as you're not really reusing a component, just duplicating it. Also in the AgentRegistration component you're using the raw string instead
e.g.

<PageTitle>{t("agentRegistration.title")}</PageTitle>

compared to

<PageTitle>{t(`${NAMESPACE}.title`)}</PageTitle>

const [submitError, setSubmitError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with our legacy BecomeVolunteer form found on components/forms/BecomeVolunteer/BecomeVolunteer.tsx, we used tanstack useForm hook which reduced the need for all of these useState

} finally {
setIsSubmitting(false);
}
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 70-99 would be cleaner as a custom hook. We do have a custom usePostRequest hook but it seems a bit outdated (uses fetch API over axios)

min-height: 0;
flex: 1;
`;

@DarrellRoberts DarrellRoberts Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you move this PageWrapper to and export this from @/components/AgentRegistration/styled (like the other styled components) then you don't have to declare it twice such as here & in the AgentRegistration.tsx component

@DarrellRoberts DarrellRoberts left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few code-cleaniless points from me.

Also I imagine we want this component to also be what the "Join as a volunteer" to link to as well?

Image

This links to the legacy BecomeVolunteer form and sits on route: /[lang]/forms/volunteer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: volunteer self-registration with email confirmation

3 participants