@ticketboothapp/booking 1.2.63 → 1.2.65
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 +158 -106
- package/src/components/booking/BookingFlow.tsx +2 -3
- package/src/components/booking/ChangeBookingFlow.tsx +158 -103
- package/src/components/booking/ChangeBookingPricingDriftPanel.tsx +130 -5
- package/src/components/booking/booking-flow-types.ts +3 -2
- package/src/lib/booking/change-booking-pricing-drift.ts +157 -9
- package/src/lib/booking/change-flow-pricing.ts +5 -6
- package/src/lib/booking-api.ts +17 -0
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
getAvailabilities,
|
|
8
8
|
cancelReservation,
|
|
9
9
|
cancelReservationBestEffort,
|
|
10
|
-
createPaymentIntent,
|
|
11
10
|
quoteChangeBooking,
|
|
12
11
|
confirmFreeChangeBooking,
|
|
13
12
|
createChangeBookingPaymentIntent,
|
|
@@ -62,10 +61,11 @@ import {
|
|
|
62
61
|
} from '../../lib/booking/change-flow-pricing';
|
|
63
62
|
import {
|
|
64
63
|
mergePriceSummaryLinesForDrift,
|
|
64
|
+
mergeLineComparisonsWithFullDrift,
|
|
65
65
|
normalizePricingDriftDetailFromQuote,
|
|
66
66
|
normalizeTicketPricingTraceFromQuote,
|
|
67
67
|
sumPriceSummaryLinesMajorUnits,
|
|
68
|
-
|
|
68
|
+
computePricingDriftDelta,
|
|
69
69
|
} from '../../lib/booking/change-booking-pricing-drift';
|
|
70
70
|
import { ChangeBookingPricingDriftPanel } from './ChangeBookingPricingDriftPanel';
|
|
71
71
|
import {
|
|
@@ -99,8 +99,6 @@ import type { BookingFlowUiOptions } from './booking-flow-ui';
|
|
|
99
99
|
import type { ChangeBookingFlowProps, ChangeFlowSelectionPreview } from './booking-flow-types';
|
|
100
100
|
import { BOOKING_FLOW_ABANDON_EVENT } from '../../providers/booking-dialog-provider';
|
|
101
101
|
|
|
102
|
-
export type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
103
|
-
|
|
104
102
|
/**
|
|
105
103
|
* ## Pricing contract (customer self-serve)
|
|
106
104
|
*
|
|
@@ -109,7 +107,6 @@ export type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
|
109
107
|
* `serverPreview.priceSummaryLines` when the quote includes rows; otherwise falls back to FE-built lines after totals confirm.
|
|
110
108
|
*
|
|
111
109
|
* Until the first successful quote, `selfServeCheckoutPlaceholder` still avoids showing unchecked totals.
|
|
112
|
-
*
|
|
113
110
|
*/
|
|
114
111
|
function mergeQuoteSliceWithServerPreview(
|
|
115
112
|
slice: ReturnType<typeof sliceChangeQuoteForUi>,
|
|
@@ -631,8 +628,7 @@ function resolveInitialAvailabilityFromBooking(
|
|
|
631
628
|
}
|
|
632
629
|
|
|
633
630
|
/**
|
|
634
|
-
*
|
|
635
|
-
* Duplicated from {@link NewBookingFlow} intentionally so each flow can evolve independently.
|
|
631
|
+
* Embed fork (ticketbooth provider dashboard): same self-serve **change booking** as {@link ChangeBookingFlow}.
|
|
636
632
|
*/
|
|
637
633
|
export function AdminChangeBookingFlow({
|
|
638
634
|
product,
|
|
@@ -679,6 +675,7 @@ export function AdminChangeBookingFlow({
|
|
|
679
675
|
onShowManage,
|
|
680
676
|
getSuccessUrl,
|
|
681
677
|
suppressCalendarDateScroll,
|
|
678
|
+
mode: bookingAppMode,
|
|
682
679
|
} = useBookingApp();
|
|
683
680
|
const availabilitiesCache = useAvailabilitiesCache();
|
|
684
681
|
const [availabilities, setAvailabilities] = useState<Availability[]>([]);
|
|
@@ -799,6 +796,9 @@ export function AdminChangeBookingFlow({
|
|
|
799
796
|
}, [initialValues?.dateTime, companyTimezone]);
|
|
800
797
|
/** Do not render catalog-/FE-derived dollar amounts in UI until `quoteChangeBooking` returns `serverDisplay`. */
|
|
801
798
|
const suppressSelfServeCurrencyUi = true;
|
|
799
|
+
/** Self-serve change flow only (no provider/admin pricing paths). */
|
|
800
|
+
const isCustomerSelfServeChange = true;
|
|
801
|
+
const isAdmin = false;
|
|
802
802
|
|
|
803
803
|
useEffect(() => {
|
|
804
804
|
setPartnerAttributionConfirmed(false);
|
|
@@ -1790,27 +1790,27 @@ export function AdminChangeBookingFlow({
|
|
|
1790
1790
|
|
|
1791
1791
|
const initialAddOnMinQtyByKey = useMemo(() => {
|
|
1792
1792
|
const map = new Map<string, number>();
|
|
1793
|
-
if (
|
|
1793
|
+
if (!isCustomerSelfServeChange) return map;
|
|
1794
1794
|
for (const sel of initialAddOnBaselineSelections) {
|
|
1795
1795
|
const key = `${sel.addOnId.trim()}::${sel.variantId?.trim() || ''}`;
|
|
1796
1796
|
map.set(key, (map.get(key) ?? 0) + Math.max(1, Number(sel.quantity) || 1));
|
|
1797
1797
|
}
|
|
1798
1798
|
return map;
|
|
1799
|
-
}, [
|
|
1799
|
+
}, [isCustomerSelfServeChange, initialAddOnBaselineSelections]);
|
|
1800
1800
|
|
|
1801
1801
|
const initialAddOnMinTotalByAddOnId = useMemo(() => {
|
|
1802
1802
|
const map = new Map<string, number>();
|
|
1803
|
-
if (
|
|
1803
|
+
if (!isCustomerSelfServeChange) return map;
|
|
1804
1804
|
for (const sel of initialAddOnBaselineSelections) {
|
|
1805
1805
|
const addOnId = sel.addOnId.trim();
|
|
1806
1806
|
map.set(addOnId, (map.get(addOnId) ?? 0) + Math.max(1, Number(sel.quantity) || 1));
|
|
1807
1807
|
}
|
|
1808
1808
|
return map;
|
|
1809
|
-
}, [
|
|
1809
|
+
}, [isCustomerSelfServeChange, initialAddOnBaselineSelections]);
|
|
1810
1810
|
|
|
1811
1811
|
const applyChangeFlowAddOnFloor = useCallback(
|
|
1812
1812
|
(nextSelections: Array<{ addOnId: string; variantId?: string; quantity?: number }>) => {
|
|
1813
|
-
if (
|
|
1813
|
+
if (!isCustomerSelfServeChange || initialAddOnMinQtyByKey.size === 0) return nextSelections;
|
|
1814
1814
|
const qtyByKey = new Map<string, number>();
|
|
1815
1815
|
for (const sel of nextSelections) {
|
|
1816
1816
|
const key = `${sel.addOnId.trim()}::${sel.variantId?.trim() || ''}`;
|
|
@@ -1837,7 +1837,7 @@ export function AdminChangeBookingFlow({
|
|
|
1837
1837
|
return { addOnId, variantId, quantity: qty };
|
|
1838
1838
|
});
|
|
1839
1839
|
},
|
|
1840
|
-
[
|
|
1840
|
+
[isCustomerSelfServeChange, initialAddOnMinQtyByKey, initialAddOnMinTotalByAddOnId]
|
|
1841
1841
|
);
|
|
1842
1842
|
|
|
1843
1843
|
const updateAddOnSelections = useCallback(
|
|
@@ -2451,14 +2451,14 @@ export function AdminChangeBookingFlow({
|
|
|
2451
2451
|
const options = selectedAvailability?.returnOptions ?? [];
|
|
2452
2452
|
const serverReturnMap = latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId;
|
|
2453
2453
|
const floor = effectiveChangeFlowReturnUnitFloorPerPerson;
|
|
2454
|
-
const applyReturnFloor = floor != null &&
|
|
2454
|
+
const applyReturnFloor = floor != null && isCustomerSelfServeChange;
|
|
2455
2455
|
return options.map((opt) => {
|
|
2456
2456
|
const vacancyCredit = changeFlowSeatCreditForReturnAvailabilityId(opt.returnAvailabilityId);
|
|
2457
2457
|
const adjustedVacancies = Math.max(0, opt.vacancies ?? 0) + vacancyCredit;
|
|
2458
2458
|
const rawPerPerson = opt.priceAdjustmentByCurrency?.[currency] ?? 0;
|
|
2459
2459
|
const flooredPerPerson = applyReturnFloor ? Math.max(rawPerPerson, floor) : rawPerPerson;
|
|
2460
2460
|
let perPerson = flooredPerPerson;
|
|
2461
|
-
if (
|
|
2461
|
+
if (isCustomerSelfServeChange && serverReturnMap && opt.returnAvailabilityId) {
|
|
2462
2462
|
const sid = opt.returnAvailabilityId.trim();
|
|
2463
2463
|
const sp = serverReturnMap[sid];
|
|
2464
2464
|
if (sp != null && Number.isFinite(sp)) {
|
|
@@ -2481,7 +2481,7 @@ export function AdminChangeBookingFlow({
|
|
|
2481
2481
|
}, [
|
|
2482
2482
|
selectedAvailability?.returnOptions,
|
|
2483
2483
|
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2484
|
-
|
|
2484
|
+
isCustomerSelfServeChange,
|
|
2485
2485
|
currency,
|
|
2486
2486
|
changeFlowSeatCreditForReturnAvailabilityId,
|
|
2487
2487
|
latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId,
|
|
@@ -2490,11 +2490,11 @@ export function AdminChangeBookingFlow({
|
|
|
2490
2490
|
const selectedReturnOptionWithFloor = useMemo(() => {
|
|
2491
2491
|
if (!selectedReturnOption) return selectedReturnOption;
|
|
2492
2492
|
const floor = effectiveChangeFlowReturnUnitFloorPerPerson;
|
|
2493
|
-
const applyReturnFloor = floor != null &&
|
|
2493
|
+
const applyReturnFloor = floor != null && isCustomerSelfServeChange;
|
|
2494
2494
|
const rawPerPerson = selectedReturnOption.priceAdjustmentByCurrency?.[currency] ?? 0;
|
|
2495
2495
|
let perPerson = rawPerPerson;
|
|
2496
2496
|
const serverReturnMap = latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId;
|
|
2497
|
-
if (
|
|
2497
|
+
if (isCustomerSelfServeChange && serverReturnMap && selectedReturnOption.returnAvailabilityId) {
|
|
2498
2498
|
const sid = selectedReturnOption.returnAvailabilityId.trim();
|
|
2499
2499
|
const sp = serverReturnMap[sid];
|
|
2500
2500
|
if (sp != null && Number.isFinite(sp)) {
|
|
@@ -2515,7 +2515,7 @@ export function AdminChangeBookingFlow({
|
|
|
2515
2515
|
}, [
|
|
2516
2516
|
selectedReturnOption,
|
|
2517
2517
|
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2518
|
-
|
|
2518
|
+
isCustomerSelfServeChange,
|
|
2519
2519
|
currency,
|
|
2520
2520
|
latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId,
|
|
2521
2521
|
]);
|
|
@@ -2556,7 +2556,7 @@ export function AdminChangeBookingFlow({
|
|
|
2556
2556
|
/** Quote `ticketUnitPriceByCategory` overrides catalog units for ticket rows (customer self-serve only). */
|
|
2557
2557
|
const pricingForTicketSelector = useMemo(() => {
|
|
2558
2558
|
const m = latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory;
|
|
2559
|
-
if (
|
|
2559
|
+
if (!isCustomerSelfServeChange || !m) return pricing;
|
|
2560
2560
|
return pricing.map((r) => {
|
|
2561
2561
|
const u = m[r.category.toUpperCase()];
|
|
2562
2562
|
if (u == null || !Number.isFinite(u)) return r;
|
|
@@ -2567,7 +2567,7 @@ export function AdminChangeBookingFlow({
|
|
|
2567
2567
|
baseInDisplayCurrency: u,
|
|
2568
2568
|
};
|
|
2569
2569
|
});
|
|
2570
|
-
}, [pricing, latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory,
|
|
2570
|
+
}, [pricing, latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory, isCustomerSelfServeChange]);
|
|
2571
2571
|
|
|
2572
2572
|
// Price breakdown: mid-layer returns line items (base + one per rule/deal). UI renders each line; rate in brackets when used.
|
|
2573
2573
|
const getPriceBreakdown = useCallback((category: string, priceCAD: number, baseInDisplayCurrency: number | undefined, appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }> = []): PriceBreakdownData | null => {
|
|
@@ -2672,7 +2672,7 @@ export function AdminChangeBookingFlow({
|
|
|
2672
2672
|
|
|
2673
2673
|
// When return selection (or refreshed availabilities) caps the party below current ticket counts, trim tickets (non-admin).
|
|
2674
2674
|
useEffect(() => {
|
|
2675
|
-
if (
|
|
2675
|
+
if (isAdmin || !selectedAvailability) return;
|
|
2676
2676
|
const cap = effectivePartySizeCap;
|
|
2677
2677
|
if (totalQuantity <= cap) return;
|
|
2678
2678
|
const over = totalQuantity - cap;
|
|
@@ -2700,7 +2700,7 @@ export function AdminChangeBookingFlow({
|
|
|
2700
2700
|
effectivePartySizeCap,
|
|
2701
2701
|
totalQuantity,
|
|
2702
2702
|
selectedAvailability,
|
|
2703
|
-
|
|
2703
|
+
isAdmin,
|
|
2704
2704
|
changeBookingMinimumQuantities,
|
|
2705
2705
|
]);
|
|
2706
2706
|
|
|
@@ -2774,7 +2774,6 @@ export function AdminChangeBookingFlow({
|
|
|
2774
2774
|
*/
|
|
2775
2775
|
const changeFlowProtectedReturnAdjustment = useMemo(() => {
|
|
2776
2776
|
if (totalQuantity <= 0) return returnPriceAdjustment;
|
|
2777
|
-
if (false) return returnPriceAdjustment;
|
|
2778
2777
|
if (effectiveChangeFlowReturnUnitFloorPerPerson == null) return returnPriceAdjustment;
|
|
2779
2778
|
const livePerPerson =
|
|
2780
2779
|
returnOptionCatalogPerPerson ?? (selectedReturnOption?.priceAdjustmentByCurrency?.[currency] ?? 0);
|
|
@@ -2788,7 +2787,6 @@ export function AdminChangeBookingFlow({
|
|
|
2788
2787
|
}, [
|
|
2789
2788
|
totalQuantity,
|
|
2790
2789
|
returnPriceAdjustment,
|
|
2791
|
-
false,
|
|
2792
2790
|
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2793
2791
|
selectedReturnOption,
|
|
2794
2792
|
returnOptionCatalogPerPerson,
|
|
@@ -2799,7 +2797,7 @@ export function AdminChangeBookingFlow({
|
|
|
2799
2797
|
|
|
2800
2798
|
/** Return row amount for PriceSummary, Stripe breakdown, and CheckoutModal (catalog vs protected same-product-option). */
|
|
2801
2799
|
const checkoutReturnLineAmount = useMemo(() => {
|
|
2802
|
-
if (
|
|
2800
|
+
if (isCustomerSelfServeChange) {
|
|
2803
2801
|
return changeFlowProtectedReturnAdjustment;
|
|
2804
2802
|
}
|
|
2805
2803
|
if (changeFlowApplyReceiptPaidFloors) {
|
|
@@ -2807,7 +2805,7 @@ export function AdminChangeBookingFlow({
|
|
|
2807
2805
|
}
|
|
2808
2806
|
return returnPriceAdjustment;
|
|
2809
2807
|
}, [
|
|
2810
|
-
|
|
2808
|
+
isCustomerSelfServeChange,
|
|
2811
2809
|
changeFlowApplyReceiptPaidFloors,
|
|
2812
2810
|
changeFlowProtectedReturnAdjustment,
|
|
2813
2811
|
returnPriceAdjustment,
|
|
@@ -2879,13 +2877,12 @@ export function AdminChangeBookingFlow({
|
|
|
2879
2877
|
return [...feeLineItems, ...addOnLines];
|
|
2880
2878
|
}, [feeLineItems, addOnSelections, addOns]);
|
|
2881
2879
|
|
|
2880
|
+
|
|
2882
2881
|
const checkoutPriceSummaryLines = useMemo((): PriceSummaryLine[] => {
|
|
2883
2882
|
if (!selectedAvailability) return [];
|
|
2884
2883
|
const returnLineAmount = checkoutReturnLineAmount;
|
|
2885
|
-
/** Show row when a return is selected and either the priced amount is non-zero or a receipt return floor exists (catalog slot can be $0 while floored total is not). */
|
|
2886
2884
|
const showReturnLine =
|
|
2887
|
-
Boolean(selectedReturnOption) &&
|
|
2888
|
-
(Math.abs(returnLineAmount) > 0.0005 || effectiveChangeFlowReturnUnitFloorPerPerson != null);
|
|
2885
|
+
Boolean(selectedReturnOption) && Math.abs(returnLineAmount) > 0.0005;
|
|
2889
2886
|
return [
|
|
2890
2887
|
...ticketLineItemsForChangeFlowDisplay.map((line): PriceSummaryLine => {
|
|
2891
2888
|
const rate = pricing.find((r) => r.category === line.category);
|
|
@@ -2897,8 +2894,6 @@ export function AdminChangeBookingFlow({
|
|
|
2897
2894
|
);
|
|
2898
2895
|
return {
|
|
2899
2896
|
kind: 'ticket',
|
|
2900
|
-
lineKey: undefined,
|
|
2901
|
-
editable: false,
|
|
2902
2897
|
category: line.category,
|
|
2903
2898
|
qty: line.qty,
|
|
2904
2899
|
itemTotal: line.itemTotal,
|
|
@@ -2933,8 +2928,6 @@ export function AdminChangeBookingFlow({
|
|
|
2933
2928
|
fee.name.toLowerCase().includes('license'));
|
|
2934
2929
|
return {
|
|
2935
2930
|
kind: 'line' as const,
|
|
2936
|
-
lineKey: undefined,
|
|
2937
|
-
editable: false,
|
|
2938
2931
|
label: feeLineItems.some((f) => f.name === fee.name)
|
|
2939
2932
|
? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
|
|
2940
2933
|
: fee.name,
|
|
@@ -2960,7 +2953,6 @@ export function AdminChangeBookingFlow({
|
|
|
2960
2953
|
effectiveCancellationPolicyLabel,
|
|
2961
2954
|
feeLineItemsWithAddOns,
|
|
2962
2955
|
feeLineItems,
|
|
2963
|
-
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2964
2956
|
]);
|
|
2965
2957
|
|
|
2966
2958
|
const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
|
|
@@ -3131,7 +3123,45 @@ export function AdminChangeBookingFlow({
|
|
|
3131
3123
|
|
|
3132
3124
|
const clientMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.clientLineItems);
|
|
3133
3125
|
const useBeClientLines = clientMappedFromApi.length > 0;
|
|
3134
|
-
|
|
3126
|
+
/**
|
|
3127
|
+
* Checkout passes tax/discount via PriceSummary props, not always as `priceSummaryLines`. Include them here so
|
|
3128
|
+
* drift rows and their sums align with `changeFlowNewBookingTotal` (subtotal + tax − promo).
|
|
3129
|
+
*/
|
|
3130
|
+
const clientLinesForMerge: PriceSummaryLine[] = (() => {
|
|
3131
|
+
if (useBeClientLines) return clientMappedFromApi;
|
|
3132
|
+
const lines: PriceSummaryLine[] = [...checkoutPriceSummaryLines];
|
|
3133
|
+
const hasTaxLine = lines.some(
|
|
3134
|
+
(l) => l.kind === 'line' && String(l.type ?? '').toUpperCase() === 'TAX',
|
|
3135
|
+
);
|
|
3136
|
+
if (!hasTaxLine && !isTaxIncludedInPrice && Math.abs(effectiveTax) >= 0.005) {
|
|
3137
|
+
lines.push({
|
|
3138
|
+
kind: 'line',
|
|
3139
|
+
label: t('booking.tax') !== 'booking.tax' ? t('booking.tax') : 'Taxes and fees',
|
|
3140
|
+
amount: effectiveTax,
|
|
3141
|
+
type: 'TAX',
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
const hasPromoSummaryLine = lines.some(
|
|
3145
|
+
(l) =>
|
|
3146
|
+
l.kind === 'line' &&
|
|
3147
|
+
(/PROMO|DISCOUNT|VOUCHER|GIFT/i.test(String(l.type ?? '')) ||
|
|
3148
|
+
(l.amount < -0.005 && /promo|discount/i.test(l.label))),
|
|
3149
|
+
);
|
|
3150
|
+
if (!hasPromoSummaryLine && Math.abs(effectivePromoDiscountAmount) >= 0.005) {
|
|
3151
|
+
const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
|
|
3152
|
+
const promoLabel =
|
|
3153
|
+
trimmedPromoCode.length > 0
|
|
3154
|
+
? `Promo: ${trimmedPromoCode}`
|
|
3155
|
+
: originalReceipt?.promoLabel?.trim() || 'Discount';
|
|
3156
|
+
lines.push({
|
|
3157
|
+
kind: 'line',
|
|
3158
|
+
label: promoLabel,
|
|
3159
|
+
amount: -effectivePromoDiscountAmount,
|
|
3160
|
+
type: 'PROMO_CODE',
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
return lines;
|
|
3164
|
+
})();
|
|
3135
3165
|
|
|
3136
3166
|
const serverMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.serverLineItems);
|
|
3137
3167
|
const useBeServerLines = serverMappedFromApi.length > 0;
|
|
@@ -3175,14 +3205,35 @@ export function AdminChangeBookingFlow({
|
|
|
3175
3205
|
}
|
|
3176
3206
|
|
|
3177
3207
|
const mergedForDrift = mergePriceSummaryLinesForDrift(clientLinesForMerge, serverLinesForMerge);
|
|
3178
|
-
|
|
3208
|
+
const rows =
|
|
3179
3209
|
api?.lineComparisons && api.lineComparisons.length > 0
|
|
3180
|
-
?
|
|
3210
|
+
? mergeLineComparisonsWithFullDrift(api.lineComparisons, mergedForDrift)
|
|
3181
3211
|
: mergedForDrift;
|
|
3182
3212
|
|
|
3213
|
+
const receiptAnchoring = api?.receiptAnchoring;
|
|
3214
|
+
let driftRows = rows;
|
|
3215
|
+
if (receiptAnchoring) {
|
|
3216
|
+
const adj = roundMoney(
|
|
3217
|
+
receiptAnchoring.reconciledTotalMajorUnits -
|
|
3218
|
+
receiptAnchoring.requestedCatalogLineSumMajorUnits,
|
|
3219
|
+
);
|
|
3220
|
+
if (Math.abs(adj) >= 0.005) {
|
|
3221
|
+
driftRows = [
|
|
3222
|
+
...rows,
|
|
3223
|
+
{
|
|
3224
|
+
key: 'meta:receipt-anchoring',
|
|
3225
|
+
label: 'Receipt anchoring adjustment (BE only — not included in app total)',
|
|
3226
|
+
clientAmount: null,
|
|
3227
|
+
serverAmount: adj,
|
|
3228
|
+
delta: computePricingDriftDelta(null, adj),
|
|
3229
|
+
},
|
|
3230
|
+
];
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3183
3234
|
const hasTotalDelta = totalDelta != null && Math.abs(totalDelta) >= 0.005;
|
|
3184
3235
|
|
|
3185
|
-
if (
|
|
3236
|
+
if (driftRows.length === 0 && !hasTotalDelta) {
|
|
3186
3237
|
return null;
|
|
3187
3238
|
}
|
|
3188
3239
|
|
|
@@ -3211,7 +3262,7 @@ export function AdminChangeBookingFlow({
|
|
|
3211
3262
|
|
|
3212
3263
|
return (
|
|
3213
3264
|
<ChangeBookingPricingDriftPanel
|
|
3214
|
-
rows={
|
|
3265
|
+
rows={driftRows}
|
|
3215
3266
|
clientTotal={clientTotalForDrift}
|
|
3216
3267
|
serverTotal={serverTotalFromQuote}
|
|
3217
3268
|
totalDelta={totalDelta}
|
|
@@ -3219,6 +3270,7 @@ export function AdminChangeBookingFlow({
|
|
|
3219
3270
|
locale={locale}
|
|
3220
3271
|
ticketCartDetail={ticketCartDetail.length > 0 ? ticketCartDetail : undefined}
|
|
3221
3272
|
serverTicketPricingTrace={latestChangeQuote.ticketPricingTrace ?? undefined}
|
|
3273
|
+
receiptAnchoring={receiptAnchoring}
|
|
3222
3274
|
footnote={
|
|
3223
3275
|
usesBeLinePayload
|
|
3224
3276
|
? 'Lines use pricingDriftDetail.clientLineItems / serverLineItems when the quote includes them; totals prefer explicit major-unit fields, then sums of those lines, then the live cart / receipt preview.'
|
|
@@ -3237,6 +3289,12 @@ export function AdminChangeBookingFlow({
|
|
|
3237
3289
|
locale,
|
|
3238
3290
|
ticketLineItemsForChangeFlowDisplay,
|
|
3239
3291
|
pricingForTicketSelector,
|
|
3292
|
+
isTaxIncludedInPrice,
|
|
3293
|
+
effectiveTax,
|
|
3294
|
+
effectivePromoDiscountAmount,
|
|
3295
|
+
appliedPromoCode,
|
|
3296
|
+
originalReceipt?.promoLabel,
|
|
3297
|
+
t,
|
|
3240
3298
|
]);
|
|
3241
3299
|
|
|
3242
3300
|
/** Replaces PriceSummary with non-numeric status until quote returns authoritative totals (no FE dollar amounts). */
|
|
@@ -3398,8 +3456,7 @@ export function AdminChangeBookingFlow({
|
|
|
3398
3456
|
countsChanged ||
|
|
3399
3457
|
addOnsChanged ||
|
|
3400
3458
|
returnChanged,
|
|
3401
|
-
// Authoritative for "real user change" gating
|
|
3402
|
-
// ignore option-id noise and only consider user-visible booking deltas.
|
|
3459
|
+
// Authoritative for "real user change" gating: ignore option-id noise; only user-visible deltas.
|
|
3403
3460
|
hasOperationalChangesFromInitial:
|
|
3404
3461
|
dateChanged ||
|
|
3405
3462
|
pickupChanged ||
|
|
@@ -3429,28 +3486,25 @@ export function AdminChangeBookingFlow({
|
|
|
3429
3486
|
const hasChangeSelection = changeSelectionDetails.hasChangesFromInitial;
|
|
3430
3487
|
|
|
3431
3488
|
const changeFlowNeedsServerPrice =
|
|
3432
|
-
true &&
|
|
3433
3489
|
hasChangeSelection &&
|
|
3434
3490
|
!!initialValues?.bookingReference?.trim() &&
|
|
3435
3491
|
!!lastName.trim();
|
|
3436
3492
|
|
|
3437
|
-
const isChangeQuoteBlocked =
|
|
3438
|
-
const requiresReturnInChangeFlow =
|
|
3493
|
+
const isChangeQuoteBlocked = latestChangeQuote?.canProceed === false;
|
|
3494
|
+
const requiresReturnInChangeFlow = !!initialValues?.returnAvailabilityId?.trim();
|
|
3439
3495
|
const missingRequiredReturnSelection = requiresReturnInChangeFlow && !selectedReturnOption;
|
|
3440
3496
|
|
|
3441
3497
|
const changeFlowSubmitDisabled =
|
|
3442
3498
|
missingRequiredReturnSelection ||
|
|
3443
|
-
(
|
|
3444
|
-
changeFlowNeedsServerPrice &&
|
|
3499
|
+
(changeFlowNeedsServerPrice &&
|
|
3445
3500
|
(changeQuoteLoading || (!latestChangeQuote && !changeQuoteFetchError)));
|
|
3446
3501
|
|
|
3447
|
-
const providerTotalsPreview = null;
|
|
3448
3502
|
const hasEffectiveChangeSelection = hasChangeSelection;
|
|
3449
3503
|
|
|
3450
3504
|
const displayedChangeAmounts = resolveChangeFlowDisplayedAmounts({
|
|
3451
|
-
providerPreview:
|
|
3505
|
+
providerPreview: null,
|
|
3452
3506
|
serverQuotePreview:
|
|
3453
|
-
|
|
3507
|
+
latestChangeQuote?.serverDisplay
|
|
3454
3508
|
? latestChangeQuote.serverDisplay
|
|
3455
3509
|
: null,
|
|
3456
3510
|
fromCart: {
|
|
@@ -3466,7 +3520,7 @@ export function AdminChangeBookingFlow({
|
|
|
3466
3520
|
const changeFlowClientEstimateDue = (() => {
|
|
3467
3521
|
if (!originalReceipt) return totalPrice;
|
|
3468
3522
|
// Customer self-serve: amount due comes from POST .../change/quote (`amountDueCents` / priceDiff), not FE delta math.
|
|
3469
|
-
if (
|
|
3523
|
+
if (latestChangeQuote != null && !changeQuoteFetchError) {
|
|
3470
3524
|
return normalizeNearZeroOwed(latestChangeQuote.priceDiff);
|
|
3471
3525
|
}
|
|
3472
3526
|
return changeFlowBalanceVsOriginal({
|
|
@@ -3514,8 +3568,8 @@ export function AdminChangeBookingFlow({
|
|
|
3514
3568
|
const checkoutFormError =
|
|
3515
3569
|
(error || '') ||
|
|
3516
3570
|
(missingRequiredReturnSelection ? 'Removing return option in self-serve is not available. Please contact support.' : '') ||
|
|
3517
|
-
(
|
|
3518
|
-
(
|
|
3571
|
+
(isChangeQuoteBlocked ? (latestChangeQuote?.reasonIfBlocked ?? '') : '') ||
|
|
3572
|
+
(changeQuoteFetchError ?? '');
|
|
3519
3573
|
|
|
3520
3574
|
const changeFlowSelectionPreview = useMemo((): ChangeFlowSelectionPreview | null => {
|
|
3521
3575
|
if (!selectedAvailability || totalQuantity <= 0) return null;
|
|
@@ -3621,7 +3675,7 @@ export function AdminChangeBookingFlow({
|
|
|
3621
3675
|
|
|
3622
3676
|
/** Debounced server quote so CTA + “amount owed” match PaymentIntent; avoids free confirm when FE estimate ≠ BE. */
|
|
3623
3677
|
useEffect(() => {
|
|
3624
|
-
if (
|
|
3678
|
+
if (!isCustomerSelfServeChange) {
|
|
3625
3679
|
setChangeQuoteLoading(false);
|
|
3626
3680
|
setChangeQuoteFetchError(null);
|
|
3627
3681
|
setLatestChangeQuote(null);
|
|
@@ -3712,7 +3766,7 @@ export function AdminChangeBookingFlow({
|
|
|
3712
3766
|
window.clearTimeout(timer);
|
|
3713
3767
|
};
|
|
3714
3768
|
}, [
|
|
3715
|
-
|
|
3769
|
+
isCustomerSelfServeChange,
|
|
3716
3770
|
hasChangeSelection,
|
|
3717
3771
|
selectedAvailability,
|
|
3718
3772
|
selectedAvailability?.dateTime,
|
|
@@ -3900,7 +3954,7 @@ export function AdminChangeBookingFlow({
|
|
|
3900
3954
|
selectedAvailability,
|
|
3901
3955
|
selectedReturnOption,
|
|
3902
3956
|
companyTimezone,
|
|
3903
|
-
|
|
3957
|
+
isAdmin,
|
|
3904
3958
|
initialValues?.returnAvailabilityId,
|
|
3905
3959
|
initialValues?.returnDateTime,
|
|
3906
3960
|
]);
|
|
@@ -3963,9 +4017,9 @@ export function AdminChangeBookingFlow({
|
|
|
3963
4017
|
const firstWithInventory = dates.find((d) => {
|
|
3964
4018
|
const rows = availabilitiesByDate[d] ?? [];
|
|
3965
4019
|
if (rows.length === 0) return false;
|
|
3966
|
-
if (
|
|
4020
|
+
if (isAdmin) return rows.some((a) => (a.vacancies ?? 0) > 0);
|
|
3967
4021
|
if (
|
|
3968
|
-
|
|
4022
|
+
isCustomerSelfServeChange &&
|
|
3969
4023
|
changeFlowInitialTicketCount > 0
|
|
3970
4024
|
) {
|
|
3971
4025
|
return rows.some(
|
|
@@ -3974,7 +4028,7 @@ export function AdminChangeBookingFlow({
|
|
|
3974
4028
|
}
|
|
3975
4029
|
return rows.some((a) => (a.vacancies ?? 0) > 0);
|
|
3976
4030
|
});
|
|
3977
|
-
const first = firstWithInventory ?? (
|
|
4031
|
+
const first = firstWithInventory ?? (isAdmin && dates[0] ? dates[0] : undefined);
|
|
3978
4032
|
if (!first) return;
|
|
3979
4033
|
|
|
3980
4034
|
hasAutoSelectedPartnerDateRef.current = true;
|
|
@@ -3997,8 +4051,8 @@ export function AdminChangeBookingFlow({
|
|
|
3997
4051
|
selectedDate,
|
|
3998
4052
|
dates,
|
|
3999
4053
|
availabilitiesByDate,
|
|
4000
|
-
|
|
4001
|
-
|
|
4054
|
+
isAdmin,
|
|
4055
|
+
isCustomerSelfServeChange,
|
|
4002
4056
|
changeFlowInitialTicketCount,
|
|
4003
4057
|
getCalendarEffectiveOutboundVacancies,
|
|
4004
4058
|
useWindowScroll,
|
|
@@ -4036,7 +4090,7 @@ export function AdminChangeBookingFlow({
|
|
|
4036
4090
|
};
|
|
4037
4091
|
|
|
4038
4092
|
const handleQuantityChange = (category: string, delta: number) => {
|
|
4039
|
-
const maxAvailable =
|
|
4093
|
+
const maxAvailable = isAdmin ? Number.MAX_SAFE_INTEGER : effectivePartySizeCap;
|
|
4040
4094
|
const currentQty = quantities[category] || 0;
|
|
4041
4095
|
const minQ =
|
|
4042
4096
|
changeBookingMinimumQuantities != null
|
|
@@ -4044,7 +4098,7 @@ export function AdminChangeBookingFlow({
|
|
|
4044
4098
|
: 0;
|
|
4045
4099
|
const newQty = Math.max(minQ, currentQty + delta);
|
|
4046
4100
|
// Admin can overbook; non-admin cannot exceed vacancies
|
|
4047
|
-
if (delta > 0 && !
|
|
4101
|
+
if (delta > 0 && !isAdmin && orderSummary.totalQuantity >= maxAvailable) {
|
|
4048
4102
|
return;
|
|
4049
4103
|
}
|
|
4050
4104
|
setQuantities(prev => ({
|
|
@@ -4177,28 +4231,28 @@ export function AdminChangeBookingFlow({
|
|
|
4177
4231
|
return;
|
|
4178
4232
|
}
|
|
4179
4233
|
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4234
|
+
// Validate email (required)
|
|
4235
|
+
if (!email) {
|
|
4236
|
+
setError(t('booking.enterEmail') || 'Please enter your email address');
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4185
4239
|
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4240
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4241
|
+
setError(t('booking.invalidEmail') || 'Please enter a valid email address');
|
|
4242
|
+
return;
|
|
4243
|
+
}
|
|
4190
4244
|
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4245
|
+
// Validate first name (required)
|
|
4246
|
+
if (!firstName?.trim()) {
|
|
4247
|
+
setError(t('booking.enterFirstName') || 'Please enter your first name');
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4196
4250
|
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4251
|
+
// Validate last name (required for manage booking lookup)
|
|
4252
|
+
if (!lastName?.trim()) {
|
|
4253
|
+
setError(t('booking.enterLastName') || 'Please enter your last name');
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4202
4256
|
|
|
4203
4257
|
// Allow checkout if pickup location is selected OR if user chose "I don't know"
|
|
4204
4258
|
if (product.pickupLocations && product.pickupLocations.length > 0 && !pickupLocationId && !pickupLocationSkipped) {
|
|
@@ -4225,24 +4279,23 @@ export function AdminChangeBookingFlow({
|
|
|
4225
4279
|
return;
|
|
4226
4280
|
}
|
|
4227
4281
|
|
|
4228
|
-
|
|
4229
4282
|
const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
|
|
4230
4283
|
clientChannelSource: inferClientBookingSourceFromProductIds(
|
|
4231
4284
|
product.productId,
|
|
4232
4285
|
availabilityProductOptionId,
|
|
4233
4286
|
),
|
|
4234
4287
|
forcePartnerPortalChannel: partnerPortalBooking,
|
|
4235
|
-
forceDashboardSource:
|
|
4288
|
+
forceDashboardSource: bookingAppMode === 'provider-dashboard',
|
|
4236
4289
|
});
|
|
4237
4290
|
|
|
4238
4291
|
// Get the hotel name if a pickup location was selected
|
|
4239
4292
|
const selectedPickupLocation = pickupLocationId
|
|
4240
4293
|
? product.pickupLocations?.find(loc => loc.id === pickupLocationId)
|
|
4241
4294
|
: null;
|
|
4295
|
+
let quotedPriceDiff: number | null = null;
|
|
4242
4296
|
let changeIntentIdForCheckout: string | undefined;
|
|
4243
4297
|
let changeBookingReferenceForPaidFlow: string | undefined;
|
|
4244
4298
|
|
|
4245
|
-
{
|
|
4246
4299
|
const changeBookingReference = initialValues?.bookingReference?.trim();
|
|
4247
4300
|
const changeLastName = lastName.trim();
|
|
4248
4301
|
if (!changeBookingReference || !changeLastName) {
|
|
@@ -4270,6 +4323,7 @@ export function AdminChangeBookingFlow({
|
|
|
4270
4323
|
},
|
|
4271
4324
|
currency
|
|
4272
4325
|
);
|
|
4326
|
+
quotedPriceDiff = quoteSlice.priceDiff;
|
|
4273
4327
|
changeBookingReferenceForPaidFlow = changeBookingReference;
|
|
4274
4328
|
changeIntentIdForCheckout = quoteSlice.changeIntentId ?? undefined;
|
|
4275
4329
|
setLatestChangeQuote(
|
|
@@ -4329,13 +4383,12 @@ export function AdminChangeBookingFlow({
|
|
|
4329
4383
|
if (!changeIntentIdForCheckout) {
|
|
4330
4384
|
throw new Error('Missing change intent for payment.');
|
|
4331
4385
|
}
|
|
4332
|
-
}
|
|
4333
4386
|
|
|
4334
4387
|
pendingReservationRef.current = null;
|
|
4335
4388
|
|
|
4336
4389
|
// Note: Do NOT call onSuccess here for paid bookings — we're about to show the Stripe
|
|
4337
4390
|
// CheckoutModal. onSuccess (e.g. closing the parent dialog) should only run when we're
|
|
4338
|
-
// actually done (free booking redirect
|
|
4391
|
+
// actually done (free booking redirect). Calling it here
|
|
4339
4392
|
// would close the dialog before the payment modal opens.
|
|
4340
4393
|
|
|
4341
4394
|
// Update stored booking data (no holds reservation — change flow keys off booking reference elsewhere).
|
|
@@ -4362,10 +4415,10 @@ export function AdminChangeBookingFlow({
|
|
|
4362
4415
|
// Backend will charge totalAmount and store this as the receipt so /manage matches.
|
|
4363
4416
|
const taxForBreakdown = effectivePromoDiscountAmount > 0 ? effectiveTax : tax;
|
|
4364
4417
|
const amountDueForCheckout = changeFlowBalanceVsOriginal({
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4418
|
+
newTotal: changeFlowNewBookingTotal,
|
|
4419
|
+
originalReceiptTotal: originalReceipt?.total ?? 0,
|
|
4420
|
+
audience: 'customer',
|
|
4421
|
+
});
|
|
4369
4422
|
const lines = [
|
|
4370
4423
|
...ticketLineItemsForChangeFlowDisplay.map((line) => ({
|
|
4371
4424
|
label: line.category,
|
|
@@ -4452,7 +4505,6 @@ export function AdminChangeBookingFlow({
|
|
|
4452
4505
|
})()
|
|
4453
4506
|
);
|
|
4454
4507
|
|
|
4455
|
-
|
|
4456
4508
|
const ticketLinesForModal: CheckoutModalLineItem[] = ticketLineItemsForChangeFlowDisplay.map((line) => {
|
|
4457
4509
|
const rate = pricing.find((r) => r.category === line.category);
|
|
4458
4510
|
const breakdown = getPriceBreakdown(
|
|
@@ -4473,7 +4525,7 @@ export function AdminChangeBookingFlow({
|
|
|
4473
4525
|
// Paid change: always return to stable ref+lastName + explicit intent (not reservationRef).
|
|
4474
4526
|
// /manage-booking runs bounded refresh only when `from=change_payment` (see manage-booking page).
|
|
4475
4527
|
successUrlOverride:
|
|
4476
|
-
|
|
4528
|
+
changeBookingReferenceForPaidFlow
|
|
4477
4529
|
? (() => {
|
|
4478
4530
|
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
4479
4531
|
const ref = encodeURIComponent(
|
|
@@ -4501,7 +4553,7 @@ export function AdminChangeBookingFlow({
|
|
|
4501
4553
|
promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
|
|
4502
4554
|
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
|
|
4503
4555
|
changeTotals:
|
|
4504
|
-
|
|
4556
|
+
originalReceipt
|
|
4505
4557
|
? {
|
|
4506
4558
|
previousTotal: originalReceipt.total,
|
|
4507
4559
|
newTotal: displayChangeFlowProposedTotal,
|
|
@@ -4570,8 +4622,6 @@ export function AdminChangeBookingFlow({
|
|
|
4570
4622
|
}
|
|
4571
4623
|
};
|
|
4572
4624
|
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
4625
|
if (activeOptions.length === 0) {
|
|
4576
4626
|
return (
|
|
4577
4627
|
<div className="flex items-center justify-center py-16">
|
|
@@ -4655,12 +4705,12 @@ export function AdminChangeBookingFlow({
|
|
|
4655
4705
|
syncVisibleWeekToSelectedDate={true}
|
|
4656
4706
|
selectableSoldOutDate={changeFlowOriginalDate}
|
|
4657
4707
|
partySizeRequiredForCalendarSelection={
|
|
4658
|
-
|
|
4708
|
+
isCustomerSelfServeChange && !isAdmin && changeFlowInitialTicketCount > 0
|
|
4659
4709
|
? changeFlowInitialTicketCount
|
|
4660
4710
|
: undefined
|
|
4661
4711
|
}
|
|
4662
4712
|
getEffectiveVacancies={
|
|
4663
|
-
|
|
4713
|
+
isCustomerSelfServeChange && !isAdmin && changeFlowInitialTicketCount > 0
|
|
4664
4714
|
? getCalendarEffectiveOutboundVacancies
|
|
4665
4715
|
: undefined
|
|
4666
4716
|
}
|
|
@@ -4684,7 +4734,7 @@ export function AdminChangeBookingFlow({
|
|
|
4684
4734
|
earliestDate={earliestAvailabilityDate}
|
|
4685
4735
|
onVisibleRangeChange={handleVisibleRangeChange}
|
|
4686
4736
|
currency={currency}
|
|
4687
|
-
showCapacity={
|
|
4737
|
+
showCapacity={isAdmin}
|
|
4688
4738
|
extraDiscountPercent={calendarDiscountPercent}
|
|
4689
4739
|
capDiscountBadgesToBookingDate={changeFlowOriginalDate}
|
|
4690
4740
|
/>
|
|
@@ -4740,7 +4790,7 @@ export function AdminChangeBookingFlow({
|
|
|
4740
4790
|
selectedTicketCount={totalQuantity}
|
|
4741
4791
|
optionsMap={optionsMap}
|
|
4742
4792
|
hasAnyMostPopular={hasAnyMostPopular}
|
|
4743
|
-
isAdmin={
|
|
4793
|
+
isAdmin={isAdmin}
|
|
4744
4794
|
pickupLocationSkipped={pickupLocationSkipped}
|
|
4745
4795
|
t={t}
|
|
4746
4796
|
onTimeSelect={handleTimeSelect}
|
|
@@ -4756,7 +4806,7 @@ export function AdminChangeBookingFlow({
|
|
|
4756
4806
|
companyTimezone={companyTimezone}
|
|
4757
4807
|
currency={currency}
|
|
4758
4808
|
locale={locale}
|
|
4759
|
-
isAdmin={
|
|
4809
|
+
isAdmin={isAdmin}
|
|
4760
4810
|
t={t}
|
|
4761
4811
|
onReturnSelect={(option) => {
|
|
4762
4812
|
const raw = selectedAvailability.returnOptions?.find(
|
|
@@ -4784,7 +4834,7 @@ export function AdminChangeBookingFlow({
|
|
|
4784
4834
|
resourceCount={selectedReturnOption ? null : (selectedAvailability.resourceCount ?? null)}
|
|
4785
4835
|
currency={currency}
|
|
4786
4836
|
locale={locale}
|
|
4787
|
-
isAdmin={
|
|
4837
|
+
isAdmin={isAdmin}
|
|
4788
4838
|
isSimplifiedPricingView={isSimplifiedPricingView}
|
|
4789
4839
|
t={t}
|
|
4790
4840
|
onQuantityChange={handleQuantityChange}
|
|
@@ -4806,7 +4856,7 @@ export function AdminChangeBookingFlow({
|
|
|
4806
4856
|
currency={currency}
|
|
4807
4857
|
locale={locale}
|
|
4808
4858
|
onSelectionsChange={updateAddOnSelections}
|
|
4809
|
-
minimumTotalByAddOnId={initialAddOnMinTotalByAddOnId}
|
|
4859
|
+
minimumTotalByAddOnId={isCustomerSelfServeChange ? initialAddOnMinTotalByAddOnId : undefined}
|
|
4810
4860
|
suppressPrices={false}
|
|
4811
4861
|
/>
|
|
4812
4862
|
)}
|
|
@@ -4836,7 +4886,7 @@ export function AdminChangeBookingFlow({
|
|
|
4836
4886
|
currency={currency}
|
|
4837
4887
|
locale={locale}
|
|
4838
4888
|
t={t}
|
|
4839
|
-
extraBetweenTaxAndTotal={
|
|
4889
|
+
extraBetweenTaxAndTotal={<></>}
|
|
4840
4890
|
extraBeforeSubtotal={undefined}
|
|
4841
4891
|
firstName={firstName}
|
|
4842
4892
|
lastName={lastName}
|
|
@@ -4844,7 +4894,7 @@ export function AdminChangeBookingFlow({
|
|
|
4844
4894
|
onFirstNameChange={(v) => { setFirstName(v); setError(''); }}
|
|
4845
4895
|
onLastNameChange={(v) => { setLastName(v); setError(''); }}
|
|
4846
4896
|
onEmailChange={(v) => { setEmail(v); setError(''); }}
|
|
4847
|
-
readOnlyContactFields
|
|
4897
|
+
readOnlyContactFields
|
|
4848
4898
|
pickupLocations={
|
|
4849
4899
|
selectedDate && product.pickupLocations && product.pickupLocations.length > 0
|
|
4850
4900
|
? product.pickupLocations
|
|
@@ -4890,7 +4940,9 @@ export function AdminChangeBookingFlow({
|
|
|
4890
4940
|
onCheckout={handleCheckout}
|
|
4891
4941
|
submitLabel={changeCheckoutButtonLabel ?? deferredInvoiceSubmitLabel}
|
|
4892
4942
|
hideSubmitButton={
|
|
4893
|
-
showCheckoutModal ||
|
|
4943
|
+
showCheckoutModal ||
|
|
4944
|
+
!hasEffectiveChangeSelection ||
|
|
4945
|
+
isChangeQuoteBlocked
|
|
4894
4946
|
}
|
|
4895
4947
|
submitDisabled={changeFlowSubmitDisabled}
|
|
4896
4948
|
attributionSummary={flowUi?.partnerAttributionSummary}
|