Initial commit

Next.js + Express event management app for Hope Family Church.
This commit is contained in:
2026-07-23 15:26:47 +02:00
commit 3d381944d2
246 changed files with 57565 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
# 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:<backend-port>
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 %; 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.