@ticketboothapp/booking 1.2.99 → 1.2.101
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/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 +205 -2
- 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.101",
|
|
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",
|
|
@@ -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
|