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
| Rule | Detail |
|---|---|
| Module format | ESM only — import / export |
| React | Externalized via CDN import map — do not bundle React |
| Additional CDN deps | Declare in journey.yaml → ui.dependencies |
| CSS | Import from your entry file; esbuild extracts and inlines it |
| Node built-ins | Forbidden — fs, path, crypto, etc. are not available (this runs in the browser) |
| Secrets | Never 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
| Export | Purpose |
|---|---|
BookingFlow | Step container component that manages step navigation using your journey.yaml steps config |
CabinSelection | Interactive deck-plan-based cabin picker with category theming and availability states |
DeckPlan | Standalone deck plan renderer (SVG-based, themed by category) |
renderBookingFlowStep | Step renderer — returns a framework React node for handled steps, or null so your tenant code renders its own component |
renderImmersiveCabinPickStep | Renders the immersive cabin selection step (deck plan + detail panel) |
DEFAULT_VESSELS, VESSELS, registerVessels, resolveVessel | Vessel registry for deck plan data |
CATEGORY_STYLES, categoryKeyForName | Category-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
| Token | Default | Used for |
|---|---|---|
--abyss | #0a1929 | Dark backgrounds, headers, selection outlines |
--ocean | #14304a | Balcony-category accents |
--horizon | #4a9d8f | Horizon stateroom / junior suite accents |
--gold | #c8a565 | Captain's suite, primary buttons, eyebrow text |
--rust | #b85c2c | Twin/triple share accents |
Layout tokens
| Token | Default | Used for |
|---|---|---|
--cabin-paper | #fdf8e9 | Fallback card backgrounds |
--cabin-bone | #faf6ec | Light text on dark headers, gradient base |
--cabin-char | #1a2434 | Body text |
--cabin-warm | #7a6b4f | Labels, compass text |
--cabin-bg | Radial gradient | Full-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}¤cy=${currency}`);
Always include tenantId as a query parameter or in the request body. Key endpoints:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/journeys/v1/config?tenantId= | Journey config (steps, branding, channels) |
| GET | /api/journeys/v1/catalog/trips | Neutral trip/package catalog (/catalog/voyages remains an alias) |
| GET | /api/journeys/v1/catalog/categories | Room or cabin category list |
| GET | /api/journeys/v1/catalog/rooms | Room options for a trip + category (/catalog/cabins remains an alias) |
| GET | /api/journeys/v1/catalog/activities | Package activities for a trip |
| POST | /api/journeys/v1/baskets | Create a basket |
| GET | /api/journeys/v1/baskets/:id/prices | Basket pricing (totals, deposit, balance) |
| POST | /api/journeys/v1/baskets/:id/bookings | Create 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
- Fetch: Platform retrieves
journeys/src/**from GitLab (or reads fromLOCAL_CONFIG_DIRin development). - Bundle: esbuild compiles the entry point as ESM, externalizing React and declared dependencies, extracting CSS.
- Shell: The bundled JS and CSS are injected into an HTML shell using
ui.title,ui.favicon_url,ui.meta, and the import map. - Serve: The result is served at
/:tenantId/with ETag caching (Cache-Control: public, max-age=60, stale-while-revalidate=300). - 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
| Allowed | Not allowed |
|---|---|
.jsx, .js, .css, .json | .ts, .tsx (in customer repos) |
Browser APIs (fetch, localStorage, DOM) | Node built-ins (fs, path, crypto) |
ESM import / export | CommonJS 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.