Rides
Local dashboard — riders, rides, drivers, pricing, promotions & payments
—
Loading…

Living snapshot of the system — what apps exist, how they talk to the backend, and where things live in the repo. Update this panel (and ARCHITECTURE.md) when the architecture changes.

System architecture Four clients (Flutter rider and driver apps, React landing site, this admin dashboard) call a NestJS API with REST and Socket.IO gateways, backed by feature modules, PostgreSQL, local uploads, and external services Google, Twilio and Stripe. Rider app Flutter Driver app Flutter Landing site React + Vite Admin dashboard this page · /admin NestJS API — backend/api REST + validation · Socket.IO gateways /rider & /driver FEATURE MODULES Auth Driver Auth Riders Drivers Driver Applications Rides Matching Realtime Payments Pricing Promotions SMS / OTP Feedback Support Messages Admin News Home Content Feature Flags Route Cache PostgreSQL TypeORM · incl. driver_applications Local /uploads Driver docs · promo & news images EXTERNAL SERVICES (CALLED FROM MODULES) Google Sign-In · Maps / Places Twilio SMS OTP Stripe Payments · Connect payouts Newest addition: landing site → POST /driver-applications → driver_applications table

Apps

  • Rider app — Flutter, apps/rider_app. Google-first auth + phone OTP, simulate mode, EN/MS/中文, dark mode.
  • Driver app — Flutter, apps/driver_app. Sign-up, onboarding funnel (incomplete → pending → approved), go online, earnings.
  • Landing site — React + Vite, apps/landing. Public marketing page: rider/driver offering, 🇲🇾/🇸🇬 requirements, driver registration form, live-editable name & logo.
  • Admin dashboard — this page, served by the backend at /admin. Local-dev only, no auth guard yet.

Backend & integrations

  • NestJS + PostgreSQL (TypeORM, schema sync on start; DB auto-created by ensure-database.ts).
  • Realtime — Socket.IO gateways /rider & /driver: live location, offers, ride status.
  • Payments — Stripe charges + Connect payouts. SMS — Twilio OTP. Google — Sign-In, Maps/Places.
  • Driver leads — public POST /driver-applications from the landing form → driver_applications table (kept separate from the driver-app onboarding funnel shown in the Applications tab).

Design language

Own style, not a clone: indigo accent #4F46E5, wavy headers, pill navigation. Grab/TADA used only as structural references. The landing site follows the same language.

Docs

  • ARCHITECTURE.md — living design doc (stack, data model, build status).
  • README.md — repo guide & conventions.
  • apps/landing/README.md — landing setup, brand & copy editing.
  • backend/api/README.md — backend setup & credentials.

Runbook — install, configure, run & build

Everything needed to go from a fresh clone to a running system.

1 · Install & run the server (backend/api)

Prerequisites: Node 20+, npm, Docker (for local Postgres — or use AWS RDS).

cd backend/api
cp .env.example .env     # then edit — see card 2
docker compose up -d     # Postgres 16 (user/pass/db: ridehailing)
npm install
npm run start:dev        # auto-creates the DB, API on :3000

This dashboard: http://localhost:3000/admin. Production: npm run build then npm run start:prod. Useful extras: npm run typecheck, npm run db:ensure.

