@voyant-travel/bookings-react 0.201.1 → 0.203.0

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.
Files changed (76) hide show
  1. package/README.md +8 -8
  2. package/dist/admin/bookings-host.d.ts +1 -2
  3. package/dist/admin/bookings-host.js +2 -3
  4. package/dist/admin/index.d.ts +6 -44
  5. package/dist/admin/index.js +2 -65
  6. package/dist/components/booking-create-utils.d.ts +1 -1
  7. package/dist/components/booking-create-utils.js +5 -17
  8. package/dist/components/booking-dialog.d.ts +4 -19
  9. package/dist/components/booking-dialog.js +3 -12
  10. package/dist/components/booking-list.d.ts +1 -2
  11. package/dist/components/booking-list.js +5 -13
  12. package/dist/components/bookings-page.d.ts +1 -2
  13. package/dist/components/bookings-page.js +2 -2
  14. package/dist/hooks/booking-create-types.d.ts +34 -0
  15. package/dist/hooks/booking-create-types.js +1 -0
  16. package/dist/hooks/index.d.ts +2 -4
  17. package/dist/hooks/index.js +1 -4
  18. package/dist/hooks/use-booking-mutation.d.ts +3 -65
  19. package/dist/hooks/use-booking-mutation.js +1 -10
  20. package/dist/i18n/en-create-list.d.ts +0 -1
  21. package/dist/i18n/en-create-list.js +0 -1
  22. package/dist/i18n/en.d.ts +0 -1
  23. package/dist/i18n/messages-create-list.d.ts +0 -1
  24. package/dist/i18n/provider.d.ts +0 -2
  25. package/dist/i18n/ro-create-list.d.ts +0 -1
  26. package/dist/i18n/ro-create-list.js +0 -1
  27. package/dist/i18n/ro.d.ts +0 -1
  28. package/dist/journey/components/booking-journey-rules.d.ts +0 -22
  29. package/dist/journey/components/booking-journey-rules.js +0 -54
  30. package/dist/journey/components/journey-steps/payment-step.js +1 -1
  31. package/dist/journey/components/journey-steps/shared.d.ts +3 -3
  32. package/dist/journey/components/journey-steps.d.ts +1 -1
  33. package/dist/journey/components/journey-steps.js +1 -1
  34. package/dist/journey/index.d.ts +2 -12
  35. package/dist/journey/index.js +1 -11
  36. package/dist/journey/types.d.ts +3 -208
  37. package/dist/journey/types.js +2 -6
  38. package/dist/storefront/index.d.ts +0 -2
  39. package/dist/storefront/index.js +0 -2
  40. package/dist/ui.d.ts +0 -2
  41. package/dist/ui.js +0 -2
  42. package/package.json +33 -33
  43. package/dist/admin/booking-journey-host.d.ts +0 -23
  44. package/dist/admin/booking-journey-host.js +0 -280
  45. package/dist/admin/journey-billing-duplicate-warning.d.ts +0 -2
  46. package/dist/admin/journey-billing-duplicate-warning.js +0 -26
  47. package/dist/admin/journey-departure-picker.d.ts +0 -6
  48. package/dist/admin/journey-departure-picker.js +0 -91
  49. package/dist/admin/journey-travel-credit-picker.d.ts +0 -2
  50. package/dist/admin/journey-travel-credit-picker.js +0 -72
  51. package/dist/admin/journey-units-picker.d.ts +0 -10
  52. package/dist/admin/journey-units-picker.js +0 -60
  53. package/dist/admin/pages/booking-journey-page.d.ts +0 -11
  54. package/dist/admin/pages/booking-journey-page.js +0 -26
  55. package/dist/admin/pages/booking-new-page.d.ts +0 -15
  56. package/dist/admin/pages/booking-new-page.js +0 -50
  57. package/dist/components/booking-create-page.d.ts +0 -13
  58. package/dist/components/booking-create-page.js +0 -11
  59. package/dist/components/booking-create-sheet.d.ts +0 -34
  60. package/dist/components/booking-create-sheet.js +0 -815
  61. package/dist/hooks/use-booking-convert-mutation.d.ts +0 -80
  62. package/dist/hooks/use-booking-convert-mutation.js +0 -24
  63. package/dist/hooks/use-booking-create-mutation.d.ts +0 -336
  64. package/dist/hooks/use-booking-create-mutation.js +0 -43
  65. package/dist/hooks/use-booking-dual-create-mutation.d.ts +0 -337
  66. package/dist/hooks/use-booking-dual-create-mutation.js +0 -45
  67. package/dist/journey/components/booking-journey.d.ts +0 -2
  68. package/dist/journey/components/booking-journey.js +0 -485
  69. package/dist/storefront/storefront-booking-errors.d.ts +0 -12
  70. package/dist/storefront/storefront-booking-errors.js +0 -50
  71. package/dist/storefront/storefront-booking-journey.d.ts +0 -71
  72. package/dist/storefront/storefront-booking-journey.js +0 -327
  73. package/dist/storefront/storefront-booking-page.d.ts +0 -35
  74. package/dist/storefront/storefront-booking-page.js +0 -215
  75. package/dist/storefront/storefront-checkout-bodies.d.ts +0 -36
  76. package/dist/storefront/storefront-checkout-bodies.js +0 -69
