Files
hope-events/docs/FEATURES-ROUND-1.md
T
joshuaandClaude Opus 5.5 6010641629 Pick sessions (frontend), roadmap and handover docs
Event wizard: how people book (whole course / pick sessions), capacity
per session and per-option session mode. Session picker on the public
registration page, Manual registration and the kiosk; at the door points
to Manual registration. Register, tickets and My Events handle picked
sessions. Adds docs/ROADMAP-v2.md and docs/HANDOVER.md so work can carry
on from another machine, and a pick-sessions e2e test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:25:21 +02:00

20 KiB
Raw Blame History

Hope Events — New Features

Planned features for the next round of development. Each section covers what the feature does, who uses it, how it fits the existing platform, and the edge cases to handle. See FEATURES.md for what already exists.


Contents

  1. Coupon codes
  2. Waitlist
  3. Group tickets
  4. Offline scanning
  5. Parental consent (youth events)
  6. Sponsor a spot
  7. Ticket transfer
  8. Post-event feedback form
  9. Group / room allocation
  10. Registration funnel stats
  11. 2FA for Admins and Supervisors
  12. Admin "impersonate user"
  13. Suggested build order

1. Coupon codes

Discount codes applied at registration. The main use case is staff registering for events at a reduced or free rate, but codes also work for promos, leaders, and volunteers.

Who: Supervisors create and manage codes. Users enter them at checkout. Supervisors can also apply them in Manual registration, At the door and the kiosk.

Code settings

Setting Options
Code Unique text, case-insensitive (e.g. STAFF50)
Discount type Percentage · Fixed amount · Comp (100% free)
Scope Whole event · Specific ticket option(s) · Specific variant(s)
Events One event, several events, or all events
Valid from / until Optional date window
Max total uses Optional
Max uses per user Optional (default 1)
Min quantity Optional (e.g. 4+ tickets)
Restricted to Anyone · Specific users · Roles (e.g. Staff and above) · Email domain
Stack with early-bird Yes (apply on top of early-bird price) / No (best price wins)
Active On / off toggle

How it works

  • The user enters a code on the registration page. It is validated server-side and the discount shows as its own line before payment.
  • The discount is stored as a separate discount line on the registration, with its value locked at apply time, the same way early-bird tranches are locked. Pricing refresh never re-prices an applied coupon, even if the code later expires or is disabled.
  • Restricted codes (by user, role or domain) need the user to be logged in. Guest registration can't use them. This stops staff codes leaking in group chats.
  • Role-based staff pricing (optional): a ticket option can also carry a "Staff price" that applies automatically for Staff+ users without a code.

Edge cases

  • Edit options: percentage codes recalculate over the new item total. Fixed-amount codes stay fixed. Comp codes cover only in-scope options.
  • Cancellation / refund: a cancelled registration releases its use count.
  • Discount can't go below R0. A comp code on a fully covered registration marks it Paid and generates tickets.
  • Concurrency: check use limits inside a transaction so the last use can't be claimed twice.

Plumbing

  • Invoices and receipts show the code and discount line.
  • New report: Discounts. Uses per code, total value given away, per event.
  • Finance and profit reports subtract discounts from expected revenue.
  • Audit log: code created, edited, disabled, and manually applied by staff.

Data model (sketch)

model Coupon {
  id               String   @id @default(cuid())
  code             String   @unique
  type             CouponType      // PERCENT | FIXED | COMP
  value            Decimal?
  stackEarlyBird   Boolean  @default(false)
  validFrom        DateTime?
  validUntil       DateTime?
  maxUses          Int?
  maxUsesPerUser   Int?     @default(1)
  minQuantity      Int?
  restrictRoles    Role[]
  restrictDomain   String?
  active           Boolean  @default(true)
  events           CouponEvent[]
  options          CouponOption[]
  allowedUsers     CouponUser[]
  redemptions      CouponRedemption[]
  createdById      String
  createdAt        DateTime @default(now())
}

model CouponRedemption {
  id              String   @id @default(cuid())
  couponId        String
  registrationId  String
  userId          String
  discountAmount  Decimal          // locked at apply time
  appliedById     String?          // staff member if applied manually
  createdAt       DateTime @default(now())
  releasedAt      DateTime?        // set on cancellation
}

2. Waitlist