2 · Configure the server (.env)

  • DATABASE_URL — local docker: postgres://ridehailing:ridehailing@localhost:5432/ridehailing; for RDS also set DATABASE_SSL=true (+ optional DATABASE_SSL_CA_PATH).
  • JWT_ACCESS_SECRET / JWT_REFRESH_SECRET — generate with openssl rand -base64 48.
  • SMS_PROVIDER — console (OTP printed to server logs, zero setup) or twilio + the three TWILIO_* vars.
  • GOOGLE_CLIENT_ID_WEB/ANDROID/IOS — OAuth client IDs for Google Sign-In.
  • STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET — optional; payments run with mock data if unset.
  • ADMIN_PASSWORD — protects this dashboard: every /admin/* API call needs the x-admin-token header (the login overlay handles it).
  • CORS_ORIGINS — comma-separated production allowlist; unset = dev-open. Native apps are unaffected (no Origin header).
  • HITCH_MAX_TRIPS_PER_DAY (default 2), PUBLIC_BASE_URL, PORT. Platform commission/flat fee/processor fee/competitor benchmark moved off env vars onto the Platform fees tab (DB-backed, admin-editable).

3 · Landing site (apps/landing)

cd apps/landing
npm install
npm run dev              # :5173, proxies /api → :3000

Production build: VITE_API_BASE_URL=https://api.example.com npm run build → static dist/ folder, host anywhere. VITE_SIMULATE_BACKEND (default true) makes the driver form fall back to a clearly-labelled simulated success when the API is unreachable — set it to false in production. The on-page booking simulator is pure client-side, always available.

4 · Flutter apps (rider & driver)

cd apps/rider_app      # or apps/driver_app
cp env.example.json env.json   # edit API_BASE_URL etc.
flutter pub get
flutter run

Config is loaded from env.json at startup (bundled asset — no --dart-define flags). Set "SIMULATE_BACKEND": "true" there to run the whole app on mock data with no backend. Google Maps keys also go in AndroidManifest / AppDelegate — see each app's README.

Complete reference

The full surface of the system — expand a section.

📡 API reference — every endpoint, grouped by audience

Generated from the controllers (kept in sync by hand — re-scan src/**/*.controller.ts when adding endpoints). Auth column: public (none), rider JWT / driver JWT (Bearer token), admin (x-admin-token header).

Open this section to render…
🗄️ Data model — tables & relationships

Core entities and how they relate (TypeORM, schema-sync in dev). Standalone config/content tables not shown in the diagram: otp_codes, pricing_config, promotions, news_posts, plan_ahead_content, feature_flags, route_cache (cached Google Directions results).

🎚️ Feature flags — every remote toggle
  • Server-enforced in matching (take effect immediately): driver.offers (master switch — off = rides wait for manual assignment), driver.hitchMode, driver.destinationMode, driver.autoAccept, driver.backToBackTrips.
  • Driver-app UI gating (picked up at app start): driver.chat, driver.earnings, driver.payouts, driver.rateRider, driver.tripHistory, driver.support.
  • Rider-app UI gating: rider.promotions, rider.news, rider.planAhead, rider.scheduledRides, rider.chat, rider.ratings, rider.support.
  • Flags seed themselves (all enabled) at boot — see src/feature-flags/feature-flags.service.ts; toggle them in the Features tab.
📱 Feature matrix — what each app does today

Rider app (Flutter)

  • Google + phone-OTP auth; Profile → Connect Google preserves phone accounts; app/Google sign-out
  • Booking: Places autocomplete, tier picker, fare estimate, promo codes
  • Live tracking (socket + polling), driver chat, ratings
  • Scheduled rides, trip history
  • Payments: Stripe cards, wallet-style methods
  • Promotions, news, plan-ahead content
  • Dark mode, EN/MS/中文, simulate mode
  • Support requests & feedback threads, account deletion

Driver app (Flutter)

  • Google + phone-OTP auth (/driver-auth); separate role tokens and admin approval; Connect Google + tracking cleanup on logout
  • Onboarding wizard (full-time / Hitch) + document upload
  • Go online/offline, live offers with 15s countdown
  • Trip lifecycle: heading → arrived → start → complete
  • In-ride chat, rate rider
  • Earnings, payouts (Stripe Connect), trip history
  • Destination mode, auto-accept, back-to-back queue
  • Dark mode, EN/MS/中文, simulate mode

Landing site (React)

  • Editable brand (name/tagline/logo) — live or in code
  • Rider features section
  • Booking simulator (SG/MY, real fare formula)
  • Driver offer + 🇲🇾/🇸🇬 requirements
  • Driver registration → driver_applications
  • Simulate-mode fallback when API is down

Admin (this page)

  • Password login (ADMIN_PASSWORD)
  • Live ops map (5s refresh) + analytics dashboard
  • Rides ops: assign, progress, cancel, refund, chat
  • Driver fleet, detail view, suspend, application queue
  • Payouts: balances, Stripe Connect transfers, history
  • Riders, payment test-cards, OTP log
  • Pricing tiers, promotions, home content, news
  • Support queue + two-way feedback threads
  • Feature flags; this Project reference tab
