Events

Events and Registrations

Every registration is tied to an event. The event record controls capacity, waitlisting, pricing, registration questions, and email content.


A registration is stored in biztechRegistrations with:

  • Partition key: id = user email (e.g., "alice@student.ubc.ca")
  • Sort key: eventID;year = semicolon-joined string (e.g., "blueprint;2026")

This means one user can have one registration per event, and all registrations for an event share the same sort key prefix.

biztechRegistrations

├─ PK: alice@student.ubc.ca  SK: blueprint;2026{ registrationStatus: "registered" }
├─ PK: alice@student.ubc.ca  SK: kickstart;2025{ registrationStatus: "checkedIn" }
├─ PK: bob@student.ubc.ca    SK: blueprint;2026{ registrationStatus: "waitlist" }
└─ ...

The event-query GSI reverses this, indexing by eventID;year as the partition key to efficiently list all registrations for one event.


What the Registration Handler Reads from the Event

When updateHelper() processes a registration, the first thing it does is fetch the event:

const existingEvent = await db.getOne(eventID, EVENTS_TABLE, { year })
if (isEmpty(existingEvent))
  throw helpers.notFoundResponse('Event', eventID, year)

Then it uses these fields:

Event FieldHow It's Used
capacCompared with registeredCount to determine if the event is full
registrationQuestionsQuestions with participantCap have per-choice capacity tracked
pricing.membersMember price determines if the event is free or paid
pricing.nonMembersNon-member price for non-BizTech users
enameUsed in email templates (confirmation, waitlist, acceptance emails)
startDate, endDate, elocation, descriptionIncluded in email templates and calendar invite generation
imageUrlUsed as Stripe product image for paid events

Capacity and Waitlisting

The capacity check happens inside updateHelper() in services/registrations/handler.js:

1. Fetch event → get event.capac
2. Call getEventCounts(eventID, year) → count registrations
3. If registeredCount >= event.capacset status to "waitlist"

How getEventCounts() Works

File: services/registrations/helpers.js

  1. Queries the biztechRegistrations table using the event-query GSI with eventID;year
  2. Iterates all registrations, counting by status (excludes partners):
    • registeredCount — status is "registered"
    • checkedInCount — status is "checkedIn"
    • waitlistCount — status is "waitlist"
  3. Returns the counts

Only attendees count toward capacity

Partner registrations (isPartner: true) are excluded from the capacity count. They do not count toward capac and are never waitlisted.

Dynamic Workshop Caps

If an event's registrationQuestions include questions with a participantCap field, getEventCounts() also tallies how many registrants selected each option. This enables per-workshop capacity tracking — though the backend enforcement is currently commented out in updateHelper.


Registration Status Flow

StatusWhen It's SetMeaning
registeredDefault on successful creationUser is confirmed for the event
waitlistWhen registeredCount >= capacEvent is full, user is on waitlist
incompleteAfter Stripe session created but before paymentUser has a checkout link but hasn't paid
checkedInAdmin marks attendance (QR scan or manual)User arrived at the event
cancelledAdmin cancels the registrationRegistration is cancelled
acceptedApplication-based events: admin approvesUser is accepted (paid event: needs payment)
acceptedPendingApplication-based events: accepted + free eventAccepted, no payment needed
acceptedCompleteStripe webhook after accepted user paysAccepted and payment confirmed
rejectedApplication-based events: admin rejectsNot accepted

Application-Based Event Flow

When event.isApplicationBased is true, the registration flow changes:

Register"registered" (pending review)

   ├─ Admin accepts + event is free → "acceptedPending"
   ├─ Admin accepts + event is paid → "accepted" → user pays → "acceptedComplete"
   └─ Admin rejects → "rejected"

The acceptance logic checks pricing:

const pricing = isMember
  ? eventExists.pricing?.members ?? 0
  : eventExists.pricing?.nonMembers ?? 0

if (pricing === 0) {
  data.registrationStatus = 'acceptedPending'
}

Registration Data Structure

Each registration record includes:

FieldTypeSource
idStringUser email (PK)
eventID;yearStringEvent key (SK)
registrationStatusStringOne of the statuses above
isPartnerBooleantrue for partner registrations
dynamicResponsesObjectAnswers to event registrationQuestions, keyed by questionId
pointsNumberPoints earned at the event
checkoutLinkStringStripe checkout URL (paid events, pre-payment)
createdAtNumberRegistration timestamp

Dynamic Responses

When a user fills out the registration form, their answers to registrationQuestions are stored in dynamicResponses:

{
  "dynamicResponses": {
    "q-why-attend": "I want to learn about product design",
    "q-workshop": "UX Research"
  }
}

The keys match the questionId values from event.registrationQuestions.


Frontend Registration Form

Attendees: src/components/Events/AttendeeEventRegistrationForm.tsx Partners: src/components/Events/PartnerEventRegistrationForm.tsx Page: src/pages/event/[eventId]/[year]/register/index.tsx

The registration form is built from two sources:

  1. Standard fields (hardcoded): email, first name, last name, year level, faculty, major, pronouns, dietary restrictions, "how did you hear about us"
  2. Dynamic fields (from event.registrationQuestions): rendered based on question typeTEXT, SELECT, CHECKBOX, UPLOAD, WORKSHOP_SELECTION, SKILLS

Registration Strategy

The registration page uses a strategy pattern (RegistrationStateOld) to handle different event states (upcoming, current, past, sold out) with different UI states.


Admin Dashboard: Viewing Registrations

Endpoint: GET /events/{id}/{year}?users=true Handler: services/events/handler.jsget

When the admin dashboard fetches event data with ?users=true:

  1. Scans all registrations for the event via the event-query GSI
  2. Collects all unique user emails
  3. Batch-gets user records from biztechUsers (up to 100 per batch)
  4. Merges each user record with their registrationStatus
  5. Returns the enriched list in the response

This powers the Data Table tab in the admin event dashboard, showing all registrants with their profile data and registration status.


Key Files

FilePurpose
services/registrations/handler.jsRegistration CRUD, updateHelper() with capacity check
services/registrations/helpers.jsgetEventCounts()
services/events/handler.jsget?users=true for admin dashboard
src/pages/event/[eventId]/[year]/register/index.tsxAttendee registration page
src/pages/event/[eventId]/[year]/register/partner/index.tsxPartner registration page
src/components/Events/AttendeeEventRegistrationForm.tsxAttendee registration form
src/components/Events/PartnerEventRegistrationForm.tsxPartner registration form
src/queries/registrations.tsRegistration fetch hooks

Previous
Image Upload