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