🚀 Deployment & security checklist (before going public)
  • Set a strong ADMIN_PASSWORD; never expose /admin without it.
  • Development console SMS uses 111111 without request throttling. Production and real SMS retain the request limit; configure real SMS before launch.
  • Replace dev JWT secrets (openssl rand -base64 48) and rotate any credential that ever appeared in a committed file — check .env.example holds placeholders only.
  • Lock down CORS in main.ts (currently origin: true for dev) to your real origins.
  • Production defaults to DATABASE_SYNCHRONIZE=false. Keep automatic schema changes disabled on restored data; review migrations before future schema changes.
  • Use DATABASE_SSL=true + DATABASE_SSL_CA_PATH (RDS CA bundle) for managed Postgres.
  • Set STRIPE_WEBHOOK_SECRET and point a real Stripe webhook at POST /webhooks/stripe.
  • Add rate limiting / CAPTCHA to the public POST /driver-applications endpoint before heavy marketing traffic.
  • Build the landing with VITE_SIMULATE_BACKEND=false and a real VITE_API_BASE_URL; host dist/ behind HTTPS.
  • Move /uploads to S3/CDN (local disk today) and put the API behind TLS with PUBLIC_BASE_URL set.
  • Known deferred items: FCM push, Redis presence, PostGIS geo queries, scheduler for far-future rides (ARCHITECTURE.md §14).
🩺 Troubleshooting
  • Every panel says "Could not load" — API is up but DB unreachable: check DATABASE_URL / Postgres container.
  • Login overlay rejects you — ADMIN_PASSWORD unset or mismatched in .env; restart after editing.
  • Features tab empty — flags seed at boot; restart the backend once.
  • Ride stuck in requested — no eligible driver online and driver.offers may be off; assign manually from Rides or bring a driver online.
  • Landing form always "succeeds" — that's simulate mode (yellow warning shown); start the backend or build with VITE_SIMULATE_BACKEND=false.
  • OTP never arrives — SMS_PROVIDER=console prints codes to the server log instead of texting.
  • Diagrams below don't render — Mermaid CDN unreachable; connect to the internet once and reopen this tab.
  • Stale page after editing admin.html — served with no-store; hard-refresh clears any bfcache copy.

Flows & documentation

End-to-end flows as they're implemented today. Diagrams are drawn with Mermaid (loaded from CDN on first open — needs internet once) and follow the dashboard theme.

1 · Booking & ride lifecycle

The rider is charged when the ride is requested and refunded on cancel. Status chain: requested → matched → enRoute → arrived → inProgress → completed (or cancelled at any point before completion).

2 · Driver matching

Nearest-eligible ranking (haversine). Eligibility honors vehicle tier, Hitch daily-trip cap, destination mode, auto-accept and back-to-back queueing — all gated by feature flags. Alongside that one-at-a-time push, the Jobs tab (driver.availableJobs flag) lets any eligible driver browse and directly claim a still-unmatched ride instead of waiting for an offer.

3 · Driver onboarding & landing-site leads

Two entry points: the landing site produces leads (driver_applications), while a real Driver account is created by the driver app and walks the approval funnel reviewed in the Applications tab.

4 · Authentication (Google-first, then phone)

Google OAuth native credentials and real-device verification are still required; see docs/GOOGLE-SIGN-IN.md. Rider/driver access and refresh tokens are separated; account linking requires an existing authenticated session. Same dual path for riders and drivers: verified email immediately via Google, phone OTP optionally attached to the same account (Bearer token prevents duplicates).

5 · Payments & driver payouts

Stripe throughout: rider charge on request, refund on cancel; per-trip earnings ledger (gross / commission / net) and payouts via Stripe Connect Express.

6 · Realtime channels

Socket.IO namespaces with an in-memory presence map (Redis deferred). REST fallback for location pushes; persisted last-known position for this dashboard and cold-start matching.

7 · Content management (promos, news, home, pricing)

Everything the rider app shows that isn't a ride is edited here and served from the same API — images upload to local /uploads.

8 · Support & feedback

Support requests are a resolve-only queue; feedback is real two-way messaging with one thread per (rider, topic) — general, payment dispute, or account deletion.

Loading…

Every ride that's ever touched a payment — charged, refunded, or failed — in one place. Click View for the fare breakdown, refund action, and the rider↔driver message thread for that ride.

Loading…

In-trip audio recordings, started opt-in by either the rider or the driver — the audio file itself stays on that person's phone (not uploaded here yet). This is just the log of that a recording happened, so it stays visible even after the owner deletes it on their end (soft delete — Restore undoes that).

Loading…

