Initial commit
Next.js + Express event management app for Hope Family Church.
This commit is contained in:
@@ -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 %; 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.
|
||||
Reference in New Issue
Block a user