Transfers now send the new holder a link (to the email/phone typed) and only complete once they accept; they can decline and the sender can cancel. No response reveals whether contact details belong to an account, and attendee account links are no longer sent to payers. Terms of Use and Privacy Policy are updated for all features since 1.12, with a full cookie policy and a "Cookie settings" control. The /docs route list now covers every mounted route, and account activation auto-login is fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CP62L9JxHomA36aYP1ekCe
29 KiB
Hope Events — Feature Overview
A complete map of what the platform does, grouped by who uses it. For setup and deployment see README.md; for the API see backend/API_DOCUMENTATION.md; for setting defaults see SETTINGS.md; for history see CHANGELOG.md.
Stack: Next.js (React, Tailwind, shadcn/ui) frontend · Express + Prisma + PostgreSQL backend · Yoco for card payments · SMTP for email · WAWP for WhatsApp · PM2 for process management · optional Sentry for error monitoring.
Contents
- Roles
- Public site
- Events
- Registration
- Payments, donations & refunds
- Tickets & check-in
- Attendee forms
- Messaging & notifications
- User dashboard (My Events)
- Staff tools
- Supervisor tools
- Admin tools
- Cashup & event costs
- Reports
- Site settings & setup wizard
- Accounts & security
- Help system
- Background jobs
- Operations
- Page map
Roles
Four roles, each including everything the roles below it can do:
| Role | Can do |
|---|---|
| User | Browse events, register, pay, donate, view tickets, fill in forms, manage their own profile |
| Staff | Scan tickets, view/print event tickets, view registrations and form responses |
| Supervisor | Create and manage events, record payments/refunds, assign donations, at-the-door desk, manual registration, bulk email/WhatsApp, reports, site banner, self-service kiosk |
| Admin | Manage users and roles, all registrations, event costs and cashup (close/reopen events), audit log, site settings, WhatsApp connection, backups |
Public site
- Home page — hero section with the organisation's name and branding, upcoming and in-progress events, and a "Join Us" button (becomes "My Dashboard" when logged in).
- Events list (
/events) — event cards with picture, dates, location, price, and a Register / Sold Out / Contact us button. - Event detail (
/events/[id]) — description, ticket options with variant and early-bird pricing, low-stock and sold-out badges, downloadable attachments, embedded Google Map with "Directions" link, and an Add to calendar (.ics) button. - Short links — events can have an alias (e.g.
/camp-2025) that redirects to the event. Only plausible aliases hit the database, so bot probes are rejected cheaply. - Contact page (
/contact) — organisation email, phone, address and map. - Legal pages — Terms of Use and Privacy Policy, filled from Site Settings.
- Site banner — a site-wide announcement bar set by supervisors.
- Lockdown rules (
/lockdown-rules) — static rules page for the youth lockdown event. - SEO —
sitemap.xml(every public page and event),robots.txt, and Open Graph/Twitter link-preview metadata. - Theming — brand colours, logo and favicon come from Site Settings.
Events
Created and edited by supervisors through a 6-step wizard: Basic Details → Items & Pricing → Sections → Form → Visibility → Attachments.
- Basic details — title, description, start/end dates, registration deadline, picture (upload or URL), location (defaults to the org's address), contact person.
- Contact-only events — for things like baptisms: shown publicly with a "Contact us" popup instead of registration, with no tickets.
- Ticket options — multiple options per event (e.g. Adult, Child, Dinner, T-shirt), each with a price and optional stock limit. One option is marked as the main ticket, which is what gets checked in at the door.
- Variants — sub-types of an option (sizes, colours, Adult/Child/VIP), each with an optional price override and its own stock limit.
- Early-bird tiers — deadline- and/or stock-limited cheaper prices per option or variant. Prices are locked in per purchase ("tranches"), so buying more later doesn't re-price what was already bought.
- Sections — named scan points (e.g. "Main hall", "Dinner") limited to certain ticket options, so a scanner at that point only accepts those tickets.
- Visibility — active/inactive, hidden (reachable only by link), scheduled go-live date, require login vs. allow guest registration, or no registration at all.
- Attachments — downloadable files (maps, schedules, waivers) shown on the event page.
- Notify recipients — choose which staff get registration/payment alerts and the daily summary for the event.
- Sold-out handling — an event is sold out when every stock-limited option is exhausted. "X remaining" badges appear at a threshold that scales with capacity.
Registration
- Online registration (
/register/[eventId]) — pick options, variants and quantities; see early-bird prices and stock warnings; fill in the event form. - Guest registration — when an event doesn't require login, an inactive account is created and an activation link is sent by email or WhatsApp.
- Statuses — Pending, Confirmed, Partially Paid, Paid, Cancelled.
- Edit options — users can add or change items on their registration (not for past or closed events).
- Self-cancellation — allowed only before any payment; admins can cancel any time.
- Pricing refresh — when an unpaid registration is viewed or paid, expired early-bird prices are re-evaluated, and the user is warned if the total changed.
- Manual registration (supervisor) — register someone on their behalf, creating an account if needed. Includes a "Record Payment" tab.
- Duplicate-account protection — if an email and phone belong to two different accounts, the registration is blocked instead of merging them.
- Group tickets — on events with "Named attendees" turned on, someone booking several places can name each person (with optional email/phone). Each named person gets their own ticket and their own attendee form; unnamed places share one ticket. Tickets go all to the payer, or each to the person (falling back to the payer). Attendees matching an existing account see their ticket in My Events. Names can be added or changed later from My Events, and attendees can be sent a link to fill in their own form. Available on the registration page, manual registration, At the door and the kiosk.
- Waitlist — on events with a waitlist, sold-out tickets show "Join waitlist" (on the event page, the register page and the event list). When places free up — a cancellation, a smaller booking or a higher stock limit — the next person is emailed/WhatsApped a claim link and the places are held for them (24 hours by default) so nobody else can buy them. Unclaimed offers expire and move on; people who asked for more than came free can be offered what's free or skipped. Claiming works without logging in. People can leave from My Events.
- Parental consent (youth events) — on events that require it, every attendee gives a date of birth; for anyone under 18, a parent or guardian gets an email and a WhatsApp with a consent link. They read the event's consent text, optionally give medical info, an emergency contact and photo consent, type their name to sign, and give or decline. Tickets are issued but can't be checked in until consent is given (supervisors can override, audited). Reminders go after 2 days, then every 3 days. Medical details are encrypted and deleted a set number of days after the event.
- Coupon codes — percentage, fixed-amount or free (comp) codes, entered under "Have a code?" on the registration page, in My Events (for unpaid registrations), manual registration, At the door and the kiosk. Codes can be limited to events, ticket options or variants, dates, total and per-person uses, a minimum quantity, and specific people, roles or an email domain (restricted codes need a login). Early-bird prices either stack or the better price wins. The discount is locked in when applied and shows on invoices, receipts, emails and the Yoco checkout; cancelling frees the use. Managed from Supervisor → Coupon codes.
- Staff price — a ticket option or variant can have a staff price that Staff and above get automatically, without a code.
Payments, donations & refunds
- Online card payments via Yoco — checkout for the full outstanding balance, confirmed by a signed webhook.
- Payment links — supervisors can generate and send a Yoco link for any amount (including partial).
- Manual payments — cash, card, EFT, voucher or other, recorded by staff. Each payment records who recorded it as well as who it's for.
- Refunds — negative payments against an original payment, netted against the original method.
- Impersonate user (Admin → Manage users) — an admin with 2FA can view the site as a
user, to debug problems.
- It needs a reason, lasts at most 30 minutes, and shows a bright "Viewing as" banner on every page.
- It is strictly read-only. A server-side guard refuses every write made with the impersonation token before it reaches any route, and pages don't even refresh prices while viewing.
- Admins can't be impersonated.
- The start, end, reason and every page and API call are in the audit log, with each session's trail.
- Optional email to the user (
notify_user_on_impersonation).
- Registration funnel stats — first-party and consent-based, with no third-party analytics.
- A cookie notice asks once. Only visitors who allow it get a random visit id.
- We record event page views and registration starts. Submits and payments come from the registration itself.
- Each record holds only the stage, the traffic source (utm_source, or the short-link
alias, which short links now pass as
?src=) and the device type. - The Registration funnel report shows counts and conversion per stage over a date range, a daily trend, and breakdowns by source, device and ticket option.
- A "Conversion (past month)" tile on the supervisor/admin dashboards covers live events.
- Raw rows are rolled up into daily totals after 90 days.
- Post-event feedback — a Feedback step in the event wizard.
- Questions use the form builder, with new star rating (1–5) and multiple choice types (also available on attendee forms).
- The form goes out at a chosen time to everyone who checked in, or to everyone
registered, each with a personal link (
/feedback/<token>). One reminder goes 3 days later to anyone who hasn't answered. - Answers can be anonymous or named. "Send now" is available, and the questions lock once people have answered.
- Sponsor a spot — per-event setting. A "Sponsor a spot" button on the event page
lets anyone, including guests, pay for one or more spots (at the main ticket price)
or any amount through Yoco. They can give anonymously or add a name and a message.
The money is a donation tagged to the event and goes into its sponsorship pool, which
supervisors assign to registrations with the usual donation flow.
- An optional public progress bar ("12 of 20 spots sponsored") with an optional goal.
- Sponsors get a thank-you receipt, and an Automations thank-you can go to all of an event's sponsors.
- The Donations breakdown report shows each pool: received, assigned and remaining.
- Who is helped is never shown.
- Donations — users can donate from their dashboard (
/dashboard/user/donate). Supervisors can assign a donation (fully or partly) to someone's registration and unassign it later. Assignments are kept as separate records, so the original donation never changes. - Yoco reconciliation — imported Yoco transactions can be matched to a registration, recorded as a donation (optionally choosing the donor), or ignored.
- Invoices & receipts — branded PDFs: an invoice (itemised, early-bird discount, balance due, pay-now link and QR code) and a payment receipt. Sent automatically and re-sendable on demand by email or WhatsApp.
- Method normalisation — Apple Pay / Google Pay / checkout payments count as "Card" in reports and user views.
Tickets & check-in
- Automatic tickets — QR-coded tickets are generated when a registration is fully paid (or free), and sent as a PDF by email and/or WhatsApp.
- Quantity tickets — one ticket can cover several people and be redeemed partially (e.g. check in 2 of 3).
- Named tickets — group bookings can give each person their own ticket, shown with their name on the PDF, the scanner and the staff ticket list.
- QR scanner — works in any mobile browser. A preview checks the ticket is for the selected event/section, then a confirmation screen shows holder, type and remaining quantity.
- Check-in confirmation — holders get an email/WhatsApp saying how many were checked in and how many remain.
- Scan stats — recent scans and per-event scan counts.
- Offline scanning — while online, a staff member locks an event section (or the whole event) to their device and downloads its tickets. They can then keep scanning with no connection; scans are queued on the device and sync automatically when it's back online (never redeeming more than is left). Only one device can hold a section, and while it's locked nobody else can scan it, online or offline. Problems found while syncing go into a report for a supervisor. Supervisors can force-release a lost device. The scanner can be added to the home screen (PWA) and opens offline.
- Printing — A4 sheets (8 tickets per page) and A6 single tickets.
- Resend — send tickets to a different email/phone at the door.
Attendee forms
- Form builder per event — field types: Yes/No, Text, Paragraph, Number, Date, plus Statement and Heading blocks; required/optional, help text, reordering. The whole form can be marked required.
- Drafts — partially filled forms are auto-saved.
- Responses — view, filter (event, user, past/inactive) and print one response per A4 page. Supervisors can edit responses.
Messaging & notifications
Every user picks a notification preference: Email, WhatsApp, or Both.
Automatic notifications
| Trigger | Goes to |
|---|---|
| Registration created / updated | Registrant (with invoice if unpaid) + event notify recipients |
| Payment recorded / donation applied / unassigned | Registrant (with receipt) + admins |
| Refund processed | Registrant + admins |
| Tickets generated | Registrant (ticket PDF) |
| Ticket checked in | Ticket holder |
| Daily summary, 07:00, for each live event that hasn't started yet | Registrations inbox + event notify recipients |
| Account registered, activation, login, password changed/reset, account closed | The account holder |
Bulk messaging (supervisor)
- Email attendees and WhatsApp attendees — filter by status or pick specific attendees, preview, send now or schedule for later.
- Placeholders —
{{name}},{{event.title}},{{event.link}},{{event.start}},{{balance}}, and{{payment.link}}(a live Yoco link for each recipient's outstanding balance). - Broadcasts — ad-hoc email/WhatsApp to any users or addresses, not tied to one event.
- Automations — schedule a sequence of lifecycle emails for an event (pre-event, final reminder, thank-you, promo for another event).
- Manage scheduled — view, edit or delete queued jobs per channel. Sends run in the background and are purged 24 h after sending.
WhatsApp is connected through the WAWP API: an admin links a number in Site Settings → WhatsApp (QR code or pairing code), with status, restart and logout controls. Disconnected sessions are recovered automatically.
User dashboard (My Events)
- Registrations with status badges, event thumbnails, and Closed/Past/Inactive tags. Past and closed events are hidden by default.
- Registration detail: itemised bill (early-bird lines grouped), payments, Invoice and Receipt buttons, edit, cancel, Make payment.
- Upcoming events and tickets side by side, with bulk ticket email/WhatsApp.
- Ticket transfer — offer a ticket to someone else before the event by typing their name and email/phone (no user search, and nothing reveals whether they have an account). They get a link to accept or decline; the ticket only moves once they accept, and the sender can cancel until then (offers expire after 7 days or at the event start). On accepting they get a new ticket and QR code, and an inactive account with an activation link if they're new. The old code stops working, and scanning it says it was transferred, both online and in offline-mode sync reports. One place can be split off a ticket covering several people. Billing stays with the payer. It's not allowed once a ticket has been scanned, and it can be turned off per event (supervisors can always transfer, from At the door → Tickets). On youth events the new holder needs their own parental consent, and the previous holder's form answers are removed. Every transfer is in the audit log.
- Payment history (
/dashboard/user/payments) — paginated, with filters by date, method and payment/refund. - Donate page.
- Forms — complete outstanding attendee forms.
- Profile & Security — edit details and notification preference, change password, see recent account activity (logins, password changes, device), sign out everywhere, deactivate or delete my data.
Staff tools
- Staff dashboard — scan stats and quick actions.
- Ticket scanning (
/dashboard/staff/ticket-scanning) — camera QR scanner with event and section filters. - Event tickets & printing (
/dashboard/staff/event-tickets) — list, search and print an event's tickets.
Supervisor tools
- Dashboard — KPIs (active events, revenue, donations, registrations, tickets sold, each vs. the previous month), revenue trend chart, top events, and recent scans. Tiles link to the matching report.
- Manage events — the 6-step event wizard, ticket options, variants, sections, form and attachments.
- At the door — search by name/phone/ticket ID, walk-in registration, payments, a Check-In tab for redeeming main tickets by quantity, and ticket printing/resending.
- Self-service kiosk (
/self-service) — a touchscreen page a supervisor unlocks for an event, where visitors register themselves. Existing accounts are matched by email/phone, and any change to their details needs confirmation. - Manual registration — register someone on their behalf and record their payment.
- Payments — tabs for Payment, Refund, Donations (assign/unassign), Reconcile (Yoco transactions), and Payment Link.
- Coupon codes (
/dashboard/supervisor/coupons) — create, edit and turn off discount codes, and see who used each one. - Rooms & groups (
/dashboard/supervisor/allocations):- Per event, allocation sets such as "Rooms", "Small groups" or "Buses". Each set has groups with an optional capacity, gender restriction and leader (staff).
- A drag-and-drop board, plus auto-allocate: fill evenly, respecting capacity and gender, keeping bookings together or splitting them.
- Printable per-group and master lists. On youth events they can include medical details, and viewing those is audited.
- The group can show on tickets and in My Events.
- Email and WhatsApp attendees can be filtered to one group.
- Leaders see their people under Staff → My groups.
- Works on named attendees. Events can ask each person's gender.
- Offline scanning (
/dashboard/supervisor/offline-scans) — devices holding offline locks (with force release) and sync reports to review. - Waitlist (
/dashboard/supervisor/waitlist) — the queue per event: offer a place now, move people up or down, or remove them. - Parental consent (
/dashboard/supervisor/consent) — consent status per attendee on youth events, resend (one or all pending), re-request after the consent text changes, and a printable medical & emergency list (audited). - Email attendees / WhatsApp attendees — bulk and scheduled messaging.
- Forms — view, filter, edit and print form responses.
- Reports — see Reports.
- Site banner (
/set-banner) — set the announcement bar.
Admin tools
- Admin dashboard — same KPIs as supervisor, plus admin quick actions.
- Manage users — server-side search and filters (role, active), paging, edit role and notification preference, revoke sessions, delete or anonymise personal data.
- Registrations — all registrations, with filters (event, status, text), status counts, revenue and outstanding totals, and per-registration options, form answers and payments (with who recorded each one).
- Cashup — see below.
- Audit log (
/dashboard/admin/audit-log) — who did what and when for refunds, donation assign/unassign, manual registrations, staff cancellations, event create/update/delete, settings changes, and coupon codes (created, edited, turned off, applied or removed by staff). - Site settings — see below.
Cashup & event costs
- Event costs — once-off or per-item costs (optionally linked to a ticket option), with a "paid from takings" method so cash on hand is correct.
- Expected vs. actual — expected takings per method (cash/card/EFT/other) compared with actual amounts, with variance shown.
- Cash denomination counts — entered per staff member, at any time. The event's actual cash is the sum of these counts.
- Payment accountability by staff member — what each person recorded, by method, with live cash variance.
- Close / quick close / reopen an event — closing locks it from further edits and hides it from the kiosk and user actions. Drafts are saved as you go.
- Report tab — a read-only cashup summary, shown automatically once the event is closed.
- Cashup audit trail — every close/reopen with totals and who did it.
Reports
A full-width workspace with shared filters (events, date range with presets, include past/inactive/closed) and a searchable grid of reports, each opening in a viewer with its own filters, stat tiles and bar charts.
| Category | Reports |
|---|---|
| Financial | Payments between dates · Revenue summary (by method) · Revenue detailed · Finance report (revenue & costs, after discounts) · Profit report · Discounts (coupon codes) |
| Registration | Registration funnel (views → started → submitted → paid, daily trend, by source / device / ticket option) · Attendees per event (grouped by option) · Registration type counts · Master orders breakdown · Registration status breakdown (count by registration or ticket quantity) · Parental consent status · Medical & emergency list · Feedback summary (response rate, average ratings, choice breakdowns, comments; compare events) |
| Ticketing | Ticket usage summary |
| Donations | Donations breakdown (used / unused per event, plus the sponsorship pool) |
| Cashup | Cashup reconciliation · Cashup audit trail |
Export: Print, branded PDF download, styled Excel (.xlsx) workbook,
Email (to yourself), or WhatsApp (the PDF to your own number). Dashboard tiles
deep-link into reports with ?report=&range=.
Site settings & setup wizard
Setup wizard (/setup) — on a fresh database, walks through organisation
details, the first admin account and branding. No manual seeding needed.
Site Settings (/dashboard/admin/settings) — stored in the database and
applied without a restart. Secrets are encrypted with AES-256-GCM.
| Tab | Contains |
|---|---|
| Organisation | Name, tagline, contact email/phone, address (with map link) |
| Branding | Primary/secondary/accent colours, logo, favicon, colours suggested from the logo, live website/email preview on desktop and mobile |
| Notifications | Registrations inbox and admin notification emails |
| SMTP host/port/TLS/user/password/from, with a "Test connection" button | |
| Legal | Operator, Information Officer, website, effective date for the Terms/Privacy pages |
| WAWP credentials, connect a number, status/restart/logout | |
| Backups | List, run now, download database backups |
See SETTINGS.md for the default of every setting.
Accounts & security
- Register, log in, forgot/reset password, activate account (for accounts created on someone's behalf).
- JWT authentication (HS256 pinned) with session revocation (token versioning) — users can sign out everywhere, and admins can do it for any user.
- Two-factor authentication — a 6-digit login code by email or WhatsApp
(10-minute expiry, 5 attempts, resend), with "Trust this device for 30 days".
Required for Admins and Supervisors: their next login sends a code that turns
it on, and they can change the channel but not turn it off (skipped only when no
email or WhatsApp channel can deliver a code;
TWO_FACTOR_REQUIRED_ROLES=falseis the emergency switch). Optional for everyone else from Profile & Security. Admins can reset it for a locked-out user, and the kiosk asks for the code at setup. - Login lockout after repeated failed attempts, plus login rate limiting
(
TRUST_PROXYto read real IPs behind a proxy). - Security event log — logins, password changes and resets, with device and IP.
- Account closure — deactivate, or anonymise personal data while keeping financial records.
- Upload hardening — server-generated filenames, and uploads served with a
sandbox CSP and
nosniff.
Help system
A floating "Need help?" button on every page opens a page-specific guide
(tabs, quick links). Every page has dedicated content (frontend/src/content/help/),
falling back to a general guide.
Background jobs
| Job | Schedule |
|---|---|
| Scheduled email/WhatsApp worker | Polls every 30 s (SCHEDULED_EMAILS_INTERVAL_MS) and purges sent jobs after 24 h |
| Daily event summary email | 07:00 daily |
| Waitlist: expire unclaimed offers, offer freed places | Every 10 minutes (WAITLIST_JOB_ENABLED=false to disable) |
| Funnel analytics: roll raw visit rows older than 90 days into daily totals | Daily (ANALYTICS_ROLLUP_ENABLED=false to disable) |
| Post-event feedback forms and their 3-day reminder | Every 15 minutes (FEEDBACK_JOB_ENABLED=false to disable) |
| Parental consent reminders, and deleting medical details after events | 09:00 daily (CONSENT_JOBS_ENABLED=false to disable) |
Database backup (pg_dump, gzipped, 14-day retention) |
02:00 daily (BACKUP_ENABLED=false to disable) |
Temp file cleanup (files older than 7 days in /temp) |
Daily |
Operations
- PM2 (
ecosystem.config.js) runs backend and frontend, restarting either one above 500 MB of memory. - Sentry error monitoring when
SENTRY_DSN/NEXT_PUBLIC_SENTRY_DSNare set. - Unhandled promise rejections are logged and don't crash the server.
- Backend status page (
/) and built-in API docs page (/docs). - Jest tests for early-bird tranche pricing and donation balances.
- Versioning via git tags and CHANGELOG.md.
Page map
| Path | Who | What |
|---|---|---|
/ |
Public | Home |
/events, /events/[id] |
Public | Event list and detail |
/[alias] |
Public | Event short link |
/register/[eventId] |
Public / User | Registration |
/registration/success |
Public / User | Confirmation, form and pay |
/payment/success, /payment/failure, /payment/cancel |
Public | Yoco return pages |
/forms |
Public / User | Attendee forms |
/contact, /legal/terms, /legal/privacy, /lockdown-rules |
Public | Info pages |
/login, /register, /forgot-password, /reset-password, /activate-account |
Public | Accounts |
/setup |
First run | Setup wizard |
/consent/[token] |
Parent/guardian | Give or decline parental consent |
/waitlist/claim/[token] |
Public | Claim a waitlist place |
/self-service |
Supervisor-unlocked | Kiosk |
/set-banner |
Supervisor | Site banner |
/dashboard/user (+ /payments, /donate, /forms, /profile, /reset-password) |
User | My Events |
/dashboard/staff (+ /ticket-scanning, /event-tickets) |
Staff | Staff tools |
/dashboard/supervisor (+ /events, /event-options, /at-the-door, /manual, /manual-registration, /payments, /coupons, /waitlist, /consent, /offline-scans, /allocations, /email-attendees, /whatsapp-attendees, /forms, /reports) |
Supervisor | Supervisor tools |
/dashboard/admin (+ /users, /registrations, /forms, /cashup, /cashup/[id], /audit-log, /settings) |
Admin | Admin tools |