Extends the earlier user-dashboard fix: canEditActive is renamed canModifyActive and now also gates the "Make payment" and "Cancel registration" actions, not just editing. Backend enforcement added to cancelRegistration and createYocoCheckout to block past-event self-service payment/cancellation server-side (cashup-closed events were already blocked via assertEventOpen; admins/supervisors are exempt from the past-date check, consistent with existing overrides). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hope Events Platform
Hope Events is a full-stack event management platform that allows users to browse events, register for them, make payments, and manage their tickets.
Project Structure
The project is divided into two main parts:
- Backend: Node.js + Express + Prisma + PostgreSQL
- Frontend: Next.js (React) + Tailwind CSS
Backend
The backend provides a RESTful API for managing users, events, registrations, payments, uploads, and tickets. It includes:
- JWT-based authentication and role-based authorization
- Event and registration management
- Payment integration with Yoco (checkout + webhooks)
- Ticket generation (PDF + QR) and email delivery
- Static uploads for event images
Documentation: see Backend API Documentation and Backend README for environment setup and endpoints.
- Backend API Documentation: ./backend/API_DOCUMENTATION.md
- Backend README: ./backend/README.md
Frontend
The frontend is a Next.js app that provides:
- User authentication (login, register)
- Browse/search events and view details
- Register for events and pay via Yoco
- Manage and view tickets
Documentation: see Frontend README for environment and scripts.
- Frontend README: ./frontend/README.md
Getting Started (Local Development)
Prerequisites
- Node.js 18+ (recommended)
- npm or yarn
- PostgreSQL database
1) Install dependencies
-
Backend
- cd backend
- npm install
-
Frontend
- cd ../frontend
- npm install
2) Configure environment variables
-
Backend: create backend/.env and set required variables (see backend/README.md). At minimum:
- DATABASE_URL
- JWT_SECRET
- PORT (optional; defaults to 3000 if not set)
- YOCO_SECRET_KEY (for payments)
- YOCO_WEBHOOK_SECRET (to verify webhooks)
- EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FROM (for ticket emails)
- FRONTEND_URL or APP_BASE_URL (used in email links)
-
Frontend: create frontend/.env.local and set
- NEXT_PUBLIC_API_URL=http://localhost: Example for default backend: NEXT_PUBLIC_API_URL=http://localhost:5000
-
Organisation name, logo, brand colour, and notification emails are configured via the admin panel (Admin → Site Settings) and do not need to be set in .env files.
3) Run the apps
-
Start backend (development):
- cd backend
- npm run dev
- By default runs at http://localhost:3000 (or the PORT you set)
-
Start frontend (development):
- cd ../frontend
- npm run dev
- Next.js dev server runs at http://localhost:3000 by default; if it conflicts with backend, it will pick another port (e.g., 3001). Ensure NEXT_PUBLIC_API_URL points to the backend port.
Deployment
Backend Deployment
- Provision a PostgreSQL database and set DATABASE_URL.
- Set all required environment variables (see backend/README.md).
- Run Prisma migrations (if applicable).
- Start the server with: npm start (or a process manager like PM2).
- Expose the webhook endpoint /api/webhooks/yoco publicly and configure the YOCO webhook to point to it.
- Ensure the uploads directory public/uploads exists and is writable.
Frontend Deployment
- Build the frontend:
- cd frontend
- npm run build
- Run with npm start (Next.js) on your server, or deploy to a platform like Vercel.
- Ensure NEXT_PUBLIC_API_URL points to the public URL of your backend (including port if not 80/443).
Ports and URLs
- Backend default port is 3000 unless overridden by PORT.
- Frontend dev server commonly uses 3000; set NEXT_PUBLIC_API_URL to avoid clashes (e.g., backend 3000, frontend 3001).
Useful Links
- Backend API Documentation: ./backend/API_DOCUMENTATION.md
- Backend README: ./backend/README.md
- Frontend README: ./frontend/README.md
Recent Changes
Sold-out handling, tiered stock warnings & UI polish
Backend
GET /api/eventsnow computes and returnsisSoldOut: booleanper event — true when every option with a stock limit (stockLimit > 0) is fully sold out. Events with only unlimited options are never sold out.POST /api/settings/test-smtperror responses now include arawfield (the original error message) alongside the human-readablemessage. Error code 530 (Microsoft "Client not authenticated to send mail") is now correctly mapped to the authentication-failure message.
Frontend
- Event listing (
/events):EventCardshows a disabled "Sold Out" button (red tint) whenisSoldOutis true, replacing the Register button. Free events now show "Register" without the "- R0.00" suffix. - Event detail page (
/events/[id]): computes sold-out state from returned optionavailableCount; shows a disabled "Sold Out" button instead of the Register link. Options with price0display "Free" instead of "R0.00". - Registration page (
/register/[eventId]): shows a red "This event is sold out" banner and replaces the Register button with a disabled "Sold Out" button when all limited options are exhausted. - Tiered low-stock threshold: the "X remaining" badge now uses capacity-based percentages — ≤ 50 tickets: 20 %; 51–200: 15 %; 201–1,000: 10 %; 1,000+: 5 %. Changed from
Math.ceiltoMath.roundto prevent rounding-up inflation of the threshold. Fixed a React rendering bug wherestockLimit && stockLimit > 0 && ...evaluated to the number0(which React renders as visible text) instead of a boolean — this caused "00" to appear next to options with unlimited stock (stockLimit = 0). - Event image upload: the EventForm (used in the create modal) now shows a file upload button with inline preview alongside the URL input field. Files are uploaded to
POST /api/uploads/event-imageand the returned URL is set automatically. - Attachments during event creation: step 6 ("Attachments") of the supervisor create-event wizard now lets you stage files. They are uploaded to
POST /api/events/:id/attachmentsimmediately after the event record is created, so attachments no longer require a separate edit session. - SMTP test details: on failure the "Test connection" result now shows a collapsible "Show technical details" section exposing the raw SMTP error alongside the friendly message.
ApiErrorinsrc/lib/api.tsnow carries adatafield with the full JSON response body.
Variants, stock limits, early-bird pricing & cancellation guard
Backend
OptionVariantmodel: event options can now have sub-variants (sizes, colours, ticket types — e.g. Adult/Child/VIP under a single "Ticket" option). Variants have an optionalpriceoverride and their ownstockLimit.- Stock limits:
stockLimitadded toEventOptionandEarlyBirdTier. A value of0means unlimited. All stock counts exclude cancelled registrations. - Early-bird concurrency handling: once a Yoco checkout has been issued at an early-bird price, that price is honoured even if another user simultaneously exhausts the tier's stock. Stock-based forfeiture only applies at registration time (via
resolveOptionPrice). Deadline expiry at payment time forfeits the price; stock races after the checkout is issued do not. - Early-bird stock forfeiture: if a tier's deadline passes after registration but before payment,
refreshPricingForRegistration(called before every Yoco checkout) re-evaluates the tier and updatespriceSnapshot. If prices changed,POST /api/payments/yoco-checkoutreturns{ priceUpdated: true, newTotal }(HTTP 200) so the frontend can show a warning before retrying. priceSnapshot/appliedTierIdonRegistrationOption: price and tier are snapshotted at registration creation.computeRegistrationTotalDueusespriceSnapshotas authoritative when present (legacy rows without a snapshot fall back to deadline-only re-evaluation).- Cancelled registration tickets:
GET /api/tickets/myticketsnow excludes tickets from cancelled registrations at the database level. - Cancellation payment guard:
DELETE /api/registrations/:idblocks non-admins from cancelling a registration that has positive payments. Admins can cancel at any time. - Variant CRUD:
POST /api/events/options/:id/variants,PUT /api/events/variants/:id,DELETE /api/events/variants/:id. - Testing mode (
NODE_ENV=testing): behaves like development (permissive CORS, all debug routes) but validates required env vars (DATABASE_URL,JWT_SECRET) at startup and enables rate limiting.
Frontend
- Register page (
/register/[eventId]): variant picker — when an option has variants, shows each variant with its own qty control, stock badge (sold out / X remaining at ≤ 15 %), and price. Submission payload includesvariantId. Early-bird notice shown when any active tier is present. - Event detail page (
/events/[id]): ticket section shows variant breakdown with "From R…" pricing, early-bird strikethrough, and sold-out/nearly-out badges. - User dashboard: "Cancel registration" button appears in the registration modal when no payments have been made. Inline confirmation before the API call.
- Pay page: if the backend returns
priceUpdated: true, an amber warning banner shows the new total and a dismiss button before the user can retry. - Supervisor events page (
/dashboard/supervisor/events): 6-step create/edit modal — "Basic Details", "Items & Pricing", "Sections", "Form", "Visibility", "Attachments". In create mode, step tabs are not clickable (linear navigation only); in edit mode any step can be jumped to. Options in "Items & Pricing" are collapsible cards; Variants and Early-bird tiers are expandable sub-sections within each option./dashboard/supervisor/sectionsnow redirects to the Events page. - Legal pages: Terms of Use updated with early-bird pricing terms, partial payment terms, and self-cancellation policy. Privacy Policy updated with data anonymisation details.
Site settings & first-time setup wizard
- Admin → Site Settings (
/dashboard/admin/settings): organisation details, branding (colour + logo), notification emails, SMTP email delivery, and legal page content are stored in the database and editable at runtime. - Setup wizard (
/setup): on a fresh deployment (empty database) the platform automatically shows a wizard — organisation details, admin account creation, and branding. No manual DB seeding required. /api/settings(public): exposes public settings (org details, legal keys) for navbar and legal pages./api/setup(public, one-time): creates the first admin account; blocked once any user exists.- Settings cache (
settingsCache.js): in-process 60-second cache with transparent AES-256-GCM decryption for sensitive keys. - SMTP via admin panel: host, port, TLS, from address, username, and password are all configurable without touching
.env. Password is stored AES-256-GCM encrypted (key derived fromJWT_SECRET). - Legal pages dynamic: Terms of Use and Privacy Policy populate org name, contact email, website URL, operator name, Information Officer details, and effective date from the settings DB.
ORG_NAME,ORG_TAGLINE,EMAIL_HEADER_COLOR,REGISTRATIONS_EMAIL, SMTP vars no longer required in.env— all managed via the admin panel (env vars still work as fallbacks).
Admin dashboard improvements
- User management (
/dashboard/admin/users): filtering (role, active status, text search) is now server-side so page size is always consistent; per-page selector (10/25/50/100); "Delete data" button anonymises a user's personal information viaPOST /api/users/:id/anonymize. - Registrations (
/dashboard/admin/registrations): event dropdown, status, and fuzzy text filters; expandable rows showing ticket options and form responses loaded on demand.
Forms page (/dashboard/supervisor/forms, /dashboard/admin/forms)
- Print opens a clean popup window (no site chrome) with one form response per A4 page — event name, attendee details, registration ID, and answers in a two-column grid.
- User filter replaced raw "User ID" text input with a searchable name/email/phone dropdown.
- Event filter includes "Include past events" and "Include inactive events" checkboxes.
- Filters in a responsive grid; dropdowns constrained to
max-w-xs.
Ticket printing
- At-the-door individual print: individual ticket print now generates a proper A6 ticket with QR code, event title, date, type, holder name, and ID — no longer just a UUID.
- Staff event-tickets page: A4 print layout matches at-the-door (2 columns × 4 rows, 8 tickets per page).
Form builder & events
- Form builder: up/down reorder buttons; type badge (Text/Number/Date/etc.); separate quick-add buttons for Statement and Heading fields.
- Early-bird tiers: no manual order number — tiers ordered by list position; labelled Deadline and Price inputs.
Bulk messaging dropdowns
- Attendee selection dropdown constrained to
max-w-xs/w-64panel on email-attendees and whatsapp-attendees pages.
Backend
GET /api/userssupports server-side?search=,?role=,?isActive=filtering.POST /api/users/:id/anonymize— erases personal data (admin only).GET|PUT /api/settings,GET /api/settings/needs-setup,POST /api/setup— settings and setup endpoints.POST /api/uploads/logo— logo upload (admin, or unauthenticated during first-time setup).backend/src/utils/encryption.js— AES-256-GCM encrypt/decrypt; key derived fromJWT_SECRETvia scrypt.- SMTP transporter rebuilt dynamically when settings cache detects a config change — no restart required after updating SMTP settings.
License
This project is licensed under the MIT License.