The scheduled-job store never persisted the channel field, so the send worker always fell through to its email branch regardless of what was requested. Also purges sent jobs 24h after sending instead of keeping them forever, and surfaces who each scheduled job will go to in the admin "manage scheduled" lists (now correctly filtered per channel too). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hope Events — Frontend
Next.js 15 (App Router) frontend for the Hope Events platform used by Hope Family Church (events.hopehenley.co.za).
Table of Contents
- Overview
- Tech Stack
- Prerequisites
- Installation
- Environment Variables
- Running the App
- Project Structure
- Pages & Routes
- Authentication & Roles
- Payment Flow
- Notifications
- Deployment
- Troubleshooting
Overview
The frontend handles:
- Public event listings and event detail pages
- User registration, login, and account management
- Attendee registration and Yoco checkout
- Ticket viewing and delivery
- Staff ticket scanning (QR scanner)
- Supervisor tools: event management, payments, bulk messaging (email + WhatsApp), reports, at-the-door check-in
- Admin tools: user management, WhatsApp instance management, form builder
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, React 19) |
| Language | TypeScript |
| Styling | Tailwind CSS 3 |
| UI Primitives | Radix UI |
| Forms | React Hook Form + Zod |
| Icons | Lucide React |
| QR Scanning | @zxing/browser, react-webcam |
| QR Generation | qrcode |
| Date handling | date-fns, react-day-picker |
Prerequisites
- Node.js 18+
- npm
- A running backend API (see
../backend/README.md)
Installation
cd frontend
npm install
Environment Variables
Create frontend/.env.local:
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_API_URL |
Yes | Base URL of the backend API, e.g. https://api.yourdomain.com or http://localhost:5000 for local dev |
Organisation name, brand colour, logo, and contact email are now stored in the database and managed via Admin → Site Settings.
NEXT_PUBLIC_APP_NAME,NEXT_PUBLIC_ORG_NAME,NEXT_PUBLIC_BRAND_COLOR, andNEXT_PUBLIC_CONTACT_EMAILare no longer needed and can be removed from your.env.local.
Running the App
# Development (Turbopack)
npm run dev
# Production build + start
npm run build
npm start
# Lint
npm run lint
Project Structure
frontend/src/
├── app/ # Next.js App Router pages
│ ├── (auth)/ # Login, register, forgot/reset password
│ ├── [redirectUrl]/ # Event alias redirects
│ ├── activate-account/ # Account activation from email link
│ ├── dashboard/
│ │ ├── layout.tsx # Shared dashboard shell + nav
│ │ ├── admin/ # Admin-only pages
│ │ ├── supervisor/ # Supervisor + admin pages
│ │ ├── staff/ # Staff + above pages
│ │ └── user/ # All authenticated users
│ ├── events/ # Public event listing + detail
│ ├── forms/ # Public form submission
│ ├── legal/ # Privacy policy, terms
│ ├── payment/ # Payment success/failure/cancel
│ ├── register/[eventId]/ # Event registration flow
│ ├── registration/ # Post-registration success
│ ├── reset-password/ # Password reset (token flow)
│ ├── self-service/ # Guest self-service portal
│ ├── set-banner/ # Quick banner editor (supervisor)
│ └── layout.tsx # Root layout
├── components/ # Shared UI components
├── hooks/ # Custom React hooks (useAuth, etc.)
└── lib/ # Utilities (api.ts fetch wrapper, etc.)
Pages & Routes
Public
| Route | Description |
|---|---|
/ |
Landing page |
/events |
Public event listing — shows "Sold Out" button when all limited-stock options are exhausted; free events show "Register" without a price suffix |
/events/[id] |
Event detail page — sold-out/registration-closed/not-yet-open states; zero-price options display "Free"; tiered low-stock badges (≤ 50 tickets: 20 %; 51–200: 15 %; 201–1 000: 10 %; 1 000+: 5 %) |
/[redirectUrl] |
Event alias redirect |
/register/[eventId] |
Registration flow — sold-out banner + disabled button when event is fully booked; tiered stock badges; variant qty controls; early-bird notice |
/forms |
Public form submission |
/legal/privacy |
Privacy policy |
/legal/terms |
Terms of service |
/lockdown-rules |
House rules page |
/payment/success |
Yoco checkout success |
/payment/failure |
Yoco checkout failure |
/payment/cancel |
Yoco checkout cancelled |
/registration/success |
Post-registration confirmation |
/self-service |
Guest self-service (find tickets by email) |
/activate-account |
Account activation via email link |
/reset-password |
Password reset (token from email) |
Auth
| Route | Description |
|---|---|
/login |
Login form |
/register |
Sign-up form |
/forgot-password |
Request password reset |
User Dashboard — /dashboard/user/
| Route | Description |
|---|---|
/dashboard/user |
My registrations + tickets overview |
/dashboard/user/profile |
Edit profile, notification preferences |
/dashboard/user/donate |
Make a donation |
/dashboard/user/forms |
Complete registration forms |
/dashboard/user/reset-password |
Change password (authenticated) |
Staff Dashboard — /dashboard/staff/
| Route | Description |
|---|---|
/dashboard/staff |
Staff overview |
/dashboard/staff/ticket-scanning |
QR code scanner for check-in |
/dashboard/staff/event-tickets |
All tickets for an event |
Supervisor Dashboard — /dashboard/supervisor/
| Route | Description |
|---|---|
/dashboard/supervisor |
Supervisor overview + quick links |
/dashboard/supervisor/events |
Create/edit/manage events — 6-step wizard (Basic Details, Items & Pricing, Sections, Form, Visibility, Attachments); form builder with up/down reorder, type badges, separate shortcut buttons for statement/heading fields; early-bird tiers with labelled deadline/price inputs; event image file upload with preview; attachments can be staged during creation (uploaded after save) |
/dashboard/supervisor/event-options |
Manage ticket types / options |
/dashboard/supervisor/payments |
View and manage payments |
/dashboard/supervisor/manual |
Manual payment entry |
/dashboard/supervisor/manual-registration |
Register an attendee manually |
/dashboard/supervisor/at-the-door |
At-the-door check-in management |
/dashboard/supervisor/email-attendees |
Bulk email + automations + broadcasts + scheduled |
/dashboard/supervisor/whatsapp-attendees |
Bulk WhatsApp + automations + broadcasts + scheduled |
/dashboard/supervisor/reports |
Generate/download/email reports |
/dashboard/supervisor/forms |
Event form builder and response viewer — three modes: view responses (printable, page-break per submission), fill/edit responses, edit form structure; improved form builder with reorder buttons and type badges |
/dashboard/supervisor/sections |
Manage event sections |
/set-banner |
Quick banner editor |
Admin Dashboard — /dashboard/admin/
| Route | Description |
|---|---|
/dashboard/admin |
Admin overview |
/dashboard/admin/users |
User management — list, inline edit, deactivate, role assignment, per-page selector (10/25/50/100), server-side search/filter by name/email/phone/role/status, "Delete data" button to anonymise a user's personal information |
/dashboard/admin/registrations |
All registrations — filter by event, status, and fuzzy text search; expandable rows showing ticket options and form responses |
/dashboard/admin/whatsapp |
WhatsApp instance management (WAWP) |
/dashboard/admin/forms |
View all form responses (inherits supervisor/forms) |
/dashboard/admin/settings |
Site Settings — organisation details, logo, brand colour, notification emails, SMTP delivery (with test connection; friendly errors + collapsible raw details on failure), legal page content |
Setup — /setup
| Route | Description |
|---|---|
/setup |
First-time setup wizard — shown automatically on a fresh deployment (empty database). Creates the first admin account and initial site settings in three steps: organisation details, admin account, branding. After completion, redirected to login. |
Authentication & Roles
Auth state is managed via the useAuth hook (src/hooks/useAuth.ts). The JWT is stored in localStorage and sent as a Bearer token on every API call via src/lib/api.ts (apiFetch).
Role access matrix
| Page group | user |
staff |
supervisor |
admin |
|---|---|---|---|---|
| Public pages | Yes | Yes | Yes | Yes |
/dashboard/user/* |
Yes | Yes | Yes | Yes |
/dashboard/staff/* |
— | Yes | Yes | Yes |
/dashboard/supervisor/* |
— | — | Yes | Yes |
/dashboard/admin/* |
— | — | — | Yes |
Unauthenticated users are redirected to /login. Insufficient-role users see an in-page warning.
Notification preferences
Users set their preference on the profile page:
email— email notifications onlywhatsapp— WhatsApp notifications onlyboth— both channels
The supervisor bulk-messaging pages show preference indicators in user/attendee dropdowns:
- Indigo badge — email preference matches (email/both) on the email page
- Green badge — WhatsApp preference matches (whatsapp/both) on the WA page
- Amber badge — preference mismatch (will still receive message)
A "Select Email/both" or "Select WhatsApp/both" quick-select button is available in each dropdown.
Payment Flow
- User registers for a paid event → registration created with
status: pending - User clicks "Pay" → calls
POST /api/payments/yoco-checkout - Backend returns a Yoco
redirectUrl→ frontend opens it in a new tab - User pays on Yoco's hosted page → Yoco sends webhook to backend
- Backend reconciles payment, updates registration status, generates and emails tickets
- User sees
/payment/successand tickets appear in their dashboard
Outstanding balances can be paid at any time from the "Make payment" button on /dashboard/user — this always creates a checkout for the full remaining balance. Partial-amount payment links can only be generated by a supervisor/admin from /dashboard/supervisor/payments.
Notifications
Bulk Email / WhatsApp (email-attendees, whatsapp-attendees)
Both pages share the same feature set:
- Attendees tab — send or schedule a message to attendees of a specific event; filter by payment status; templates: custom, payment reminder, event reminder, ticket delivery
- Automations tab — schedule pre-event (1 week), final reminder (24/48 h), thank-you, and promo messages with smart default timing based on the event's dates
- Broadcasts tab — send to a selected user list plus ad-hoc email addresses or phone numbers
- Scheduled tab — view, edit (reschedule / update content), or cancel queued jobs
Mismatch warnings appear when selected users prefer a different channel. Falling back to all attendees if no matching-preference attendees exist.
The attendee selection dropdown is constrained to a fixed width (max-w-xs / w-64 panel) so it does not grow to fill the page on wide screens.
Deployment
Vercel (recommended)
- Push the
frontend/directory to GitHub - Create a Vercel project pointing to
frontend/ - Set env var:
NEXT_PUBLIC_API_URL=https://your-api.domain.com - Deploy
Self-hosted (Node.js)
npm run build
npm start # port 3000 by default
Use nginx/Caddy as a reverse proxy.
PM2
pm2 start npm --name hope-events-frontend -- start
pm2 save && pm2 startup
Checklist
NEXT_PUBLIC_API_URLset to production backend URL- Backend
FRONTEND_URLincludes this frontend's origin - Yoco webhook URL reachable over HTTPS
npm run buildcompletes without errors
Troubleshooting
| Symptom | Fix |
|---|---|
| 4xx/5xx API calls | Check NEXT_PUBLIC_API_URL is correct and reachable from the browser |
| CORS errors | Confirm backend FRONTEND_URL includes your frontend origin |
| Port conflict in dev | Next.js auto-selects 3001+ if 3000 is taken — keep NEXT_PUBLIC_API_URL pointing at the backend port |
| Images not showing | Backend must serve /uploads and public/uploads must be writable |
| Login loop | Clear localStorage and re-login; check JWT_SECRET on backend hasn't changed |
| QR scanner not working | Browser requires camera permission; @zxing/browser requires HTTPS in production |