# 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 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). ## 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/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 %; 51–200: 15 %; 201–1,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.