@voyant-travel/bookings-react 0.185.0 → 0.186.1

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.
@@ -203,6 +203,19 @@ export function BookingJourney(props) {
203
203
  (quote.data.available === false ||
204
204
  (quote.data.invalidReason != null && quote.data.invalidReason !== ""));
205
205
  const quoteBlocked = hasQuoteError || quoteUnpriceable;
206
+ // The pristine baseline quote is intentionally un-priceable: a product has no
207
+ // price until its price driver — a room, or a traveler count — is chosen, and
208
+ // the engine reports that empty draft as `no_sell_amount_configured`. Suppress
209
+ // the un-priceable banner (and the side-panel error) for exactly that baseline
210
+ // so step 1 doesn't scream "can't be priced" before the buyer has picked
211
+ // anything. Any other reason (e.g. `rates_missing` on an already-picked stay,
212
+ // #2638), or the same code once a driver IS picked (a room that carries no
213
+ // price), still surfaces. Commit stays gated on `quoteBlocked` regardless, so
214
+ // an unpriced booking can never be submitted.
215
+ const priceDriversConfigured = (draft?.configure?.optionSelections?.length ?? 0) > 0 ||
216
+ Object.values(draft?.configure?.pax ?? {}).reduce((sum, n) => sum + (n ?? 0), 0) > 0;
217
+ const isPristineUnconfigured = quote.data?.invalidReason === "no_sell_amount_configured" && !priceDriversConfigured;
218
+ const showQuoteUnpriceable = quoteBlocked && !isPristineUnconfigured;
206
219
  const quoteReady = Boolean(quote.data?.quoteId);
207
220
  // Step navigation only hard-blocks on a thrown quote error (a transient fetch
208
221
  // failure that a retry fixes). An un-priceable quote (e.g. `rates_missing`
@@ -404,7 +417,7 @@ export function BookingJourney(props) {
404
417
  setConfirmError(null);
405
418
  void quote.refetch();
406
419
  };
407
- const quoteErrorBanner = quoteBlocked ? (_jsxs("div", { role: "alert", "aria-live": "polite", className: "flex flex-col items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-3 text-destructive text-sm", children: [_jsx("span", { children: hasQuoteError
420
+ const quoteErrorBanner = showQuoteUnpriceable ? (_jsxs("div", { role: "alert", "aria-live": "polite", className: "flex flex-col items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 p-3 text-destructive text-sm", children: [_jsx("span", { children: hasQuoteError
408
421
  ? messages.bookingJourney.validation.quoteFailed
409
422
  : messages.bookingJourney.validation.pricingUnavailable }), hasQuoteError ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: retryQuote, disabled: quote.isQuoting, children: messages.bookingJourney.validation.retryQuote })) : null] })) : null;
410
423
  // Renders one step's content. Shared by both layouts — the wizard shows
@@ -31,7 +31,7 @@ export function PriceSidePanel({ pricing, isQuoting, invalidReason, entitySummar
31
31
  // Departure shows directly under the product title — it's the most-glanced
32
32
  // fact, so it doesn't belong buried in the recap accordion.
33
33
  const departureText = draft ? stepHeadline("departure", draft, messages, locale) : "";
34
- return (_jsxs(Card, { className: className, children: [entitySummary?.heroImageUrl ? (_jsx("img", { src: entitySummary.heroImageUrl, alt: entitySummary.name, className: "aspect-video w-full object-cover" })) : null, entitySummary ? (_jsx(EntityHeader, { summary: entitySummary, departureText: departureText })) : null, _jsxs(CardContent, { className: "space-y-4", children: [steps && steps.length > 0 && currentStep && draft ? (_jsx(StepRecap, { steps: steps, currentStep: currentStep, draft: draft })) : null, invalidReason ? _jsx("p", { className: "text-destructive text-sm", children: invalidReason }) : null, !showPricing ? (_jsx("p", { className: "border-t pt-4 text-muted-foreground text-sm", children: pricingHint })) : isQuoting && !pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx(Skeleton, { className: "h-4 w-24" }), _jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "h-4 w-20" })] })) : null, showPricing && pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx("ul", { className: "space-y-1 text-sm", children: pricing.lines.map((line) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [line.label, line.quantity ? (_jsxs("span", { className: "text-muted-foreground", children: [" \u00D7 ", line.quantity] })) : null] }), _jsx("span", { children: formatMoney(line.totalAmount, pricing.currency, locale) })] }, `${line.kind}-${line.label}-${line.totalAmount}`))) }), pricing.taxes.length > 0 ? (_jsx("ul", { className: "space-y-1 border-t pt-2 text-sm text-muted-foreground", children: pricing.taxes.map((tax) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [tax.label, tax.rate > 0 ? ` (${formatTaxRate(tax.rate)})` : ""] }), _jsx("span", { children: formatMoney(tax.amount, pricing.currency, locale) })] }, tax.code))) })) : null, _jsxs("div", { className: "flex justify-between border-t pt-2 font-medium", children: [_jsx("span", { children: messages.bookingJourney.sidePanel.total }), _jsx("span", { children: formatMoney(pricing.total, pricing.currency, locale) })] })] })) : null, pricingExtras ? _jsx("div", { className: "border-t pt-4", children: pricingExtras }) : null] })] }));
34
+ return (_jsxs(Card, { className: className, children: [entitySummary?.heroImageUrl ? (_jsx("img", { src: entitySummary.heroImageUrl, alt: entitySummary.name, className: "aspect-video w-full object-cover" })) : null, entitySummary ? (_jsx(EntityHeader, { summary: entitySummary, departureText: departureText })) : null, _jsxs(CardContent, { className: "space-y-4", children: [steps && steps.length > 0 && currentStep && draft ? (_jsx(StepRecap, { steps: steps, currentStep: currentStep, draft: draft })) : null, invalidReason && showPricing ? (_jsx("p", { className: "text-destructive text-sm", children: messages.bookingJourney.validation.pricingUnavailable })) : null, !showPricing ? (_jsx("p", { className: "border-t pt-4 text-muted-foreground text-sm", children: pricingHint })) : isQuoting && !pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx(Skeleton, { className: "h-4 w-24" }), _jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "h-4 w-20" })] })) : null, showPricing && pricing ? (_jsxs("div", { className: "space-y-2 border-t pt-4", children: [_jsx("ul", { className: "space-y-1 text-sm", children: pricing.lines.map((line) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [line.label, line.quantity ? (_jsxs("span", { className: "text-muted-foreground", children: [" \u00D7 ", line.quantity] })) : null] }), _jsx("span", { children: formatMoney(line.totalAmount, pricing.currency, locale) })] }, `${line.kind}-${line.label}-${line.totalAmount}`))) }), pricing.taxes.length > 0 ? (_jsx("ul", { className: "space-y-1 border-t pt-2 text-sm text-muted-foreground", children: pricing.taxes.map((tax) => (_jsxs("li", { className: "flex justify-between", children: [_jsxs("span", { children: [tax.label, tax.rate > 0 ? ` (${formatTaxRate(tax.rate)})` : ""] }), _jsx("span", { children: formatMoney(tax.amount, pricing.currency, locale) })] }, tax.code))) })) : null, _jsxs("div", { className: "flex justify-between border-t pt-2 font-medium", children: [_jsx("span", { children: messages.bookingJourney.sidePanel.total }), _jsx("span", { children: formatMoney(pricing.total, pricing.currency, locale) })] })] })) : null, pricingExtras ? _jsx("div", { className: "border-t pt-4", children: pricingExtras }) : null] })] }));
35
35
  }
