@ticketboothapp/booking 1.2.98 → 1.2.100
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/package.json +3 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +5 -8
- package/src/components/booking/BookingDialog.tsx +29 -16
- package/src/components/booking/ChangeBookingDialog.tsx +10 -2
- package/src/components/booking/CheckoutModal.tsx +4 -1
- package/src/components/booking/NewBookingFlow.tsx +494 -134
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +208 -6
- package/src/components/booking/booking-flow-types.ts +50 -2
- package/src/components/booking/booking-flow-ui.ts +2 -0
- package/src/contexts/AvailabilitiesCacheContext.tsx +2 -2
- package/src/lib/booking/pricing.ts +1 -0
- package/src/lib/booking-api.ts +24 -1
- package/src/runtime/types.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ticketboothapp/booking",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.100",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"./booking-flow.css": "./src/components/booking/booking-flow.css",
|
|
15
15
|
"./runtime": "./src/runtime/index.ts",
|
|
16
16
|
"./contexts/booking-app-context": "./src/contexts/BookingAppContext.tsx",
|
|
17
|
+
"./contexts/company-context": "./src/contexts/CompanyContext.tsx",
|
|
18
|
+
"./contexts/availabilities-cache-context": "./src/contexts/AvailabilitiesCacheContext.tsx",
|
|
17
19
|
"./providers/booking-dialog-provider": "./src/providers/booking-dialog-provider.tsx",
|
|
18
20
|
"./hooks/useBookingSourceMetadataFromLocation": "./src/hooks/useBookingSourceMetadataFromLocation.ts",
|
|
19
21
|
"./hooks/useIsBookingLaunchLive": "./src/hooks/useIsBookingLaunchLive.ts",
|
|
@@ -3439,12 +3439,9 @@ export function AdminChangeBookingFlow({
|
|
|
3439
3439
|
// Percentage/fixed promos: tax on discounted amount (promo before GST per CRA guidance).
|
|
3440
3440
|
// Vouchers and gift cards: tax on full subtotal (voucher discount includes tax on free portion; gift card is payment).
|
|
3441
3441
|
// Change booking: get-promo-discount uses forBookingChange + prior promo from receipt so expired codes stay locked (BE).
|
|
3442
|
-
const
|
|
3443
|
-
lockedPromoCode
|
|
3444
|
-
? Math.max(0, originalReceiptPromoAdjustment.amount)
|
|
3445
|
-
: 0;
|
|
3442
|
+
const receiptPromoFallbackAmount = Math.max(0, originalReceiptPromoAdjustment.amount);
|
|
3446
3443
|
const effectivePromoDiscountAmount =
|
|
3447
|
-
promoDiscountAmount > 0 ? promoDiscountAmount :
|
|
3444
|
+
promoDiscountAmount > 0 ? promoDiscountAmount : receiptPromoFallbackAmount;
|
|
3448
3445
|
const effectivePromoLineType =
|
|
3449
3446
|
isGiftCard || originalReceiptPromoAdjustment.type === 'GIFT_CARD'
|
|
3450
3447
|
? 'GIFT_CARD'
|
|
@@ -5444,7 +5441,7 @@ export function AdminChangeBookingFlow({
|
|
|
5444
5441
|
...(effectivePromoDiscountAmount > 0
|
|
5445
5442
|
? [
|
|
5446
5443
|
{
|
|
5447
|
-
label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
|
|
5444
|
+
label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
|
|
5448
5445
|
amount: -effectivePromoDiscountAmount,
|
|
5449
5446
|
type: effectivePromoLineType,
|
|
5450
5447
|
},
|
|
@@ -5522,7 +5519,7 @@ export function AdminChangeBookingFlow({
|
|
|
5522
5519
|
isTaxIncludedInPrice,
|
|
5523
5520
|
taxRate: pricingConfig?.taxRate ?? 0,
|
|
5524
5521
|
promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
|
|
5525
|
-
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
|
|
5522
|
+
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
|
|
5526
5523
|
});
|
|
5527
5524
|
setShowAdminPaymentChoice(true);
|
|
5528
5525
|
setLoading(false);
|
|
@@ -5573,7 +5570,7 @@ export function AdminChangeBookingFlow({
|
|
|
5573
5570
|
isTaxIncludedInPrice,
|
|
5574
5571
|
taxRate: pricingConfig?.taxRate ?? 0,
|
|
5575
5572
|
promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
|
|
5576
|
-
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
|
|
5573
|
+
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
|
|
5577
5574
|
changeTotals:
|
|
5578
5575
|
isCustomerSelfServeChange && originalReceipt
|
|
5579
5576
|
? {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { useBookingSourceMetadataFromLocation } from '../../hooks/useBookingSourceMetadataFromLocation';
|
|
5
5
|
import { useBookingDialog } from '../../providers/booking-dialog-provider';
|
|
6
6
|
import { useBookingHost } from '../../runtime';
|
|
@@ -49,15 +49,28 @@ function BookFlowScreen({
|
|
|
49
49
|
const apiProductId = config?.productId ?? productId;
|
|
50
50
|
|
|
51
51
|
// Build minimal product from config for immediate availability fetch (no /products wait)
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const staticProduct = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
catalog.getStaticProductByIdOrSlug
|
|
55
|
+
? (catalog.getStaticProductByIdOrSlug(productId) as Product | null)
|
|
56
|
+
: null,
|
|
57
|
+
[catalog, productId]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const minimalProduct = useMemo(
|
|
61
|
+
() =>
|
|
62
|
+
config && catalog.buildMinimalProductFromConfig
|
|
63
|
+
? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
|
|
64
|
+
: null,
|
|
65
|
+
[catalog, config, env.COMPANY_ID]
|
|
66
|
+
);
|
|
56
67
|
|
|
57
68
|
useEffect(() => {
|
|
58
69
|
if (isPartialLaunch) return; // No API needed for partial launch
|
|
59
|
-
let cancelled = false;
|
|
60
70
|
setError(null);
|
|
71
|
+
setProduct(null);
|
|
72
|
+
if (staticProduct || minimalProduct) return;
|
|
73
|
+
let cancelled = false;
|
|
61
74
|
getProduct(apiProductId, env.COMPANY_ID)
|
|
62
75
|
.then((p) => {
|
|
63
76
|
if (!cancelled && p) setProduct(p);
|
|
@@ -67,13 +80,14 @@ function BookFlowScreen({
|
|
|
67
80
|
if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load product');
|
|
68
81
|
});
|
|
69
82
|
return () => { cancelled = true; };
|
|
70
|
-
}, [apiProductId, config, isPartialLaunch]);
|
|
83
|
+
}, [apiProductId, config, env.COMPANY_ID, isPartialLaunch, minimalProduct, staticProduct]);
|
|
71
84
|
|
|
72
85
|
useEffect(() => {
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
const loadedProduct = product?.productId === apiProductId ? product : staticProduct ?? minimalProduct;
|
|
87
|
+
if (loadedProduct) {
|
|
88
|
+
onProductLoaded?.(loadedProduct.name);
|
|
75
89
|
}
|
|
76
|
-
}, [product
|
|
90
|
+
}, [apiProductId, minimalProduct, onProductLoaded, product, staticProduct]);
|
|
77
91
|
|
|
78
92
|
// Partial launch: show collage + description immediately (no API)
|
|
79
93
|
if (isPartialLaunch) {
|
|
@@ -84,8 +98,10 @@ function BookFlowScreen({
|
|
|
84
98
|
);
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
const displayProduct = product?.productId === apiProductId ? product : staticProduct ?? minimalProduct;
|
|
102
|
+
|
|
103
|
+
// No local/static product: must wait for API (product not in products-config)
|
|
104
|
+
if (!displayProduct) {
|
|
89
105
|
if (error) {
|
|
90
106
|
return (
|
|
91
107
|
<div className={`${styles.screen} booking-flow-preflight`}>
|
|
@@ -107,10 +123,7 @@ function BookFlowScreen({
|
|
|
107
123
|
);
|
|
108
124
|
}
|
|
109
125
|
|
|
110
|
-
|
|
111
|
-
const displayProduct = product ?? minimalProduct!;
|
|
112
|
-
|
|
113
|
-
if (error && !product) {
|
|
126
|
+
if (error && !displayProduct) {
|
|
114
127
|
return (
|
|
115
128
|
<div className={`${styles.screen} booking-flow-preflight`}>
|
|
116
129
|
<div className="flex flex-col items-center justify-center py-16 gap-4">
|
|
@@ -107,12 +107,20 @@ export default function ChangeBookingDialog({
|
|
|
107
107
|
: null,
|
|
108
108
|
[config, catalog, env.COMPANY_ID]
|
|
109
109
|
);
|
|
110
|
+
const staticProduct = useMemo(
|
|
111
|
+
() =>
|
|
112
|
+
catalog.getStaticProductByIdOrSlug
|
|
113
|
+
? (catalog.getStaticProductByIdOrSlug(bookingProductId) as Product | null)
|
|
114
|
+
: null,
|
|
115
|
+
[bookingProductId, catalog]
|
|
116
|
+
);
|
|
110
117
|
|
|
111
118
|
useEffect(() => {
|
|
112
119
|
if (!isOpen) return;
|
|
113
120
|
let cancelled = false;
|
|
114
121
|
setError(null);
|
|
115
|
-
|
|
122
|
+
setProduct(staticProduct ?? minimalProduct);
|
|
123
|
+
if (staticProduct || minimalProduct) return;
|
|
116
124
|
getProduct(apiProductId, env.COMPANY_ID)
|
|
117
125
|
.then((p) => {
|
|
118
126
|
if (!cancelled && p) setProduct(p);
|
|
@@ -123,7 +131,7 @@ export default function ChangeBookingDialog({
|
|
|
123
131
|
return () => {
|
|
124
132
|
cancelled = true;
|
|
125
133
|
};
|
|
126
|
-
}, [isOpen, apiProductId, minimalProduct]);
|
|
134
|
+
}, [isOpen, apiProductId, minimalProduct, staticProduct]);
|
|
127
135
|
|
|
128
136
|
useEffect(() => {
|
|
129
137
|
if (isOpen) {
|
|
@@ -334,9 +334,12 @@ export function CheckoutModal({
|
|
|
334
334
|
: []),
|
|
335
335
|
...feeLineItems.map((fee) => {
|
|
336
336
|
const isMoraineLakeRoadAccessFee = fee.name.toLowerCase().includes('moraine') && (fee.name.toLowerCase().includes('access') || fee.name.toLowerCase().includes('road') || fee.name.toLowerCase().includes('license'));
|
|
337
|
+
const showQuantityLabel = fee.showQuantityLabel !== false;
|
|
337
338
|
return {
|
|
338
339
|
kind: 'line' as const,
|
|
339
|
-
label:
|
|
340
|
+
label: showQuantityLabel
|
|
341
|
+
? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
|
|
342
|
+
: fee.name,
|
|
340
343
|
amount: fee.totalAmount,
|
|
341
344
|
type: 'fee',
|
|
342
345
|
tooltip: isMoraineLakeRoadAccessFee
|