@ticketboothapp/booking 1.2.191 → 1.2.193
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect,
|
|
3
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
4
4
|
import { formatBookingRefForDisplay } from '../../lib/format-booking-ref';
|
|
5
5
|
import {
|
|
6
6
|
fetchProducts,
|
|
@@ -11,23 +11,10 @@ import {
|
|
|
11
11
|
import { PickupLocationSelector } from './PickupLocationSelector';
|
|
12
12
|
import { useTranslations } from '../../lib/booking/i18n';
|
|
13
13
|
import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
|
|
14
|
+
import { useBookingHost } from '../../runtime';
|
|
15
|
+
import { resolvePickupLocationProduct } from './pickup-location-product';
|
|
14
16
|
import styles from './PickupLocationDialog.module.css';
|
|
15
17
|
|
|
16
|
-
function findProductWithPickupLocations(
|
|
17
|
-
products: Product[],
|
|
18
|
-
bookingProductId: string
|
|
19
|
-
): Product | null {
|
|
20
|
-
for (const p of products) {
|
|
21
|
-
if (p.productId === bookingProductId) return p;
|
|
22
|
-
if (p.pickupLocations?.some((loc) => loc.id === bookingProductId)) return p;
|
|
23
|
-
const opt = (p as Product & { options?: { optionId: string }[] })?.options?.find(
|
|
24
|
-
(o) => o.optionId === bookingProductId
|
|
25
|
-
);
|
|
26
|
-
if (opt) return p;
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
/** Itinerary rows shown on manage-booking (may include optional label/time from API). */
|
|
32
19
|
export interface PickupDialogItineraryStep {
|
|
33
20
|
label?: string | null;
|
|
@@ -108,7 +95,8 @@ export function PickupLocationDialog({
|
|
|
108
95
|
onSuccess,
|
|
109
96
|
}: PickupLocationDialogProps) {
|
|
110
97
|
const { t } = useTranslations();
|
|
111
|
-
const
|
|
98
|
+
const { catalog } = useBookingHost();
|
|
99
|
+
const [liveProducts, setLiveProducts] = useState<Product[]>([]);
|
|
112
100
|
const [loading, setLoading] = useState(false);
|
|
113
101
|
const [error, setError] = useState<string | null>(null);
|
|
114
102
|
const [selectedLocationId, setSelectedLocationId] = useState<string | null>(
|
|
@@ -130,33 +118,53 @@ export function PickupLocationDialog({
|
|
|
130
118
|
const lastName = booking.customer?.lastName ?? '';
|
|
131
119
|
const isPrivateShuttle = booking.productType === 'PRIVATE_SHUTTLE';
|
|
132
120
|
|
|
121
|
+
const staticProduct = useMemo(
|
|
122
|
+
() =>
|
|
123
|
+
catalog.getStaticProductByIdOrSlug
|
|
124
|
+
? (catalog.getStaticProductByIdOrSlug(booking.productId) as Product | null)
|
|
125
|
+
: null,
|
|
126
|
+
[booking.productId, catalog]
|
|
127
|
+
);
|
|
133
128
|
const product = useMemo(
|
|
134
|
-
() =>
|
|
135
|
-
[
|
|
129
|
+
() => resolvePickupLocationProduct(staticProduct, liveProducts, booking.productId),
|
|
130
|
+
[staticProduct, liveProducts, booking.productId]
|
|
136
131
|
);
|
|
137
|
-
const pickupLocations = product?.pickupLocations ?? [];
|
|
138
|
-
const destinations = product?.destinations ?? [];
|
|
132
|
+
const pickupLocations = useMemo(() => product?.pickupLocations ?? [], [product]);
|
|
133
|
+
const destinations = useMemo(() => product?.destinations ?? [], [product]);
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
if (!
|
|
142
|
-
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!isOpen) return;
|
|
137
|
+
setError(null);
|
|
138
|
+
if (staticProduct) {
|
|
139
|
+
setLoading(false);
|
|
143
140
|
return;
|
|
144
141
|
}
|
|
145
|
-
|
|
146
|
-
setError(null);
|
|
147
|
-
try {
|
|
148
|
-
const data = await fetchProducts(companyId);
|
|
149
|
-
setProducts(data);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
setError(e instanceof Error ? e.message : 'Failed to load pickup locations');
|
|
152
|
-
} finally {
|
|
142
|
+
if (!companyId.trim()) {
|
|
153
143
|
setLoading(false);
|
|
144
|
+
setError('Missing company information');
|
|
145
|
+
return;
|
|
154
146
|
}
|
|
155
|
-
}, [companyId]);
|
|
156
147
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
let cancelled = false;
|
|
149
|
+
setLiveProducts([]);
|
|
150
|
+
setLoading(true);
|
|
151
|
+
fetchProducts(companyId)
|
|
152
|
+
.then((data) => {
|
|
153
|
+
if (!cancelled) setLiveProducts(data);
|
|
154
|
+
})
|
|
155
|
+
.catch((e) => {
|
|
156
|
+
if (!cancelled) {
|
|
157
|
+
setError(e instanceof Error ? e.message : 'Failed to load pickup locations');
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
.finally(() => {
|
|
161
|
+
if (!cancelled) setLoading(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return () => {
|
|
165
|
+
cancelled = true;
|
|
166
|
+
};
|
|
167
|
+
}, [isOpen, companyId, staticProduct]);
|
|
160
168
|
|
|
161
169
|
useEffect(() => {
|
|
162
170
|
if (isOpen) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Product } from '../../lib/booking-api';
|
|
2
|
+
|
|
3
|
+
export function findProductForPickupLocations(
|
|
4
|
+
products: Product[],
|
|
5
|
+
bookingProductId: string,
|
|
6
|
+
): Product | null {
|
|
7
|
+
for (const product of products) {
|
|
8
|
+
if (product.productId === bookingProductId) return product;
|
|
9
|
+
if (product.pickupLocations?.some((location) => location.id === bookingProductId)) {
|
|
10
|
+
return product;
|
|
11
|
+
}
|
|
12
|
+
if (product.options?.some((option) => option.optionId === bookingProductId)) return product;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Keep manage-booking pickup choices aligned with the normal booking flow's static catalog. */
|
|
18
|
+
export function resolvePickupLocationProduct(
|
|
19
|
+
staticProduct: Product | null,
|
|
20
|
+
liveProducts: Product[],
|
|
21
|
+
bookingProductId: string,
|
|
22
|
+
): Product | null {
|
|
23
|
+
return staticProduct ?? findProductForPickupLocations(liveProducts, bookingProductId);
|
|
24
|
+
}
|
|
@@ -13,6 +13,8 @@ const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
|
|
|
13
13
|
const SAMSON_MALL_PICKUP_LOCATION_NAME = 'samson mall';
|
|
14
14
|
|
|
15
15
|
const LAKE_LOUISE_HOTEL_PICKUP_LOCATIONS = new Map([
|
|
16
|
+
['loc_Ll05oVACjjAx', 'HI Lake Louise Alpine Centre'],
|
|
17
|
+
['loc_VAJ0L8amxnC5', 'Fairmont Lake Louise'],
|
|
16
18
|
['loc_arPLLVG1xKxw', 'Mountaineer Lodge'],
|
|
17
19
|
['loc_9ObwCZ7MxfjC', 'Paradise Lodge and Bungalows'],
|
|
18
20
|
['loc_ieW6EfdVeYd4', 'Post Hotel'],
|
|
@@ -94,6 +94,7 @@ import {
|
|
|
94
94
|
resolveInitialAvailabilityFromBooking,
|
|
95
95
|
} from '../src/components/booking/change-booking-flow-helpers';
|
|
96
96
|
import { collapseAdminPromoLines } from '../src/components/booking/admin-change-receipt-lines';
|
|
97
|
+
import { resolvePickupLocationProduct } from '../src/components/booking/pickup-location-product';
|
|
97
98
|
import {
|
|
98
99
|
buildAdminChangePaymentChoiceData,
|
|
99
100
|
buildAdminChangePayNowCheckoutModalData,
|
|
@@ -168,6 +169,43 @@ function test(name: string, fn: () => void | Promise<void>): void {
|
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
test('manage-booking pickup locations prefer the normal booking static product', () => {
|
|
173
|
+
const staticProduct = {
|
|
174
|
+
productId: 'product_1',
|
|
175
|
+
name: 'Static product',
|
|
176
|
+
description: null,
|
|
177
|
+
status: 'ACTIVE',
|
|
178
|
+
options: [{ optionId: 'option_1' }],
|
|
179
|
+
pickupLocations: [{ id: 'pickup_kept', name: 'Kept pickup' }],
|
|
180
|
+
} as NonNullable<Parameters<typeof resolvePickupLocationProduct>[0]>;
|
|
181
|
+
const liveProduct = {
|
|
182
|
+
...staticProduct,
|
|
183
|
+
name: 'Live product',
|
|
184
|
+
pickupLocations: [
|
|
185
|
+
{ id: 'pickup_kept', name: 'Kept pickup' },
|
|
186
|
+
{ id: 'pickup_removed', name: 'Removed pickup' },
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const resolved = resolvePickupLocationProduct(staticProduct, [liveProduct], 'option_1');
|
|
191
|
+
|
|
192
|
+
assert.equal(resolved, staticProduct);
|
|
193
|
+
assert.deepEqual(resolved?.pickupLocations?.map((location) => location.id), ['pickup_kept']);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('manage-booking pickup locations fall back to a live product matched by option id', () => {
|
|
197
|
+
const liveProduct = {
|
|
198
|
+
productId: 'product_1',
|
|
199
|
+
name: 'Live product',
|
|
200
|
+
description: null,
|
|
201
|
+
status: 'ACTIVE',
|
|
202
|
+
options: [{ optionId: 'option_1' }],
|
|
203
|
+
pickupLocations: [{ id: 'pickup_1', name: 'Pickup one' }],
|
|
204
|
+
} as NonNullable<Parameters<typeof resolvePickupLocationProduct>[0]>;
|
|
205
|
+
|
|
206
|
+
assert.equal(resolvePickupLocationProduct(null, [liveProduct], 'option_1'), liveProduct);
|
|
207
|
+
});
|
|
208
|
+
|
|
171
209
|
test('availability cache revalidates only when a covered range is stale', () => {
|
|
172
210
|
assert.equal(shouldRevalidateAvailabilityCache(true, false), false);
|
|
173
211
|
assert.equal(shouldRevalidateAvailabilityCache(true, true), true);
|