Files
hope-events/README.md
T
joshua 3d381944d2 Initial commit
Next.js + Express event management app for Hope Family Church.
2026-07-23 15:26:47 +02:00

13 KiB
Raw Blame History

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.