A full-stack event transportation application built with Next.js, Node.js, Express, Socket.io, and PostgreSQL. Designed for event transportation within a 10-mile radius with smart driver dispatch and real-time tracking.
- Smart Dispatch Algorithm: Assigns the best driver based on proximity and availability
- Real-time Updates: Socket.io for live location tracking and trip status
- Trip Queuing: Drivers can handle up to 2 trips (1 active + 1 queued)
- Fixed Locations: Passengers select from predefined pickup/dropoff points
- Role-Based Access: Separate interfaces for Passengers, Drivers, and Admins
- Browser geolocation to detect current location
- Select destination from predefined locations
- View pickup location on map
- Real-time trip status updates
- Driver assignment notifications with ETA
- Socket-based ride updates
- Online/Offline toggle
- Continuous background location tracking (watchPosition)
- Location updates emitted every 5 seconds
- View current and queued trips
- Google Maps deep link navigation
- Start/Complete trip actions
- Visual queue indicator for next ride
- Haversine Formula: Distance calculation without external APIs
- PostGIS: Spatial queries for proximity matching
- Monorepo Structure: Organized
/clientand/server - Mobile-First PWA: Responsive design with bottom navigation
- TypeScript Ready: Can be migrated to TypeScript
- Framework: Next.js 14 (Pages Router)
- Styling: Tailwind CSS
- Icons: Lucide React
- Maps: Mapbox GL JS
- Real-time: Socket.io Client
- HTTP: Axios
- Runtime: Node.js
- Framework: Express.js
- Real-time: Socket.io
- Database: PostgreSQL (Supabase)
- Spatial: PostGIS
event-loop/
├── client/ # Next.js frontend
│ ├── components/
│ │ ├── Layout.js # Mobile-responsive layout
│ │ ├── MapView.js # Mapbox map component
│ │ └── ProtectedRoute.js # Auth wrapper
│ ├── context/
│ │ └── UserContext.js # Global user state
│ ├── lib/
│ │ ├── api.js # REST API client
│ │ └── socket.js # Socket.io client
│ ├── pages/
│ │ ├── login.js # Authentication
│ │ ├── passenger/
│ │ │ └── index.js # Request ride interface
│ │ ├── driver/
│ │ │ └── index.js # Drive interface
│ │ └── admin/
│ │ └── index.js # Admin dashboard
│ └── styles/
│ └── globals.css # Tailwind + custom styles
│
├── server/ # Express backend
│ ├── src/
│ │ ├── config/
│ │ │ └── database.js # PostgreSQL pool
│ │ ├── controllers/
│ │ │ └── tripController.js
│ │ ├── routes/
│ │ │ ├── auth.js
│ │ │ ├── trips.js
│ │ │ └── locations.js
│ │ ├── services/
│ │ │ └── dispatchService.js # Smart dispatch algorithm
│ │ ├── socket/
│ │ │ └── handlers.js # Socket.io events
│ │ ├── utils/
│ │ │ └── distance.js # Haversine formula
│ │ └── index.js # Main server
│ └── db/
│ ├── schema.sql # Database schema
│ └── seed.sql # Sample data
│
└── README.md # This file
- Node.js 16+
- PostgreSQL 14+ with PostGIS extension
- Mapbox account (free tier) for maps
git clone <repository-url>
cd event-loop- Create account at supabase.com
- Create new project
- Go to SQL Editor
- Run
server/db/schema.sql - Run
server/db/seed.sql - Copy connection string from Settings → Database
# Install PostgreSQL and PostGIS
brew install postgresql postgis # macOS
# or
sudo apt-get install postgresql postgis # Linux
# Create database
createdb eventloop
psql eventloop -c "CREATE EXTENSION IF NOT EXISTS postgis;"
# Run schema and seed
psql eventloop -f server/db/schema.sql
psql eventloop -f server/db/seed.sqlcd server
npm install
# Create .env file
cp .env.example .envEdit server/.env:
PORT=5000
DATABASE_URL=postgresql://username:password@localhost:5432/eventloop
NODE_ENV=development
CLIENT_URL=http://localhost:3000Start server:
npm run devServer runs on http://localhost:5000
cd client
npm install
# Create .env.local file
cp .env.example .env.localEdit client/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:5000
NEXT_PUBLIC_SOCKET_URL=http://localhost:5000
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_mapbox_token_hereGet Mapbox token at mapbox.com/account/access-tokens
Start client:
npm run devClient runs on http://localhost:3000
- Username:
passenger1topassenger10 - Password:
pass123
- Username:
driver1todriver5 - Password:
driver123
- Username:
admin1toadmin3 - Password:
admin123
- Login as
passenger1/pass123 - Browser requests geolocation permission
- Select destination from dropdown
- Click "Request Ride"
- System finds nearest pickup location
- Smart dispatch finds best driver
- Modal shows assigned driver with ETA
- Trip status updates in real-time
- Login as
driver1/driver123 - Click "Go Online" button
- Browser requests location permission
- Location tracked continuously (watchPosition)
- Updates sent to server every 5 seconds
- Wait for ride assignment (socket event)
- View trip details (pickup/dropoff)
- Click "Navigate to Pickup" → Opens Google Maps
- Click "Start Trip" when passenger picked up
- Click "Navigate to Dropoff" → Opens Google Maps
- Click "Complete Trip" when finished
- If queued trip exists, it automatically becomes active
- Navigate to
/create-event - Enter an Event Name (required)
- Optionally set a Slug — auto-derived from the name if left blank (e.g. "Acme Summit" →
acme-summit) - Click Create Event
- On success, the page shows:
- Event name and slug
- Auto-generated admin username (e.g.
admin@acme-summit) - A one-time Login Token — copy it before navigating away
- Click Go to Admin Login to be taken to
/loginwith the token pre-filled and auto-submitted
The login token is only shown once. If lost, a new one must be generated directly in the database.
Located in server/src/services/dispatchService.js:
- Fetch online drivers with < 2 active trips
- Calculate scores:
- IDLE drivers: Direct time to pickup (Haversine distance ÷ 30mph)
- BUSY drivers: (Time to dropoff) + (Time from dropoff to new pickup)
- Return driver with lowest score
- Queue trip if driver is BUSY (set
queued_order = 1)
POST /api/tenants- Create a new tenant and auto-generate its admin user. Returns{ tenant, admin: { username, login_token } }. NoX-Tenant-IDheader required.
POST /api/auth/login- Login userPOST /api/auth/token-login- Login via magic link token (noX-Tenant-IDrequired)GET /api/auth/me/:userId- Get user details
GET /api/locations- Get all locationsGET /api/locations/:id- Get location by ID
POST /api/trips/request- Request ridePOST /api/trips/:id/complete- Complete tripGET /api/trips/driver/:driverId- Get driver's tripsGET /api/trips/passenger/:passengerId- Get passenger's trips
authenticate
socket.emit('authenticate', { user_id: 4, role: 'driver' });update_location (Driver only)
socket.emit('update_location', {
user_id: 4,
latitude: 40.7128,
longitude: -74.0060
});driver_status_change
socket.emit('driver_status_change', {
user_id: 4,
is_online: true
});trip_status_update
socket.emit('trip_status_update', {
trip_id: 1,
status: 'IN_PROGRESS',
driver_id: 4
});connection_success - Connection established
new_ride - New trip assigned
{
trip: { /* trip details */ },
isQueued: false,
queuePosition: 0
}trip_status_update - Trip status changed
driver_location_update - Driver location changed (admin only)
admin_dashboard_update - Full dashboard data (admin only)
- id, username, password, role (passenger/driver/admin)
- user_id, is_online, current_lat, current_long, current_socket_id, status (IDLE/BUSY)
- id, name, latitude, longitude
- id, passenger_id, driver_id, pickup_location_id, dropoff_location_id
- status (REQUESTED/ASSIGNED/IN_PROGRESS/COMPLETED)
- queued_order (0 = current, 1+ = queued)
# Backend tests (when implemented)
cd server
npm test
# Frontend tests (when implemented)
cd client
npm test# Backend
cd server
npm start
# Frontend
cd client
npm run build
npm startPORT=5000
DATABASE_URL=postgresql://...
NODE_ENV=production
CLIENT_URL=https://your-frontend.comNEXT_PUBLIC_API_URL=https://your-backend.com
NEXT_PUBLIC_SOCKET_URL=https://your-backend.com
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_token- Push to GitHub
- Connect repository to hosting platform
- Set environment variables
- Deploy
- Push to GitHub
- Connect repository to Vercel
- Set environment variables
- Deploy
Already cloud-hosted. Just use connection string in production.
- Admin dashboard with live map
- Trip history pages
- Driver ratings and reviews
- Price calculation
- Payment integration
- Push notifications (FCM)
- Service worker for offline support
- Real routing with OSRM or Mapbox Directions
- Driver earnings dashboard
- Trip cancellation
- Multi-language support
- Unit and integration tests
- Set
NEXT_PUBLIC_MAPBOX_TOKENinclient/.env.local - Get token at mapbox.com/account/access-tokens
- Check backend is running on correct port
- Verify
NEXT_PUBLIC_SOCKET_URLmatches backend URL - Check CORS settings in
server/src/index.js
- Grant location permissions in browser
- Check browser console for errors
- Verify driver is "Online"
- Check
DATABASE_URLis correct - Verify PostgreSQL is running
- Ensure PostGIS extension is enabled
Apache 2.0
Devakh Rashie