Next.js 14 web application for managing church events and running check-in stations.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (Pages Router) |
| UI | React 18 |
| HTTP client | Axios |
| Real-time | Socket.io client 4 |
| File upload | react-dropzone |
| Date formatting | date-fns |
| Notifications | react-hot-toast |
frontend/
├── pages/
│ ├── _app.js # Global providers (LangProvider, Toaster)
│ ├── _document.js # Custom HTML document
│ ├── index.js # Events list page
│ ├── checkin.js # Check-in station page
│ └── events/
│ └── [id].js # Event detail & attendee management page
├── components/
│ ├── Layout.js # Sidebar, nav, theme/language toggle, support bar
│ ├── EventFormModal.js # Create / edit event modal
│ └── ImportModal.js # CSV / Excel import modal
├── hooks/
│ └── useSocket.js # Singleton Socket.io hook shared across pages
├── contexts/
│ └── LangContext.js # Language state (Indonesian / English)
├── lib/
│ ├── api.js # Axios instance + eventsApi / attendeesApi helpers
│ └── i18n.js # All UI strings for both languages
├── styles/
│ └── globals.css # Design tokens, layout, component styles
├── next.config.js
└── package.json
- Displays all events as cards with live attendee stats (registered, checked-in, remaining, progress bar)
- New Event button opens
EventFormModal - Edit (✏️) and Delete (🗑️) buttons on each card
- Password-protected events require the event password before the edit or delete action is allowed
- Live stat updates arrive silently via Socket.io — no page refresh needed
- Full attendee table with search, filter (All / Checked In / Pending), and sortable columns
- Add attendees manually or import from a CSV/XLS/XLSX file
- Check-in and undo check-in per attendee (undo requires confirmation)
- Password-protected events show a password gate before any content is visible; attendee data is not fetched from the network until the password is verified
- Live banner appears at the top whenever any operator checks someone in from another tab/device
- Designed for operators running a check-in desk
- Select an event from the list; protected events require the password before the station unlocks
- Real-time attendee search by name or phone number (300 ms debounce)
- One-click check-in with instant feedback and a live recent check-ins feed
- Live stat bar shows current checked-in count, remaining, and total
Events can be protected with a password (set in EventFormModal). The password gate appears in three places:
- Events tab — before opening the edit form or delete confirmation
- Event detail page — before the attendee list is shown or fetched
- Check-in station — before the search interface unlocks
All pages share a singleton Socket.io connection (hooks/useSocket.js). Whenever any operator performs an action (check-in, import, add/delete attendee), every open tab and device watching the same event updates instantly without a refresh.
The import modal accepts .csv, .xls, and .xlsx files. Column headers are matched flexibly — Google Form exports work out of the box. Rows are inserted in the same order they appear in the file. On success the modal closes automatically.
All interface strings are defined in lib/i18n.js and toggled via the sidebar language switcher. Supported languages:
- 🇮🇩 Indonesian (
id) — default - 🇬🇧 English (
en)
Theme preference is persisted in localStorage and applied before first paint via an inline script in _document.js to avoid flash of unstyled content.
Create a .env.local file in the frontend/ directory:
NEXT_PUBLIC_EKKLESIA_API_URL=http://localhost:4000| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_EKKLESIA_API_URL |
http://localhost:4000 |
Base URL of the backend API |
- Node.js 20+
- Backend API running (see
backend/README.md)
# 1. Install dependencies
cd frontend
npm install
# 2. Create .env.local (see above)
# 3. Start development server
npm run devOpen http://localhost:3000.
npm run build
npm startFrom the project root:
docker compose up --buildThe frontend container starts on port 3000 and depends on the backend container being up first.
All HTTP requests go through the helpers in lib/api.js:
eventsApi.getAll()
eventsApi.getOne(id)
eventsApi.create(data)
eventsApi.update(id, data)
eventsApi.delete(id)
eventsApi.verifyPassword(id, password)
attendeesApi.getAll(eventId, params)
attendeesApi.create(eventId, data)
attendeesApi.import(eventId, file) ← multipart/form-data
attendeesApi.checkIn(eventId, attendeeId)
attendeesApi.undoCheckIn(eventId, attendeeId)
attendeesApi.delete(eventId, attendeeId)
attendeesApi.deleteAll(eventId)
The Axios instance has a 30-second timeout. Error responses from the API are surfaced as toast notifications.
Update the WA_URL constant at the top of components/Layout.js:
const WA_URL = "https://wa.me/+6289618113757";Update the APP_VERSION constant at the top of components/Layout.js:
const APP_VERSION = "v2.0.3-beta.0";- Add a new entry to the
translationsobject inlib/i18n.js, mirroring all keys from theenblock. - Add the language metadata to the
LANGSarray at the bottom of the same file. - The sidebar language switcher cycles through all entries in
LANGSautomatically.