@voyant-travel/catalog-react 0.117.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +36 -0
- package/dist/admin/catalog-vertical-host.d.ts +45 -0
- package/dist/admin/catalog-vertical-host.d.ts.map +1 -0
- package/dist/admin/catalog-vertical-host.js +230 -0
- package/dist/admin/cruise-detail-host.d.ts +11 -0
- package/dist/admin/cruise-detail-host.d.ts.map +1 -0
- package/dist/admin/cruise-detail-host.js +33 -0
- package/dist/admin/dynamic-catalog-host.d.ts +13 -0
- package/dist/admin/dynamic-catalog-host.d.ts.map +1 -0
- package/dist/admin/dynamic-catalog-host.js +17 -0
- package/dist/admin/index.d.ts +133 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +144 -0
- package/dist/admin/open-in-new-tab.d.ts +7 -0
- package/dist/admin/open-in-new-tab.d.ts.map +1 -0
- package/dist/admin/open-in-new-tab.js +10 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.js +7 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-index-page.js +17 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-detail-page.js +7 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-index-page.js +19 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-detail-page.js +7 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-index-page.js +12 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-detail-page.js +12 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-index-page.js +12 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-detail-page.js +7 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-index-page.js +12 -0
- package/dist/admin/product-detail-host.d.ts +18 -0
- package/dist/admin/product-detail-host.d.ts.map +1 -0
- package/dist/admin/product-detail-host.js +40 -0
- package/dist/admin/scheduled-catalog-host.d.ts +15 -0
- package/dist/admin/scheduled-catalog-host.d.ts.map +1 -0
- package/dist/admin/scheduled-catalog-host.js +19 -0
- package/dist/admin/vertical-detail-host.d.ts +13 -0
- package/dist/admin/vertical-detail-host.d.ts.map +1 -0
- package/dist/admin/vertical-detail-host.js +62 -0
- package/dist/booking-engine/index.d.ts +26 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +25 -0
- package/dist/booking-engine/use-booking-commit.d.ts +61 -0
- package/dist/booking-engine/use-booking-commit.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-commit.js +47 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts +20 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft-shape.js +9 -0
- package/dist/booking-engine/use-booking-draft.d.ts +102 -0
- package/dist/booking-engine/use-booking-draft.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft.js +93 -0
- package/dist/booking-engine/use-booking-hold.d.ts +30 -0
- package/dist/booking-engine/use-booking-hold.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-hold.js +44 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts +23 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-journey-api.js +24 -0
- package/dist/booking-engine/use-booking-quote.d.ts +711 -0
- package/dist/booking-engine/use-booking-quote.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-quote.js +122 -0
- package/dist/catalog-enrichment-mappers.d.ts +162 -0
- package/dist/catalog-enrichment-mappers.d.ts.map +1 -0
- package/dist/catalog-enrichment-mappers.js +190 -0
- package/dist/catalog-enrichment.d.ts +203 -0
- package/dist/catalog-enrichment.d.ts.map +1 -0
- package/dist/catalog-enrichment.js +130 -0
- package/dist/catalog-offers-client.d.ts +58 -0
- package/dist/catalog-offers-client.d.ts.map +1 -0
- package/dist/catalog-offers-client.js +61 -0
- package/dist/catalog-search-params.d.ts +45 -0
- package/dist/catalog-search-params.d.ts.map +1 -0
- package/dist/catalog-search-params.js +30 -0
- package/dist/catalog-surfaces.d.ts +17 -0
- package/dist/catalog-surfaces.d.ts.map +1 -0
- package/dist/catalog-surfaces.js +26 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +65 -0
- package/dist/components/availability-calendar.d.ts +33 -0
- package/dist/components/availability-calendar.d.ts.map +1 -0
- package/dist/components/availability-calendar.js +65 -0
- package/dist/components/catalog-browse-page.d.ts +41 -0
- package/dist/components/catalog-browse-page.d.ts.map +1 -0
- package/dist/components/catalog-browse-page.js +47 -0
- package/dist/components/catalog-card.d.ts +68 -0
- package/dist/components/catalog-card.d.ts.map +1 -0
- package/dist/components/catalog-card.js +52 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts +16 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts.map +1 -0
- package/dist/components/catalog-detail-cruise-cards.js +54 -0
- package/dist/components/catalog-detail-departures.d.ts +25 -0
- package/dist/components/catalog-detail-departures.d.ts.map +1 -0
- package/dist/components/catalog-detail-departures.js +240 -0
- package/dist/components/catalog-detail-parts.d.ts +70 -0
- package/dist/components/catalog-detail-parts.d.ts.map +1 -0
- package/dist/components/catalog-detail-parts.js +282 -0
- package/dist/components/catalog-detail-sheet.d.ts +93 -0
- package/dist/components/catalog-detail-sheet.d.ts.map +1 -0
- package/dist/components/catalog-detail-sheet.js +68 -0
- package/dist/components/catalog-detail-view.d.ts +39 -0
- package/dist/components/catalog-detail-view.d.ts.map +1 -0
- package/dist/components/catalog-detail-view.js +157 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts +8 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts.map +1 -0
- package/dist/components/catalog-enrichment-fetchers.js +7 -0
- package/dist/components/catalog-faceted-filter.d.ts +30 -0
- package/dist/components/catalog-faceted-filter.d.ts.map +1 -0
- package/dist/components/catalog-faceted-filter.js +24 -0
- package/dist/components/catalog-filter-rail.d.ts +25 -0
- package/dist/components/catalog-filter-rail.d.ts.map +1 -0
- package/dist/components/catalog-filter-rail.js +88 -0
- package/dist/components/catalog-gallery.d.ts +27 -0
- package/dist/components/catalog-gallery.d.ts.map +1 -0
- package/dist/components/catalog-gallery.js +66 -0
- package/dist/components/catalog-hit.d.ts +27 -0
- package/dist/components/catalog-hit.d.ts.map +1 -0
- package/dist/components/catalog-hit.js +57 -0
- package/dist/components/catalog-page-cards.d.ts +21 -0
- package/dist/components/catalog-page-cards.d.ts.map +1 -0
- package/dist/components/catalog-page-cards.js +174 -0
- package/dist/components/catalog-page-config.d.ts +17 -0
- package/dist/components/catalog-page-config.d.ts.map +1 -0
- package/dist/components/catalog-page-config.js +369 -0
- package/dist/components/catalog-page.d.ts +88 -0
- package/dist/components/catalog-page.d.ts.map +1 -0
- package/dist/components/catalog-page.js +148 -0
- package/dist/components/catalog-range-filter.d.ts +34 -0
- package/dist/components/catalog-range-filter.d.ts.map +1 -0
- package/dist/components/catalog-range-filter.js +72 -0
- package/dist/components/catalog-search-page.d.ts +239 -0
- package/dist/components/catalog-search-page.d.ts.map +1 -0
- package/dist/components/catalog-search-page.js +63 -0
- package/dist/components/catalog-search-tab-panel.d.ts +42 -0
- package/dist/components/catalog-search-tab-panel.d.ts.map +1 -0
- package/dist/components/catalog-search-tab-panel.js +199 -0
- package/dist/components/catalog-vertical-detail-page.d.ts +33 -0
- package/dist/components/catalog-vertical-detail-page.d.ts.map +1 -0
- package/dist/components/catalog-vertical-detail-page.js +100 -0
- package/dist/components/cruise-detail-page-parts.d.ts +72 -0
- package/dist/components/cruise-detail-page-parts.d.ts.map +1 -0
- package/dist/components/cruise-detail-page-parts.js +146 -0
- package/dist/components/cruise-detail-page.d.ts +21 -0
- package/dist/components/cruise-detail-page.d.ts.map +1 -0
- package/dist/components/cruise-detail-page.js +201 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts +40 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page-parts.js +43 -0
- package/dist/components/dynamic-catalog-page.d.ts +23 -0
- package/dist/components/dynamic-catalog-page.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page.js +270 -0
- package/dist/components/media-gallery.d.ts +13 -0
- package/dist/components/media-gallery.d.ts.map +1 -0
- package/dist/components/media-gallery.js +42 -0
- package/dist/components/product-detail-page-parts.d.ts +106 -0
- package/dist/components/product-detail-page-parts.d.ts.map +1 -0
- package/dist/components/product-detail-page-parts.js +130 -0
- package/dist/components/product-detail-page.d.ts +57 -0
- package/dist/components/product-detail-page.d.ts.map +1 -0
- package/dist/components/product-detail-page.js +175 -0
- package/dist/components/scheduled-catalog-page.d.ts +34 -0
- package/dist/components/scheduled-catalog-page.d.ts.map +1 -0
- package/dist/components/scheduled-catalog-page.js +6 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/use-catalog-offers.d.ts +186 -0
- package/dist/hooks/use-catalog-offers.d.ts.map +1 -0
- package/dist/hooks/use-catalog-offers.js +105 -0
- package/dist/hooks/use-catalog-search.d.ts +109 -0
- package/dist/hooks/use-catalog-search.d.ts.map +1 -0
- package/dist/hooks/use-catalog-search.js +52 -0
- package/dist/i18n/en.d.ts +397 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +396 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +335 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +816 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +397 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +396 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/schemas-catalog-offers.d.ts +290 -0
- package/dist/schemas-catalog-offers.d.ts.map +1 -0
- package/dist/schemas-catalog-offers.js +155 -0
- package/dist/schemas.d.ts +143 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +76 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +18 -0
- package/package.json +150 -0
- package/src/styles.css +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-booking-quote.d.ts","sourceRoot":"","sources":["../../src/booking-engine/use-booking-quote.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,2DAA2D,CAAA;AAGlE,OAAO,EAAE,KAAK,wBAAwB,EAAwB,MAAM,8BAA8B,CAAA;AAElG,MAAM,WAAW,sBAAuB,SAAQ,wBAAwB;IACtE,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;IAC5B,wEAAwE;IACxE,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;QACxD,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwD9D"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { quoteResponseV1, } from "@voyant-travel/catalog-contracts/booking-engine/contracts";
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useBookingJourneyApi } from "./use-booking-journey-api.js";
|
|
6
|
+
/**
|
|
7
|
+
* Live-quote a draft. Re-fetches on every meaningful draft change,
|
|
8
|
+
* with a 250ms debounce. Per booking-journey-architecture §5.
|
|
9
|
+
*
|
|
10
|
+
* Returns:
|
|
11
|
+
* - `data` — the latest QuoteResponseV1 (or `null` while loading)
|
|
12
|
+
* - `isQuoting` — true while a fetch is in flight
|
|
13
|
+
* - `requote` — manual trigger (e.g. for "refresh price" buttons)
|
|
14
|
+
*/
|
|
15
|
+
export function useBookingQuote(options) {
|
|
16
|
+
const api = useBookingJourneyApi(options);
|
|
17
|
+
const debounceMs = options.debounceMs ?? 250;
|
|
18
|
+
const enabled = options.enabled !== false && !!options.draft;
|
|
19
|
+
// Stabilize the draft snapshot via a serialized signature so
|
|
20
|
+
// TanStack Query's queryKey only changes when meaningful fields
|
|
21
|
+
// change.
|
|
22
|
+
const signature = options.draft ? signDraft(options.draft) : null;
|
|
23
|
+
const [debouncedSignature, setDebouncedSignature] = useState(signature);
|
|
24
|
+
const draftRef = useRef(options.draft);
|
|
25
|
+
draftRef.current = options.draft;
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!signature) {
|
|
28
|
+
setDebouncedSignature(null);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const t = setTimeout(() => setDebouncedSignature(signature), debounceMs);
|
|
32
|
+
return () => clearTimeout(t);
|
|
33
|
+
}, [signature, debounceMs]);
|
|
34
|
+
const query = useQuery({
|
|
35
|
+
queryKey: ["booking-quote", options.surface ?? "admin", debouncedSignature],
|
|
36
|
+
queryFn: async () => {
|
|
37
|
+
const draft = draftRef.current;
|
|
38
|
+
if (!draft)
|
|
39
|
+
return null;
|
|
40
|
+
return runQuote(api, draft, options.scope);
|
|
41
|
+
},
|
|
42
|
+
enabled,
|
|
43
|
+
// Each meaningful draft edit changes the query key. Keep the previous
|
|
44
|
+
// quote's data visible while the new one fetches so the journey updates
|
|
45
|
+
// in place (price swaps when ready) instead of blanking → falling back
|
|
46
|
+
// to the minimal shape → flashing the whole Configure step on every
|
|
47
|
+
// traveler/room change.
|
|
48
|
+
placeholderData: keepPreviousData,
|
|
49
|
+
});
|
|
50
|
+
const requote = useMutation({
|
|
51
|
+
mutationFn: async () => {
|
|
52
|
+
const draft = draftRef.current;
|
|
53
|
+
if (!draft)
|
|
54
|
+
throw new Error("no draft to requote");
|
|
55
|
+
return runQuote(api, draft, options.scope);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
return useMemo(() => ({
|
|
59
|
+
data: query.data ?? null,
|
|
60
|
+
isQuoting: query.isFetching || requote.isPending,
|
|
61
|
+
error: query.error ?? requote.error ?? null,
|
|
62
|
+
requote: () => requote.mutateAsync(),
|
|
63
|
+
refetch: query.refetch,
|
|
64
|
+
}), [query.data, query.isFetching, query.error, query.refetch, requote]);
|
|
65
|
+
}
|
|
66
|
+
async function runQuote(api, draft, scope) {
|
|
67
|
+
// Storefront callers don't surface `sourceKind` in URLs (the
|
|
68
|
+
// server resolves provenance from (module, id) via the catalog
|
|
69
|
+
// plane). Operator callers know the kind upfront and pass it.
|
|
70
|
+
// Either way, only send the source pointer fields when the
|
|
71
|
+
// caller actually has them — empty strings would otherwise fail
|
|
72
|
+
// the server-side validation that requires a non-empty kind.
|
|
73
|
+
const body = {
|
|
74
|
+
entityModule: draft.entity.module,
|
|
75
|
+
entityId: draft.entity.id,
|
|
76
|
+
scope: {
|
|
77
|
+
locale: scope?.locale ?? "en-GB",
|
|
78
|
+
audience: scope?.audience ?? defaultAudience(api.apiBase),
|
|
79
|
+
market: scope?.market ?? "default",
|
|
80
|
+
currency: scope?.currency,
|
|
81
|
+
},
|
|
82
|
+
draft,
|
|
83
|
+
};
|
|
84
|
+
if (draft.entity.sourceKind)
|
|
85
|
+
body.sourceKind = draft.entity.sourceKind;
|
|
86
|
+
if (draft.entity.sourceConnectionId)
|
|
87
|
+
body.sourceConnectionId = draft.entity.sourceConnectionId;
|
|
88
|
+
if (draft.entity.sourceRef)
|
|
89
|
+
body.sourceRef = draft.entity.sourceRef;
|
|
90
|
+
return api.request("POST", "/quote", quoteResponseV1, body);
|
|
91
|
+
}
|
|
92
|
+
function defaultAudience(apiBase) {
|
|
93
|
+
return apiBase.includes("/v1/public/") ? "customer" : "staff";
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Pricing-significant signature — only fields that affect price /
|
|
97
|
+
* shape go in. Avoids re-quoting on cosmetic edits like phone
|
|
98
|
+
* formatting or notes.
|
|
99
|
+
*/
|
|
100
|
+
function signDraft(draft) {
|
|
101
|
+
return JSON.stringify({
|
|
102
|
+
entity: draft.entity,
|
|
103
|
+
pax: draft.configure?.pax,
|
|
104
|
+
departureSlotId: draft.configure?.departureSlotId,
|
|
105
|
+
departureDate: draft.configure?.departureDate,
|
|
106
|
+
departureTime: draft.configure?.departureTime,
|
|
107
|
+
variantId: draft.configure?.variantId,
|
|
108
|
+
// Room/unit picks drive per-room pricing — without this the quote never
|
|
109
|
+
// re-runs when the operator changes rooms, leaving a stale base price.
|
|
110
|
+
optionSelections: draft.configure?.optionSelections,
|
|
111
|
+
cabinCategoryId: draft.configure?.cabinCategoryId,
|
|
112
|
+
cabinNumberId: draft.configure?.cabinNumberId,
|
|
113
|
+
dateRange: draft.configure?.dateRange,
|
|
114
|
+
travelerCount: draft.travelers?.length,
|
|
115
|
+
travelerBands: draft.travelers?.map((t) => t.band),
|
|
116
|
+
accommodation: draft.accommodation,
|
|
117
|
+
addons: draft.addons,
|
|
118
|
+
promotionCode: draft.promotionCode,
|
|
119
|
+
buyerType: draft.billing?.buyerType,
|
|
120
|
+
billingCountry: draft.billing?.address?.country,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { CatalogDetailEnrichment, CatalogSlotAvailability } from "./catalog-enrichment.js";
|
|
2
|
+
export interface ContentResponseEnvelope {
|
|
3
|
+
data: {
|
|
4
|
+
content: SourcedContentPayload;
|
|
5
|
+
served_locale: string;
|
|
6
|
+
match_kind: "exact" | "language_match" | "fallback_chain" | "any";
|
|
7
|
+
source: "sourced-cache" | "sourced-fresh" | "synthesized" | "owned";
|
|
8
|
+
served_stale: boolean;
|
|
9
|
+
synthesized: boolean;
|
|
10
|
+
machine_translated: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
type SourcedContentPayload = ProductContentPayload | CruiseContentPayload | AccommodationContentPayload;
|
|
14
|
+
interface ProductContentPayload {
|
|
15
|
+
product: {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string | null;
|
|
19
|
+
highlights?: string[];
|
|
20
|
+
hero_image_url?: string | null;
|
|
21
|
+
duration_days?: number | null;
|
|
22
|
+
sell_currency?: string | null;
|
|
23
|
+
supplier?: string | null;
|
|
24
|
+
country?: string | null;
|
|
25
|
+
};
|
|
26
|
+
options: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string | null;
|
|
30
|
+
}>;
|
|
31
|
+
days: Array<{
|
|
32
|
+
day_number: number;
|
|
33
|
+
title?: string | null;
|
|
34
|
+
description?: string | null;
|
|
35
|
+
location?: string | null;
|
|
36
|
+
hero_image_url?: string | null;
|
|
37
|
+
}>;
|
|
38
|
+
media: Array<{
|
|
39
|
+
url: string;
|
|
40
|
+
type: string;
|
|
41
|
+
caption?: string | null;
|
|
42
|
+
}>;
|
|
43
|
+
policies: Array<{
|
|
44
|
+
kind: string;
|
|
45
|
+
body: string;
|
|
46
|
+
}>;
|
|
47
|
+
departures?: Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
starts_at: string;
|
|
50
|
+
ends_at?: string | null;
|
|
51
|
+
status?: string | null;
|
|
52
|
+
capacity?: number | null;
|
|
53
|
+
remaining?: number | null;
|
|
54
|
+
lowest_price_cents?: number | null;
|
|
55
|
+
currency?: string | null;
|
|
56
|
+
note?: string | null;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
interface CruiseContentPayload {
|
|
60
|
+
cruise: {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
description?: string | null;
|
|
64
|
+
highlights?: string[];
|
|
65
|
+
hero_image_url?: string | null;
|
|
66
|
+
cruise_line?: string | null;
|
|
67
|
+
};
|
|
68
|
+
ship?: {
|
|
69
|
+
id?: string | null;
|
|
70
|
+
name: string;
|
|
71
|
+
ship_type?: string | null;
|
|
72
|
+
description?: string | null;
|
|
73
|
+
deck_plan_url?: string | null;
|
|
74
|
+
deck_plans?: Array<{
|
|
75
|
+
name: string;
|
|
76
|
+
level?: number | null;
|
|
77
|
+
image_url?: string | null;
|
|
78
|
+
}>;
|
|
79
|
+
capacity?: number | null;
|
|
80
|
+
decks?: number | null;
|
|
81
|
+
year_built?: number | null;
|
|
82
|
+
gallery?: string[];
|
|
83
|
+
} | null;
|
|
84
|
+
sailings?: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
source_ref?: string | null;
|
|
87
|
+
start_date: string;
|
|
88
|
+
end_date: string;
|
|
89
|
+
duration_nights?: number | null;
|
|
90
|
+
status?: string | null;
|
|
91
|
+
embarkation_port?: string | null;
|
|
92
|
+
disembarkation_port?: string | null;
|
|
93
|
+
itinerary_stops?: CruiseItineraryStopPayload[];
|
|
94
|
+
lowest_price_cents: number | null;
|
|
95
|
+
currency: string | null;
|
|
96
|
+
}>;
|
|
97
|
+
cabin_categories?: Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
code?: string | null;
|
|
100
|
+
name: string;
|
|
101
|
+
description?: string | null;
|
|
102
|
+
type?: string | null;
|
|
103
|
+
images?: string[];
|
|
104
|
+
floorplan_images?: string[];
|
|
105
|
+
square_feet?: string | null;
|
|
106
|
+
grade_codes?: string[];
|
|
107
|
+
wheelchair_accessible?: boolean;
|
|
108
|
+
capacity_max?: number | null;
|
|
109
|
+
inclusions?: string[];
|
|
110
|
+
}>;
|
|
111
|
+
itinerary_stops?: CruiseItineraryStopPayload[];
|
|
112
|
+
policies: Array<{
|
|
113
|
+
kind: string;
|
|
114
|
+
body: string;
|
|
115
|
+
}>;
|
|
116
|
+
}
|
|
117
|
+
interface AccommodationContentPayload {
|
|
118
|
+
hotel: {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
description?: string | null;
|
|
122
|
+
star_rating?: number | null;
|
|
123
|
+
hero_image_url?: string | null;
|
|
124
|
+
highlights?: string[];
|
|
125
|
+
brand?: string | null;
|
|
126
|
+
city?: string | null;
|
|
127
|
+
country?: string | null;
|
|
128
|
+
};
|
|
129
|
+
room_types?: Array<{
|
|
130
|
+
id: string;
|
|
131
|
+
code?: string | null;
|
|
132
|
+
name: string;
|
|
133
|
+
description?: string | null;
|
|
134
|
+
room_class?: string | null;
|
|
135
|
+
view?: string | null;
|
|
136
|
+
size_sqm?: number | null;
|
|
137
|
+
max_occupancy?: number | null;
|
|
138
|
+
amenities?: string[];
|
|
139
|
+
images?: string[];
|
|
140
|
+
}>;
|
|
141
|
+
amenities?: Array<{
|
|
142
|
+
id: string;
|
|
143
|
+
category?: string | null;
|
|
144
|
+
name: string;
|
|
145
|
+
}>;
|
|
146
|
+
policies?: Array<{
|
|
147
|
+
kind: string;
|
|
148
|
+
body: string;
|
|
149
|
+
}>;
|
|
150
|
+
}
|
|
151
|
+
interface CruiseItineraryStopPayload {
|
|
152
|
+
day_number: number;
|
|
153
|
+
port_name: string;
|
|
154
|
+
description?: string | null;
|
|
155
|
+
date?: string | null;
|
|
156
|
+
arrival_time?: string | null;
|
|
157
|
+
departure_time?: string | null;
|
|
158
|
+
is_at_sea?: boolean | null;
|
|
159
|
+
}
|
|
160
|
+
export declare function mapContentToEnrichment(payload: ContentResponseEnvelope, availability: Map<string, CatalogSlotAvailability>, formatSupplier?: (id: string) => string): CatalogDetailEnrichment;
|
|
161
|
+
export {};
|
|
162
|
+
//# sourceMappingURL=catalog-enrichment-mappers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-enrichment-mappers.d.ts","sourceRoot":"","sources":["../src/catalog-enrichment-mappers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export function mapContentToEnrichment(payload, availability, formatSupplier) {
|
|
2
|
+
const { content, served_locale, match_kind, source, served_stale, synthesized, machine_translated, } = payload.data;
|
|
3
|
+
const base = isCruiseContent(content)
|
|
4
|
+
? mapCruiseContentToEnrichment(content)
|
|
5
|
+
: isAccommodationContent(content)
|
|
6
|
+
? mapAccommodationContentToEnrichment(content)
|
|
7
|
+
: mapProductContentToEnrichment(content, availability, formatSupplier);
|
|
8
|
+
return {
|
|
9
|
+
...base,
|
|
10
|
+
servedLocale: served_locale,
|
|
11
|
+
matchKind: match_kind,
|
|
12
|
+
source,
|
|
13
|
+
servedStale: served_stale,
|
|
14
|
+
synthesized,
|
|
15
|
+
machineTranslated: machine_translated,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function isCruiseContent(content) {
|
|
19
|
+
return "cruise" in content;
|
|
20
|
+
}
|
|
21
|
+
function isAccommodationContent(content) {
|
|
22
|
+
return "hotel" in content;
|
|
23
|
+
}
|
|
24
|
+
function mapAccommodationContentToEnrichment(content) {
|
|
25
|
+
const rooms = content.room_types ?? [];
|
|
26
|
+
return {
|
|
27
|
+
name: content.hotel.name ?? null,
|
|
28
|
+
description: content.hotel.description ?? null,
|
|
29
|
+
highlights: content.hotel.highlights ?? [],
|
|
30
|
+
heroImageUrl: content.hotel.hero_image_url ?? null,
|
|
31
|
+
supplier: content.hotel.brand ?? null,
|
|
32
|
+
options: rooms.map((r) => ({
|
|
33
|
+
id: r.id,
|
|
34
|
+
name: r.name,
|
|
35
|
+
description: r.description ?? null,
|
|
36
|
+
images: r.images ?? [],
|
|
37
|
+
squareFeet: r.size_sqm != null ? String(r.size_sqm) : null,
|
|
38
|
+
capacityMax: r.max_occupancy ?? null,
|
|
39
|
+
amenities: r.amenities ?? [],
|
|
40
|
+
})),
|
|
41
|
+
media: rooms.flatMap((r) => (r.images ?? []).map((url) => ({ url, type: "image", caption: r.name }))),
|
|
42
|
+
policies: (content.policies ?? []).map((p) => ({ kind: p.kind, body: p.body })),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function mapProductContentToEnrichment(content, availability, formatSupplier) {
|
|
46
|
+
const supplierName = typeof content.product.supplier === "string"
|
|
47
|
+
? formatSupplier
|
|
48
|
+
? formatSupplier(content.product.supplier)
|
|
49
|
+
: content.product.supplier
|
|
50
|
+
: (content.product.supplier ?? null);
|
|
51
|
+
return {
|
|
52
|
+
name: content.product.name ?? null,
|
|
53
|
+
description: content.product.description ?? null,
|
|
54
|
+
highlights: content.product.highlights ?? [],
|
|
55
|
+
heroImageUrl: content.product.hero_image_url ?? null,
|
|
56
|
+
supplier: supplierName,
|
|
57
|
+
itinerary: content.days.map((d) => ({
|
|
58
|
+
dayNumber: d.day_number,
|
|
59
|
+
title: d.title ?? null,
|
|
60
|
+
description: d.description ?? null,
|
|
61
|
+
location: d.location ?? null,
|
|
62
|
+
heroImageUrl: d.hero_image_url ?? null,
|
|
63
|
+
})),
|
|
64
|
+
media: content.media.map((m) => ({
|
|
65
|
+
url: m.url,
|
|
66
|
+
type: m.type,
|
|
67
|
+
caption: m.caption ?? null,
|
|
68
|
+
})),
|
|
69
|
+
options: content.options.map((o) => ({
|
|
70
|
+
id: o.id,
|
|
71
|
+
name: o.name,
|
|
72
|
+
description: o.description ?? null,
|
|
73
|
+
})),
|
|
74
|
+
policies: content.policies.map((p) => ({ kind: p.kind, body: p.body })),
|
|
75
|
+
departures: (content.departures ?? []).map((d) => {
|
|
76
|
+
const slot = availability.get(d.id);
|
|
77
|
+
return {
|
|
78
|
+
id: d.id,
|
|
79
|
+
startsAt: d.starts_at,
|
|
80
|
+
endsAt: d.ends_at ?? null,
|
|
81
|
+
status: slot?.status ?? d.status ?? null,
|
|
82
|
+
unlimited: slot?.unlimited ?? null,
|
|
83
|
+
capacity: slot?.initialPax ?? d.capacity ?? null,
|
|
84
|
+
remaining: slot?.remainingPax ?? d.remaining ?? null,
|
|
85
|
+
lowestPriceCents: d.lowest_price_cents ?? null,
|
|
86
|
+
currency: d.currency ?? null,
|
|
87
|
+
note: d.note ?? null,
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function mapCruiseContentToEnrichment(content) {
|
|
93
|
+
return {
|
|
94
|
+
name: content.cruise.name ?? null,
|
|
95
|
+
description: content.cruise.description ?? null,
|
|
96
|
+
highlights: content.cruise.highlights ?? [],
|
|
97
|
+
heroImageUrl: content.cruise.hero_image_url ?? null,
|
|
98
|
+
supplier: content.cruise.cruise_line ?? null,
|
|
99
|
+
ship: content.ship
|
|
100
|
+
? {
|
|
101
|
+
id: content.ship.id ?? null,
|
|
102
|
+
name: content.ship.name,
|
|
103
|
+
shipType: content.ship.ship_type ?? null,
|
|
104
|
+
description: content.ship.description ?? null,
|
|
105
|
+
deckPlanUrl: content.ship.deck_plan_url ?? null,
|
|
106
|
+
deckPlans: (content.ship.deck_plans ?? []).map((deck) => ({
|
|
107
|
+
name: deck.name,
|
|
108
|
+
level: deck.level ?? null,
|
|
109
|
+
imageUrl: deck.image_url ?? null,
|
|
110
|
+
})),
|
|
111
|
+
capacity: content.ship.capacity ?? null,
|
|
112
|
+
decks: content.ship.decks ?? null,
|
|
113
|
+
yearBuilt: content.ship.year_built ?? null,
|
|
114
|
+
images: content.ship.gallery ?? [],
|
|
115
|
+
}
|
|
116
|
+
: null,
|
|
117
|
+
// Cruise itinerary lives per-sailing in the content payload; the cruise-level
|
|
118
|
+
// `itinerary_stops` is empty for sourced cruises. Fall back to the first
|
|
119
|
+
// sailing's stops so the Itinerary tab shows the representative route.
|
|
120
|
+
itinerary: mapCruiseItineraryStops(content.itinerary_stops?.length
|
|
121
|
+
? content.itinerary_stops
|
|
122
|
+
: (content.sailings?.[0]?.itinerary_stops ?? [])),
|
|
123
|
+
media: [],
|
|
124
|
+
options: (content.cabin_categories ?? []).map((c) => ({
|
|
125
|
+
id: c.id,
|
|
126
|
+
name: formatCruiseCabinCategoryName(c),
|
|
127
|
+
description: c.description ? stripHtmlTags(c.description) : null,
|
|
128
|
+
code: c.code ?? null,
|
|
129
|
+
type: c.type ?? null,
|
|
130
|
+
images: c.images ?? [],
|
|
131
|
+
floorplanImages: c.floorplan_images ?? [],
|
|
132
|
+
squareFeet: c.square_feet ?? null,
|
|
133
|
+
gradeCodes: c.grade_codes ?? [],
|
|
134
|
+
wheelchairAccessible: c.wheelchair_accessible ?? false,
|
|
135
|
+
capacityMax: c.capacity_max ?? null,
|
|
136
|
+
amenities: c.inclusions ?? [],
|
|
137
|
+
})),
|
|
138
|
+
policies: content.policies.map((p) => ({ kind: p.kind, body: p.body })),
|
|
139
|
+
departures: (content.sailings ?? []).map((s) => ({
|
|
140
|
+
id: s.id,
|
|
141
|
+
sourceRef: s.source_ref ?? null,
|
|
142
|
+
startsAt: s.start_date,
|
|
143
|
+
endsAt: s.end_date,
|
|
144
|
+
durationNights: s.duration_nights ?? null,
|
|
145
|
+
status: s.status ?? null,
|
|
146
|
+
embarkationPort: s.embarkation_port ?? null,
|
|
147
|
+
disembarkationPort: s.disembarkation_port ?? null,
|
|
148
|
+
lowestPriceCents: s.lowest_price_cents,
|
|
149
|
+
currency: s.currency,
|
|
150
|
+
itinerary: mapCruiseItineraryStops(s.itinerary_stops ?? []),
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function mapCruiseItineraryStops(stops) {
|
|
155
|
+
return stops.map((d) => ({
|
|
156
|
+
dayNumber: d.day_number,
|
|
157
|
+
title: d.port_name,
|
|
158
|
+
description: d.description ?? null,
|
|
159
|
+
location: d.port_name,
|
|
160
|
+
date: d.date ?? null,
|
|
161
|
+
arrivalTime: d.arrival_time ?? null,
|
|
162
|
+
departureTime: d.departure_time ?? null,
|
|
163
|
+
isAtSea: d.is_at_sea ?? null,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
function formatCruiseCabinCategoryName(category) {
|
|
167
|
+
// Upstream cabin names occasionally arrive wrapped in HTML (e.g. Viking sends
|
|
168
|
+
// `<p>Deluxe Veranda Stateroom (DV)</p>`); strip it so it never renders raw.
|
|
169
|
+
const name = stripHtmlTags(category.name).trim();
|
|
170
|
+
const code = category.code?.trim();
|
|
171
|
+
if (!name)
|
|
172
|
+
return code ?? category.type ?? "";
|
|
173
|
+
// Grade rows where the upstream has no distinct display name come through as
|
|
174
|
+
// name === code; show just the code instead of "DV2 - DV2".
|
|
175
|
+
if (code && code !== name)
|
|
176
|
+
return `${code} - ${name}`;
|
|
177
|
+
return name;
|
|
178
|
+
}
|
|
179
|
+
function stripHtmlTags(value) {
|
|
180
|
+
return value
|
|
181
|
+
.replace(/<[^>]*>/g, " ")
|
|
182
|
+
.replace(/ /gi, " ")
|
|
183
|
+
.replace(/&/gi, "&")
|
|
184
|
+
.replace(/</gi, "<")
|
|
185
|
+
.replace(/>/gi, ">")
|
|
186
|
+
.replace(/'|'/gi, "'")
|
|
187
|
+
.replace(/"/gi, '"')
|
|
188
|
+
.replace(/\s+/g, " ")
|
|
189
|
+
.trim();
|
|
190
|
+
}
|