When an event or ticket option is sold out, users can join a waitlist instead of hitting a dead end.

Who: Users join. Supervisors view and manage the queue.

How it works

  • Sold-out options show a "Join waitlist" button instead of "Sold Out".
  • The user picks an option, variant and quantity. Login is required if the event requires login. Otherwise a guest account is created as usual.
  • When stock frees up (a cancellation, a refund, or a supervisor raising the stock limit), the next person in line gets an email/WhatsApp with a claim link.
  • The claim link reserves the stock for a set time (default 24 h, set per event). If it isn't used in time, the offer moves to the next person.
  • A claimed offer goes through normal registration and payment, with early-bird and coupon rules applied as usual.

Supervisor tools

  • A waitlist tab per event shows position, person, option, quantity, joined date and offer status.
  • Supervisors can manually offer a spot to someone, reorder the queue, or remove an entry.
  • Supervisors can toggle the waitlist on or off per event.

Edge cases

  • Reserved-but-unclaimed stock counts as sold until the offer expires, so the public site doesn't oversell.
  • If an offer is for quantity 3 and only 2 free up, offer the 2 with a note, or skip to the next person who fits. This is configurable per event.
  • Users can leave the waitlist from their dashboard.
  • Add the waitlist to the Daily summary (count per event).

3. Group tickets

One payer registers several named attendees, each with their own ticket and their own form response. For example, a parent registers three kids for camp.

This is optional. The payer can still choose to receive all tickets themselves.

How it works

  • On the registration page, a toggle reads "Registering for other people?"
  • For each attendee: name, plus optional email/phone, plus the event form if there is one.
  • Ticket delivery choice per registration:
    • Send all tickets to me (default), so the payer gets every ticket.
    • Send each person their own ticket, using their email/WhatsApp. If an attendee has no contact details, their ticket falls back to the payer.
  • Tickets are individual, named tickets rather than a quantity ticket, so each one can be checked in, transferred or allocated to a room/group separately.
  • The payer stays the account owner for billing, invoices and payments.

Edge cases

  • Works alongside the existing quantity tickets, since an event can allow either.
  • If an attendee's email/phone matches an existing account, link the ticket to that account (with the same duplicate-account protection as today).
  • Attendee forms: each attendee has their own response. The payer can fill them in, or each attendee can get a link to fill in their own.
  • Feeds into parental consent (one consent request per minor attendee) and group/room allocation.

4. Offline scanning

Lets the scanner keep working when venue Wi-Fi or mobile data drops.

⚠️ Rules: offline mode is per section, and only one device can scan a section offline at a time. While offline, devices can't see each other's scans. If two devices scanned the same section offline, the same ticket could be redeemed twice. Locking each section to a single device removes that risk without needing conflict resolution.

How it works

  1. Before going offline (while online): a staff member picks an event and section and taps "Enable offline mode".
  2. The server locks that section to this device and records the device ID and the staff member. Other devices see "Offline-locked by [name] on [device]" and can't scan that section, online or offline, until it's released.
  3. The device downloads a snapshot of every ticket valid for that section (ticket ID, holder name, type, quantity remaining) into IndexedDB.
  4. While offline: scans validate against the local snapshot and update remaining quantities locally. Every scan is queued with a timestamp.
  5. When back online: queued scans sync to the server automatically. Any conflicts (for example, a ticket transferred or cancelled while offline) show in a sync report for a supervisor to review.
  6. Staff tap "Release section" to end offline mode and unlock the section.

Rules and edge cases

  • One section can only be locked to one device at a time. Other sections of the same event can be locked to other devices.
  • A supervisor can force-release a lock if a phone dies or goes missing. Unsynced scans on that device are lost unless the device reconnects later, in which case they sync and are flagged.
  • Snapshot age: show "Ticket list downloaded at 17:42". Tickets bought after that time won't be on the device, and the scanner shows "Not found in offline list. Check at the door desk."
  • The snapshot is stored only for the lock's duration and cleared on release.
  • Check-in confirmation messages to holders are sent after sync, not instantly.
  • Audit log: lock, release, force-release, and sync with scan count.
  • Built as a PWA (service worker caches the scanner page and assets).

For youth events, a parent or guardian must give consent before an attendee under 18 can attend. The parent gets an email and a WhatsApp with a link to review and give consent.

