@ticketboothapp/booking 1.2.103 → 1.2.105
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 +1 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +80 -49
- package/src/components/booking/ChangeBookingFlow.tsx +79 -45
- package/src/components/booking/NewBookingFlow.tsx +117 -48
- package/src/components/booking/PickupLocationSelector.tsx +47 -18
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +68 -34
- package/src/contexts/AvailabilitiesCacheContext.tsx +4 -2
- package/src/lib/booking/pricing.ts +5 -1
- package/src/lib/booking-api.ts +2 -0
|
@@ -263,18 +263,30 @@ function findMergedAvailabilityForSelection(
|
|
|
263
263
|
selected: Availability | null
|
|
264
264
|
): Availability | undefined {
|
|
265
265
|
if (!selected) return undefined;
|
|
266
|
-
const optId = selected
|
|
266
|
+
const optId = getAvailabilityOptionId(selected) || undefined;
|
|
267
267
|
const dt = selected.dateTime;
|
|
268
268
|
const availId = selected.availabilityId?.trim();
|
|
269
269
|
if (availId && optId) {
|
|
270
270
|
const exact = merged.find(
|
|
271
271
|
(a) =>
|
|
272
272
|
a.availabilityId === availId &&
|
|
273
|
-
(a
|
|
273
|
+
getAvailabilityOptionId(a) === optId
|
|
274
274
|
);
|
|
275
275
|
if (exact) return exact;
|
|
276
276
|
}
|
|
277
|
-
return merged.find((a) => a.dateTime === dt && a
|
|
277
|
+
return merged.find((a) => a.dateTime === dt && getAvailabilityOptionId(a) === optId);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function getAvailabilityOptionId(availability: Availability, fallbackOptionId?: string): string {
|
|
281
|
+
const productOptionId = availability.productOptionId?.trim();
|
|
282
|
+
if (productOptionId) return productOptionId;
|
|
283
|
+
const productId = availability.productId?.trim();
|
|
284
|
+
if (productId?.startsWith('po_')) return productId;
|
|
285
|
+
return fallbackOptionId?.trim() ?? '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getAvailabilityCacheKey(availability: Availability): string {
|
|
289
|
+
return `${availability.dateTime}-${getAvailabilityOptionId(availability)}`;
|
|
278
290
|
}
|
|
279
291
|
|
|
280
292
|
/** Ticket rates for one availability row — mirrors main cart [pricing] useMemo so baseline slots match BE. */
|
|
@@ -288,7 +300,7 @@ function buildPricingFromAvailability(
|
|
|
288
300
|
isSimplifiedPricingView: boolean,
|
|
289
301
|
) {
|
|
290
302
|
if (!selectedAvailability || !pricingConfig) return [];
|
|
291
|
-
const optionId = selectedAvailability
|
|
303
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
292
304
|
const selectedOption = activeOptions.find((opt) => opt.optionId === optionId);
|
|
293
305
|
const precomputed = optionId ? precomputedPricesByOption?.[optionId] : undefined;
|
|
294
306
|
const rateToDisplayPrice = (backendInDisplayCurrency: number) =>
|
|
@@ -306,11 +318,17 @@ function buildPricingFromAvailability(
|
|
|
306
318
|
appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }>,
|
|
307
319
|
) => {
|
|
308
320
|
const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
|
|
321
|
+
const hasOptionScopedPrice = baseInDisplayCurrency > 0;
|
|
322
|
+
const priceCADForBreakdown =
|
|
323
|
+
hasOptionScopedPrice && currency === 'CAD' ? baseInDisplayCurrency : backendPriceCAD;
|
|
324
|
+
const displayCurrencyPriceForFallback = hasOptionScopedPrice
|
|
325
|
+
? baseInDisplayCurrency
|
|
326
|
+
: backendInDisplayCurrency;
|
|
309
327
|
const isPublicMode = isSimplifiedPricingView;
|
|
310
328
|
const breakdown = computePriceBreakdown(
|
|
311
329
|
pricingConfig,
|
|
312
330
|
currency,
|
|
313
|
-
|
|
331
|
+
priceCADForBreakdown,
|
|
314
332
|
basePriceCAD,
|
|
315
333
|
hasFees,
|
|
316
334
|
appliedAdjustments,
|
|
@@ -318,8 +336,8 @@ function buildPricingFromAvailability(
|
|
|
318
336
|
baseInDisplayCurrency,
|
|
319
337
|
isPublicMode,
|
|
320
338
|
);
|
|
321
|
-
const price = breakdown?.finalPrice ?? rateToDisplayPrice(
|
|
322
|
-
return { category, baseInDisplayCurrency, appliedAdjustments, price, priceCAD:
|
|
339
|
+
const price = breakdown?.finalPrice ?? rateToDisplayPrice(displayCurrencyPriceForFallback);
|
|
340
|
+
return { category, baseInDisplayCurrency, appliedAdjustments, price, priceCAD: priceCADForBreakdown };
|
|
323
341
|
};
|
|
324
342
|
return (
|
|
325
343
|
selectedAvailability.rates?.map((rate) => {
|
|
@@ -770,6 +788,14 @@ export function ChangeBookingFlow({
|
|
|
770
788
|
return '';
|
|
771
789
|
}
|
|
772
790
|
});
|
|
791
|
+
const selectedDateRangeExtensionKey = useMemo(() => {
|
|
792
|
+
if (!visibleRange || !selectedDate) return '';
|
|
793
|
+
try {
|
|
794
|
+
return isAfter(parseISO(selectedDate), visibleRange.end) ? selectedDate : '';
|
|
795
|
+
} catch {
|
|
796
|
+
return '';
|
|
797
|
+
}
|
|
798
|
+
}, [selectedDate, visibleRange]);
|
|
773
799
|
const [isItinerarySticky, setIsItinerarySticky] = useState(false);
|
|
774
800
|
const isItineraryStickyRef = useRef(false);
|
|
775
801
|
const [isMobile, setIsMobile] = useState(false);
|
|
@@ -980,6 +1006,7 @@ export function ChangeBookingFlow({
|
|
|
980
1006
|
|
|
981
1007
|
const availabilityPromises = activeOptions.map(async (option) => {
|
|
982
1008
|
const result = await getAvailabilities(option.optionId, startDateStr, endDateStr, {
|
|
1009
|
+
...(!isPrivateShuttle ? { responseMode: 'booking-day' as const } : {}),
|
|
983
1010
|
promoCode: appliedPromoCode || undefined,
|
|
984
1011
|
...(pricingProfileIdForAvailabilities
|
|
985
1012
|
? { pricingProfileId: pricingProfileIdForAvailabilities }
|
|
@@ -1024,10 +1051,10 @@ export function ChangeBookingFlow({
|
|
|
1024
1051
|
let mergedOut: Availability[] = [];
|
|
1025
1052
|
setAvailabilities((prev) => {
|
|
1026
1053
|
const existingMap = new Map(
|
|
1027
|
-
prev.map((avail) => [
|
|
1054
|
+
prev.map((avail) => [getAvailabilityCacheKey(avail), avail])
|
|
1028
1055
|
);
|
|
1029
1056
|
allFetchedAvailabilities.forEach((avail) => {
|
|
1030
|
-
existingMap.set(
|
|
1057
|
+
existingMap.set(getAvailabilityCacheKey(avail), avail);
|
|
1031
1058
|
});
|
|
1032
1059
|
mergedOut = Array.from(existingMap.values());
|
|
1033
1060
|
return mergedOut;
|
|
@@ -1054,16 +1081,17 @@ export function ChangeBookingFlow({
|
|
|
1054
1081
|
activeOptionIdsKey,
|
|
1055
1082
|
appliedPromoCode,
|
|
1056
1083
|
pricingProfileIdForAvailabilities,
|
|
1084
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
1057
1085
|
)
|
|
1058
1086
|
: null;
|
|
1059
1087
|
if (cacheKey && availabilitiesCache) {
|
|
1060
1088
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
1061
1089
|
const existingAvailabilities = existingCache?.availabilities ?? [];
|
|
1062
1090
|
const mergedAvailabilitiesMap = new Map(
|
|
1063
|
-
existingAvailabilities.map((a) => [
|
|
1091
|
+
existingAvailabilities.map((a) => [getAvailabilityCacheKey(a), a])
|
|
1064
1092
|
);
|
|
1065
1093
|
allFetchedAvailabilities.forEach((a) => {
|
|
1066
|
-
mergedAvailabilitiesMap.set(
|
|
1094
|
+
mergedAvailabilitiesMap.set(getAvailabilityCacheKey(a), a);
|
|
1067
1095
|
});
|
|
1068
1096
|
const mergedPrecomputed = { ...(existingCache?.precomputedPricesByOption ?? {}) };
|
|
1069
1097
|
results.forEach((r) => {
|
|
@@ -1178,11 +1206,11 @@ export function ChangeBookingFlow({
|
|
|
1178
1206
|
? endOfLatestDay
|
|
1179
1207
|
: visibleRange.end;
|
|
1180
1208
|
|
|
1181
|
-
//
|
|
1182
|
-
//
|
|
1183
|
-
if (
|
|
1209
|
+
// Include an externally seeded selected date only when it falls outside the current calendar window.
|
|
1210
|
+
// Normal date clicks are already inside visibleRange and should not re-fetch the broad availability range.
|
|
1211
|
+
if (selectedDateRangeExtensionKey) {
|
|
1184
1212
|
try {
|
|
1185
|
-
const selectedDateObj = parseISO(
|
|
1213
|
+
const selectedDateObj = parseISO(selectedDateRangeExtensionKey);
|
|
1186
1214
|
if (isAfter(selectedDateObj, clampedEnd)) {
|
|
1187
1215
|
clampedEnd = selectedDateObj;
|
|
1188
1216
|
}
|
|
@@ -1198,6 +1226,7 @@ export function ChangeBookingFlow({
|
|
|
1198
1226
|
activeOptionIdsKey,
|
|
1199
1227
|
appliedPromoCode,
|
|
1200
1228
|
pricingProfileIdForAvailabilities,
|
|
1229
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
1201
1230
|
)
|
|
1202
1231
|
: null;
|
|
1203
1232
|
const cached = cacheKey ? availabilitiesCache!.get(cacheKey) : undefined;
|
|
@@ -1291,6 +1320,7 @@ export function ChangeBookingFlow({
|
|
|
1291
1320
|
// Fetch availabilities for all product options in parallel (no currency param - we use precomputedPrices for display)
|
|
1292
1321
|
const availabilityPromises = activeOptions.map(async (option) => {
|
|
1293
1322
|
const result = await getAvailabilities(option.optionId, startDateStr, endDateStr, {
|
|
1323
|
+
...(!isPrivateShuttle ? { responseMode: 'booking-day' as const } : {}),
|
|
1294
1324
|
promoCode: appliedPromoCode || undefined,
|
|
1295
1325
|
...(pricingProfileIdForAvailabilities
|
|
1296
1326
|
? { pricingProfileId: pricingProfileIdForAvailabilities }
|
|
@@ -1339,12 +1369,12 @@ export function ChangeBookingFlow({
|
|
|
1339
1369
|
// Merge with existing availabilities (avoid duplicates by dateTime + productOptionId)
|
|
1340
1370
|
setAvailabilities(prev => {
|
|
1341
1371
|
const existingMap = new Map(
|
|
1342
|
-
prev.map(avail => [
|
|
1372
|
+
prev.map(avail => [getAvailabilityCacheKey(avail), avail])
|
|
1343
1373
|
);
|
|
1344
1374
|
|
|
1345
1375
|
// Merge new availabilities - update existing ones or add new ones
|
|
1346
1376
|
allFetchedAvailabilities.forEach(avail => {
|
|
1347
|
-
const key =
|
|
1377
|
+
const key = getAvailabilityCacheKey(avail);
|
|
1348
1378
|
// Always update to get latest data (vacancies, prices, etc.)
|
|
1349
1379
|
existingMap.set(key, avail);
|
|
1350
1380
|
});
|
|
@@ -1373,10 +1403,10 @@ export function ChangeBookingFlow({
|
|
|
1373
1403
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
1374
1404
|
const existingAvailabilities = existingCache?.availabilities ?? [];
|
|
1375
1405
|
const mergedAvailabilitiesMap = new Map(
|
|
1376
|
-
existingAvailabilities.map((a) => [
|
|
1406
|
+
existingAvailabilities.map((a) => [getAvailabilityCacheKey(a), a])
|
|
1377
1407
|
);
|
|
1378
1408
|
allFetchedAvailabilities.forEach((a) => {
|
|
1379
|
-
mergedAvailabilitiesMap.set(
|
|
1409
|
+
mergedAvailabilitiesMap.set(getAvailabilityCacheKey(a), a);
|
|
1380
1410
|
});
|
|
1381
1411
|
const mergedPrecomputed = { ...(existingCache?.precomputedPricesByOption ?? {}) };
|
|
1382
1412
|
results.forEach((r) => {
|
|
@@ -1424,7 +1454,7 @@ export function ChangeBookingFlow({
|
|
|
1424
1454
|
activeOptionIdsKey,
|
|
1425
1455
|
isPrivateShuttle,
|
|
1426
1456
|
companyTimezone,
|
|
1427
|
-
|
|
1457
|
+
selectedDateRangeExtensionKey,
|
|
1428
1458
|
appliedPromoCode,
|
|
1429
1459
|
pricingProfileIdForAvailabilities,
|
|
1430
1460
|
cancellationPolicyProfileIdForAvailabilities,
|
|
@@ -1622,7 +1652,7 @@ export function ChangeBookingFlow({
|
|
|
1622
1652
|
timesForSelectedDate
|
|
1623
1653
|
.map(
|
|
1624
1654
|
(avail) =>
|
|
1625
|
-
`${avail.dateTime}|${avail
|
|
1655
|
+
`${avail.dateTime}|${getAvailabilityOptionId(avail)}|${avail.vacancies ?? 0}`,
|
|
1626
1656
|
)
|
|
1627
1657
|
.join('||'),
|
|
1628
1658
|
[timesForSelectedDate],
|
|
@@ -2054,7 +2084,9 @@ export function ChangeBookingFlow({
|
|
|
2054
2084
|
const calculateStaySummary = useCallback((returnDateTime: Date): string | null => {
|
|
2055
2085
|
if (!selectedAvailability) return null;
|
|
2056
2086
|
|
|
2057
|
-
const availabilityProductOptionId =
|
|
2087
|
+
const availabilityProductOptionId =
|
|
2088
|
+
getAvailabilityOptionId(selectedAvailability) ||
|
|
2089
|
+
(activeOptions.length === 1 ? activeOptions[0]?.optionId : undefined);
|
|
2058
2090
|
const selectedOption = activeOptions.find(opt => opt.optionId === availabilityProductOptionId);
|
|
2059
2091
|
if (!selectedOption) return null;
|
|
2060
2092
|
|
|
@@ -2130,7 +2162,9 @@ export function ChangeBookingFlow({
|
|
|
2130
2162
|
// Helper function to compute itinerary display for storage (returns same shape as "Your Itinerary" box)
|
|
2131
2163
|
const computeItineraryDisplay = useCallback((): ItineraryDisplayStep[] | null => {
|
|
2132
2164
|
if (!selectedAvailability) return null;
|
|
2133
|
-
const availabilityProductOptionId =
|
|
2165
|
+
const availabilityProductOptionId =
|
|
2166
|
+
getAvailabilityOptionId(selectedAvailability) ||
|
|
2167
|
+
(activeOptions.length === 1 ? activeOptions[0]?.optionId : undefined);
|
|
2134
2168
|
const selectedOption = activeOptions.find(opt => opt.optionId === availabilityProductOptionId);
|
|
2135
2169
|
if (!selectedOption) return null;
|
|
2136
2170
|
const tourStartTime = parseISO(selectedAvailability.dateTime);
|
|
@@ -2369,13 +2403,13 @@ export function ChangeBookingFlow({
|
|
|
2369
2403
|
const selectedMs = parseAvailabilityDateTime(selectedAvailability.dateTime).getTime();
|
|
2370
2404
|
const initialMs = parseAvailabilityDateTime(initialValues.dateTime).getTime();
|
|
2371
2405
|
if (selectedMs !== initialMs) return false;
|
|
2372
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
2406
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
2373
2407
|
const initialOpt = changeFlowResolvedInitialProductOptionId;
|
|
2374
2408
|
if (selectedOpt != null && initialOpt != null) {
|
|
2375
2409
|
return selectedOpt === initialOpt;
|
|
2376
2410
|
}
|
|
2377
2411
|
const legacyInitial = initialValues.productOptionId?.trim() || null;
|
|
2378
|
-
const legacySelected = selectedAvailability
|
|
2412
|
+
const legacySelected = getAvailabilityOptionId(selectedAvailability) || null;
|
|
2379
2413
|
return !legacySelected || !legacyInitial || legacySelected === legacyInitial;
|
|
2380
2414
|
}, [
|
|
2381
2415
|
selectedAvailability,
|
|
@@ -2402,13 +2436,13 @@ export function ChangeBookingFlow({
|
|
|
2402
2436
|
return false;
|
|
2403
2437
|
}
|
|
2404
2438
|
if (!selectedDate || selectedDate !== initialDay) return false;
|
|
2405
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
2439
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
2406
2440
|
const initialOpt = changeFlowResolvedInitialProductOptionId;
|
|
2407
2441
|
if (selectedOpt != null && initialOpt != null) {
|
|
2408
2442
|
return selectedOpt === initialOpt;
|
|
2409
2443
|
}
|
|
2410
2444
|
const legacyInitial = initialValues.productOptionId?.trim() || null;
|
|
2411
|
-
const legacySelected = selectedAvailability
|
|
2445
|
+
const legacySelected = getAvailabilityOptionId(selectedAvailability) || null;
|
|
2412
2446
|
return Boolean(legacySelected && legacyInitial && legacySelected === legacyInitial);
|
|
2413
2447
|
}, [
|
|
2414
2448
|
selectedDate,
|
|
@@ -2605,7 +2639,9 @@ export function ChangeBookingFlow({
|
|
|
2605
2639
|
// Price breakdown: mid-layer returns line items (base + one per rule/deal). UI renders each line; rate in brackets when used.
|
|
2606
2640
|
const getPriceBreakdown = useCallback((category: string, priceCAD: number, baseInDisplayCurrency: number | undefined, appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }> = []): PriceBreakdownData | null => {
|
|
2607
2641
|
if (!pricingConfig) return null;
|
|
2608
|
-
const selectedOption = activeOptions.find(
|
|
2642
|
+
const selectedOption = activeOptions.find(
|
|
2643
|
+
opt => opt.optionId === (selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '')
|
|
2644
|
+
);
|
|
2609
2645
|
const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
|
|
2610
2646
|
const isPublicMode = isSimplifiedPricingView;
|
|
2611
2647
|
return computePriceBreakdown(
|
|
@@ -3037,7 +3073,7 @@ export function ChangeBookingFlow({
|
|
|
3037
3073
|
if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
|
|
3038
3074
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
3039
3075
|
if (!companyId) return '';
|
|
3040
|
-
const optionId = selectedAvailability
|
|
3076
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
3041
3077
|
if (!optionId || !quantitiesSignature) return '';
|
|
3042
3078
|
return [
|
|
3043
3079
|
appliedPromoCode,
|
|
@@ -3054,7 +3090,7 @@ export function ChangeBookingFlow({
|
|
|
3054
3090
|
}, [
|
|
3055
3091
|
appliedPromoCode,
|
|
3056
3092
|
selectedAvailability?.dateTime,
|
|
3057
|
-
selectedAvailability
|
|
3093
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3058
3094
|
selectedAvailability?.availabilityId,
|
|
3059
3095
|
totalQuantity,
|
|
3060
3096
|
product.companyId,
|
|
@@ -3097,7 +3133,7 @@ export function ChangeBookingFlow({
|
|
|
3097
3133
|
} = promoDiscountParamsRef.current;
|
|
3098
3134
|
if (!code || !sel) return;
|
|
3099
3135
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
3100
|
-
const optionId = sel
|
|
3136
|
+
const optionId = getAvailabilityOptionId(sel);
|
|
3101
3137
|
if (!companyId || !optionId) return;
|
|
3102
3138
|
const items = lines.map((l) => ({ category: l.category, qty: l.qty }));
|
|
3103
3139
|
if (items.length === 0) return;
|
|
@@ -3240,7 +3276,7 @@ export function ChangeBookingFlow({
|
|
|
3240
3276
|
const initialOpt =
|
|
3241
3277
|
changeFlowResolvedInitialProductOptionId ??
|
|
3242
3278
|
(initialValues.productOptionId?.trim() || null);
|
|
3243
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
3279
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
3244
3280
|
const optionChanged = Boolean(
|
|
3245
3281
|
selectedOpt != null && initialOpt != null && initialOpt !== selectedOpt
|
|
3246
3282
|
);
|
|
@@ -3608,8 +3644,7 @@ export function ChangeBookingFlow({
|
|
|
3608
3644
|
return;
|
|
3609
3645
|
}
|
|
3610
3646
|
|
|
3611
|
-
const optionId =
|
|
3612
|
-
selectedAvailability.productOptionId?.trim() || activeOptions[0]?.optionId;
|
|
3647
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
3613
3648
|
if (!optionId) {
|
|
3614
3649
|
setLatestChangeQuote(null);
|
|
3615
3650
|
setChangeQuoteLoading(false);
|
|
@@ -3683,7 +3718,7 @@ export function ChangeBookingFlow({
|
|
|
3683
3718
|
hasChangeSelection,
|
|
3684
3719
|
selectedAvailability,
|
|
3685
3720
|
selectedAvailability?.dateTime,
|
|
3686
|
-
selectedAvailability
|
|
3721
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3687
3722
|
initialValues?.bookingReference,
|
|
3688
3723
|
lastName,
|
|
3689
3724
|
missingRequiredReturnSelection,
|
|
@@ -3742,7 +3777,7 @@ export function ChangeBookingFlow({
|
|
|
3742
3777
|
|
|
3743
3778
|
const mostPopularOption = activeOptions.find(opt => opt.mostPopular);
|
|
3744
3779
|
const candidate = mostPopularOption
|
|
3745
|
-
? timesForSelectedDate.find(avail => avail
|
|
3780
|
+
? timesForSelectedDate.find(avail => getAvailabilityOptionId(avail) === mostPopularOption.optionId && avail.vacancies > 0)
|
|
3746
3781
|
: null;
|
|
3747
3782
|
const fallback = timesForSelectedDate.find(avail => avail.vacancies > 0);
|
|
3748
3783
|
const toSelect = candidate ?? fallback;
|
|
@@ -3768,9 +3803,9 @@ export function ChangeBookingFlow({
|
|
|
3768
3803
|
useEffect(() => {
|
|
3769
3804
|
if (selectedAvailability && availabilities.length > 0) {
|
|
3770
3805
|
const updatedAvailability = availabilities.find(
|
|
3771
|
-
|
|
3806
|
+
avail =>
|
|
3772
3807
|
avail.dateTime === selectedAvailability.dateTime &&
|
|
3773
|
-
avail
|
|
3808
|
+
getAvailabilityOptionId(avail) === getAvailabilityOptionId(selectedAvailability)
|
|
3774
3809
|
);
|
|
3775
3810
|
if (updatedAvailability) {
|
|
3776
3811
|
setSelectedAvailability(updatedAvailability);
|
|
@@ -3801,7 +3836,7 @@ export function ChangeBookingFlow({
|
|
|
3801
3836
|
setImplicitReturnBaselineId(null);
|
|
3802
3837
|
}, [
|
|
3803
3838
|
selectedAvailability?.dateTime,
|
|
3804
|
-
selectedAvailability
|
|
3839
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3805
3840
|
initialValues?.returnAvailabilityId,
|
|
3806
3841
|
initialValues?.returnDateTime,
|
|
3807
3842
|
]);
|
|
@@ -3873,7 +3908,7 @@ export function ChangeBookingFlow({
|
|
|
3873
3908
|
]);
|
|
3874
3909
|
|
|
3875
3910
|
// Fetch add-ons when availability (product option) is selected; clear selections when option changes
|
|
3876
|
-
const availabilityProductOptionId = selectedAvailability
|
|
3911
|
+
const availabilityProductOptionId = selectedAvailability ? getAvailabilityOptionId(selectedAvailability) || null : null;
|
|
3877
3912
|
const prevAvailabilityProductOptionIdRef = useRef<string | null>(null);
|
|
3878
3913
|
useEffect(() => {
|
|
3879
3914
|
if (!availabilityProductOptionId || !product.companyId) {
|
|
@@ -3893,7 +3928,7 @@ export function ChangeBookingFlow({
|
|
|
3893
3928
|
if (date === selectedDate) return;
|
|
3894
3929
|
if (selectedAvailability) {
|
|
3895
3930
|
changeFlowOutboundAnchorRef.current = {
|
|
3896
|
-
productOptionId: selectedAvailability
|
|
3931
|
+
productOptionId: getAvailabilityOptionId(selectedAvailability) || null,
|
|
3897
3932
|
minutesFromMidnight: getMinutesFromMidnightInTimezone(
|
|
3898
3933
|
selectedAvailability.dateTime,
|
|
3899
3934
|
companyTimezone
|
|
@@ -4030,8 +4065,8 @@ export function ChangeBookingFlow({
|
|
|
4030
4065
|
[selectedAvailability]
|
|
4031
4066
|
);
|
|
4032
4067
|
const selectedAvailabilityKey = useMemo(
|
|
4033
|
-
() => `${selectedAvailability?.dateTime ?? ''}::${selectedAvailability
|
|
4034
|
-
[selectedAvailability
|
|
4068
|
+
() => `${selectedAvailability?.dateTime ?? ''}::${selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : ''}`,
|
|
4069
|
+
[selectedAvailability]
|
|
4035
4070
|
);
|
|
4036
4071
|
// Remember where promo was successfully applied to avoid self-clearing on same selection.
|
|
4037
4072
|
const promoAppliedSelectionKeyRef = useRef<string | null>(null);
|
|
@@ -4183,8 +4218,7 @@ export function ChangeBookingFlow({
|
|
|
4183
4218
|
.map(([category, count]) => ({ category, count }));
|
|
4184
4219
|
|
|
4185
4220
|
// Get the productOptionId from the selected availability (we tagged it when fetching)
|
|
4186
|
-
const availabilityProductOptionId = selectedAvailability
|
|
4187
|
-
|| activeOptions[0]?.optionId;
|
|
4221
|
+
const availabilityProductOptionId = getAvailabilityOptionId(selectedAvailability);
|
|
4188
4222
|
|
|
4189
4223
|
if (!availabilityProductOptionId) {
|
|
4190
4224
|
setError('No product option selected');
|