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

507 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](./FEATURES.md) for what already exists.
---
## Contents
1. [Coupon codes](#1-coupon-codes)
2. [Waitlist](#2-waitlist)
3. [Group tickets](#3-group-tickets)
4. [Offline scanning](#4-offline-scanning)
5. [Parental consent (youth events)](#5-parental-consent-youth-events)
6. [Sponsor a spot](#6-sponsor-a-spot)
7. [Ticket transfer](#7-ticket-transfer)
8. [Post-event feedback form](#8-post-event-feedback-form)
9. [Group / room allocation](#9-group--room-allocation)
10. [Registration funnel stats](#10-registration-funnel-stats)
11. [2FA for Admins and Supervisors](#11-2fa-for-admins-and-supervisors)
12. [Admin "impersonate user"](#12-admin-impersonate-user)
13. [Suggested build order](#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)
```prisma
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).
---
## 5. Parental consent (youth events)
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.