36
36
  function EntityHeader({ summary, departureText, }) {
37
37
  // Text block only — the hero image is rendered as a direct Card child above.
@@ -10,7 +10,7 @@ export declare function useBookingQuestions(options?: UseBookingQuestionsOptions
10
10
  label: string;
11
11
  description: string | null;
12
12
  target: "service" | "extra" | "traveler" | "booking" | "lead_traveler" | "booker";
13
- fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
13
+ fieldType: "number" | "boolean" | "date" | "other" | "file" | "textarea" | "text" | "email" | "phone" | "datetime" | "single_select" | "multi_select" | "country";
14
14
  placeholder: string | null;
15
15
  helpText: string | null;
16
16
  isRequired: boolean;
@@ -7,7 +7,7 @@ export declare function useContactRequirements(options?: UseContactRequirementsO
7
7
  id: string;
8
8
  productId: string;
9
9
  optionId: string | null;
10
- fieldKey: "other" | "email" | "phone" | "address" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
10
+ fieldKey: "other" | "address" | "email" | "phone" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
11
11
  scope: "traveler" | "booking" | "lead_traveler" | "booker";
12
12
  isRequired: boolean;
13
13
  perTraveler: boolean;
@@ -57,7 +57,7 @@ export declare function getContactRequirementsQueryOptions(client: FetchWithVali
57
57
  id: string;
58
58
  productId: string;
59
59
  optionId: string | null;
60
- fieldKey: "other" | "email" | "phone" | "address" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
60
+ fieldKey: "other" | "address" | "email" | "phone" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
61
61
  scope: "traveler" | "booking" | "lead_traveler" | "booker";
62
62
  isRequired: boolean;
63
63
  perTraveler: boolean;
@@ -73,7 +73,7 @@ export declare function getContactRequirementsQueryOptions(client: FetchWithVali
73
73
  id: string;
74
74
  productId: string;
75
75
  optionId: string | null;
76
- fieldKey: "other" | "email" | "phone" | "address" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
76
+ fieldKey: "other" | "address" | "email" | "phone" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
77
77
  scope: "traveler" | "booking" | "lead_traveler" | "booker";
78
78
  isRequired: boolean;
79
79
  perTraveler: boolean;
@@ -90,7 +90,7 @@ export declare function getContactRequirementsQueryOptions(client: FetchWithVali
90
90
  id: string;
91
91
  productId: string;
92
92
  optionId: string | null;
93
- fieldKey: "other" | "email" | "phone" | "address" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
93
+ fieldKey: "other" | "address" | "email" | "phone" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
94
94
  scope: "traveler" | "booking" | "lead_traveler" | "booker";
95
95
  isRequired: boolean;
96
96
  perTraveler: boolean;
@@ -109,7 +109,7 @@ export declare function getContactRequirementsQueryOptions(client: FetchWithVali
109
109
  id: string;
110
110
  productId: string;
111
111
  optionId: string | null;
112
- fieldKey: "other" | "email" | "phone" | "address" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
112
+ fieldKey: "other" | "address" | "email" | "phone" | "nationality" | "first_name" | "last_name" | "date_of_birth" | "passport_number" | "passport_expiry" | "dietary_requirements" | "accessibility_needs" | "special_requests";
113
113
  scope: "traveler" | "booking" | "lead_traveler" | "booker";
114
114
  isRequired: boolean;
115
115
  perTraveler: boolean;
@@ -132,7 +132,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
132
132
  label: string;
133
133
  description: string | null;
134
134
  target: "service" | "extra" | "traveler" | "booking" | "lead_traveler" | "booker";
135
- fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
135
+ fieldType: "number" | "boolean" | "date" | "other" | "file" | "textarea" | "text" | "email" | "phone" | "datetime" | "single_select" | "multi_select" | "country";
136
136
  placeholder: string | null;
137
137
  helpText: string | null;
138
138
  isRequired: boolean;
@@ -150,7 +150,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
150
150
  label: string;
151
151
  description: string | null;
152
152
  target: "service" | "extra" | "traveler" | "booking" | "lead_traveler" | "booker";
153
- fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
153
+ fieldType: "number" | "boolean" | "date" | "other" | "file" | "textarea" | "text" | "email" | "phone" | "datetime" | "single_select" | "multi_select" | "country";
154
154
  placeholder: string | null;
155
155
  helpText: string | null;
156
156
  isRequired: boolean;
@@ -169,7 +169,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
169
169
  label: string;
170
170
  description: string | null;
171
171
  target: "service" | "extra" | "traveler" | "booking" | "lead_traveler" | "booker";
172
- fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
172
+ fieldType: "number" | "boolean" | "date" | "other" | "file" | "textarea" | "text" | "email" | "phone" | "datetime" | "single_select" | "multi_select" | "country";
173
173
  placeholder: string | null;
174
174
  helpText: string | null;
175
175
  isRequired: boolean;
@@ -190,7 +190,7 @@ export declare function getBookingQuestionsQueryOptions(client: FetchWithValidat
190
190
  label: string;
191
191
  description: string | null;
192
192
  target: "service" | "extra" | "traveler" | "booking" | "lead_traveler" | "booker";
193
- fieldType: "number" | "boolean" | "date" | "other" | "file" | "email" | "phone" | "textarea" | "text" | "datetime" | "single_select" | "multi_select" | "country";
193
+ fieldType: "number" | "boolean" | "date" | "other" | "file" | "textarea" | "text" | "email" | "phone" | "datetime" | "single_select" | "multi_select" | "country";
194
194
  placeholder: string | null;
195
195
  helpText: string | null;
196
196
  isRequired: boolean;
@@ -22,10 +22,10 @@ export declare const questionFieldTypeSchema: z.ZodEnum<{
22
22
  date: "date";
23
23
  other: "other";
24
24
  file: "file";
25
- email: "email";
26
- phone: "phone";
27
25
  textarea: "textarea";
28
26
  text: "text";
27
+ email: "email";
28
+ phone: "phone";
29
29
  datetime: "datetime";
30
30
  single_select: "single_select";
31
31
  multi_select: "multi_select";
@@ -51,10 +51,10 @@ export declare const bookingQuestionSchema: z.ZodObject<{
51
51
  date: "date";
52
52
  other: "other";
53
53
  file: "file";
54
- email: "email";
55
- phone: "phone";
56
54
  textarea: "textarea";
57
55
  text: "text";
56
+ email: "email";
57
+ phone: "phone";
58
58
  datetime: "datetime";
59
59
  single_select: "single_select";
60
60
  multi_select: "multi_select";
@@ -79,9 +79,9 @@ export declare const bookingQuestionOptionSchema: z.ZodObject<{
79
79
  export type BookingQuestionOption = z.infer<typeof bookingQuestionOptionSchema>;
80
80
  export declare const contactFieldKeySchema: z.ZodEnum<{
81
81
  other: "other";
82
+ address: "address";
82
83
  email: "email";
83
84
  phone: "phone";
84
- address: "address";
85
85
  nationality: "nationality";
86
86
  first_name: "first_name";
87
87
  last_name: "last_name";
@@ -104,9 +104,9 @@ export declare const contactRequirementSchema: z.ZodObject<{
104
104
  optionId: z.ZodNullable<z.ZodString>;
105
105
  fieldKey: z.ZodEnum<{
106
106
  other: "other";
107
+ address: "address";
107
108
  email: "email";
108
109
  phone: "phone";
109
- address: "address";
110
110
  nationality: "nationality";
111
111
  first_name: "first_name";
112
112
  last_name: "last_name";
@@ -162,10 +162,10 @@ export declare const bookingQuestionListResponse: z.ZodObject<{
162
162
  date: "date";
163
163
  other: "other";
164
164
  file: "file";
165
- email: "email";
166
- phone: "phone";
167
165
  textarea: "textarea";
168
166
  text: "text";
167
+ email: "email";
168
+ phone: "phone";
169
169
  datetime: "datetime";
170
170
  single_select: "single_select";
171
171
  multi_select: "multi_select";
@@ -202,9 +202,9 @@ export declare const contactRequirementListResponse: z.ZodObject<{
202
202
  optionId: z.ZodNullable<z.ZodString>;
203
203
  fieldKey: z.ZodEnum<{
204
204
  other: "other";
205
+ address: "address";
205
206
  email: "email";
206
207
  phone: "phone";
207
- address: "address";
208
208
  nationality: "nationality";
209
209
  first_name: "first_name";
210
210
  last_name: "last_name";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/bookings-react",
3
- "version": "0.185.0",
3
+ "version": "0.186.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -150,24 +150,24 @@
150
150
  "react-dom": "^19.0.0",
151
151
  "react-hook-form": "^7.80.0",
152
152
  "zod": "^4.0.0",
153
- "@voyant-travel/accommodations": "^0.145.0",
154
153
  "@voyant-travel/admin": "^0.128.3",
155
- "@voyant-travel/bookings": "^0.185.0",
156
- "@voyant-travel/catalog": "^0.183.0",
157
- "@voyant-travel/catalog-react": "^0.183.0",
158
- "@voyant-travel/distribution-react": "^0.175.0",
159
- "@voyant-travel/relationships-react": "^0.185.0",
160
- "@voyant-travel/finance": "^0.185.0",
161
- "@voyant-travel/finance-react": "^0.185.0",
162
- "@voyant-travel/identity-react": "^0.185.0",
163
- "@voyant-travel/legal-react": "^0.185.0",
164
- "@voyant-travel/commerce-react": "^0.67.0",
165
- "@voyant-travel/cruises": "^0.184.0",
166
- "@voyant-travel/inventory-react": "^0.67.0",
167
- "@voyant-travel/inventory": "^0.14.21",
168
- "@voyant-travel/storefront-react": "^0.187.0",
154
+ "@voyant-travel/bookings": "^0.186.1",
155
+ "@voyant-travel/accommodations": "^0.146.0",
156
+ "@voyant-travel/catalog": "^0.184.0",
157
+ "@voyant-travel/catalog-react": "^0.184.0",
158
+ "@voyant-travel/distribution-react": "^0.176.0",
159
+ "@voyant-travel/relationships-react": "^0.186.0",
160
+ "@voyant-travel/finance-react": "^0.186.0",
161
+ "@voyant-travel/identity-react": "^0.186.0",
162
+ "@voyant-travel/legal-react": "^0.186.0",
163
+ "@voyant-travel/cruises": "^0.185.0",
164
+ "@voyant-travel/inventory-react": "^0.68.0",
165
+ "@voyant-travel/inventory": "^0.14.22",
169
166
  "@voyant-travel/ui": "^0.109.3",
170
- "@voyant-travel/operations-react": "^0.66.0"
167
+ "@voyant-travel/storefront-react": "^0.188.0",
168
+ "@voyant-travel/operations-react": "^0.67.0",
169
+ "@voyant-travel/finance": "^0.186.0",
170
+ "@voyant-travel/commerce-react": "^0.68.0"
171
171
  },
172
172
  "peerDependenciesMeta": {
173
173
  "@tanstack/react-table": {
@@ -251,26 +251,26 @@
251
251
  "typescript": "^6.0.3",
252
252
  "vitest": "^4.1.9",
253
253
  "zod": "^4.4.3",
254
- "@voyant-travel/accommodations": "^0.145.0",
254
+ "@voyant-travel/accommodations": "^0.146.0",
255
255
  "@voyant-travel/admin": "^0.128.3",
256
- "@voyant-travel/bookings": "^0.185.0",
257
- "@voyant-travel/catalog": "^0.183.0",
258
- "@voyant-travel/catalog-react": "^0.183.0",
259
- "@voyant-travel/distribution-react": "^0.175.0",
260
- "@voyant-travel/relationships-react": "^0.185.0",
261
- "@voyant-travel/finance": "^0.185.0",
262
- "@voyant-travel/finance-react": "^0.185.0",
263
- "@voyant-travel/identity-react": "^0.185.0",
264
- "@voyant-travel/legal-react": "^0.185.0",
265
- "@voyant-travel/commerce-react": "^0.67.0",
266
- "@voyant-travel/cruises": "^0.184.0",
267
- "@voyant-travel/inventory-react": "^0.67.0",
268
- "@voyant-travel/inventory": "^0.14.21",
256
+ "@voyant-travel/bookings": "^0.186.1",
257
+ "@voyant-travel/catalog": "^0.184.0",
258
+ "@voyant-travel/catalog-react": "^0.184.0",
259
+ "@voyant-travel/distribution-react": "^0.176.0",
260
+ "@voyant-travel/relationships-react": "^0.186.0",
261
+ "@voyant-travel/finance": "^0.186.0",
262
+ "@voyant-travel/finance-react": "^0.186.0",
263
+ "@voyant-travel/identity-react": "^0.186.0",
264
+ "@voyant-travel/legal-react": "^0.186.0",
265
+ "@voyant-travel/commerce-react": "^0.68.0",
266
+ "@voyant-travel/cruises": "^0.185.0",
267
+ "@voyant-travel/inventory-react": "^0.68.0",
268
+ "@voyant-travel/inventory": "^0.14.22",
269
269
  "@voyant-travel/react": "^0.104.2",
270
- "@voyant-travel/storefront-react": "^0.187.0",
270
+ "@voyant-travel/storefront-react": "^0.188.0",
271
271
  "@voyant-travel/ui": "^0.109.3",
272
272
  "@voyant-travel/voyant-typescript-config": "^0.1.0",
273
- "@voyant-travel/operations-react": "^0.66.0"
273
+ "@voyant-travel/operations-react": "^0.67.0"
274
274
  },
275
275
  "files": [
276
276
  "dist",