Skip to main content

Tenant UI source

Your booking experience UI lives in journeys/src/ in your tenant config repo. The platform fetches these files from GitLab, bundles them with esbuild, and serves the result as a single-page application. You own the UI code; the platform owns the build and hosting pipeline.

File structure

journeys/
journey.yaml # Configuration (documented in journey.yaml reference)
src/
main.jsx # Required entry point
App.jsx # Step orchestrator, URL param parsing
api.js # API client for /api/journeys/v1/*
styles.css # Tenant styles (imported from main.jsx)
steps/ # Per-step components
TripSelect.jsx
DepartureTravellers.jsx
RoomCategory.jsx
RoomPick.jsx
...

Entry point

The bundler looks for main.jsx or main.tsx in journeys/src/. This file must exist — the build fails without it.

A minimal entry point mounts your root component:

import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(<App />);

Module system and externals

RuleDetail
Module formatESM only — import / export
ReactExternalized via CDN import map — do not bundle React
Additional CDN depsDeclare in journey.yamlui.dependencies
CSSImport from your entry file; esbuild extracts and inlines it
Node built-insForbidden — fs, path, crypto, etc. are not available (this runs in the browser)
SecretsNever in UI source — credentials stay server-side

The platform provides react, react-dom, react-dom/client, and react/jsx-runtime via an import map. Any package listed in ui.dependencies is also externalized. Everything else is bundled by esbuild.

Framework components (@kaptio/journeys-ui)

The platform ships a shared component library available to tenant UI via the @kaptio/journeys-ui import. This is aliased at bundle time to the platform's framework code — you do not install it as a dependency. The deck-plan components are cruise-specific; touring tenants can keep the same step IDs while rendering room-option components from tenant source.

import {
BookingFlow,
CabinSelection,
DeckPlan,
renderBookingFlowStep,
renderImmersiveCabinPickStep,
} from '@kaptio/journeys-ui';

Available exports

ExportPurpose
BookingFlowStep container component that manages step navigation using your journey.yaml steps config
CabinSelectionInteractive deck-plan-based cabin picker with category theming and availability states
DeckPlanStandalone deck plan renderer (SVG-based, themed by category)
renderBookingFlowStepStep renderer — returns a framework React node for handled steps, or null so your tenant code renders its own component
renderImmersiveCabinPickStepRenders the immersive cabin selection step (deck plan + detail panel)
DEFAULT_VESSELS, VESSELS, registerVessels, resolveVesselVessel registry for deck plan data
CATEGORY_STYLES, categoryKeyForNameCategory-to-theme mapping

Do not call renderImmersiveCabinPickStep merely because the step ID is cabin-pick. Gate the cruise renderer on the journey's catalog model or on the presence of vessel/deck-plan configuration; touring UIs should render the room choices returned by /catalog/rooms.

Integration pattern

Use renderBookingFlowStep in your step switch to delegate framework-handled steps:

import { renderBookingFlowStep } from '@kaptio/journeys-ui';

function renderStep(step, steps, context, handlers) {
const frameworkNode = renderBookingFlowStep(step, steps, context, handlers);
if (frameworkNode) return frameworkNode;

// Tenant-owned steps
switch (step) {
case 'trip-select': return <TripSelect {...context} />;
case 'activities-quote': return <Activities {...context} />;
// ...
}
}

CSS theming tokens

Framework components use CSS custom properties for theming. Override them in your stylesheet to match your brand:

Brand palette tokens

TokenDefaultUsed for
--abyss#0a1929Dark backgrounds, headers, selection outlines
--ocean#14304aBalcony-category accents
--horizon#4a9d8fHorizon stateroom / junior suite accents
--gold#c8a565Captain's suite, primary buttons, eyebrow text
--rust#b85c2cTwin/triple share accents

Layout tokens

TokenDefaultUsed for
--cabin-paper#fdf8e9Fallback card backgrounds
--cabin-bone#faf6ecLight text on dark headers, gradient base
--cabin-char#1a2434Body text
--cabin-warm#7a6b4fLabels, compass text
--cabin-bgRadial gradientFull-page cabin selection background

Override example

:root {
--abyss: #1B365D;
--gold: #C5A572;
--horizon: #3D8B7A;
}

Import this file from main.jsx so esbuild includes it in the bundle.


URL parameters

Deep links are a tenant UI responsibility — the platform does not parse browser query parameters. See URL parameters for the full parameter contract, resolution logic, and implementation patterns.


API calls

All API calls use relative paths to the same origin:

const response = await fetch(`/api/journeys/v1/catalog/trips?tenantId=${tenantId}&currency=${currency}`);

Always include tenantId as a query parameter or in the request body. Key endpoints:

MethodPathPurpose
GET/api/journeys/v1/config?tenantId=Journey config (steps, branding, channels)
GET/api/journeys/v1/catalog/tripsNeutral trip/package catalog (/catalog/voyages remains an alias)
GET/api/journeys/v1/catalog/categoriesRoom or cabin category list
GET/api/journeys/v1/catalog/roomsRoom options for a trip + category (/catalog/cabins remains an alias)
GET/api/journeys/v1/catalog/activitiesPackage activities for a trip
POST/api/journeys/v1/basketsCreate a basket
GET/api/journeys/v1/baskets/:id/pricesBasket pricing (totals, deposit, balance)
POST/api/journeys/v1/baskets/:id/bookingsCreate booking (checkout)

Externally quoted services such as insurance or eSIMs are not added to Basket Service through components. Tenant UIs use the Journeys ancillary contract to quote and select them, then let checkout orchestration persist the resulting ad-hoc itinerary item. See Extending journeys with third-party services.


Bundle lifecycle

  1. Fetch: Platform retrieves journeys/src/** from GitLab (or reads from LOCAL_CONFIG_DIR in development).
  2. Bundle: esbuild compiles the entry point as ESM, externalizing React and declared dependencies, extracting CSS.
  3. Shell: The bundled JS and CSS are injected into an HTML shell using ui.title, ui.favicon_url, ui.meta, and the import map.
  4. Serve: The result is served at /:tenantId/ with ETag caching (Cache-Control: public, max-age=60, stale-while-revalidate=300).
  5. Invalidate: Pushes to journeys/** on the configured branch trigger a webhook that invalidates the bundle cache and triggers an async rebuild.

Last-good fallback

If a build fails (e.g. syntax error in tenant source), the platform serves the previous successful bundle until a new successful build replaces it. Monitor platform logs for Tenant UI build failed events.


Language rules

AllowedNot allowed
.jsx, .js, .css, .json.ts, .tsx (in customer repos)
Browser APIs (fetch, localStorage, DOM)Node built-ins (fs, path, crypto)
ESM import / exportCommonJS require / module.exports
Relative imports (./App.jsx)Absolute filesystem paths

TypeScript (.tsx) is supported in platform test fixtures but customer tenant repos use JSX only to keep the build simple and predictable.