Simulated fleet — there's no real driver app yet, so assigning a driver to a ride on the Rides tab is how a "match" happens. A driver goes busy when assigned and back to available when that ride ends or is cancelled.

Loading…

Every driver-app sign-up, including incomplete ones still working through the onboarding wizard (their already-uploaded documents show here too). Review the details and documents, then Approve (they can go online immediately) or Reject with a reason (shown to them in the app; they can fix and resubmit).

Loading…

Who is online right now and every non-terminal ride, refreshed every 5 seconds while this tab is open. The map (OpenStreetMap, no API key) shows online drivers 🚗 and ride pickups 📍.

Online drivers

Loading…

Active rides

Loading…

Launch numbers: totals plus daily volume/GMV for the last 14 days.

Loading…

What the platform owes each driver (lifetime net earnings − payouts). Pay out records the payout and, when Stripe + the driver's Connect account are set up, sends a real transfer — otherwise it just records it (paid outside Stripe).

Outstanding balances

Loading…

Payout history

Loading…
Loading…

Click Payment on a rider to view their saved cards, or attach a Stripe test mode card for exercising the charge-on-request / refund-on-cancel flow.

Loading…
Loading…

What riders are actually charged: baseFare + distance(km) × perKmRate, floored at minFare, ×peakMultiplier during the peak window. Only enabled tiers show up in the app. Edit a row and hit Save.

Loading…

What the platform keeps from a driver's fare, and what's shown on the driver's trip-summary / Earnings screens about it. Fee mode is mutually exclusive — only the active mode's rate is actually charged. The processor fee and "you saved" line are shown/hidden in the app via the driver.showProcessorFeeLine / driver.showSavingsMessage feature flags (Features tab), independent of the rates set here. Changes only apply to trips completed after saving — past trips keep the numbers they were actually charged.

Loading…

How far a ride offer can go looking for a driver. A driver whose GPS is further than this from the pickup point is never offered that ride, even if nobody closer is online — the ride just keeps searching (and stays claimable from the driver app's Jobs tab) until either a driver within range comes online or the rider cancels. Nearest-driver-first ranking inside that radius is unaffected by this setting. Takes effect on the next matching scan (within ~10s) — no restart needed.

Loading…

Fare = tier price × time-band multiplier (by day type: weekday / weekend / holiday / holiday eve) × rain surge (manual toggle) × demand surge (manual dial or auto-computed from live driver supply vs. pending requests). Draw a zone on the map to give an area its own rules — a pickup outside every zone uses the Global default rules instead.

Click "Draw new zone", then click the map to place each corner, then Finish.

Zones

Loading…
Loading…

Public holidays

Only the holiday date itself needs adding — the day before it is automatically treated as "holiday eve" everywhere.

Loading…

Preview / test

Check what multiplier a pickup point would get right now, or at a chosen date/time — without touching a real ride.

Home-screen promo banners. Only active ones show in the app, in sort order (lowest first). Upload a photo (cropped to the banner's shape) or fall back to an emoji + color.

Loading…

Copy for the Home screen's "Plan ahead" card — tapping it in the app opens a detail page showing "Detail page text" (falls back to the short body if left blank).

Loading…

Posts shown in the rider app's News tab. Only published posts are visible there — draft one first, then flip it on when ready.

Loading…

Submitted from the rider app's Profile screen — Contact support and Dispute a payment both land here. No auto-response; follow up with the rider directly (phone/email on file under the Riders tab), then mark Resolve.

Loading…

Real two-way messaging — general feedback (Settings), payment disputes, and account-deletion requests all land here as separate threads per rider, unlike Support above. Every rider message gets an instant auto-reply (like Contact support), and you can also type a real reply that shows up in the app.

Loading…

Whether each rider/driver has actually registered a device for push (FCM), and when. A push failing usually means one of: the app never got this far (permission denied, or native Firebase config missing — see the banner below), the registration succeeded but the token is now stale (reinstalled app, cleared data), or delivery itself failed (bad credentials, wrong Firebase project). Use Send test on a row to find out which.

Riders

Loading…

Drivers

Loading…

Remote on/off switches for whole features in the rider and driver apps. Apps fetch these at startup; a few (matching/offers, hitch, destination mode, auto-accept, back-to-back) are also enforced server-side, so flipping them here takes effect immediately — no app update needed.

Loading…