Skip to content

feat: Create member registration form structure and flow #39

Open
FinleyNeilson wants to merge 16 commits into
mainfrom
forms
Open

feat: Create member registration form structure and flow #39
FinleyNeilson wants to merge 16 commits into
mainfrom
forms

Conversation

@FinleyNeilson
Copy link
Copy Markdown
Contributor

This PR establishes the technical foundation for the LUG@UoA registration process. It focuses on the underlying Server Action architecture and the creation of the page skeletons that mirror the original Google Form flow.

Closes #37
Closes #38

FinleyNeilson and others added 8 commits April 26, 2026 15:57
add form pages with information from original member registration form
add branching logic for each page depending on user selections
also start refactor for client side checking
- Add RegistrationForm file that was accidently left out of previous commit
- Rename 'form' directory to 'registration'
Copy link
Copy Markdown
Collaborator

@WilliamTayNZ WilliamTayNZ left a comment

Choose a reason for hiding this comment

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

Good first prototype, form workflow generally works

Copy link
Copy Markdown
Collaborator

@WilliamTayNZ WilliamTayNZ left a comment

Choose a reason for hiding this comment

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

Remove package-lock.json, we are using PNPM ONLY
(Finley, please follow the instructions for repo conventions!)

Copy link
Copy Markdown
Collaborator

@WilliamTayNZ WilliamTayNZ left a comment

Choose a reason for hiding this comment

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

There were mistakes in the original schema, so some field names/values will need to change when i share the new one, e.g. skill/involvement will be linuxSkillLevel/potentialInvolvement.

Comment thread src/app/registration/page.tsx
defaultValue={state?.fields?.email || ""} // This is what prevents the clearing
className={`border p-2 w-full ${state?.error?.includes("email") ? "border-red-500" : "border-gray-300"}`}
/>
{state?.error?.includes("email") && (
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.

Seems like no other page in pages handles errors right now? I think you can just put a generic error message in RegistrationForm for now, so errors at any step will be visible

Comment thread src/app/registration/pages/NewOtherPage.tsx Outdated
Comment thread src/app/registration/pages/NewNonUoaPage.tsx
FinleyNeilson and others added 2 commits May 4, 2026 18:35
- Sync field names and radio/checkbox values with new Prisma Enums
  (LinuxSkillLevel, PotentialInvolvement, YearLevel).
- Implement generic error handling in RegistrationForm
- Improve accessibility by replacing generic <div>/ <p> structures
  with semantic <fieldset>, <legend>, and properly linked <label> tags.
- Remove redundant hidden "page" inputs from individual page components.
- Update UI components to use <textarea> for longer inputs.
@FinleyNeilson FinleyNeilson requested a review from WilliamTayNZ May 4, 2026 10:02
@WilliamTayNZ
Copy link
Copy Markdown
Collaborator

WilliamTayNZ commented May 11, 2026

Good improvements!

  • The form is using real HTML input types now, like type="text" and type="email", instead of custom input types

  • Inputs have more accessible labels

  • Radio and checkbox groups are wrapped with fieldset/legend, giving each group a clear accessible question

  • Checkbox groups like faculty and potentialInvolvement use the same name for each option, which means the server can read all selected values with formData.getAll(checkboxGroupName).

  • The generic error in RegistrationForm resolves my earlier comment, so now errors at any step are visible.

@WilliamTayNZ
Copy link
Copy Markdown
Collaborator

Server action: suggested changes

  1. Good job implementing the generic error - perhaps we can now try to make the error messages more specific to which error occurred?

  2. I think we should split the current FormState concept into two clearer types:

  • RegistrationDraft: the saved in-progress form data stored in the cookie
  • RegistrationFormState: the temporary server action state used for errors / failed submissions

Right now FormState is kinda doing both jobs at once, which is messy.

First, we should define the possible form pages:

type RegistrationPage =
  | "start"
  | "returningUoa"
  | "newMember"
  | "newUoa"
  | "newNonUoa" // change this from "newOther", the page name asw
  | "final";

Then the cookie draft state could look like this:

type RegistrationDraft = {
  page: RegistrationPage;

  // Start page
  email?: string;
  isConditionalReturningMember?: string;

  // New member page
  firstName?: string;
  lastName?: string;
  isCurrentUoaStudent?: string;

  // Returning/current UoA fields
  upi?: string;
  studentId?: string;

  // Current UoA only
  faculty?: string[];
  programme?: string;
  yearLevel?: string;

  // Non-UoA only
  primaryAffiliation?: string;
  nonUoaExcerpt?: string;
  nonUoaPitch?: string;

  // Final page
  linuxSkillLevel?: string;
  potentialInvolvement?: string[];
  discordUsername?: string;
};

This is separate from the final validated domain type. It's just the partially completed multi-step form state that lets us preserve answers between steps and submit the full form at the end.

The useActionState return state should be smaller and only represent temporary action feedback, like errors after a failed submit. Something like:

type RegistrationFormState = {
  error?: string;
  fields?: Partial<RegistrationDraft>;
} | null

Partial<RegistrationDraft> means all the RegistrationDraft fields are optional, so the action state doesn't need to contain values for all of them. This is useful because the full registration draft should already be preserved in the cookie, so RegistrationFormState.fields only needs to hold the fields from the current page (where the submission failed) so they can be re-rendered to the user

@WilliamTayNZ
Copy link
Copy Markdown
Collaborator

Cookie implementation

For the cookie state, the main purpose is to preserve the user’s draft between steps, supporting back/forward navigation, and making sure the final submit has all the data from earlier steps.

The current approach is close, but we shouldn't use Object.fromEntries(formData) to save drafts, as for checkbox groups, it will only take the last value if there are multiple values (for example if the user selected 5 faculty options, it will only take 1)

Instead, at each step, collect fields explicitly using formData.get(...) for single-value fields and formData.getAll(...) for checkbox groups. Then merge that step's data into the existing draft cookie.

We should also safely parse the cookie with try/catch, because JSON.parse(cookie) can throw an error if the cookie is malformed or stale.

Also, with secure: true the browser may not be able to send the cookie to the server in local dev, since it means "only send this cookie over HTTPS", but localhost uses HTTP. So change it to: secure: process.env.NODE_ENV === "production" . We don't need to explicitly set this environment variable, since Node/Next normally sets this variable based on the command that was ran (for example, pnpm dev runs in development mode, not production)

Ideal flow:
Each step submit validates that step
Save that step’s fields into the draft cookie
Redirect to the next step
Render each step using saved draft data as default values
Final submit merges the final step fields with the saved draft
Server-side code runs the full validation and creates the member
Clear the cookie
Redirect to success

I also think we should implement the back-navigation now, because it'll make it much easier to verify that the cookie draft state is actually being saved and restored correctly

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.

[frontend] create form skeletons that mirror the LUG registration form [frontend] create server action-driven architecture

3 participants