joshua 91b352de18 Add closed/past/inactive status badges to user dashboard event titles
Adds an EventStatusBadge component shown next to every event title on
the dashboard (registrations list, upcoming events, tickets, and the
registration modal), with precedence Closed > Past > Inactive when
more than one applies — reusing the badge styling already established
in the supervisor events list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 16:56:32 +02:00
2026-07-23 15:26:47 +02:00
2026-07-23 15:26:47 +02:00
2026-07-23 15:26:47 +02:00
2026-07-23 15:26:47 +02:00

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

  • 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):

  • 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

  1. Provision a PostgreSQL database and set DATABASE_URL.
  2. Set all required environment variables (see backend/README.md).
  3. Run Prisma migrations (if applicable).
  4. Start the server with: npm start (or a process manager like PM2).
  5. Expose the webhook endpoint /api/webhooks/yoco publicly and configure the YOCO webhook to point to it.
  6. Ensure the uploads directory public/uploads exists and is writable.

Frontend Deployment

  1. Build the frontend:
    • cd frontend
    • npm run build
  2. Run with npm start (Next.js) on your server, or deploy to a platform like Vercel.
  3. 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).
  • 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/events now computes and returns isSoldOut: boolean per 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-smtp error responses now include a raw field (the original error message) alongside the human-readable message. Error code 530 (Microsoft "Client not authenticated to send mail") is now correctly mapped to the authentication-failure message.

Frontend

  • Event listing (/events): EventCard shows a disabled "Sold Out" button (red tint) when isSoldOut is 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 option availableCount; shows a disabled "Sold Out" button instead of the Register link. Options with price 0 display "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 %; 51200: 15 %; 2011,000: 10 %; 1,000+: 5 %. Changed from Math.ceil to Math.round to prevent rounding-up inflation of the threshold. Fixed a React rendering bug where stockLimit && stockLimit > 0 && ... evaluated to the number 0 (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-image and 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/attachments immediately 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. ApiError in src/lib/api.ts now carries a data field with the full JSON response body.

Variants, stock limits, early-bird pricing & cancellation guard

Backend

  • OptionVariant model: event options can now have sub-variants (sizes, colours, ticket types — e.g. Adult/Child/VIP under a single "Ticket" option). Variants have an optional price override and their own stockLimit.
  • Stock limits: stockLimit added to EventOption and EarlyBirdTier. A value of 0 means 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 updates priceSnapshot. If prices changed, POST /api/payments/yoco-checkout returns { priceUpdated: true, newTotal } (HTTP 200) so the frontend can show a warning before retrying.
  • priceSnapshot / appliedTierId on RegistrationOption: price and tier are snapshotted at registration creation. computeRegistrationTotalDue uses priceSnapshot as authoritative when present (legacy rows without a snapshot fall back to deadline-only re-evaluation).
  • Cancelled registration tickets: GET /api/tickets/mytickets now excludes tickets from cancelled registrations at the database level.
  • Cancellation payment guard: DELETE /api/registrations/:id blocks 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 includes variantId. 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/sections now 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 from JWT_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 via POST /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-64 panel on email-attendees and whatsapp-attendees pages.

Backend

  • GET /api/users supports 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 from JWT_SECRET via 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.

S
Description
No description provided
Readme 806 KiB
v1.0.0 Latest
2026-07-23 15:36:38 +02:00
Languages
TypeScript 66.9%
JavaScript 32.9%
CSS 0.2%