@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
|
@@ -130,7 +130,7 @@ function findMergedPrivateShuttleAvailability(
|
|
|
130
130
|
tz: string
|
|
131
131
|
): Availability | undefined {
|
|
132
132
|
if (!sel || !optionId || !selectedDateStr) return undefined;
|
|
133
|
-
const oidMatches = (a: Availability) => (a
|
|
133
|
+
const oidMatches = (a: Availability) => getPrivateShuttleAvailabilityOptionId(a) === optionId;
|
|
134
134
|
const availId = sel.availabilityId?.trim();
|
|
135
135
|
if (availId) {
|
|
136
136
|
const hit = merged.find((a) => a.availabilityId === availId && oidMatches(a));
|
|
@@ -146,6 +146,40 @@ function findMergedPrivateShuttleAvailability(
|
|
|
146
146
|
return onDay[0];
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function getPrivateShuttleAvailabilityOptionId(availability: Availability): string {
|
|
150
|
+
const productOptionId = availability.productOptionId?.trim();
|
|
151
|
+
if (productOptionId) return productOptionId;
|
|
152
|
+
const productId = availability.productId?.trim();
|
|
153
|
+
return productId?.startsWith('po_') ? productId : '';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getPrivateShuttleAvailabilityCacheKey(availability: Availability): string {
|
|
157
|
+
return `${availability.dateTime}-${getPrivateShuttleAvailabilityOptionId(availability)}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getResourceRate(availability: Availability | undefined | null) {
|
|
161
|
+
return availability?.rates?.find((r) => r.rateId === 'RESOURCE' || r.category === 'RESOURCE');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function applyResourceAdjustments(
|
|
165
|
+
basePrice: number,
|
|
166
|
+
availability: Availability | undefined | null,
|
|
167
|
+
currency: Currency
|
|
168
|
+
): number {
|
|
169
|
+
const resourceRate = getResourceRate(availability);
|
|
170
|
+
const adjustmentTotal =
|
|
171
|
+
resourceRate?.appliedAdjustments?.reduce(
|
|
172
|
+
(sum, adjustment) => sum + (adjustment.changeByCurrency?.[currency] ?? 0),
|
|
173
|
+
0
|
|
174
|
+
) ??
|
|
175
|
+
resourceRate?.applied_adjustments?.reduce(
|
|
176
|
+
(sum, adjustment) => sum + (adjustment.changeByCurrency?.[currency] ?? 0),
|
|
177
|
+
0
|
|
178
|
+
) ??
|
|
179
|
+
0;
|
|
180
|
+
return basePrice + adjustmentTotal;
|
|
181
|
+
}
|
|
182
|
+
|
|
149
183
|
export function PrivateShuttleBookingFlow({
|
|
150
184
|
product,
|
|
151
185
|
productId,
|
|
@@ -394,11 +428,9 @@ export function PrivateShuttleBookingFlow({
|
|
|
394
428
|
|
|
395
429
|
let mergedAvailabilities: Availability[] = [];
|
|
396
430
|
setAvailabilities((prev) => {
|
|
397
|
-
const existingMap = new Map(
|
|
398
|
-
prev.map((avail) => [`${avail.dateTime}-${avail.productId || avail.productOptionId}`, avail])
|
|
399
|
-
);
|
|
431
|
+
const existingMap = new Map(prev.map((avail) => [getPrivateShuttleAvailabilityCacheKey(avail), avail]));
|
|
400
432
|
result.availabilities.forEach((avail) => {
|
|
401
|
-
existingMap.set(
|
|
433
|
+
existingMap.set(getPrivateShuttleAvailabilityCacheKey(avail), avail);
|
|
402
434
|
});
|
|
403
435
|
mergedAvailabilities = Array.from(existingMap.values());
|
|
404
436
|
return mergedAvailabilities;
|
|
@@ -476,11 +508,9 @@ export function PrivateShuttleBookingFlow({
|
|
|
476
508
|
|
|
477
509
|
let mergedAvailabilities: Availability[] = [];
|
|
478
510
|
setAvailabilities((prev) => {
|
|
479
|
-
const existingMap = new Map(
|
|
480
|
-
prev.map((avail) => [`${avail.dateTime}-${avail.productId || avail.productOptionId}`, avail])
|
|
481
|
-
);
|
|
511
|
+
const existingMap = new Map(prev.map((avail) => [getPrivateShuttleAvailabilityCacheKey(avail), avail]));
|
|
482
512
|
result.availabilities.forEach((avail) => {
|
|
483
|
-
existingMap.set(
|
|
513
|
+
existingMap.set(getPrivateShuttleAvailabilityCacheKey(avail), avail);
|
|
484
514
|
});
|
|
485
515
|
mergedAvailabilities = Array.from(existingMap.values());
|
|
486
516
|
return mergedAvailabilities;
|
|
@@ -498,12 +528,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
498
528
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
499
529
|
const mergedAvailabilitiesMap = new Map(
|
|
500
530
|
(existingCache?.availabilities ?? []).map((availability) => [
|
|
501
|
-
|
|
531
|
+
getPrivateShuttleAvailabilityCacheKey(availability),
|
|
502
532
|
availability,
|
|
503
533
|
])
|
|
504
534
|
);
|
|
505
535
|
result.availabilities.forEach((availability) => {
|
|
506
|
-
mergedAvailabilitiesMap.set(
|
|
536
|
+
mergedAvailabilitiesMap.set(getPrivateShuttleAvailabilityCacheKey(availability), availability);
|
|
507
537
|
});
|
|
508
538
|
availabilitiesCache.merge(cacheKey, {
|
|
509
539
|
fetchedRanges: existingCache?.fetchedRanges ?? fetchedRangesRef.current,
|
|
@@ -687,13 +717,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
687
717
|
if (existing.length === 0) return result.availabilities;
|
|
688
718
|
const existingMap = new Map(
|
|
689
719
|
existing.map((avail) => [
|
|
690
|
-
|
|
720
|
+
getPrivateShuttleAvailabilityCacheKey(avail),
|
|
691
721
|
avail,
|
|
692
722
|
])
|
|
693
723
|
);
|
|
694
724
|
result.availabilities.forEach((avail) => {
|
|
695
|
-
|
|
696
|
-
existingMap.set(key, avail);
|
|
725
|
+
existingMap.set(getPrivateShuttleAvailabilityCacheKey(avail), avail);
|
|
697
726
|
});
|
|
698
727
|
return Array.from(existingMap.values());
|
|
699
728
|
})();
|
|
@@ -779,9 +808,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
779
808
|
const optionsAvailableForSelectedDate = useMemo(() => {
|
|
780
809
|
if (!selectedDate) return [];
|
|
781
810
|
const dateAvailabilities = availabilitiesByDate[selectedDate] || [];
|
|
782
|
-
const optionIds = new Set(
|
|
783
|
-
dateAvailabilities.map((a) => a.productId || a.productOptionId).filter(Boolean)
|
|
784
|
-
);
|
|
811
|
+
const optionIds = new Set(dateAvailabilities.map(getPrivateShuttleAvailabilityOptionId).filter(Boolean));
|
|
785
812
|
return activeOptions.filter((opt) => optionIds.has(opt.optionId));
|
|
786
813
|
}, [selectedDate, availabilitiesByDate, activeOptions]);
|
|
787
814
|
|
|
@@ -819,12 +846,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
819
846
|
setAvailabilities((prev) => {
|
|
820
847
|
const merged = new Map(
|
|
821
848
|
prev.map((availability) => [
|
|
822
|
-
|
|
849
|
+
getPrivateShuttleAvailabilityCacheKey(availability),
|
|
823
850
|
availability,
|
|
824
851
|
])
|
|
825
852
|
);
|
|
826
853
|
result.availabilities.forEach((availability) => {
|
|
827
|
-
merged.set(
|
|
854
|
+
merged.set(getPrivateShuttleAvailabilityCacheKey(availability), availability);
|
|
828
855
|
});
|
|
829
856
|
return Array.from(merged.values());
|
|
830
857
|
});
|
|
@@ -841,12 +868,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
841
868
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
842
869
|
const mergedAvailabilities = new Map(
|
|
843
870
|
(existingCache?.availabilities ?? []).map((availability) => [
|
|
844
|
-
|
|
871
|
+
getPrivateShuttleAvailabilityCacheKey(availability),
|
|
845
872
|
availability,
|
|
846
873
|
])
|
|
847
874
|
);
|
|
848
875
|
result.availabilities.forEach((availability) => {
|
|
849
|
-
mergedAvailabilities.set(
|
|
876
|
+
mergedAvailabilities.set(getPrivateShuttleAvailabilityCacheKey(availability), availability);
|
|
850
877
|
});
|
|
851
878
|
availabilitiesCache.merge(cacheKey, {
|
|
852
879
|
fetchedRanges: existingCache?.fetchedRanges ?? fetchedRangesRef.current,
|
|
@@ -898,22 +925,23 @@ export function PrivateShuttleBookingFlow({
|
|
|
898
925
|
|
|
899
926
|
const getOptionPrice = useCallback(
|
|
900
927
|
(optionId: string) => {
|
|
928
|
+
const opt = activeOptions.find((o) => o.optionId === optionId);
|
|
901
929
|
const dateAvailabilities = selectedDate ? availabilitiesByDate[selectedDate] || [] : [];
|
|
902
930
|
const avail = dateAvailabilities.find(
|
|
903
|
-
(a) => (a
|
|
931
|
+
(a) => getPrivateShuttleAvailabilityOptionId(a) === optionId
|
|
904
932
|
);
|
|
933
|
+
if (currency === 'CAD' && opt?.pricing?.['RESOURCE'] != null) {
|
|
934
|
+
return applyResourceAdjustments(opt.pricing['RESOURCE'], avail, currency);
|
|
935
|
+
}
|
|
905
936
|
if (avail?.rates) {
|
|
906
|
-
const resourceRate = avail
|
|
907
|
-
(r) => r.rateId === 'RESOURCE' || r.category === 'RESOURCE'
|
|
908
|
-
);
|
|
937
|
+
const resourceRate = getResourceRate(avail);
|
|
909
938
|
if (resourceRate?.priceByCurrency?.[currency] != null)
|
|
910
939
|
return resourceRate.priceByCurrency[currency];
|
|
911
940
|
if (currency === 'CAD' && resourceRate?.price != null) return resourceRate.price;
|
|
912
941
|
}
|
|
913
942
|
if (resourcePriceByOption?.[optionId]?.[currency] != null)
|
|
914
943
|
return resourcePriceByOption[optionId][currency];
|
|
915
|
-
if (resourcePriceByCurrency?.[currency] != null) return resourcePriceByCurrency[currency];
|
|
916
|
-
const opt = activeOptions.find((o) => o.optionId === optionId);
|
|
944
|
+
if (currency === 'CAD' && resourcePriceByCurrency?.[currency] != null) return resourcePriceByCurrency[currency];
|
|
917
945
|
return opt?.pricing?.['RESOURCE'] ?? 0;
|
|
918
946
|
},
|
|
919
947
|
[
|
|
@@ -927,10 +955,14 @@ export function PrivateShuttleBookingFlow({
|
|
|
927
955
|
);
|
|
928
956
|
|
|
929
957
|
const resourcePrice = useMemo(() => {
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
958
|
+
if (currency === 'CAD' && selectedOptionConfig?.pricing?.['RESOURCE'] != null)
|
|
959
|
+
return applyResourceAdjustments(
|
|
960
|
+
selectedOptionConfig.pricing['RESOURCE'],
|
|
961
|
+
selectedAvailability,
|
|
962
|
+
currency
|
|
933
963
|
);
|
|
964
|
+
if (selectedAvailability?.rates) {
|
|
965
|
+
const resourceRate = getResourceRate(selectedAvailability);
|
|
934
966
|
if (resourceRate) {
|
|
935
967
|
const fromRate =
|
|
936
968
|
resourceRate.priceByCurrency?.[currency] ??
|
|
@@ -940,10 +972,12 @@ export function PrivateShuttleBookingFlow({
|
|
|
940
972
|
}
|
|
941
973
|
if (selectedOption && resourcePriceByOption?.[selectedOption]?.[currency] != null)
|
|
942
974
|
return resourcePriceByOption[selectedOption][currency];
|
|
943
|
-
if (resourcePriceByCurrency?.[currency] != null) return resourcePriceByCurrency[currency];
|
|
975
|
+
if (currency === 'CAD' && resourcePriceByCurrency?.[currency] != null) return resourcePriceByCurrency[currency];
|
|
944
976
|
if (precomputedPrices?.['RESOURCE']?.[currency] != null)
|
|
945
977
|
return precomputedPrices['RESOURCE'][currency];
|
|
946
|
-
|
|
978
|
+
if (selectedOptionConfig?.pricing?.['RESOURCE'] != null)
|
|
979
|
+
return selectedOptionConfig.pricing['RESOURCE'];
|
|
980
|
+
return 0;
|
|
947
981
|
}, [
|
|
948
982
|
selectedAvailability,
|
|
949
983
|
resourcePriceByOption,
|
|
@@ -1173,7 +1207,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
1173
1207
|
setSelectedOption(opt.optionId);
|
|
1174
1208
|
const dateAvailabilities = availabilitiesByDate[selectedDate] || [];
|
|
1175
1209
|
const avail = dateAvailabilities.find(
|
|
1176
|
-
(a) => (a
|
|
1210
|
+
(a) => getPrivateShuttleAvailabilityOptionId(a) === opt.optionId
|
|
1177
1211
|
);
|
|
1178
1212
|
setSelectedAvailability(avail || null);
|
|
1179
1213
|
}
|
|
@@ -1231,7 +1265,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
1231
1265
|
if (selectedDate) {
|
|
1232
1266
|
const dateAvailabilities = availabilitiesByDate[selectedDate] || [];
|
|
1233
1267
|
const avail = dateAvailabilities.find(
|
|
1234
|
-
(a) => (a
|
|
1268
|
+
(a) => getPrivateShuttleAvailabilityOptionId(a) === optionId
|
|
1235
1269
|
);
|
|
1236
1270
|
setSelectedAvailability(avail || null);
|
|
1237
1271
|
} else {
|
|
@@ -61,10 +61,12 @@ export function buildAvailabilitiesCacheKey(
|
|
|
61
61
|
productId: string,
|
|
62
62
|
optionIdsKey: string,
|
|
63
63
|
promoCode: string | null,
|
|
64
|
-
pricingProfileId?: string | null
|
|
64
|
+
pricingProfileId?: string | null,
|
|
65
|
+
cancellationPolicyProfileId?: string | null,
|
|
65
66
|
): string {
|
|
66
67
|
const pp = (pricingProfileId ?? '').trim();
|
|
67
|
-
|
|
68
|
+
const cp = (cancellationPolicyProfileId ?? '').trim();
|
|
69
|
+
return `${productId}|${optionIdsKey}|${promoCode ?? ''}|pp:${pp}|cp:${cp}`;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export function AvailabilitiesCacheProvider({ children }: { children: ReactNode }) {
|
|
@@ -331,10 +331,14 @@ export function computePriceBreakdown(
|
|
|
331
331
|
|
|
332
332
|
// Subtotal in display currency = base + adjustments; pass so final price is (subtotal + fees) * (1+tax) and breakdown adds up
|
|
333
333
|
const subtotalInDisplayCurrency = baseAmount + adjustmentSum;
|
|
334
|
+
const backendPriceForDetails =
|
|
335
|
+
currency === BASE_CURRENCY && subtotalInDisplayCurrency > 0
|
|
336
|
+
? subtotalInDisplayCurrency
|
|
337
|
+
: backendPriceCAD;
|
|
334
338
|
const details = computeTicketPriceDetails(
|
|
335
339
|
pricingConfig,
|
|
336
340
|
currency,
|
|
337
|
-
|
|
341
|
+
backendPriceForDetails,
|
|
338
342
|
hasFees,
|
|
339
343
|
subtotalInDisplayCurrency > 0 ? subtotalInDisplayCurrency : baseInDisplayCurrency
|
|
340
344
|
);
|
package/src/lib/booking-api.ts
CHANGED
|
@@ -1623,6 +1623,7 @@ export interface GetAvailabilitiesOptions {
|
|
|
1623
1623
|
promoCode?: string | null;
|
|
1624
1624
|
allOptions?: boolean;
|
|
1625
1625
|
summary?: boolean;
|
|
1626
|
+
responseMode?: 'summary' | 'booking-day';
|
|
1626
1627
|
/**
|
|
1627
1628
|
* When set, TicketBooth should return rates/precomputed prices for this partner pricing profile.
|
|
1628
1629
|
* Must match the profile linked on the partner (e.g. capabilities.pricingProfileId).
|
|
@@ -1670,6 +1671,7 @@ export async function getAvailabilities(
|
|
|
1670
1671
|
if (options?.promoCode?.trim()) params.set('promoCode', options.promoCode.trim());
|
|
1671
1672
|
if (options?.allOptions === true) params.set('allOptions', 'true');
|
|
1672
1673
|
if (options?.summary === true) params.set('summary', 'true');
|
|
1674
|
+
if (options?.responseMode) params.set('responseMode', options.responseMode);
|
|
1673
1675
|
const pricingProfileId = options?.pricingProfileId?.trim();
|
|
1674
1676
|
if (pricingProfileId) params.set('pricingProfileId', pricingProfileId);
|
|
1675
1677
|
const cancellationPolicyProfileId = options?.cancellationPolicyProfileId?.trim();
|