Event setup

  • New event setting: "Youth event: requires parental consent".
  • Supervisors write the consent text (indemnity, activities, photo permission, transport, medical treatment authorisation) in the event wizard.
  • Optional extra sections on the consent page: medical info (allergies, medication, conditions, medical aid), emergency contact, and photo consent (Yes/No, recorded separately).

How it works

  1. During registration, each attendee gives their date of birth. If they are under 18, parent/guardian name, email and phone are required.
  2. On registration, the parent gets an email and a WhatsApp (always both, regardless of anyone's notification preference) with a unique, secure consent link.
  3. The consent page shows the event details, attendee name and consent text. The parent fills in any medical/emergency fields, types their full name as a signature, ticks "I am the parent/legal guardian", and taps Give consent (or Decline).
  4. The system records the timestamp, IP address, device, typed name and the version of the consent text they agreed to.
  5. The registrant and the event's notify recipients get a confirmation.

Rules

  • Tickets are generated but marked "Consent pending" until consent is given. Scanners show a clear warning and block check-in for that ticket (supervisors can override, with an audit log entry).
  • Reminders: automatic reminder email/WhatsApp after 2 days, then every 3 days until the event, or until consent is given or declined.
  • Declined consent: notifies the registrant and supervisors. The registration stays but can't be checked in. A supervisor handles the refund.
  • Consent links expire at the event's end date, and can be resent from the registration detail screen.
  • If the consent text is edited after some parents have consented, they keep their record of the old version. Supervisors can choose to re-request consent.

Supervisor tools

  • Consent status column on registrations and At the door (Pending / Given / Declined).
  • New reports: Consent status per event, and Medical & emergency list (printable, for camp leaders). Access to the medical list is restricted to Supervisor+ and logged.
  • Bulk "Resend consent request" to everyone still pending.

Privacy

Under POPIA, processing a child's personal information needs the consent of a competent person (parent/guardian). Medical data is special personal information. Store it encrypted, restrict access, include it in the existing anonymise/delete flows, and delete it a set period after the event.


6. Sponsor a spot

A public donation flow linked to a specific event, so people can fund a spot for someone who can't afford it (bursaries).

How it works

  • A new event setting: "Allow sponsorships". This adds a "Sponsor a spot" button on the event page.
  • Sponsors choose: one full spot (the main ticket price), multiple spots, or a custom amount. Payment goes through Yoco as usual.
  • A sponsor can be anonymous, or add a name and message.
  • The money is recorded as a donation tagged to the event, and lands in a sponsorship pool for that event.
  • Supervisors assign pool funds to registrations using the existing donation assign/unassign flow, fully or partly.
  • An optional public progress bar shows "12 of 20 spots sponsored".

Plumbing

  • Sponsors get a receipt. They can optionally get a thank-you after the event through Automations.
  • The Donations breakdown report gains a sponsorship pool column (received, assigned, remaining) per event.
  • Recipients' identities are never shown to sponsors.

7. Ticket transfer

Users can hand their ticket to someone else before the event.

How it works

  • From My Events, a user taps Transfer on a ticket, and enters the new holder's name and email/phone.
  • The new holder gets the ticket by email/WhatsApp. If they don't have an account, an inactive one is created with an activation link (same as guest registration).
  • The old QR code is invalidated and a new one is issued, so screenshots of the old ticket won't scan.
  • The original payer stays responsible for the registration (billing doesn't move).

Rules

  • Allowed only before the event starts, and only for unredeemed tickets (partially redeemed quantity tickets can't be transferred).
  • Can be disabled per event. Supervisors can always transfer.
  • If the event needs a form or parental consent, the new holder must complete their own.
  • Audit log: transfer from, to, by whom and when. Offline scanning sync flags any scan of a transferred ticket's old code.

8. Post-event feedback form

An automatic survey sent after the event.

How it works

  • In the event wizard, a new Feedback step reuses the form builder, plus new field types: Rating (1–5 stars) and Multiple choice.
  • Supervisors pick a send time (e.g. the morning after the event ends). It's sent by the existing scheduled job to everyone who checked in, or to all registrants, as chosen.
  • Responses can be anonymous or named (per event setting).
  • One automatic reminder after 3 days for anyone who hasn't responded.

Reporting

  • New report: Feedback summary. Response rate, average rating per question, multiple-choice breakdown (bar charts), and a list of text answers. Exports to PDF/Excel like other reports.
  • Ratings can be compared across events (e.g. Camp 2025 vs Camp 2026).

9. Group / room allocation

Assign attendees to cabins, rooms, small groups, teams or buses. Built for camps and lockdowns.

How it works

  • Per event, supervisors create allocation sets, e.g. "Rooms", "Small groups", "Buses". An event can have several sets.
  • Each set has groups with an optional capacity, an optional gender restriction and an optional leader (a staff user).
  • Drag-and-drop board: unassigned attendees on the left, groups on the right, with capacity counters.
  • Auto-allocate: fill groups evenly, respecting capacity and gender, and optionally keeping registrations together (siblings) or splitting them (friends).
  • Works per attendee, so it relies on group tickets (named attendees).

Outputs

  • Printable lists per group (with leader), and a master list.
  • Optionally show the group/room on the attendee's ticket and in My Events.
  • Group leaders (staff) can see their group's list, including medical info if they're allowed to.
  • Bulk message by group ("Bus 2 leaves at 07:30").

10. Registration funnel stats

Shows where people drop off between seeing an event and paying.

Funnel stages (per event)

  1. Event page views (unique visitors)
  2. Registration started (opened the registration page)
  3. Registration submitted
  4. Paid (fully paid or free)

How it works

  • Lightweight first-party tracking (no third-party analytics). Log page views and stage events with an anonymous session ID. No personal data until the user logs in or registers.
  • New report: Registration funnel. Count and conversion % at each stage, over a date range, with a daily trend chart.
  • Optional breakdowns: by traffic source (UTM tags or short link alias, e.g. /camp-2025 vs direct), by device (mobile/desktop), and by ticket option.
  • A dashboard tile shows conversion % for active events.

Privacy

Respect cookie consent. Aggregate raw page-view data after 90 days.


11. 2FA for Admins and Supervisors

Two-factor authentication, required for Admin and Supervisor accounts.

How it works

  • Method: authenticator app (TOTP, e.g. Google Authenticator, Microsoft Authenticator). WhatsApp code can be offered as a backup method since WAWP is already set up.
  • Enrolment: on the next login after being given the Supervisor/Admin role, the user must set up 2FA by scanning a QR code and confirming a code.
  • Recovery codes: 10 one-time codes shown once at setup, downloadable.
  • Remember this device for 30 days (optional, per login).
  • Optional for Staff and User roles, from Profile & Security.

Admin controls

  • Admins can reset another user's 2FA (e.g. a lost phone). This revokes their sessions and forces re-enrolment.
  • Security event log entries: 2FA enabled, disabled, reset, recovery code used, failed code attempts.
  • Failed 2FA attempts count toward the existing login lockout.
  • The setup wizard enrols the first admin in 2FA.

12. Admin "impersonate user"

Lets an Admin see the site exactly as a specific user sees it, to debug problems ("my ticket isn't showing").

How it works

  • From Manage users, an Admin taps Impersonate on a user and must enter a reason (required).
  • A short-lived session is created (max 30 minutes) that acts as the user.
  • A bright banner on every page: "Viewing as [Name]. [End impersonation]".

Safety rules

  • Read-only by default. Payments, refunds, cancellations, password/email changes, deleting data, giving consent and sending messages are blocked while impersonating.
  • Can't impersonate another Admin.
  • Requires 2FA to be active on the admin's account.
  • Audit log: start, end, reason, and every page/action during the session.
  • Optional: notify the user that an admin viewed their account (an organisation-level setting).

Suggested build order

Phase Features Why
1 2FA, Coupon codes Security first. Coupons are self-contained and give an immediate win for staff.
2 Group tickets Foundation for consent, transfer and room allocation (named attendees).
3 Parental consent, Waitlist The biggest wins for youth events.
4 Offline scanning, Ticket transfer Door-day reliability. Transfer must exist before offline sync handles it.
5 Group/room allocation, Sponsor a spot Camp-specific and bursary support.
6 Feedback form, Funnel stats, Impersonate user Insights and support tooling.

Each phase should add matching help content (frontend/src/content/help/), audit log entries, a CHANGELOG entry, and updates to FEATURES.md and SETTINGS.md.