- Coupons / promo codes — admin creates flat-₹ or %-off coupons
(min booking amount, per-user use limit, total use limit, expiry, max
discount cap for percent coupons). Customers preview a discount at
POST /api/coupons/validateand apply it on the booking page; the discount is re-validated and locked in atomically inside the same transaction that creates the booking, so it can't be double-spent past its limit under concurrent requests. - Downloadable tax invoices —
GET /api/bookings/:id/invoicestreams a GST-structured PDF for any paid booking (line items: fare, convenience fee, coupon discount, GST — see the caveat about the placeholder GST rate in.env.example). Available from the customer dashboard's booking history.
- Seat-based booking — replaced the old "one booking blocks the whole
slot" logic with real seat pooling. Any number of customers can book the
same boat/date/time until
boat.capacityis reached; cancelling a booking frees its seats automatically (seats-taken is always derived from the sum of active bookings, not a separate counter). The check-and-insert runs inside a MongoDB transaction (bookingController.createBooking) so two people booking the last seat at the same instant can't both succeed. NewGET /api/boats/:id/availability?date=&time=endpoint backs a live "X seats left" indicator on the booking page. - Transactions for the other multi-write paths too: payment
verification (
paymentController.verifyPayment) and refund approval (adminController.approveRefund) now update their Booking + Payment documents atomically. - Pagination on every list endpoint: boats, admin users, admin
boats, admin bookings, admin payments, admin reviews all accept
?page=&limit=and return{ page, pageSize, totalRecords, totalPages }. The admin dashboard's Users and Bookings tables have working Prev/Next controls now instead of a hard 25-item slice. - Environment validation at startup (
config/validateEnv.js) — the server refuses to boot withoutMONGO_URI/JWT_SECRETand warns about unset optional integrations, instead of failing confusingly later. - Winston logging — structured JSON logs to
backend/logs/(error.log- combined.log) alongside readable console output; Morgan's HTTP access log now flows through Winston too.
- Docker —
backend/Dockerfile, rootDockerfile(nginx static frontend), anddocker-compose.ymlwiring Mongo (as a one-node replica set, required for transactions) + backend + frontend for one-commanddocker compose up. - CI —
.github/workflows/ci.ymlinstalls, syntax-checks, runs tests if present, and boots the server against a real Mongo service container on every push/PR. - SEO basics —
robots.txt,sitemap.xml, canonical URLs and Open Graph/Twitter Card tags on the main public pages (replaceYOUR-DOMAIN.examplewith your real domain before deploying).
This is the same JalRide UI you had before (theme, colors, layout, pages all unchanged), now wired up to a real Node.js/Express/MongoDB backend. The frontend still works completely standalone with no server running — it just runs in an offline demo mode instead of hitting the API.
- Refresh tokens & logout everywhere — access tokens now expire in 15
minutes; the frontend silently exchanges a 30-day refresh token for a
new one so sessions don't drop.
PUT /api/auth/logout-allbumps atokenVersionon the user, instantly invalidating every token issued on every device. - Email verification — registering sends a verification link to
verify-email.html?token=...;POST /api/auth/resend-verificationre-sends it. Verification is informational (doesn't block login) — tightenauthController.loginif you want to require it. - Wishlist — customers can save boats from the boats page (heart icon, live boats only) and manage them from the dashboard.
- QR ticket verification / check-in — boat owners and admins can
paste a scanned ticket's QR payload into their dashboard to verify its
signature and mark the passenger checked in (
POST /api/bookings/verify-ticket). - Refund workflow — customers can request a refund on a paid booking from their dashboard; admins see pending requests and approving one calls the real Razorpay refund API (falls back to recording the decision if Razorpay isn't configured, so nothing throws in dev).
jalride/
├── index.html, boats.html, booking.html, ticket.html, about.html ← original pages
├── login.html, register.html, forgot-password.html, reset-password.html, verify-email.html ← auth pages
├── customer-dashboard.html, owner-dashboard.html, admin-dashboard.html ← role dashboards (now with wishlist / ticket scanner / refunds)
├── css/style.css ← unchanged design system, plus a small auth-pill addition
├── js/
│ ├── boats-data.js, main.js, contact.js ← original (unchanged)
│ ├── boats.js, booking.js, ticket.js ← updated: try the API, fall back to demo data
│ ├── api.js ← new: fetch wrapper + auth/session helpers
│ ├── auth-pages.js ← new: login/register/forgot/reset form logic
│ └── customer-dashboard.js, owner-dashboard.js, admin-dashboard.js ← new: per-role dashboards
└── backend/ ← new: Node.js + Express + MongoDB REST API (see backend/README.md)
Frontend only (no backend): open index.html directly, or serve the
folder with any static server. Boats load from the bundled demo dataset,
bookings save to localStorage, and tickets render client-side — exactly
like the original prototype.
Full stack:
- Set up and start the API — see
backend/README.md(install deps, configure.env, run MongoDB, seed the admin account,npm run dev). It listens onhttp://localhost:5000by default. - Serve the frontend folder with any static server, e.g.:
npx serve . # or: python3 -m http.server 5173
- If your API isn't on
http://localhost:5000/api, setwindow.JAL_API_BASEbeforejs/api.jsloads (e.g. add<script>window.JAL_API_BASE = "https://your-api.example.com/api";</script>in each HTML<head>), and setCLIENT_URLinbackend/.envto match your frontend's origin (CORS is locked to it). - Register a customer and a boat-owner account from
register.html. Log in as the seeded admin (backend/.env→ADMIN_EMAIL/ADMIN_PASSWORD) atlogin.htmlto approve owners and boats fromadmin-dashboard.html.
- Nothing was redesigned. Same palette, type, nav, cards, and page structure.
boats.jsnow fetchesGET /api/boatsfirst; if that fails (or returns nothing), it silently keeps using the original bundled demo boats, with a small banner explaining why.booking.jschecks whether the API responded and whether you're logged in as a customer. If so, it creates a real booking, opens Razorpay Checkout, verifies payment server-side, and redirects to a server-backed ticket. Otherwise it falls back to the exact originallocalStorageflow.ticket.htmlcan render either a server-backed ticket (Mongo ID in the URL, PDF download via the API) or the original client-only ticket (fromlocalStorage).- Every page's nav now has a small auth slot (
#authArea) showing Log In/Sign Up or the logged-in user's name + dashboard link + logout — everything else in the nav is untouched.
- This environment can't run a live MongoDB instance, so the full register → book → pay → ticket flow has been verified at the level of "every route, controller and middleware loads and responds correctly" (health check, 404s, auth rejection, etc. all tested against a running Express instance), not "watched a real booking complete end-to-end." Run it locally with a real Mongo connection and test the flow yourself before relying on it.
- Razorpay and SMTP need your own credentials in
backend/.env— without them, payments return a clear "not configured" error and emails are logged to the console instead of sent, but nothing else breaks. bcryptjsis used in place ofbcrypt(same API, no native build step). Seebackend/README.mdfor details.