Skip to main content
George Khananaev
Case Study

TravelOffer: a multi-brand travel booking platform

Next.js 16.1 + MongoDB booking flow with trilingual RTL/LTR support, state-machine order flow, and Stripe payments

4 min read
Moon HolidaysJul 2025 — PresentHead of Development

Overview

Moon Holidays needed a booking platform that could serve multiple brands under the group umbrella, speak three languages (English, Arabic, Hebrew) with full RTL/LTR handling, and cover every step of the travel booking flow from search to payment. TravelOffer is what I designed and built, sitting on top of the Travel Panel core platform that powers every Moon Holidays product.

What it does

End-to-end travel booking. The customer lands on /start to pick an offer, navigates /location/[from]/[to]/[slug]/[item] to explore hotels, attractions, and transportation, selects a flight at /flight/[slug], compares alternatives at /select-offer, and moves through the checkout state machine: /order/confirm/order/payment/order/completed. Authentication supports SMS, WhatsApp, Email, and Google OAuth. The same codebase serves every brand in the group through cookie-based brand switching and a dynamic theming system, so a new brand spins up without forking the app.

Architecture

TravelOffer uses a deliberate three-layer architecture that every feature must respect:

UI Layer (Pages & Components)
Action Layer (_actions) — Thin wrappers for client access
Server Layer (_server) — Validation and business logic
Data Layer (_data) — Database and API access
Model Layer (_models) — Mongoose schemas
MongoDB Database

Each layer communicates only with adjacent layers. No cross-cutting shortcuts. The discipline comes from learning what happens in a large codebase when layers bleed into each other: every bug hides in the seams, every change ripples unpredictably, every refactor requires rewriting things twice.

Key patterns in production

  • Repository pattern for data-layer access. Every Mongoose model has a repository that owns reads and writes.
  • Server Actions as thin client-accessible wrappers for server-side mutations, following the Next.js 16 pattern.
  • Customer status state machine: pending → viewing → confirmed → paid. Transitions are explicit and enforced at the server layer.
  • Multi-brand data model: brand is a first-class concept, not a runtime config toggle. Cookie-based switching, environment-variable fallback, and configuration in _data/brand.ts.

Next.js 16 session management

Session handling uses the Next.js 16 proxy.ts deny-by-default pattern. Routes are protected unless explicitly marked public. Authentication generates OTP codes for SMS/WhatsApp/Email flows, hashes credentials with bcrypt, integrates Google OAuth, and logs every access.

Internationalization that works

next-intl powers the i18n layer. The app ships in English, Arabic, and Hebrew. RTL and LTR are not just a CSS flip: every component has dir="auto" handling for mixed-language content, CSS truncate for text overflow direction awareness, and locale-specific routing. This is the part most multi-language projects underestimate. I ended up writing small wrapper components that imposed consistent direction handling, which saved us every time a new feature arrived.

Multi-currency at the UI level

Per-service currency handling with 30+ currency symbols. The groupServicesByCurrency() utility and GroupedCurrencyDisplay component render single or stacked price displays when an itinerary spans multiple currencies (flights in USD, hotels in THB, attractions in EUR). The fallback currency is configurable per brand.

Payments and security

Stripe for payment processing with webhook verification and idempotency. bcrypt for password hashing. DOMPurify for XSS sanitization on user-generated content. Comprehensive Zod validation at every API route. Error Boundaries with graceful custom fallback UI on all major routes.

Performance optimizations

Suspense boundaries, memoization, debounced validation, dynamic imports, image optimization via Next.js with remote CDN wildcard hostname, and a 4 MB body size limit on Server Actions for file uploads. Bundle analyzer wired into npm run analyze so every release checks its own weight.

UI details that took time to get right

Four modal transition types (fade, zoom, slide, grow) with fixed or floating close buttons. Animated modals are a detail most apps skip, but a high-friction booking flow benefits from momentum in the small interactions. Mobile-first responsive design with separate mobile and desktop layouts for complex components like flight selectors and hotel galleries.

Testing

Playwright for end-to-end testing. The booking flow has enough edge cases — expiring sessions, partial form state, payment failures, upstream API outages — that unit tests alone give false confidence.

Tech stack

Next.js 16.1.2 with App Router and Turbopack, React 19.2.3, TypeScript (strict mode), Tailwind CSS, Material-UI 7.3.7, MongoDB, Mongoose ODM, Jose (JWT), Google OAuth, Stripe (server + client SDKs), next-intl, React Hook Form, Zod, bcrypt, DOMPurify, Playwright, Pino, Framer Motion, @mui/material, @emotion/react, mui-tel-input, google-libphonenumber, react-hot-toast, react-photo-view, react-signature-canvas.

Takeaway

Multi-brand theming sounds simple on paper: swap colors and logos per tenant. In practice it touches routing, metadata, analytics, emails, payment receipts, and error pages. Getting it right meant treating the brand as a first-class concept in the data model instead of a runtime config toggle, and writing direction-aware wrappers around every component the moment we hit the first RTL bug.

For a simpler open-source starter that uses many of the same patterns (Next.js frontend, FastAPI backend, JWT auth, role-based access), see PyNextStack, which I maintain as a reference implementation you can fork and ship.

Working on something similar?

I take on a handful of engagements at a time: architecture reviews, platform rescues, AI integration, and fractional technical leadership. The clearer the brief, the faster the reply.

How I work
Travel Panel: the core travel management platform
Travel Panel: the core travel management platform illustrationFeaturedMoon Holidays
11 minDec 2022 — Present

Travel Panel: the core travel management platform

FastAPI backend, Next.js operator portal, and B2B partner portal powering Moon Holidays end to end

Travel Panel is the core system at Moon Holidays. A FastAPI backend, a Next.js operator portal, a B2B partner portal, and the orchestrator for every downstream product: TravelOffer for end customers, Live Deck for call-center TVs, Vercel Controller for deployment cache, StaySync for allotment availability, and a WebSocket messenger for internal communication. Running on AWS with ALB, MemoryDB, CloudFront, S3, and more.

fastapinextjspythontypescript
Live Deck: a call center dashboard for the TV wall
Live Deck: a call center dashboard for the TV wall illustrationMoon Holidays
5 minJan 2026 — Present

Live Deck: a call center dashboard for the TV wall

React 19 + Vite 7 + React Server Components, version 2.0.1, 9 widgets, 32×18 draggable grid, SSE streaming with zombie-connection detection

Live Deck is the wall-mounted dashboard the Moon Holidays call center watches all day. Version 2.0.1. React Server Components, Vite 7, TanStack Query v5, nine widgets in a draggable 32×18 grid, real-time Aircall WebSocket streaming, SSE with zombie-connection detection, optional single-use ticket auth, and a hardened security model for unattended kiosk operation.

reacttypescriptvitersc
Moon Support Hub: an enterprise ticketing platform
Moon Support Hub: an enterprise ticketing platform illustrationMoon Holidays
6 minDec 2025 — Present

Moon Support Hub: an enterprise ticketing platform

Next.js 16 with Prisma on MongoDB, 740+ source files, 186 React components, 135+ API endpoints, 60 models, and 14 scheduled background jobs

Moon Support Hub is a full-featured enterprise support system: ticketing with SLA management, a knowledge base with publishing workflow, role-based access control, MinIO attachments, and a customer portal. Built on Next.js 16 with Prisma on MongoDB, 740+ source files, 186 React components, 135+ API endpoints, 60 models, 14 scheduled background jobs, and 8 pre-built reports.

leadershipnextjsmongodbprisma