@ticketboothapp/booking 1.2.64 → 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.
|
@@ -7,11 +7,9 @@ import {
|
|
|
7
7
|
getAvailabilities,
|
|
8
8
|
cancelReservation,
|
|
9
9
|
cancelReservationBestEffort,
|
|
10
|
-
createPaymentIntent,
|
|
11
10
|
quoteChangeBooking,
|
|
12
11
|
confirmFreeChangeBooking,
|
|
13
12
|
createChangeBookingPaymentIntent,
|
|
14
|
-
confirmBookingWithoutPayment,
|
|
15
13
|
getAddOns,
|
|
16
14
|
validatePromoCode,
|
|
17
15
|
getPromoDiscount,
|
|
@@ -33,7 +31,6 @@ import {
|
|
|
33
31
|
} from '../../lib/booking-constants';
|
|
34
32
|
import { getSundayOfWeek } from '../../lib/booking/sunday-week';
|
|
35
33
|
import { Calendar } from './Calendar';
|
|
36
|
-
import { AdminPaymentChoiceModal } from './AdminPaymentChoiceModal';
|
|
37
34
|
import { ItineraryBox } from './ItineraryBox';
|
|
38
35
|
import { ItineraryPlaceholder } from './ItineraryPlaceholder';
|
|
39
36
|
import { PickupTimeSelector } from './PickupTimeSelector';
|
|
@@ -99,7 +96,6 @@ import { getItineraryStepLabel } from '../../lib/booking/itinerary-display';
|
|
|
99
96
|
import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '../../lib/manage-booking-post-checkout';
|
|
100
97
|
import { useBookingHost } from '../../runtime';
|
|
101
98
|
import type { BookingFlowUiOptions } from './booking-flow-ui';
|
|
102
|
-
import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
|
|
103
99
|
import type { ChangeBookingFlowProps, ChangeFlowSelectionPreview } from './booking-flow-types';
|
|
104
100
|
import { BOOKING_FLOW_ABANDON_EVENT } from '../../providers/booking-dialog-provider';
|
|
105
101
|
|
|
@@ -113,8 +109,6 @@ export type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
|
113
109
|
* `serverPreview.priceSummaryLines` when the quote includes rows; otherwise falls back to FE-built lines after totals confirm.
|
|
114
110
|
*
|
|
115
111
|
* Until the first successful quote, `selfServeCheckoutPlaceholder` still avoids showing unchecked totals.
|
|
116
|
-
*
|
|
117
|
-
* **Provider dashboard** (`onChangeBooking`) — unchanged; manual pricing via `flowUi.providerDashboardChangePricingUi`.
|
|
118
112
|
*/
|
|
119
113
|
function mergeQuoteSliceWithServerPreview(
|
|
120
114
|
slice: ReturnType<typeof sliceChangeQuoteForUi>,
|
|
@@ -411,9 +405,8 @@ function pickOutboundMatchingPreviousSelection(
|
|
|
411
405
|
anchor: { productOptionId: string | null; minutesFromMidnight: number },
|
|
412
406
|
companyTimezone: string,
|
|
413
407
|
optionsMap: Map<string, { mostPopular?: boolean }>,
|
|
414
|
-
isAdmin: boolean
|
|
415
408
|
): Availability | null {
|
|
416
|
-
const selectable = timesForSelectedDate.filter((a) => (a.vacancies ?? 0) > 0
|
|
409
|
+
const selectable = timesForSelectedDate.filter((a) => (a.vacancies ?? 0) > 0);
|
|
417
410
|
if (selectable.length === 0) return null;
|
|
418
411
|
|
|
419
412
|
const withOption =
|
|
@@ -447,9 +440,8 @@ function pickReturnMatchingPreviousSelection(
|
|
|
447
440
|
sortedReturnOptions: ReturnOption[],
|
|
448
441
|
anchor: { returnLocation: string; minutesFromMidnight: number },
|
|
449
442
|
companyTimezone: string,
|
|
450
|
-
isAdmin: boolean
|
|
451
443
|
): ReturnOption | null {
|
|
452
|
-
const eligible = sortedReturnOptions.filter((o) => (o.vacancies ?? 0) > 0
|
|
444
|
+
const eligible = sortedReturnOptions.filter((o) => (o.vacancies ?? 0) > 0);
|
|
453
445
|
if (eligible.length === 0) return null;
|
|
454
446
|
|
|
455
447
|
const sameLoc = eligible.filter((o) => o.returnLocation === anchor.returnLocation);
|
|
@@ -638,7 +630,7 @@ function resolveInitialAvailabilityFromBooking(
|
|
|
638
630
|
}
|
|
639
631
|
|
|
640
632
|
/**
|
|
641
|
-
* Customer
|
|
633
|
+
* Customer **change booking** self-serve flow (`quoteChangeBooking`, Stripe).
|
|
642
634
|
* Duplicated from {@link NewBookingFlow} intentionally so each flow can evolve independently.
|
|
643
635
|
*/
|
|
644
636
|
export function ChangeBookingFlow({
|
|
@@ -663,7 +655,6 @@ export function ChangeBookingFlow({
|
|
|
663
655
|
partnerPortalBooking = false,
|
|
664
656
|
availabilityPricingProfileId,
|
|
665
657
|
availabilityCancellationPolicyProfileId,
|
|
666
|
-
onChangeBooking,
|
|
667
658
|
}: ChangeBookingFlowProps) {
|
|
668
659
|
/** Always the booking’s sold currency — not the site currency switcher / parent default. */
|
|
669
660
|
const currency = useMemo((): Currency => {
|
|
@@ -675,16 +666,6 @@ export function ChangeBookingFlow({
|
|
|
675
666
|
return DEFAULT_CURRENCY;
|
|
676
667
|
}, [originalReceipt?.currency, initialValues?.currency, currencyFromParent]);
|
|
677
668
|
|
|
678
|
-
const isManualOverrideEligibleLine = (line: { editable: boolean; type?: string; label?: string }): boolean => {
|
|
679
|
-
if (!line.editable) return false;
|
|
680
|
-
const type = (line.type ?? '').toUpperCase();
|
|
681
|
-
const label = (line.label ?? '').toLowerCase();
|
|
682
|
-
const isPromoLikeType =
|
|
683
|
-
type.includes('PROMO') || type.includes('DISCOUNT') || type.includes('VOUCHER') || type.includes('GIFT');
|
|
684
|
-
const isPromoLikeLabel =
|
|
685
|
-
label.includes('promo') || label.includes('discount') || label.includes('voucher') || label.includes('gift');
|
|
686
|
-
return !(isPromoLikeType || isPromoLikeLabel);
|
|
687
|
-
};
|
|
688
669
|
const { env, analytics } = useBookingHost();
|
|
689
670
|
const { t } = useTranslations();
|
|
690
671
|
const { locale } = useLocale();
|
|
@@ -693,7 +674,6 @@ export function ChangeBookingFlow({
|
|
|
693
674
|
const cancellationPolicyProfileIdForAvailabilities =
|
|
694
675
|
(availabilityCancellationPolicyProfileId ?? '').trim() || null;
|
|
695
676
|
const {
|
|
696
|
-
permissions,
|
|
697
677
|
isSimplifiedPricingView,
|
|
698
678
|
onShowManage,
|
|
699
679
|
getSuccessUrl,
|
|
@@ -701,7 +681,6 @@ export function ChangeBookingFlow({
|
|
|
701
681
|
mode: bookingAppMode,
|
|
702
682
|
} = useBookingApp();
|
|
703
683
|
const availabilitiesCache = useAvailabilitiesCache();
|
|
704
|
-
const isAdmin = permissions.viewerRole === 'admin';
|
|
705
684
|
const [availabilities, setAvailabilities] = useState<Availability[]>([]);
|
|
706
685
|
const [selectedAvailability, setSelectedAvailability] = useState<Availability | null>(null);
|
|
707
686
|
const [selectedReturnOption, setSelectedReturnOption] = useState<ReturnOption | null>(null);
|
|
@@ -800,12 +779,6 @@ export function ChangeBookingFlow({
|
|
|
800
779
|
differenceTotal: number;
|
|
801
780
|
};
|
|
802
781
|
} | null>(null);
|
|
803
|
-
/** Admin only: skip sending confirmation at creation (provider dashboard). */
|
|
804
|
-
const [skipConfirmationCommunications, setSkipConfirmationCommunications] = useState(false);
|
|
805
|
-
/** Admin only: disable all auto communications for this booking (provider dashboard). */
|
|
806
|
-
const [disableAutoCommunications, setDisableAutoCommunications] = useState(false);
|
|
807
|
-
/** Admin only: show choice to pay now or confirm without payment (full balance owed). */
|
|
808
|
-
const [showAdminPaymentChoice, setShowAdminPaymentChoice] = useState(false);
|
|
809
782
|
const hasAppliedInitialValuesRef = useRef(false);
|
|
810
783
|
const hasAppliedInitialQuantitiesRef = useRef(false);
|
|
811
784
|
const hasHydratedAddOnsFromReceiptRef = useRef(false);
|
|
@@ -824,10 +797,11 @@ export function ChangeBookingFlow({
|
|
|
824
797
|
return null;
|
|
825
798
|
}
|
|
826
799
|
}, [initialValues?.dateTime, companyTimezone]);
|
|
827
|
-
const isProviderDashboardChange = Boolean(onChangeBooking);
|
|
828
|
-
const isCustomerSelfServeChange = !isProviderDashboardChange;
|
|
829
800
|
/** Do not render catalog-/FE-derived dollar amounts in UI until `quoteChangeBooking` returns `serverDisplay`. */
|
|
830
|
-
const suppressSelfServeCurrencyUi =
|
|
801
|
+
const suppressSelfServeCurrencyUi = true;
|
|
802
|
+
/** Self-serve change flow only (no provider/admin pricing paths). */
|
|
803
|
+
const isCustomerSelfServeChange = true;
|
|
804
|
+
const isAdmin = false;
|
|
831
805
|
|
|
832
806
|
useEffect(() => {
|
|
833
807
|
setPartnerAttributionConfirmed(false);
|
|
@@ -837,13 +811,13 @@ export function ChangeBookingFlow({
|
|
|
837
811
|
* user picks a different return time — baseline is the first auto-selected return for this outbound.
|
|
838
812
|
*/
|
|
839
813
|
const [implicitReturnBaselineId, setImplicitReturnBaselineId] = useState<string | null>(null);
|
|
840
|
-
/**
|
|
814
|
+
/** Promo from booking is fixed — show read-only, never add new. */
|
|
841
815
|
const lockedPromoCode = initialValues?.promoCode?.trim()
|
|
842
816
|
? initialValues.promoCode.trim().toUpperCase()
|
|
843
817
|
: null;
|
|
844
818
|
/** Public self-serve only: cannot reduce tickets below original counts. */
|
|
845
819
|
const changeBookingMinimumQuantities = useMemo(() => {
|
|
846
|
-
if (!
|
|
820
|
+
if (!initialValues?.bookingItems?.length) return undefined;
|
|
847
821
|
const m: Record<string, number> = {};
|
|
848
822
|
for (const item of initialValues.bookingItems) {
|
|
849
823
|
const key = item.category?.trim();
|
|
@@ -851,30 +825,7 @@ export function ChangeBookingFlow({
|
|
|
851
825
|
m[key] = Math.max(0, Number(item.count) || 0);
|
|
852
826
|
}
|
|
853
827
|
return m;
|
|
854
|
-
}, [
|
|
855
|
-
const [adminChoiceData, setAdminChoiceData] = useState<{
|
|
856
|
-
reservationReference: string;
|
|
857
|
-
reservationExpiration?: string;
|
|
858
|
-
checkoutBreakdown: { lineItems: Array<{ label: string; amount: number; type?: string; quantity?: number }>; totalAmount: number; currency: string };
|
|
859
|
-
totalAmount: number;
|
|
860
|
-
datePart: string;
|
|
861
|
-
timePart: string;
|
|
862
|
-
availabilityProductOptionId: string;
|
|
863
|
-
itineraryDisplay?: ItineraryDisplayStep[] | null;
|
|
864
|
-
clientSecret: string;
|
|
865
|
-
ticketLinesForModal: CheckoutModalLineItem[];
|
|
866
|
-
feeLineItems: OrderSummary['feeLineItems'];
|
|
867
|
-
returnPriceAdjustment: number;
|
|
868
|
-
cancellationPolicyFee: number;
|
|
869
|
-
cancellationPolicyLabel?: string;
|
|
870
|
-
subtotal: number;
|
|
871
|
-
tax: number;
|
|
872
|
-
totalQuantity: number;
|
|
873
|
-
isTaxIncludedInPrice: boolean;
|
|
874
|
-
taxRate: number;
|
|
875
|
-
promoDiscountAmount: number;
|
|
876
|
-
discountLabel?: string | null;
|
|
877
|
-
} | null>(null);
|
|
828
|
+
}, [initialValues?.bookingItems]);
|
|
878
829
|
const [latestChangeQuote, setLatestChangeQuote] = useState<{
|
|
879
830
|
priceDiff: number;
|
|
880
831
|
currency: Currency;
|
|
@@ -1711,14 +1662,12 @@ export function ChangeBookingFlow({
|
|
|
1711
1662
|
}, [initialValues?.productId, product.productId]);
|
|
1712
1663
|
|
|
1713
1664
|
/**
|
|
1714
|
-
* Receipt pricing on protected seats/fees
|
|
1715
|
-
* + same product option) vs Rule B (`max(receipt, live)` when date or option changes).
|
|
1665
|
+
* Receipt pricing on protected seats/fees: Rule A (exact receipt unit when same calendar day
|
|
1666
|
+
* + same product option) vs Rule B (`max(receipt, live)` when date or option changes).
|
|
1716
1667
|
*/
|
|
1717
1668
|
const changeFlowApplyReceiptPaidFloors = useMemo(
|
|
1718
|
-
() =>
|
|
1719
|
-
|
|
1720
|
-
changeFlowBookingParentProductIdForFloors === product.productId.trim(),
|
|
1721
|
-
[isProviderDashboardChange, changeFlowBookingParentProductIdForFloors, product.productId],
|
|
1669
|
+
() => changeFlowBookingParentProductIdForFloors === product.productId.trim(),
|
|
1670
|
+
[changeFlowBookingParentProductIdForFloors, product.productId],
|
|
1722
1671
|
);
|
|
1723
1672
|
|
|
1724
1673
|
useEffect(() => {
|
|
@@ -2828,7 +2777,6 @@ export function ChangeBookingFlow({
|
|
|
2828
2777
|
*/
|
|
2829
2778
|
const changeFlowProtectedReturnAdjustment = useMemo(() => {
|
|
2830
2779
|
if (totalQuantity <= 0) return returnPriceAdjustment;
|
|
2831
|
-
if (isProviderDashboardChange) return returnPriceAdjustment;
|
|
2832
2780
|
if (effectiveChangeFlowReturnUnitFloorPerPerson == null) return returnPriceAdjustment;
|
|
2833
2781
|
const livePerPerson =
|
|
2834
2782
|
returnOptionCatalogPerPerson ?? (selectedReturnOption?.priceAdjustmentByCurrency?.[currency] ?? 0);
|
|
@@ -2842,7 +2790,6 @@ export function ChangeBookingFlow({
|
|
|
2842
2790
|
}, [
|
|
2843
2791
|
totalQuantity,
|
|
2844
2792
|
returnPriceAdjustment,
|
|
2845
|
-
isProviderDashboardChange,
|
|
2846
2793
|
effectiveChangeFlowReturnUnitFloorPerPerson,
|
|
2847
2794
|
selectedReturnOption,
|
|
2848
2795
|
returnOptionCatalogPerPerson,
|
|
@@ -2933,29 +2880,6 @@ export function ChangeBookingFlow({
|
|
|
2933
2880
|
return [...feeLineItems, ...addOnLines];
|
|
2934
2881
|
}, [feeLineItems, addOnSelections, addOns]);
|
|
2935
2882
|
|
|
2936
|
-
const providerPricingUi = flowUi?.providerDashboardChangePricingUi;
|
|
2937
|
-
const providerQuotedLines = providerPricingUi?.quotedLines ?? [];
|
|
2938
|
-
const providerEditableLines = providerQuotedLines.filter((line) => isManualOverrideEligibleLine(line));
|
|
2939
|
-
const showProviderPricingInlineEditor =
|
|
2940
|
-
isProviderDashboardChange && isAdmin && (
|
|
2941
|
-
providerPricingUi?.loading ||
|
|
2942
|
-
providerPricingUi?.error != null ||
|
|
2943
|
-
providerQuotedLines.length > 0
|
|
2944
|
-
);
|
|
2945
|
-
const normalizeReceiptLabel = (value: string): string =>
|
|
2946
|
-
value
|
|
2947
|
-
.toLowerCase()
|
|
2948
|
-
.replace(/\([^)]*\)/g, '')
|
|
2949
|
-
.replace(/[^a-z0-9]+/g, '')
|
|
2950
|
-
.trim();
|
|
2951
|
-
const providerEditableLineByNormalizedLabel = useMemo(() => {
|
|
2952
|
-
const m = new Map<string, (typeof providerEditableLines)[number]>();
|
|
2953
|
-
for (const line of providerEditableLines) {
|
|
2954
|
-
const key = normalizeReceiptLabel(line.label ?? '');
|
|
2955
|
-
if (key) m.set(key, line);
|
|
2956
|
-
}
|
|
2957
|
-
return m;
|
|
2958
|
-
}, [providerEditableLines]);
|
|
2959
2883
|
|
|
2960
2884
|
const checkoutPriceSummaryLines = useMemo((): PriceSummaryLine[] => {
|
|
2961
2885
|
if (!selectedAvailability) return [];
|
|
@@ -2973,13 +2897,6 @@ export function ChangeBookingFlow({
|
|
|
2973
2897
|
);
|
|
2974
2898
|
return {
|
|
2975
2899
|
kind: 'ticket',
|
|
2976
|
-
lineKey:
|
|
2977
|
-
showProviderPricingInlineEditor
|
|
2978
|
-
? providerEditableLineByNormalizedLabel.get(normalizeReceiptLabel(line.category))?.lineKey
|
|
2979
|
-
: undefined,
|
|
2980
|
-
editable:
|
|
2981
|
-
showProviderPricingInlineEditor &&
|
|
2982
|
-
providerEditableLineByNormalizedLabel.has(normalizeReceiptLabel(line.category)),
|
|
2983
2900
|
category: line.category,
|
|
2984
2901
|
qty: line.qty,
|
|
2985
2902
|
itemTotal: line.itemTotal,
|
|
@@ -3014,25 +2931,6 @@ export function ChangeBookingFlow({
|
|
|
3014
2931
|
fee.name.toLowerCase().includes('license'));
|
|
3015
2932
|
return {
|
|
3016
2933
|
kind: 'line' as const,
|
|
3017
|
-
lineKey:
|
|
3018
|
-
showProviderPricingInlineEditor
|
|
3019
|
-
? providerEditableLineByNormalizedLabel.get(
|
|
3020
|
-
normalizeReceiptLabel(
|
|
3021
|
-
feeLineItems.some((f) => f.name === fee.name)
|
|
3022
|
-
? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
|
|
3023
|
-
: fee.name
|
|
3024
|
-
)
|
|
3025
|
-
)?.lineKey
|
|
3026
|
-
: undefined,
|
|
3027
|
-
editable:
|
|
3028
|
-
showProviderPricingInlineEditor &&
|
|
3029
|
-
providerEditableLineByNormalizedLabel.has(
|
|
3030
|
-
normalizeReceiptLabel(
|
|
3031
|
-
feeLineItems.some((f) => f.name === fee.name)
|
|
3032
|
-
? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
|
|
3033
|
-
: fee.name
|
|
3034
|
-
)
|
|
3035
|
-
),
|
|
3036
2934
|
label: feeLineItems.some((f) => f.name === fee.name)
|
|
3037
2935
|
? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
|
|
3038
2936
|
: fee.name,
|
|
@@ -3058,8 +2956,6 @@ export function ChangeBookingFlow({
|
|
|
3058
2956
|
effectiveCancellationPolicyLabel,
|
|
3059
2957
|
feeLineItemsWithAddOns,
|
|
3060
2958
|
feeLineItems,
|
|
3061
|
-
showProviderPricingInlineEditor,
|
|
3062
|
-
providerEditableLineByNormalizedLabel,
|
|
3063
2959
|
]);
|
|
3064
2960
|
|
|
3065
2961
|
const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
|
|
@@ -3563,8 +3459,7 @@ export function ChangeBookingFlow({
|
|
|
3563
3459
|
countsChanged ||
|
|
3564
3460
|
addOnsChanged ||
|
|
3565
3461
|
returnChanged,
|
|
3566
|
-
// Authoritative for "real user change" gating
|
|
3567
|
-
// ignore option-id noise and only consider user-visible booking deltas.
|
|
3462
|
+
// Authoritative for "real user change" gating: ignore option-id noise; only user-visible deltas.
|
|
3568
3463
|
hasOperationalChangesFromInitial:
|
|
3569
3464
|
dateChanged ||
|
|
3570
3465
|
pickupChanged ||
|
|
@@ -3591,62 +3486,28 @@ export function ChangeBookingFlow({
|
|
|
3591
3486
|
addOnSelections,
|
|
3592
3487
|
initialAddOnMinQtyByKey,
|
|
3593
3488
|
]);
|
|
3594
|
-
const hasChangeSelection =
|
|
3595
|
-
isProviderDashboardChange
|
|
3596
|
-
? changeSelectionDetails.hasOperationalChangesFromInitial
|
|
3597
|
-
: changeSelectionDetails.hasChangesFromInitial;
|
|
3489
|
+
const hasChangeSelection = changeSelectionDetails.hasChangesFromInitial;
|
|
3598
3490
|
|
|
3599
3491
|
const changeFlowNeedsServerPrice =
|
|
3600
|
-
isCustomerSelfServeChange &&
|
|
3601
3492
|
hasChangeSelection &&
|
|
3602
3493
|
!!initialValues?.bookingReference?.trim() &&
|
|
3603
3494
|
!!lastName.trim();
|
|
3604
3495
|
|
|
3605
|
-
const isChangeQuoteBlocked =
|
|
3606
|
-
const requiresReturnInChangeFlow =
|
|
3496
|
+
const isChangeQuoteBlocked = latestChangeQuote?.canProceed === false;
|
|
3497
|
+
const requiresReturnInChangeFlow = !!initialValues?.returnAvailabilityId?.trim();
|
|
3607
3498
|
const missingRequiredReturnSelection = requiresReturnInChangeFlow && !selectedReturnOption;
|
|
3608
3499
|
|
|
3609
3500
|
const changeFlowSubmitDisabled =
|
|
3610
3501
|
missingRequiredReturnSelection ||
|
|
3611
|
-
(
|
|
3612
|
-
changeFlowNeedsServerPrice &&
|
|
3502
|
+
(changeFlowNeedsServerPrice &&
|
|
3613
3503
|
(changeQuoteLoading || (!latestChangeQuote && !changeQuoteFetchError)));
|
|
3614
3504
|
|
|
3615
|
-
const
|
|
3616
|
-
showProviderPricingInlineEditor && providerPricingUi?.totalsPreview
|
|
3617
|
-
? providerPricingUi.totalsPreview
|
|
3618
|
-
: null;
|
|
3619
|
-
const providerHasEditedLineOverrides =
|
|
3620
|
-
isProviderDashboardChange &&
|
|
3621
|
-
providerQuotedLines.some((line) => {
|
|
3622
|
-
if (!isManualOverrideEligibleLine(line)) return false;
|
|
3623
|
-
const raw = providerPricingUi?.lineAmountInputs?.[line.lineKey];
|
|
3624
|
-
const parsed = raw == null || raw.trim() === '' ? line.amount : Number(raw);
|
|
3625
|
-
if (!Number.isFinite(parsed)) return false;
|
|
3626
|
-
const rounded = Math.round(parsed * 100) / 100;
|
|
3627
|
-
return Math.abs(rounded - line.amount) > 0.0001;
|
|
3628
|
-
});
|
|
3629
|
-
const providerHasAdditionalAdjustments =
|
|
3630
|
-
isProviderDashboardChange &&
|
|
3631
|
-
(providerPricingUi?.additionalAdjustments ?? []).some((adj) => {
|
|
3632
|
-
const parsed = Number((adj.amountInput ?? '').trim());
|
|
3633
|
-
const hasAmount = Number.isFinite(parsed) && parsed > 0;
|
|
3634
|
-
const currentAmount = hasAmount ? (Math.round(parsed * 100) / 100).toFixed(2) : '';
|
|
3635
|
-
const originalAmount = adj.originalAmountInput ?? '';
|
|
3636
|
-
const currentLabel = (adj.label ?? '').trim();
|
|
3637
|
-
const originalLabel = (adj.originalLabel ?? '').trim();
|
|
3638
|
-
const currentMode = adj.mode;
|
|
3639
|
-
const originalMode = adj.originalMode;
|
|
3640
|
-
if (!originalMode) return hasAmount || currentLabel.length > 0;
|
|
3641
|
-
return currentAmount !== originalAmount || currentLabel !== originalLabel || currentMode !== originalMode;
|
|
3642
|
-
});
|
|
3643
|
-
const hasEffectiveChangeSelection =
|
|
3644
|
-
hasChangeSelection || providerHasEditedLineOverrides || providerHasAdditionalAdjustments;
|
|
3505
|
+
const hasEffectiveChangeSelection = hasChangeSelection;
|
|
3645
3506
|
|
|
3646
3507
|
const displayedChangeAmounts = resolveChangeFlowDisplayedAmounts({
|
|
3647
|
-
providerPreview:
|
|
3508
|
+
providerPreview: null,
|
|
3648
3509
|
serverQuotePreview:
|
|
3649
|
-
|
|
3510
|
+
latestChangeQuote?.serverDisplay
|
|
3650
3511
|
? latestChangeQuote.serverDisplay
|
|
3651
3512
|
: null,
|
|
3652
3513
|
fromCart: {
|
|
@@ -3662,13 +3523,13 @@ export function ChangeBookingFlow({
|
|
|
3662
3523
|
const changeFlowClientEstimateDue = (() => {
|
|
3663
3524
|
if (!originalReceipt) return totalPrice;
|
|
3664
3525
|
// Customer self-serve: amount due comes from POST .../change/quote (`amountDueCents` / priceDiff), not FE delta math.
|
|
3665
|
-
if (
|
|
3526
|
+
if (latestChangeQuote != null && !changeQuoteFetchError) {
|
|
3666
3527
|
return normalizeNearZeroOwed(latestChangeQuote.priceDiff);
|
|
3667
3528
|
}
|
|
3668
3529
|
return changeFlowBalanceVsOriginal({
|
|
3669
3530
|
newTotal: displayChangeFlowProposedTotal,
|
|
3670
3531
|
originalReceiptTotal: originalReceipt.total,
|
|
3671
|
-
audience:
|
|
3532
|
+
audience: 'customer',
|
|
3672
3533
|
});
|
|
3673
3534
|
})();
|
|
3674
3535
|
|
|
@@ -3677,14 +3538,6 @@ export function ChangeBookingFlow({
|
|
|
3677
3538
|
|
|
3678
3539
|
const changeCheckoutButtonLabel = (() => {
|
|
3679
3540
|
if (!hasEffectiveChangeSelection) return undefined;
|
|
3680
|
-
if (isProviderDashboardChange) {
|
|
3681
|
-
const est = Math.round(changeFlowClientEstimateDue * 100) / 100;
|
|
3682
|
-
return est > 0
|
|
3683
|
-
? `Change booking (${formatCurrencyAmount(est, currency, locale as 'en' | 'fr')})`
|
|
3684
|
-
: est < 0
|
|
3685
|
-
? `Change booking (${formatCurrencyAmount(est, currency, locale as 'en' | 'fr')})`
|
|
3686
|
-
: 'Change booking (no charge)';
|
|
3687
|
-
}
|
|
3688
3541
|
if (changeFlowNeedsServerPrice) {
|
|
3689
3542
|
if (changeQuoteLoading) {
|
|
3690
3543
|
const tr = t('booking.updatingPrice');
|
|
@@ -3718,39 +3571,8 @@ export function ChangeBookingFlow({
|
|
|
3718
3571
|
const checkoutFormError =
|
|
3719
3572
|
(error || '') ||
|
|
3720
3573
|
(missingRequiredReturnSelection ? 'Removing return option in self-serve is not available. Please contact support.' : '') ||
|
|
3721
|
-
(
|
|
3722
|
-
(
|
|
3723
|
-
|
|
3724
|
-
const providerPricingOverrides =
|
|
3725
|
-
isProviderDashboardChange && providerQuotedLines.length > 0
|
|
3726
|
-
? providerQuotedLines
|
|
3727
|
-
.filter((line) => isManualOverrideEligibleLine(line))
|
|
3728
|
-
.map((line) => {
|
|
3729
|
-
const raw = providerPricingUi?.lineAmountInputs?.[line.lineKey];
|
|
3730
|
-
const parsed = raw == null || raw.trim() === '' ? line.amount : Number(raw);
|
|
3731
|
-
if (!Number.isFinite(parsed)) return null;
|
|
3732
|
-
const rounded = Math.round(parsed * 100) / 100;
|
|
3733
|
-
return Math.abs(rounded - line.amount) > 0.0001
|
|
3734
|
-
? { lineKey: line.lineKey, amount: rounded, reason: 'Provider dashboard override' }
|
|
3735
|
-
: null;
|
|
3736
|
-
})
|
|
3737
|
-
.filter((v): v is { lineKey: string; amount: number; reason: string } => v != null)
|
|
3738
|
-
: [];
|
|
3739
|
-
const providerAdditionalAdjustments =
|
|
3740
|
-
isProviderDashboardChange
|
|
3741
|
-
? (providerPricingUi?.additionalAdjustments ?? [])
|
|
3742
|
-
.map((adj) => {
|
|
3743
|
-
const parsed = Number((adj.amountInput ?? '').trim());
|
|
3744
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
3745
|
-
const rounded = Math.round(parsed * 100) / 100;
|
|
3746
|
-
const signed = adj.mode === 'DISCOUNT' ? -rounded : rounded;
|
|
3747
|
-
return {
|
|
3748
|
-
label: adj.label?.trim() || (signed < 0 ? 'Provider discount' : 'Provider charge'),
|
|
3749
|
-
amount: signed,
|
|
3750
|
-
};
|
|
3751
|
-
})
|
|
3752
|
-
.filter((v): v is { label: string; amount: number } => v != null)
|
|
3753
|
-
: [];
|
|
3574
|
+
(isChangeQuoteBlocked ? (latestChangeQuote?.reasonIfBlocked ?? '') : '') ||
|
|
3575
|
+
(changeQuoteFetchError ?? '');
|
|
3754
3576
|
|
|
3755
3577
|
const changeFlowSelectionPreview = useMemo((): ChangeFlowSelectionPreview | null => {
|
|
3756
3578
|
if (!selectedAvailability || totalQuantity <= 0) return null;
|
|
@@ -3998,8 +3820,7 @@ export function ChangeBookingFlow({
|
|
|
3998
3820
|
timesForSelectedDate,
|
|
3999
3821
|
anchor,
|
|
4000
3822
|
companyTimezone,
|
|
4001
|
-
optionsMap
|
|
4002
|
-
isAdmin
|
|
3823
|
+
optionsMap
|
|
4003
3824
|
);
|
|
4004
3825
|
changeFlowOutboundAnchorRef.current = null;
|
|
4005
3826
|
if (matched) {
|
|
@@ -4090,8 +3911,7 @@ export function ChangeBookingFlow({
|
|
|
4090
3911
|
const fromAnchor = pickReturnMatchingPreviousSelection(
|
|
4091
3912
|
sorted,
|
|
4092
3913
|
returnAnchor,
|
|
4093
|
-
companyTimezone
|
|
4094
|
-
isAdmin
|
|
3914
|
+
companyTimezone
|
|
4095
3915
|
);
|
|
4096
3916
|
changeFlowReturnAnchorRef.current = null;
|
|
4097
3917
|
if (fromAnchor) {
|
|
@@ -4414,8 +4234,6 @@ export function ChangeBookingFlow({
|
|
|
4414
4234
|
return;
|
|
4415
4235
|
}
|
|
4416
4236
|
|
|
4417
|
-
const skipContactFields = isProviderDashboardChange;
|
|
4418
|
-
if (!skipContactFields) {
|
|
4419
4237
|
// Validate email (required)
|
|
4420
4238
|
if (!email) {
|
|
4421
4239
|
setError(t('booking.enterEmail') || 'Please enter your email address');
|
|
@@ -4438,7 +4256,6 @@ export function ChangeBookingFlow({
|
|
|
4438
4256
|
setError(t('booking.enterLastName') || 'Please enter your last name');
|
|
4439
4257
|
return;
|
|
4440
4258
|
}
|
|
4441
|
-
}
|
|
4442
4259
|
|
|
4443
4260
|
// Allow checkout if pickup location is selected OR if user chose "I don't know"
|
|
4444
4261
|
if (product.pickupLocations && product.pickupLocations.length > 0 && !pickupLocationId && !pickupLocationSkipped) {
|
|
@@ -4465,45 +4282,6 @@ export function ChangeBookingFlow({
|
|
|
4465
4282
|
return;
|
|
4466
4283
|
}
|
|
4467
4284
|
|
|
4468
|
-
if (onChangeBooking) {
|
|
4469
|
-
const pickupForChange = pickupLocationId
|
|
4470
|
-
? product.pickupLocations?.find((loc) => loc.id === pickupLocationId)
|
|
4471
|
-
: null;
|
|
4472
|
-
await onChangeBooking({
|
|
4473
|
-
productId: availabilityProductOptionId,
|
|
4474
|
-
dateTime: selectedAvailability.dateTime,
|
|
4475
|
-
bookingItems,
|
|
4476
|
-
returnAvailabilityId: selectedReturnOption?.returnAvailabilityId ?? null,
|
|
4477
|
-
pickupLocationId: pickupLocationId ?? null,
|
|
4478
|
-
travelerHotel: pickupForChange?.name ?? null,
|
|
4479
|
-
startTime: selectedAvailability.dateTime ?? null,
|
|
4480
|
-
passengerCount: null,
|
|
4481
|
-
childSafetySeatsCount: null,
|
|
4482
|
-
foodRestrictions: null,
|
|
4483
|
-
addOnSelections: addOnSelections.length > 0 ? addOnSelections : null,
|
|
4484
|
-
cancellationPolicyId: cancellationPolicyId ?? initialValues?.cancellationPolicyId ?? null,
|
|
4485
|
-
promoCode: appliedPromoCode ?? null,
|
|
4486
|
-
newTotalAmount: displayChangeFlowProposedTotal,
|
|
4487
|
-
additionalHoursCount: null,
|
|
4488
|
-
pricingAdjustment:
|
|
4489
|
-
providerPricingOverrides.length > 0 || providerAdditionalAdjustments.length > 0
|
|
4490
|
-
? {
|
|
4491
|
-
mode: 'MANUAL_LINES',
|
|
4492
|
-
lineOverrides: providerPricingOverrides,
|
|
4493
|
-
additionalAdjustments: providerAdditionalAdjustments,
|
|
4494
|
-
}
|
|
4495
|
-
: undefined,
|
|
4496
|
-
capacitySeatCredit: {
|
|
4497
|
-
enabled: true,
|
|
4498
|
-
previousPassengerCount: changeFlowInitialTicketCount,
|
|
4499
|
-
previousAvailabilityId: initialValues?.availabilityId ?? null,
|
|
4500
|
-
previousReturnAvailabilityId: initialValues?.returnAvailabilityId ?? null,
|
|
4501
|
-
},
|
|
4502
|
-
});
|
|
4503
|
-
setLoading(false);
|
|
4504
|
-
return;
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
4285
|
const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
|
|
4508
4286
|
clientChannelSource: inferClientBookingSourceFromProductIds(
|
|
4509
4287
|
product.productId,
|
|
@@ -4521,7 +4299,6 @@ export function ChangeBookingFlow({
|
|
|
4521
4299
|
let changeIntentIdForCheckout: string | undefined;
|
|
4522
4300
|
let changeBookingReferenceForPaidFlow: string | undefined;
|
|
4523
4301
|
|
|
4524
|
-
if (isCustomerSelfServeChange) {
|
|
4525
4302
|
const changeBookingReference = initialValues?.bookingReference?.trim();
|
|
4526
4303
|
const changeLastName = lastName.trim();
|
|
4527
4304
|
if (!changeBookingReference || !changeLastName) {
|
|
@@ -4609,13 +4386,12 @@ export function ChangeBookingFlow({
|
|
|
4609
4386
|
if (!changeIntentIdForCheckout) {
|
|
4610
4387
|
throw new Error('Missing change intent for payment.');
|
|
4611
4388
|
}
|
|
4612
|
-
}
|
|
4613
4389
|
|
|
4614
4390
|
pendingReservationRef.current = null;
|
|
4615
4391
|
|
|
4616
4392
|
// Note: Do NOT call onSuccess here for paid bookings — we're about to show the Stripe
|
|
4617
4393
|
// CheckoutModal. onSuccess (e.g. closing the parent dialog) should only run when we're
|
|
4618
|
-
// actually done (free booking redirect
|
|
4394
|
+
// actually done (free booking redirect). Calling it here
|
|
4619
4395
|
// would close the dialog before the payment modal opens.
|
|
4620
4396
|
|
|
4621
4397
|
// Update stored booking data (no holds reservation — change flow keys off booking reference elsewhere).
|
|
@@ -4641,13 +4417,11 @@ export function ChangeBookingFlow({
|
|
|
4641
4417
|
// Build checkout breakdown from the exact same values we show in the UI and Stripe modal.
|
|
4642
4418
|
// Backend will charge totalAmount and store this as the receipt so /manage matches.
|
|
4643
4419
|
const taxForBreakdown = effectivePromoDiscountAmount > 0 ? effectiveTax : tax;
|
|
4644
|
-
const amountDueForCheckout =
|
|
4645
|
-
? changeFlowBalanceVsOriginal({
|
|
4420
|
+
const amountDueForCheckout = changeFlowBalanceVsOriginal({
|
|
4646
4421
|
newTotal: changeFlowNewBookingTotal,
|
|
4647
4422
|
originalReceiptTotal: originalReceipt?.total ?? 0,
|
|
4648
4423
|
audience: 'customer',
|
|
4649
|
-
})
|
|
4650
|
-
: totalPrice;
|
|
4424
|
+
});
|
|
4651
4425
|
const lines = [
|
|
4652
4426
|
...ticketLineItemsForChangeFlowDisplay.map((line) => ({
|
|
4653
4427
|
label: line.category,
|
|
@@ -4724,8 +4498,7 @@ export function ChangeBookingFlow({
|
|
|
4724
4498
|
roundingLabel: t('booking.rounding') || 'Rounding',
|
|
4725
4499
|
});
|
|
4726
4500
|
|
|
4727
|
-
const paymentIntent =
|
|
4728
|
-
? await createChangeBookingPaymentIntent(
|
|
4501
|
+
const paymentIntent = await createChangeBookingPaymentIntent(
|
|
4729
4502
|
(() => {
|
|
4730
4503
|
const id = changeIntentIdForCheckout ?? latestChangeQuote?.changeIntentId;
|
|
4731
4504
|
if (!id) {
|
|
@@ -4733,76 +4506,7 @@ export function ChangeBookingFlow({
|
|
|
4733
4506
|
}
|
|
4734
4507
|
return id;
|
|
4735
4508
|
})()
|
|
4736
|
-
)
|
|
4737
|
-
: await createPaymentIntent({
|
|
4738
|
-
productId: product.productId,
|
|
4739
|
-
optionId: availabilityProductOptionId,
|
|
4740
|
-
date: datePart,
|
|
4741
|
-
time: timePart,
|
|
4742
|
-
quantity: totalQuantity,
|
|
4743
|
-
customerEmail: email,
|
|
4744
|
-
customerFirstName: firstName.trim() || undefined,
|
|
4745
|
-
customerLastName: lastName.trim() || undefined,
|
|
4746
|
-
currency: currency,
|
|
4747
|
-
travelerHotel: selectedPickupLocation?.name || undefined,
|
|
4748
|
-
pickupLocationId: pickupLocationId || undefined,
|
|
4749
|
-
itineraryDisplay: itineraryDisplay ?? undefined,
|
|
4750
|
-
returnAvailabilityId: selectedReturnOption?.returnAvailabilityId,
|
|
4751
|
-
promoCode: (lockedPromoCode || appliedPromoCode) || undefined,
|
|
4752
|
-
cancellationPolicyId: cancellationPolicyId || undefined,
|
|
4753
|
-
termsAcceptedAt: termsAcceptedAt ?? undefined,
|
|
4754
|
-
checkoutBreakdown,
|
|
4755
|
-
skipConfirmationCommunications: isAdmin && skipConfirmationCommunications ? true : undefined,
|
|
4756
|
-
disableAutoCommunications: isAdmin && disableAutoCommunications ? true : undefined,
|
|
4757
|
-
...bookingSourceContext,
|
|
4758
|
-
});
|
|
4759
|
-
|
|
4760
|
-
// Admin: show choice to pay now or confirm without payment (customer owes full balance)
|
|
4761
|
-
if (isAdmin) {
|
|
4762
|
-
const adminReservationRef =
|
|
4763
|
-
initialValues?.bookingReference?.trim() ??
|
|
4764
|
-
changeBookingReferenceForPaidFlow ??
|
|
4765
|
-
'';
|
|
4766
|
-
if (!adminReservationRef) {
|
|
4767
|
-
throw new Error('Missing reservation reference for admin payment flow');
|
|
4768
|
-
}
|
|
4769
|
-
setError('');
|
|
4770
|
-
setAdminChoiceData({
|
|
4771
|
-
reservationReference: adminReservationRef,
|
|
4772
|
-
reservationExpiration: undefined,
|
|
4773
|
-
checkoutBreakdown,
|
|
4774
|
-
totalAmount: amountDueForCheckout,
|
|
4775
|
-
datePart,
|
|
4776
|
-
timePart,
|
|
4777
|
-
availabilityProductOptionId,
|
|
4778
|
-
itineraryDisplay: itineraryDisplay ?? undefined,
|
|
4779
|
-
clientSecret: paymentIntent.clientSecret ?? '',
|
|
4780
|
-
ticketLinesForModal: ticketLineItemsForChangeFlowDisplay.map((line) => {
|
|
4781
|
-
const rate = pricing.find((r) => r.category === line.category);
|
|
4782
|
-
const breakdown = getPriceBreakdown(
|
|
4783
|
-
line.category,
|
|
4784
|
-
rate?.priceCAD ?? 0,
|
|
4785
|
-
rate?.baseInDisplayCurrency,
|
|
4786
|
-
rate?.appliedAdjustments ?? []
|
|
4787
|
-
);
|
|
4788
|
-
return { line, breakdown };
|
|
4789
|
-
}),
|
|
4790
|
-
feeLineItems: feeLineItemsWithAddOns,
|
|
4791
|
-
returnPriceAdjustment: checkoutReturnLineAmount,
|
|
4792
|
-
cancellationPolicyFee,
|
|
4793
|
-
cancellationPolicyLabel: effectiveCancellationPolicyLabel,
|
|
4794
|
-
subtotal: effectiveSubtotal,
|
|
4795
|
-
tax: effectivePromoDiscountAmount > 0 ? effectiveTax : tax,
|
|
4796
|
-
totalQuantity,
|
|
4797
|
-
isTaxIncludedInPrice,
|
|
4798
|
-
taxRate: pricingConfig?.taxRate ?? 0,
|
|
4799
|
-
promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
|
|
4800
|
-
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
|
|
4801
|
-
});
|
|
4802
|
-
setShowAdminPaymentChoice(true);
|
|
4803
|
-
setLoading(false);
|
|
4804
|
-
return;
|
|
4805
|
-
}
|
|
4509
|
+
);
|
|
4806
4510
|
|
|
4807
4511
|
const ticketLinesForModal: CheckoutModalLineItem[] = ticketLineItemsForChangeFlowDisplay.map((line) => {
|
|
4808
4512
|
const rate = pricing.find((r) => r.category === line.category);
|
|
@@ -4824,7 +4528,7 @@ export function ChangeBookingFlow({
|
|
|
4824
4528
|
// Paid change: always return to stable ref+lastName + explicit intent (not reservationRef).
|
|
4825
4529
|
// /manage-booking runs bounded refresh only when `from=change_payment` (see manage-booking page).
|
|
4826
4530
|
successUrlOverride:
|
|
4827
|
-
|
|
4531
|
+
changeBookingReferenceForPaidFlow
|
|
4828
4532
|
? (() => {
|
|
4829
4533
|
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
4830
4534
|
const ref = encodeURIComponent(
|
|
@@ -4852,7 +4556,7 @@ export function ChangeBookingFlow({
|
|
|
4852
4556
|
promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
|
|
4853
4557
|
discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
|
|
4854
4558
|
changeTotals:
|
|
4855
|
-
|
|
4559
|
+
originalReceipt
|
|
4856
4560
|
? {
|
|
4857
4561
|
previousTotal: originalReceipt.total,
|
|
4858
4562
|
newTotal: displayChangeFlowProposedTotal,
|
|
@@ -4921,86 +4625,6 @@ export function ChangeBookingFlow({
|
|
|
4921
4625
|
}
|
|
4922
4626
|
};
|
|
4923
4627
|
|
|
4924
|
-
const handleConfirmWithoutPayment = async () => {
|
|
4925
|
-
if (!adminChoiceData) return;
|
|
4926
|
-
setLoading(true);
|
|
4927
|
-
setError('');
|
|
4928
|
-
try {
|
|
4929
|
-
const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
|
|
4930
|
-
clientChannelSource: inferClientBookingSourceFromProductIds(
|
|
4931
|
-
product.productId,
|
|
4932
|
-
adminChoiceData.availabilityProductOptionId,
|
|
4933
|
-
),
|
|
4934
|
-
forcePartnerPortalChannel: partnerPortalBooking,
|
|
4935
|
-
forceDashboardSource: bookingAppMode === 'provider-dashboard',
|
|
4936
|
-
});
|
|
4937
|
-
const result = await confirmBookingWithoutPayment({
|
|
4938
|
-
reservationReference: adminChoiceData.reservationReference,
|
|
4939
|
-
productId: product.productId,
|
|
4940
|
-
optionId: adminChoiceData.availabilityProductOptionId,
|
|
4941
|
-
date: adminChoiceData.datePart,
|
|
4942
|
-
time: adminChoiceData.timePart,
|
|
4943
|
-
customerEmail: email || undefined,
|
|
4944
|
-
customerFirstName: firstName.trim() || undefined,
|
|
4945
|
-
customerLastName: lastName.trim() || undefined,
|
|
4946
|
-
currency: currency,
|
|
4947
|
-
travelerHotel: product.pickupLocations?.find(loc => loc.id === pickupLocationId)?.name || undefined,
|
|
4948
|
-
pickupLocationId: pickupLocationId || undefined,
|
|
4949
|
-
itineraryDisplay: adminChoiceData.itineraryDisplay ?? undefined,
|
|
4950
|
-
termsAcceptedAt: termsAcceptedAt ?? undefined,
|
|
4951
|
-
skipConfirmationCommunications: skipConfirmationCommunications ? true : undefined,
|
|
4952
|
-
disableAutoCommunications: disableAutoCommunications ? true : undefined,
|
|
4953
|
-
checkoutBreakdown: adminChoiceData.checkoutBreakdown,
|
|
4954
|
-
depositAmount: 0,
|
|
4955
|
-
balanceAmount: adminChoiceData.totalAmount,
|
|
4956
|
-
totalAmount: adminChoiceData.totalAmount,
|
|
4957
|
-
...bookingSourceContext,
|
|
4958
|
-
});
|
|
4959
|
-
pendingReservationRef.current = null;
|
|
4960
|
-
const ref = formatBookingRefForDisplay(result.bookingReference);
|
|
4961
|
-
const ln = lastName.trim();
|
|
4962
|
-
setShowAdminPaymentChoice(false);
|
|
4963
|
-
setAdminChoiceData(null);
|
|
4964
|
-
onSuccess?.({ reservationReference: adminChoiceData.reservationReference });
|
|
4965
|
-
if (onShowManage) {
|
|
4966
|
-
onShowManage({ ref, lastName: ln });
|
|
4967
|
-
} else {
|
|
4968
|
-
const params = new URLSearchParams({ ref, lastName: ln, booking_complete: '1' });
|
|
4969
|
-
window.location.href = `/manage-booking?${params.toString()}`;
|
|
4970
|
-
}
|
|
4971
|
-
} catch (err) {
|
|
4972
|
-
setError(err instanceof Error ? err.message : 'Failed to confirm booking');
|
|
4973
|
-
} finally {
|
|
4974
|
-
setLoading(false);
|
|
4975
|
-
}
|
|
4976
|
-
};
|
|
4977
|
-
|
|
4978
|
-
const handlePayNow = () => {
|
|
4979
|
-
if (!adminChoiceData) return;
|
|
4980
|
-
setShowAdminPaymentChoice(false);
|
|
4981
|
-
setCheckoutClientSecret(adminChoiceData.clientSecret);
|
|
4982
|
-
setCheckoutModalData({
|
|
4983
|
-
reservationReference: adminChoiceData.reservationReference,
|
|
4984
|
-
reservationExpiration: adminChoiceData.reservationExpiration,
|
|
4985
|
-
customerLastName: lastName.trim(),
|
|
4986
|
-
ticketLines: adminChoiceData.ticketLinesForModal,
|
|
4987
|
-
feeLineItems: adminChoiceData.feeLineItems,
|
|
4988
|
-
returnPriceAdjustment: adminChoiceData.returnPriceAdjustment,
|
|
4989
|
-
cancellationPolicyFee: adminChoiceData.cancellationPolicyFee,
|
|
4990
|
-
cancellationPolicyLabel: adminChoiceData.cancellationPolicyLabel,
|
|
4991
|
-
subtotal: adminChoiceData.subtotal,
|
|
4992
|
-
tax: adminChoiceData.tax,
|
|
4993
|
-
total: adminChoiceData.totalAmount,
|
|
4994
|
-
totalQuantity: adminChoiceData.totalQuantity,
|
|
4995
|
-
isTaxIncludedInPrice: adminChoiceData.isTaxIncludedInPrice,
|
|
4996
|
-
taxRate: adminChoiceData.taxRate,
|
|
4997
|
-
promoDiscountAmount: adminChoiceData.promoDiscountAmount,
|
|
4998
|
-
discountLabel: adminChoiceData.discountLabel,
|
|
4999
|
-
});
|
|
5000
|
-
setShowCheckoutModal(true);
|
|
5001
|
-
setAdminChoiceData(null);
|
|
5002
|
-
};
|
|
5003
|
-
|
|
5004
4628
|
if (activeOptions.length === 0) {
|
|
5005
4629
|
return (
|
|
5006
4630
|
<div className="flex items-center justify-center py-16">
|
|
@@ -5011,17 +4635,6 @@ export function ChangeBookingFlow({
|
|
|
5011
4635
|
|
|
5012
4636
|
return (
|
|
5013
4637
|
<div className="booking-flow-root space-y-8">
|
|
5014
|
-
{/* Admin: choose to pay now or confirm without payment (full balance owed) */}
|
|
5015
|
-
<AdminPaymentChoiceModal
|
|
5016
|
-
open={!!(showAdminPaymentChoice && adminChoiceData)}
|
|
5017
|
-
totalAmount={adminChoiceData?.totalAmount ?? 0}
|
|
5018
|
-
currency={currency}
|
|
5019
|
-
loading={loading}
|
|
5020
|
-
error={error}
|
|
5021
|
-
onPayNow={handlePayNow}
|
|
5022
|
-
onConfirmWithoutPayment={handleConfirmWithoutPayment}
|
|
5023
|
-
onCancel={() => { setShowAdminPaymentChoice(false); setAdminChoiceData(null); setError(''); }}
|
|
5024
|
-
/>
|
|
5025
4638
|
{checkoutModalData && (
|
|
5026
4639
|
<CheckoutModal
|
|
5027
4640
|
open={showCheckoutModal}
|
|
@@ -5276,92 +4889,8 @@ export function ChangeBookingFlow({
|
|
|
5276
4889
|
currency={currency}
|
|
5277
4890
|
locale={locale}
|
|
5278
4891
|
t={t}
|
|
5279
|
-
extraBetweenTaxAndTotal={
|
|
5280
|
-
|
|
5281
|
-
{showProviderPricingInlineEditor && providerPricingUi?.error ? (
|
|
5282
|
-
<div className="mt-2 text-sm text-red-700">{providerPricingUi.error}</div>
|
|
5283
|
-
) : null}
|
|
5284
|
-
{showProviderPricingInlineEditor &&
|
|
5285
|
-
providerPricingUi?.loading &&
|
|
5286
|
-
providerQuotedLines.length === 0 ? (
|
|
5287
|
-
<div className="mt-2 text-sm text-stone-500">Loading price lines...</div>
|
|
5288
|
-
) : null}
|
|
5289
|
-
{showProviderPricingInlineEditor &&
|
|
5290
|
-
providerPricingUi?.helperText &&
|
|
5291
|
-
!providerPricingUi.error ? (
|
|
5292
|
-
<div className="mt-2 text-xs text-stone-500">{providerPricingUi.helperText}</div>
|
|
5293
|
-
) : null}
|
|
5294
|
-
</>
|
|
5295
|
-
}
|
|
5296
|
-
extraBeforeSubtotal={
|
|
5297
|
-
showProviderPricingInlineEditor && (providerPricingUi?.additionalAdjustments?.length ?? 0) > 0 ? (
|
|
5298
|
-
<div className="space-y-1">
|
|
5299
|
-
{providerPricingUi?.additionalAdjustments?.map((adj) => (
|
|
5300
|
-
<div key={adj.id} className="flex items-center justify-between gap-2 text-sm">
|
|
5301
|
-
<div className="flex min-w-0 items-center gap-1">
|
|
5302
|
-
<button
|
|
5303
|
-
type="button"
|
|
5304
|
-
className="rounded border border-stone-300 px-1 text-xs text-stone-600 hover:bg-stone-100"
|
|
5305
|
-
onClick={() => providerPricingUi?.onRemoveAdditionalAdjustment?.(adj.id)}
|
|
5306
|
-
>
|
|
5307
|
-
-
|
|
5308
|
-
</button>
|
|
5309
|
-
<input
|
|
5310
|
-
type="text"
|
|
5311
|
-
className="w-40 rounded border border-stone-300 px-2 py-0.5 text-sm"
|
|
5312
|
-
placeholder="Line description"
|
|
5313
|
-
value={adj.label}
|
|
5314
|
-
onChange={(e) =>
|
|
5315
|
-
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, { label: e.target.value })
|
|
5316
|
-
}
|
|
5317
|
-
/>
|
|
5318
|
-
</div>
|
|
5319
|
-
<div className="flex items-center gap-1">
|
|
5320
|
-
<select
|
|
5321
|
-
className="rounded border border-stone-300 px-1 py-0.5 text-xs"
|
|
5322
|
-
value={adj.mode}
|
|
5323
|
-
onChange={(e) =>
|
|
5324
|
-
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, {
|
|
5325
|
-
mode: e.target.value as 'DISCOUNT' | 'CHARGE',
|
|
5326
|
-
})
|
|
5327
|
-
}
|
|
5328
|
-
>
|
|
5329
|
-
<option value="DISCOUNT">-</option>
|
|
5330
|
-
<option value="CHARGE">+</option>
|
|
5331
|
-
</select>
|
|
5332
|
-
<input
|
|
5333
|
-
type="text"
|
|
5334
|
-
inputMode="decimal"
|
|
5335
|
-
className="h-6 w-24 rounded border border-stone-300 bg-white px-2 py-0.5 text-right text-sm font-medium leading-none text-stone-700"
|
|
5336
|
-
placeholder="0.00"
|
|
5337
|
-
value={adj.amountInput}
|
|
5338
|
-
onChange={(e) =>
|
|
5339
|
-
providerPricingUi?.onUpdateAdditionalAdjustment?.(adj.id, {
|
|
5340
|
-
amountInput: e.target.value,
|
|
5341
|
-
})
|
|
5342
|
-
}
|
|
5343
|
-
/>
|
|
5344
|
-
</div>
|
|
5345
|
-
</div>
|
|
5346
|
-
))}
|
|
5347
|
-
<button
|
|
5348
|
-
type="button"
|
|
5349
|
-
className="rounded border border-stone-300 px-2 py-0.5 text-xs text-stone-600 hover:bg-stone-100"
|
|
5350
|
-
onClick={() => providerPricingUi?.onAddAdditionalAdjustment?.()}
|
|
5351
|
-
>
|
|
5352
|
-
+ add line item
|
|
5353
|
-
</button>
|
|
5354
|
-
</div>
|
|
5355
|
-
) : showProviderPricingInlineEditor ? (
|
|
5356
|
-
<button
|
|
5357
|
-
type="button"
|
|
5358
|
-
className="rounded border border-stone-300 px-2 py-0.5 text-xs text-stone-600 hover:bg-stone-100"
|
|
5359
|
-
onClick={() => providerPricingUi?.onAddAdditionalAdjustment?.()}
|
|
5360
|
-
>
|
|
5361
|
-
+ add line item
|
|
5362
|
-
</button>
|
|
5363
|
-
) : undefined
|
|
5364
|
-
}
|
|
4892
|
+
extraBetweenTaxAndTotal={<></>}
|
|
4893
|
+
extraBeforeSubtotal={undefined}
|
|
5365
4894
|
firstName={firstName}
|
|
5366
4895
|
lastName={lastName}
|
|
5367
4896
|
email={email}
|
|
@@ -5402,12 +4931,12 @@ export function ChangeBookingFlow({
|
|
|
5402
4931
|
setTermsAccepted(checked);
|
|
5403
4932
|
setTermsAcceptedAt(checked ? new Date().toISOString() : null);
|
|
5404
4933
|
}}
|
|
5405
|
-
isAdmin={
|
|
4934
|
+
isAdmin={false}
|
|
5406
4935
|
showCommunicationAdminSection={false}
|
|
5407
|
-
skipConfirmationCommunications={
|
|
5408
|
-
disableAutoCommunications={
|
|
5409
|
-
onSkipConfirmationChange={
|
|
5410
|
-
onDisableCommunicationsChange={
|
|
4936
|
+
skipConfirmationCommunications={false}
|
|
4937
|
+
disableAutoCommunications={false}
|
|
4938
|
+
onSkipConfirmationChange={() => {}}
|
|
4939
|
+
onDisableCommunicationsChange={() => {}}
|
|
5411
4940
|
error={checkoutFormError}
|
|
5412
4941
|
loading={loading}
|
|
5413
4942
|
totalQuantity={totalQuantity}
|
|
@@ -5415,7 +4944,6 @@ export function ChangeBookingFlow({
|
|
|
5415
4944
|
submitLabel={changeCheckoutButtonLabel ?? deferredInvoiceSubmitLabel}
|
|
5416
4945
|
hideSubmitButton={
|
|
5417
4946
|
showCheckoutModal ||
|
|
5418
|
-
showAdminPaymentChoice ||
|
|
5419
4947
|
!hasEffectiveChangeSelection ||
|
|
5420
4948
|
isChangeQuoteBlocked
|
|
5421
4949
|
}
|
|
@@ -5424,16 +4952,10 @@ export function ChangeBookingFlow({
|
|
|
5424
4952
|
attributionConfirmLabel={flowUi?.partnerAttributionConfirmLabel}
|
|
5425
4953
|
attributionConfirmed={partnerAttributionConfirmed}
|
|
5426
4954
|
onAttributionConfirmedChange={setPartnerAttributionConfirmed}
|
|
5427
|
-
lineAmountInputs={
|
|
5428
|
-
onLineAmountInputChange={
|
|
5429
|
-
|
|
5430
|
-
}
|
|
5431
|
-
onLineAmountInputBlur={
|
|
5432
|
-
showProviderPricingInlineEditor ? providerPricingUi?.onLineAmountInputBlur : undefined
|
|
5433
|
-
}
|
|
5434
|
-
onLineAmountReset={
|
|
5435
|
-
showProviderPricingInlineEditor ? providerPricingUi?.onLineAmountReset : undefined
|
|
5436
|
-
}
|
|
4955
|
+
lineAmountInputs={undefined}
|
|
4956
|
+
onLineAmountInputChange={undefined}
|
|
4957
|
+
onLineAmountInputBlur={undefined}
|
|
4958
|
+
onLineAmountReset={undefined}
|
|
5437
4959
|
/>
|
|
5438
4960
|
</>
|
|
5439
4961
|
)}
|