@@ -1,327 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- /**
4
- * Storefront-flavored wrapper around `<BookingJourney />` —
5
- * customer-facing, no CRM picker, B2C billing default, post-commit
6
- * navigation to a confirmation page.
7
- *
8
- * Uses `surface="public"` so the engine hits `/v1/public/catalog/*`.
9
- * Per booking-journey-architecture §8.1 + §10 Phase B.
10
- *
11
- * Package-owned so Node starters and dedicated storefronts share the same
12
- * checkout behavior while route trees remain application-owned.
13
- */
14
- import { useQuery } from "@tanstack/react-query";
15
- import { computePaymentSchedule, noDepositPolicy, } from "@voyant-travel/finance/payment-policy";
16
- import { useVoyantReactContext } from "@voyant-travel/react";
17
- import { BookingJourney, } from "../journey/index.js";
18
- import { resolveContractVariables, } from "./resolve-contract-variables.js";
19
- import { buildStorefrontBookFailureMessage, } from "./storefront-booking-errors.js";
20
- import { buildStorefrontBookBody, buildStorefrontCheckoutStartBody, } from "./storefront-checkout-bodies.js";
21
- export { buildStorefrontBookBody, buildStorefrontCheckoutStartBody, buildStorefrontCommitParty, } from "./storefront-checkout-bodies.js";
22
- /**
23
- * Marker for checkout failures we've already turned into a localized,
24
- * customer-facing message. Native errors (a `fetch` network drop, a
25
- * `Response.json()` parse of an HTML 502) are NOT this type, so the outer catch
26
- * can wrap them in the generic message instead of showing raw browser/parser
27
- * text to the shopper (voyant#2638).
28
- */
29
- class CheckoutError extends Error {
30
- }
31
- const defaultMessages = {
32
- checkoutFailed: "We couldn't complete your booking. Please review your selection or try again.",
33
- requestReference: "Reference: {requestId}",
34
- reserveFailed: "This selection is no longer available. Adjust it and try again.",
35
- };
36
- export function StorefrontBookingJourney({ entityModule, entityId, sourceKind, sourceConnectionId, sourceRef, draftId, initialConfigure, initialAccommodation, entitySummary, entitySource, contractTemplateSlug, contractMarketingLabel, onContractAccepted, messages = defaultMessages, scope = {}, onNavigateToShop, onNavigateToConfirmation, onRedirectToPayment = (url) => window.location.assign(url), className, }) {
37
- const { baseUrl, fetcher } = useVoyantReactContext();
38
- // Carry the shopper's selected market/currency/locale (voyant#2643) into the
39
- // journey's live quote so checkout prices in the same scope as browse/detail,
40
- // not the default. The `(storefront)` layout provides the scope; unselected
41
- // fields stay undefined and the quote falls back to the surface default.
42
- // Resolve the contract template the journey will preview. The
43
- // per-product override wins when set; otherwise we fetch
44
- // whatever the operator marked as the active customer-scope
45
- // template in `legal/contract_templates`. A 404 means no template
46
- // has been seeded — the journey skips the preview dialog and
47
- // commits without a contract.
48
- const resolvedSlug = useResolvedContractSlug(contractTemplateSlug, baseUrl, fetcher);
49
- const operatorProfile = usePublicOperatorProfile(baseUrl, fetcher);
50
- const resolvedPolicy = useResolvedPaymentPolicy(entityModule, entityId, baseUrl, fetcher);
51
- // Storefront-specific slot wiring. NO CRM picker — customers fill
52
- // an inline contact form, which is the BookingJourney's default
53
- // when `renderLeadContactPicker` is absent. Operators who later
54
- // sign in could swap to the CRM picker mid-journey via an
55
- // upgrade-path hook (Phase E follow-up).
56
- const slots = {
57
- onCommitted(result) {
58
- onNavigateToConfirmation(result.bookingId);
59
- },
60
- onCancelled() {
61
- onNavigateToShop();
62
- },
63
- };
64
- // Default checkout-start handler — when the caller doesn't supply
65
- // its own `onContractAccepted`, we run the standard storefront
66
- // checkout flow:
67
- //
68
- // 1. POST /v1/public/catalog/book with the draft id → bookingId
69
- // 2. POST /v1/public/catalog/checkout/start with the bookingId,
70
- // the payment intent, and the captured acceptance.
71
- // 3. Route the customer based on the response: card → 302 to
72
- // Netopia; bank_transfer → instructions page; inquiry →
73
- // thanks page.
74
- //
75
- const defaultCheckoutHandler = async (acceptance, context) => {
76
- try {
77
- // Step 1 — book the entity. Send the live scoped quote id explicitly
78
- // (the server prefers `quoteId` over resolving the draft's stored
79
- // `currentQuoteId`), so a market/currency change made mid-journey books
80
- // the price the shopper is actually looking at rather than a stale one
81
- // (voyant#2643). Falls back to draft resolution when no live quote yet.
82
- const bookRes = await fetcher(apiPath(baseUrl, "/v1/public/catalog/book"), {
83
- method: "POST",
84
- credentials: "include",
85
- headers: { "content-type": "application/json" },
86
- body: JSON.stringify(buildStorefrontBookBody({
87
- draftId,
88
- quoteId: context.quoteId,
89
- draft: context.draft,
90
- acceptedAt: acceptance?.acceptedAt,
91
- })),
92
- });
93
- if (!bookRes.ok) {
94
- const errBody = (await bookRes.json().catch(() => ({})));
95
- console.error("[storefront] /book failed", errBody);
96
- // Reserve failures (e.g. 502 RESERVE_FAILED with reason
97
- // "rates_missing") must reach the customer — throw so the journey
98
- // surfaces a visible error instead of silently dropping back to
99
- // Review (voyant#2638). A missing-rate / availability reason gets the
100
- // "adjust your selection" copy; everything else the generic message.
101
- // The engine error serializer nests the upstream payload under
102
- // `context.upstreamPayload` (ReserveFailedError), not at top level.
103
- const reason = typeof errBody.context?.upstreamPayload?.reason === "string"
104
- ? errBody.context.upstreamPayload.reason
105
- : undefined;
106
- throw new CheckoutError(reason === "rates_missing"
107
- ? messages.reserveFailed
108
- : buildStorefrontBookFailureMessage(errBody, bookRes.headers.get("x-request-id"), messages.checkoutFailed, messages.requestReference));
109
- }
110
- const bookJson = (await bookRes.json());
111
- const bookingId = bookJson.bookingId;
112
- if (!bookingId) {
113
- console.error("[storefront] /book returned no bookingId", bookJson);
114
- throw new CheckoutError(messages.checkoutFailed);
115
- }
116
- // Step 2 — start checkout with the payment method selected in
117
- // the journey's Payment step. Card goes to the PSP; bank
118
- // transfer returns IBAN/reference instructions; inquiry skips
119
- // inventory/payment.
120
- const startRes = await fetcher(apiPath(baseUrl, "/v1/public/catalog/checkout/start"), {
121
- method: "POST",
122
- credentials: "include",
123
- headers: { "content-type": "application/json" },
124
- body: JSON.stringify(buildStorefrontCheckoutStartBody({
125
- bookingId,
126
- draft: context.draft,
127
- acceptance,
128
- returnOrigin: window.location.origin,
129
- })),
130
- });
131
- const json = (await startRes.json());
132
- if ("error" in json) {
133
- console.error("[storefront] /checkout/start error", json);
134
- throw new CheckoutError(messages.checkoutFailed);
135
- }
136
- switch (json.kind) {
137
- case "card_redirect":
138
- if (json.redirectUrl) {
139
- onRedirectToPayment(json.redirectUrl);
140
- }
141
- else {
142
- onNavigateToConfirmation(json.bookingId, "card_pending");
143
- }
144
- break;
145
- case "bank_transfer_instructions":
146
- // Hand the instructions to the confirmation page so the
147
- // customer leaves with the IBAN + reference visible. Stored
148
- // in sessionStorage to keep the URL clean — refreshing the
149
- // page replays the same instructions.
150
- if (typeof sessionStorage !== "undefined") {
151
- sessionStorage.setItem(`voyant.checkout.${json.bookingId}`, JSON.stringify(json));
152
- }
153
- onNavigateToConfirmation(json.bookingId, "bank_transfer");
154
- break;
155
- case "inquiry_received":
156
- onNavigateToConfirmation(json.bookingId, "inquiry");
157
- break;
158
- case "hold_placed":
159
- onNavigateToConfirmation(json.bookingId, "hold");
160
- break;
161
- }
162
- }
163
- catch (err) {
164
- console.error("[storefront] checkout flow failed", err);
165
- // Re-throw so <BookingJourney /> can render a visible checkout error
166
- // (voyant#2638). Preserve our own localized CheckoutError messages; wrap
167
- // anything else (native fetch/network error, JSON parse of an HTML 502)
168
- // in the generic message so raw browser/parser text never reaches the UI.
169
- throw err instanceof CheckoutError ? err : new Error(messages.checkoutFailed);
170
- }
171
- };
172
- return (_jsx(BookingJourney, { surface: "public", scope: { market: scope.marketId, locale: scope.locale, currency: scope.currency }, entityModule: entityModule, entityId: entityId, sourceKind: sourceKind, sourceConnectionId: sourceConnectionId, sourceRef: sourceRef, draftId: draftId, defaultBuyerType: "B2C", hideConfigure: true, initialConfigure: initialConfigure, initialAccommodation: initialAccommodation, entitySummary: entitySummary, contract: resolvedSlug
173
- ? {
174
- templateSlug: resolvedSlug,
175
- previewUrl: apiPath(baseUrl, `/v1/public/legal/contracts/templates/by-slug/${encodeURIComponent(resolvedSlug)}/preview`),
176
- acceptLanguage: typeof navigator !== "undefined" ? navigator.language : undefined,
177
- resolveVariables: ({ draft, pricing }) => {
178
- // Use the server-resolved cascade when the public
179
- // /v1/public/payment-policy/resolve endpoint has
180
- // come back; while the request is in flight, fall
181
- // back to the operator default so the customer sees
182
- // a sensible preview rather than an empty schedule.
183
- const policy = resolvedPolicy?.policy ??
184
- operatorProfile?.customerPaymentPolicy ??
185
- noDepositPolicy;
186
- const source = resolvedPolicy?.source ?? "operator_default";
187
- const schedule = pricing
188
- ? computePaymentSchedule({
189
- totalCents: pricing.total,
190
- currency: pricing.currency,
191
- departureDate: entitySummary?.startDate ?? null,
192
- }, policy)
193
- : [];
194
- return resolveContractVariables(draft, {
195
- entityModule,
196
- entityId,
197
- entitySummary,
198
- pricing,
199
- operatorInfo: operatorProfile,
200
- paymentSchedule: schedule,
201
- paymentPolicySource: source,
202
- source: entitySource,
203
- });
204
- },
205
- ...(contractMarketingLabel ? { marketingLabel: contractMarketingLabel } : {}),
206
- }
207
- : undefined, onContractAccepted: onContractAccepted ?? defaultCheckoutHandler, paymentCapabilities: {
208
- // Storefront defaults — covers the three customer-facing
209
- // payment paths a typical tour operator wants. The deployment
210
- // can override per-product (e.g. high-ticket cruises that
211
- // should always go through inquiry first).
212
- //
213
- // - acceptsCard: real-time card via the configured PSP
214
- // (renderPaymentProviderStep supplies the widget — when
215
- // absent we fall back to a "we'll email a link" message).
216
- // - acceptsBankTransfer: lock inventory, send bank details
217
- // out of band, reconcile on receipt of funds.
218
- // - acceptsInquiry: lead-only path. No inventory hold, no
219
- // charge — operator follows up manually. Useful for
220
- // custom itineraries / availability-on-request products.
221
- acceptsCard: true,
222
- acceptsBankTransfer: true,
223
- acceptsHold: false,
224
- acceptsTicketOnCredit: false,
225
- acceptsInquiry: true,
226
- }, className: className, ...slots }));
227
- }
228
- /**
229
- * Resolve which contract template to preview at the Review step.
230
- *
231
- * Order:
232
- * 1. The per-product `contractTemplateSlug` override the caller
233
- * passed in (skips the network call).
234
- * 2. The active customer-scope template returned by
235
- * `GET /v1/public/legal/contracts/templates/default?scope=customer`.
236
- *
237
- * Returns `undefined` while the request is in flight or when the
238
- * deployment hasn't seeded a customer template — the journey then
239
- * skips the dialog and routes Confirm straight to the
240
- * checkout-start handler.
241
- */
242
- function useResolvedContractSlug(override, baseUrl, fetcher) {
243
- const language = typeof navigator !== "undefined" ? navigator.language?.split("-")[0] : undefined;
244
- const { data } = useQuery({
245
- queryKey: ["public-legal-default-template", "customer", language ?? "en"],
246
- queryFn: async () => {
247
- const params = new URLSearchParams({ scope: "customer" });
248
- if (language)
249
- params.set("language", language);
250
- const res = await fetcher(apiPath(baseUrl, `/v1/public/legal/contracts/templates/default?${params.toString()}`), { credentials: "include" });
251
- if (!res.ok)
252
- return null;
253
- const json = (await res.json());
254
- return json.data?.slug ?? null;
255
- },
256
- enabled: !override,
257
- staleTime: 5 * 60 * 1000,
258
- });
259
- if (override)
260
- return override;
261
- return data ?? undefined;
262
- }
263
- /**
264
- * Fetch the operator profile (name / legal name / address / license /
265
- * default customer payment policy) from the public settings endpoint.
266
- * The result is cached for 5 minutes — operator
267
- * details rarely change, and stale-while-revalidate is fine for the
268
- * contract preview UI.
269
- *
270
- * Returns `undefined` while the request is in flight or when the
271
- * operator hasn't filled in Settings -> Organization yet — the contract
272
- * preview then renders the operator block with `-` placeholders (the
273
- * template renderer's missing-value substitution kicks in).
274
- */
275
- function usePublicOperatorProfile(baseUrl, fetcher) {
276
- const { data } = useQuery({
277
- queryKey: ["public-operator-profile"],
278
- queryFn: async () => {
279
- const res = await fetcher(apiPath(baseUrl, "/v1/public/operator-profile"), {
280
- credentials: "include",
281
- });
282
- if (!res.ok)
283
- return null;
284
- const json = (await res.json());
285
- return json.data ?? null;
286
- },
287
- staleTime: 5 * 60 * 1000,
288
- });
289
- return data ?? undefined;
290
- }
291
- /**
292
- * Server-side cascade resolution for the storefront preview.
293
- *
294
- * Calls `POST /v1/public/payment-policy/resolve` with the entity
295
- * coordinates and returns the resolved policy + which cascade layer
296
- * supplied it (operator_default | supplier | category | listing).
297
- *
298
- * Resolves at entity granularity only — the journey's later
299
- * sailing / cabin / rate-plan selections refine the cascade
300
- * server-side at booking-confirmed time. Storefront preview shows
301
- * the entity-level result, which is correct for the common case
302
- * (single sailing per cruise, single rate plan picked at booking
303
- * time, etc.) and gracefully degrades to a less-specific layer when
304
- * the journey hasn't picked yet.
305
- */
306
- function useResolvedPaymentPolicy(entityModule, entityId, baseUrl, fetcher) {
307
- const { data } = useQuery({
308
- queryKey: ["public-payment-policy", entityModule, entityId],
309
- queryFn: async () => {
310
- const res = await fetcher(apiPath(baseUrl, "/v1/public/payment-policy/resolve"), {
311
- method: "POST",
312
- credentials: "include",
313
- headers: { "content-type": "application/json" },
314
- body: JSON.stringify({ entityModule, entityId }),
315
- });
316
- if (!res.ok)
317
- return null;
318
- const json = (await res.json());
319
- return json.data ?? null;
320
- },
321
- staleTime: 5 * 60 * 1000,
322
- });
323
- return data ?? undefined;
324
- }
325
- function apiPath(baseUrl, path) {
326
- return `${baseUrl.replace(/\/$/, "")}${path}`;
327
- }
@@ -1,35 +0,0 @@
1
- import { z } from "zod";
2
- import { type StorefrontBookingJourneyMessages, type StorefrontBookingJourneyProps } from "./storefront-booking-journey.js";
3
- export declare const storefrontBookingSearchSchema: z.ZodObject<{
4
- departureSlotId: z.ZodOptional<z.ZodString>;
5
- cabinCategoryId: z.ZodOptional<z.ZodString>;
6
- cabinNumberId: z.ZodOptional<z.ZodString>;
7
- airArrangement: z.ZodOptional<z.ZodEnum<{
8
- none: "none";
9
- cruise_line: "cruise_line";
10
- independent: "independent";
11
- }>>;
12
- checkIn: z.ZodOptional<z.ZodString>;
13
- checkOut: z.ZodOptional<z.ZodString>;
14
- roomTypeId: z.ZodOptional<z.ZodString>;
15
- ratePlanId: z.ZodOptional<z.ZodString>;
16
- board: z.ZodOptional<z.ZodString>;
17
- adult: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
18
- child: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
19
- infant: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
20
- draftId: z.ZodOptional<z.ZodString>;
21
- }, z.core.$strip>;
22
- export type StorefrontBookingSearch = z.infer<typeof storefrontBookingSearchSchema>;
23
- export interface StorefrontBookingPageMessages extends StorefrontBookingJourneyMessages {
24
- marketingLabel?: string;
25
- }
26
- export type StorefrontBookingPageExtensions = Pick<StorefrontBookingJourneyProps, "className" | "contractMarketingLabel" | "contractTemplateSlug" | "onContractAccepted" | "onRedirectToPayment">;
27
- export interface StorefrontBookingPageProps {
28
- entityModule: string;
29
- entityId: string;
30
- search: StorefrontBookingSearch;
31
- messages: StorefrontBookingPageMessages;
32
- extensions?: StorefrontBookingPageExtensions;
33
- }
34
- /** Customer booking page shared by Node applications and dedicated storefronts. */
35
- export declare function StorefrontBookingPage({ entityModule, entityId, search, messages, extensions, }: StorefrontBookingPageProps): React.ReactElement;
@@ -1,215 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { useStorefrontUi } from "@voyant-travel/storefront-react/storefront";
4
- import { useMemo } from "react";
5
- import { z } from "zod";
6
- import { useBookingsUiI18nOrDefault } from "../i18n/provider.js";
7
- import { StorefrontBookingJourney, } from "./storefront-booking-journey.js";
8
- export const storefrontBookingSearchSchema = z.object({
9
- departureSlotId: z.string().optional(),
10
- cabinCategoryId: z.string().optional(),
11
- cabinNumberId: z.string().optional(),
12
- airArrangement: z.enum(["cruise_line", "independent", "none"]).optional(),
13
- checkIn: z.string().optional(),
14
- checkOut: z.string().optional(),
15
- roomTypeId: z.string().optional(),
16
- ratePlanId: z.string().optional(),
17
- board: z.string().optional(),
18
- adult: z.coerce.number().int().min(0).optional(),
19
- child: z.coerce.number().int().min(0).optional(),
20
- infant: z.coerce.number().int().min(0).optional(),
21
- draftId: z.string().optional(),
22
- });
23
- /** Customer booking page shared by Node applications and dedicated storefronts. */
24
- export function StorefrontBookingPage({ entityModule, entityId, search, messages, extensions, }) {
25
- const { apiUrl, navigate, scope } = useStorefrontUi();
26
- const draftId = useMemo(() => search.draftId ?? generateDraftId(), [search.draftId]);
27
- const initialConfigure = buildInitialConfigure(search);
28
- const initialAccommodation = buildInitialAccommodation(search);
29
- const { summary, source } = useEntityContent(apiUrl, entityModule, entityId, search);
30
- return (_jsx(StorefrontBookingJourney, { entityModule: entityModule, entityId: entityId, draftId: draftId, initialConfigure: initialConfigure, initialAccommodation: initialAccommodation, entitySummary: summary, entitySource: source, messages: messages, scope: scope, onNavigateToShop: () => navigate({ to: "/shop" }), onNavigateToConfirmation: (bookingId, kind) => navigate({
31
- to: "/shop/confirmation/$bookingId",
32
- params: { bookingId },
33
- ...(kind ? { search: { kind } } : {}),
34
- }), ...(messages.marketingLabel !== undefined
35
- ? { contractMarketingLabel: messages.marketingLabel }
36
- : {}), ...extensions }));
37
- }
38
- function buildInitialConfigure(search) {
39
- const configure = {
40
- pax: {
41
- adult: search.adult ?? 1,
42
- child: search.child ?? 0,
43
- infant: search.infant ?? 0,
44
- },
45
- };
46
- if (search.departureSlotId)
47
- configure.departureSlotId = search.departureSlotId;
48
- if (search.cabinCategoryId)
49
- configure.cabinCategoryId = search.cabinCategoryId;
50
- if (search.cabinNumberId)
51
- configure.cabinNumberId = search.cabinNumberId;
52
- if (search.airArrangement)
53
- configure.airArrangement = search.airArrangement;
54
- if (search.checkIn && search.checkOut) {
55
- configure.dateRange = { checkIn: search.checkIn, checkOut: search.checkOut };
56
- }
57
- if (search.roomTypeId)
58
- configure.roomTypeId = search.roomTypeId;
59
- if (search.ratePlanId)
60
- configure.ratePlanId = search.ratePlanId;
61
- if (search.board)
62
- configure.board = search.board;
63
- return configure;
64
- }
65
- function buildInitialAccommodation(search) {
66
- if (!search.roomTypeId)
67
- return undefined;
68
- return {
69
- rooms: [
70
- {
71
- optionUnitId: search.roomTypeId,
72
- quantity: 1,
73
- ...(search.ratePlanId ? { ratePlanId: search.ratePlanId } : {}),
74
- },
75
- ],
76
- travelerAssignments: {},
77
- };
78
- }
79
- function useEntityContent(apiUrl, entityModule, entityId, search) {
80
- const { locale } = useBookingsUiI18nOrDefault();
81
- const url = entityContentUrl(apiUrl, entityModule, entityId);
82
- const { data } = useQuery({
83
- queryKey: ["public-entity-summary", entityModule, entityId],
84
- queryFn: async () => {
85
- if (!url)
86
- return null;
87
- const response = await fetch(url, { credentials: "include" });
88
- if (!response.ok)
89
- return null;
90
- const json = (await response.json());
91
- return json.data ?? null;
92
- },
93
- enabled: Boolean(url),
94
- staleTime: 60_000,
95
- });
96
- const content = data?.content ?? null;
97
- const source = useMemo(() => resolveEntitySource(entityModule, data?.provenance, content), [entityModule, data?.provenance, content]);
98
- const summary = useMemo(() => resolveEntitySummary(entityModule, content, search, locale), [content, entityModule, search, locale]);
99
- return { summary, source };
100
- }
101
- function entityContentUrl(apiUrl, entityModule, entityId) {
102
- const encodedId = encodeURIComponent(entityId);
103
- if (entityModule === "cruises")
104
- return `${apiUrl}/v1/public/cruises/${encodedId}/content`;
105
- if (entityModule === "accommodations") {
106
- return `${apiUrl}/v1/public/accommodations/${encodedId}/content`;
107
- }
108
- if (entityModule === "products")
109
- return `${apiUrl}/v1/public/products/${encodedId}/content`;
110
- return null;
111
- }
112
- function resolveEntitySummary(entityModule, content, search, locale) {
113
- if (!content)
114
- return undefined;
115
- if (entityModule === "products")
116
- return resolveProductSummary(content, search, locale);
117
- if (entityModule === "cruises")
118
- return resolveCruiseSummary(content, search, locale);
119
- if (entityModule === "accommodations") {
120
- return resolveAccommodationSummary(content, search, locale);
121
- }
122
- return undefined;
123
- }
124
- function resolveProductSummary(content, search, locale) {
125
- const subtitle = [
126
- content.product.duration_days
127
- ? `${content.product.duration_days} day${content.product.duration_days === 1 ? "" : "s"}`
128
- : null,
129
- content.product.country ?? null,
130
- ].filter(Boolean);
131
- const departure = content.departures?.find((item) => item.id === search.departureSlotId);
132
- return {
133
- name: content.product.name,
134
- subtitle: subtitle.join(" · ") || undefined,
135
- heroImageUrl: content.product.hero_image_url ?? content.media?.[0]?.url ?? undefined,
136
- vertical: "products",
137
- whenLabel: departure ? formatDate(departure.starts_at, locale) : undefined,
138
- locationLabel: content.product.departure_city ?? content.product.country ?? undefined,
139
- startDate: departure?.starts_at ?? undefined,
140
- endDate: departure?.ends_at ?? undefined,
141
- destination: content.product.country ?? content.product.departure_city ?? undefined,
142
- };
143
- }
144
- function resolveCruiseSummary(content, search, locale) {
145
- const sailing = content.sailings.find((item) => item.id === search.departureSlotId);
146
- const subtitle = [
147
- content.cruise.duration_nights
148
- ? `${content.cruise.duration_nights} night${content.cruise.duration_nights === 1 ? "" : "s"}`
149
- : null,
150
- content.ship?.name ?? null,
151
- ].filter(Boolean);
152
- const route = sailing
153
- ? sailing.embarkation_port && sailing.disembarkation_port
154
- ? `${sailing.embarkation_port} → ${sailing.disembarkation_port}`
155
- : (sailing.embarkation_port ?? null)
156
- : null;
157
- return {
158
- name: content.cruise.name,
159
- subtitle: subtitle.join(" · ") || undefined,
160
- heroImageUrl: content.cruise.hero_image_url ?? undefined,
161
- vertical: "cruises",
162
- whenLabel: sailing ? formatDate(sailing.start_date, locale) : undefined,
163
- locationLabel: route ?? undefined,
164
- startDate: sailing?.start_date ?? undefined,
165
- endDate: sailing?.end_date ?? undefined,
166
- destination: route ?? undefined,
167
- };
168
- }
169
- function resolveAccommodationSummary(content, search, locale) {
170
- const stars = content.hotel.star_rating ? "★".repeat(Math.floor(content.hotel.star_rating)) : null;
171
- return {
172
- name: content.hotel.name,
173
- subtitle: stars ?? undefined,
174
- heroImageUrl: content.hotel.hero_image_url ?? undefined,
175
- vertical: "accommodations",
176
- whenLabel: search.checkIn && search.checkOut
177
- ? `${formatDate(search.checkIn, locale)} → ${formatDate(search.checkOut, locale)}`
178
- : undefined,
179
- startDate: search.checkIn ?? undefined,
180
- endDate: search.checkOut ?? undefined,
181
- destination: content.hotel.city ?? content.hotel.country ?? undefined,
182
- };
183
- }
184
- function resolveEntitySource(entityModule, provenance, content) {
185
- if (!provenance)
186
- return undefined;
187
- const supplierName = entityModule === "products" && content
188
- ? (content.product.supplier ?? "")
189
- : "";
190
- return {
191
- kind: provenance.source_kind,
192
- connectionId: provenance.source_connection_id ?? "",
193
- ref: provenance.source_ref ?? "",
194
- supplier: { id: "", name: supplierName },
195
- };
196
- }
197
- function formatDate(iso, locale) {
198
- try {
199
- return new Date(iso).toLocaleDateString(locale, {
200
- weekday: "short",
201
- day: "numeric",
202
- month: "short",
203
- year: "numeric",
204
- });
205
- }
206
- catch {
207
- return iso;
208
- }
209
- }
210
- function generateDraftId() {
211
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.randomUUID) {
212
- return `bdrf_${globalThis.crypto.randomUUID().replace(/-/g, "")}`;
213
- }
214
- return `bdrf_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
215
- }
@@ -1,36 +0,0 @@
1
- import type { ContractAcceptanceEvent, Draft } from "../journey/index.js";
2
- export declare function buildStorefrontBookBody(input: {
3
- draftId: string;
4
- quoteId?: string;
5
- draft: Draft;
6
- acceptedAt?: string;
7
- }): {
8
- draftId: string;
9
- quoteId: string | undefined;
10
- party: Record<string, unknown>;
11
- paymentIntent: {
12
- type: "hold";
13
- };
14
- idempotencyKey: string;
15
- };
16
- export declare function buildStorefrontCheckoutStartBody(input: {
17
- bookingId: string;
18
- draft: Draft;
19
- acceptance: ContractAcceptanceEvent | null;
20
- returnOrigin: string;
21
- }): {
22
- returnOrigin: string;
23
- payerName?: string | undefined;
24
- payerEmail?: string | undefined;
25
- contractAcceptance?: {
26
- templateId: string;
27
- templateSlug: string;
28
- acceptedTerms: true;
29
- acceptedMarketing: boolean;
30
- acceptedAt: string;
31
- renderedHtml: string;
32
- } | undefined;
33
- bookingId: string;
34
- paymentIntent: "hold" | "card" | "bank_transfer" | "inquiry";
35
- };
36
- export declare function buildStorefrontCommitParty(draft: Draft): Record<string, unknown>;