@ticketboothapp/booking 1.2.63 → 1.2.64

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.
@@ -11,6 +11,7 @@ import {
11
11
  quoteChangeBooking,
12
12
  confirmFreeChangeBooking,
13
13
  createChangeBookingPaymentIntent,
14
+ confirmBookingWithoutPayment,
14
15
  getAddOns,
15
16
  validatePromoCode,
16
17
  getPromoDiscount,
@@ -32,6 +33,7 @@ import {
32
33
  } from '../../lib/booking-constants';
33
34
  import { getSundayOfWeek } from '../../lib/booking/sunday-week';
34
35
  import { Calendar } from './Calendar';
36
+ import { AdminPaymentChoiceModal } from './AdminPaymentChoiceModal';
35
37
  import { ItineraryBox } from './ItineraryBox';
36
38
  import { ItineraryPlaceholder } from './ItineraryPlaceholder';
37
39
  import { PickupTimeSelector } from './PickupTimeSelector';
@@ -62,10 +64,11 @@ import {
62
64
  } from '../../lib/booking/change-flow-pricing';
63
65
  import {
64
66
  mergePriceSummaryLinesForDrift,
67
+ mergeLineComparisonsWithFullDrift,
65
68
  normalizePricingDriftDetailFromQuote,
66
69
  normalizeTicketPricingTraceFromQuote,
67
70
  sumPriceSummaryLinesMajorUnits,
68
- enrichLineComparisonsWithMergedRows,
71
+ computePricingDriftDelta,
69
72
  } from '../../lib/booking/change-booking-pricing-drift';
70
73
  import { ChangeBookingPricingDriftPanel } from './ChangeBookingPricingDriftPanel';
71
74
  import {
@@ -96,6 +99,7 @@ import { getItineraryStepLabel } from '../../lib/booking/itinerary-display';
96
99
  import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '../../lib/manage-booking-post-checkout';
97
100
  import { useBookingHost } from '../../runtime';
98
101
  import type { BookingFlowUiOptions } from './booking-flow-ui';
102
+ import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
99
103
  import type { ChangeBookingFlowProps, ChangeFlowSelectionPreview } from './booking-flow-types';
100
104
  import { BOOKING_FLOW_ABANDON_EVENT } from '../../providers/booking-dialog-provider';
101
105
 
@@ -110,6 +114,7 @@ export type { ChangeBookingFlowProps } from './booking-flow-types';
110
114
  *
111
115
  * Until the first successful quote, `selfServeCheckoutPlaceholder` still avoids showing unchecked totals.
112
116
  *
117
+ * **Provider dashboard** (`onChangeBooking`) — unchanged; manual pricing via `flowUi.providerDashboardChangePricingUi`.
113
118
  */
114
119
  function mergeQuoteSliceWithServerPreview(
115
120
  slice: ReturnType<typeof sliceChangeQuoteForUi>,
@@ -406,8 +411,9 @@ function pickOutboundMatchingPreviousSelection(
406
411
  anchor: { productOptionId: string | null; minutesFromMidnight: number },
407
412
  companyTimezone: string,
408
413
  optionsMap: Map<string, { mostPopular?: boolean }>,
414
+ isAdmin: boolean
409
415
  ): Availability | null {
410
- const selectable = timesForSelectedDate.filter((a) => (a.vacancies ?? 0) > 0);
416
+ const selectable = timesForSelectedDate.filter((a) => (a.vacancies ?? 0) > 0 || isAdmin);
411
417
  if (selectable.length === 0) return null;
412
418
 
413
419
  const withOption =
@@ -441,8 +447,9 @@ function pickReturnMatchingPreviousSelection(
441
447
  sortedReturnOptions: ReturnOption[],
442
448
  anchor: { returnLocation: string; minutesFromMidnight: number },
443
449
  companyTimezone: string,
450
+ isAdmin: boolean
444
451
  ): ReturnOption | null {
445
- const eligible = sortedReturnOptions.filter((o) => (o.vacancies ?? 0) > 0);
452
+ const eligible = sortedReturnOptions.filter((o) => (o.vacancies ?? 0) > 0 || isAdmin);
446
453
  if (eligible.length === 0) return null;
447
454
 
448
455
  const sameLoc = eligible.filter((o) => o.returnLocation === anchor.returnLocation);
@@ -631,7 +638,7 @@ function resolveInitialAvailabilityFromBooking(
631
638
  }
632
639
 
633
640
  /**
634
- * Customer self-serve **change booking** only (no standard new-booking paths).
641
+ * Customer self-serve and provider-dashboard **change booking** only (no standard new-booking paths).
635
642
  * Duplicated from {@link NewBookingFlow} intentionally so each flow can evolve independently.
636
643
  */
637
644
  export function ChangeBookingFlow({
@@ -656,6 +663,7 @@ export function ChangeBookingFlow({
656
663
  partnerPortalBooking = false,
657
664
  availabilityPricingProfileId,
658
665
  availabilityCancellationPolicyProfileId,
666
+ onChangeBooking,
659
667
  }: ChangeBookingFlowProps) {
660
668
  /** Always the booking’s sold currency — not the site currency switcher / parent default. */
661
669
  const currency = useMemo((): Currency => {
@@ -667,6 +675,16 @@ export function ChangeBookingFlow({
667
675
  return DEFAULT_CURRENCY;
668
676
  }, [originalReceipt?.currency, initialValues?.currency, currencyFromParent]);
669
677
 
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
+ };
670
688
  const { env, analytics } = useBookingHost();
671
689
  const { t } = useTranslations();
672
690
  const { locale } = useLocale();
@@ -675,12 +693,15 @@ export function ChangeBookingFlow({
675
693
  const cancellationPolicyProfileIdForAvailabilities =
676
694
  (availabilityCancellationPolicyProfileId ?? '').trim() || null;
677
695
  const {
696
+ permissions,
678
697
  isSimplifiedPricingView,
679
698
  onShowManage,
680
699
  getSuccessUrl,
681
700
  suppressCalendarDateScroll,
701
+ mode: bookingAppMode,
682
702
  } = useBookingApp();
683
703
  const availabilitiesCache = useAvailabilitiesCache();
704
+ const isAdmin = permissions.viewerRole === 'admin';
684
705
  const [availabilities, setAvailabilities] = useState<Availability[]>([]);
685
706
  const [selectedAvailability, setSelectedAvailability] = useState<Availability | null>(null);
686
707
  const [selectedReturnOption, setSelectedReturnOption] = useState<ReturnOption | null>(null);
@@ -779,6 +800,12 @@ export function ChangeBookingFlow({
779
800
  differenceTotal: number;
780
801
  };
781
802
  } | 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);
782
809
  const hasAppliedInitialValuesRef = useRef(false);
783
810
  const hasAppliedInitialQuantitiesRef = useRef(false);
784
811
  const hasHydratedAddOnsFromReceiptRef = useRef(false);
@@ -797,8 +824,10 @@ export function ChangeBookingFlow({
797
824
  return null;
798
825
  }
799
826
  }, [initialValues?.dateTime, companyTimezone]);
827
+ const isProviderDashboardChange = Boolean(onChangeBooking);
828
+ const isCustomerSelfServeChange = !isProviderDashboardChange;
800
829
  /** Do not render catalog-/FE-derived dollar amounts in UI until `quoteChangeBooking` returns `serverDisplay`. */
801
- const suppressSelfServeCurrencyUi = true;
830
+ const suppressSelfServeCurrencyUi = isCustomerSelfServeChange;
802
831
 
803
832
  useEffect(() => {
804
833
  setPartnerAttributionConfirmed(false);
@@ -808,13 +837,13 @@ export function ChangeBookingFlow({
808
837
  * user picks a different return time — baseline is the first auto-selected return for this outbound.
809
838
  */
810
839
  const [implicitReturnBaselineId, setImplicitReturnBaselineId] = useState<string | null>(null);
811
- /** Promo from booking is fixed — show read-only, never add new. */
840
+ /** Any change flow (self-serve or provider): promo from booking is fixed — show read-only, never add new. */
812
841
  const lockedPromoCode = initialValues?.promoCode?.trim()
813
842
  ? initialValues.promoCode.trim().toUpperCase()
814
843
  : null;
815
844
  /** Public self-serve only: cannot reduce tickets below original counts. */
816
845
  const changeBookingMinimumQuantities = useMemo(() => {
817
- if (!initialValues?.bookingItems?.length) return undefined;
846
+ if (!isCustomerSelfServeChange || !initialValues?.bookingItems?.length) return undefined;
818
847
  const m: Record<string, number> = {};
819
848
  for (const item of initialValues.bookingItems) {
820
849
  const key = item.category?.trim();
@@ -822,7 +851,30 @@ export function ChangeBookingFlow({
822
851
  m[key] = Math.max(0, Number(item.count) || 0);
823
852
  }
824
853
  return m;
825
- }, [initialValues?.bookingItems]);
854
+ }, [isCustomerSelfServeChange, initialValues?.bookingItems]);
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);
826
878
  const [latestChangeQuote, setLatestChangeQuote] = useState<{
827
879
  priceDiff: number;
828
880
  currency: Currency;
@@ -1659,12 +1711,14 @@ export function ChangeBookingFlow({
1659
1711
  }, [initialValues?.productId, product.productId]);
1660
1712
 
1661
1713
  /**
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
+ * Receipt pricing on protected seats/fees — **customer self-serve only**: Rule A (exact receipt unit when same calendar day
1715
+ * + same product option) vs Rule B (`max(receipt, live)` when date or option changes). Provider dashboard uses live catalog.
1664
1716
  */
1665
1717
  const changeFlowApplyReceiptPaidFloors = useMemo(
1666
- () => changeFlowBookingParentProductIdForFloors === product.productId.trim(),
1667
- [changeFlowBookingParentProductIdForFloors, product.productId],
1718
+ () =>
1719
+ !isProviderDashboardChange &&
1720
+ changeFlowBookingParentProductIdForFloors === product.productId.trim(),
1721
+ [isProviderDashboardChange, changeFlowBookingParentProductIdForFloors, product.productId],
1668
1722
  );
1669
1723
 
1670
1724
  useEffect(() => {
@@ -1790,27 +1844,27 @@ export function ChangeBookingFlow({
1790
1844
 
1791
1845
  const initialAddOnMinQtyByKey = useMemo(() => {
1792
1846
  const map = new Map<string, number>();
1793
- if (false) return map;
1847
+ if (!isCustomerSelfServeChange) return map;
1794
1848
  for (const sel of initialAddOnBaselineSelections) {
1795
1849
  const key = `${sel.addOnId.trim()}::${sel.variantId?.trim() || ''}`;
1796
1850
  map.set(key, (map.get(key) ?? 0) + Math.max(1, Number(sel.quantity) || 1));
1797
1851
  }
1798
1852
  return map;
1799
- }, [true, initialAddOnBaselineSelections]);
1853
+ }, [isCustomerSelfServeChange, initialAddOnBaselineSelections]);
1800
1854
 
1801
1855
  const initialAddOnMinTotalByAddOnId = useMemo(() => {
1802
1856
  const map = new Map<string, number>();
1803
- if (false) return map;
1857
+ if (!isCustomerSelfServeChange) return map;
1804
1858
  for (const sel of initialAddOnBaselineSelections) {
1805
1859
  const addOnId = sel.addOnId.trim();
1806
1860
  map.set(addOnId, (map.get(addOnId) ?? 0) + Math.max(1, Number(sel.quantity) || 1));
1807
1861
  }
1808
1862
  return map;
1809
- }, [true, initialAddOnBaselineSelections]);
1863
+ }, [isCustomerSelfServeChange, initialAddOnBaselineSelections]);
1810
1864
 
1811
1865
  const applyChangeFlowAddOnFloor = useCallback(
1812
1866
  (nextSelections: Array<{ addOnId: string; variantId?: string; quantity?: number }>) => {
1813
- if (false || initialAddOnMinQtyByKey.size === 0) return nextSelections;
1867
+ if (!isCustomerSelfServeChange || initialAddOnMinQtyByKey.size === 0) return nextSelections;
1814
1868
  const qtyByKey = new Map<string, number>();
1815
1869
  for (const sel of nextSelections) {
1816
1870
  const key = `${sel.addOnId.trim()}::${sel.variantId?.trim() || ''}`;
@@ -1837,7 +1891,7 @@ export function ChangeBookingFlow({
1837
1891
  return { addOnId, variantId, quantity: qty };
1838
1892
  });
1839
1893
  },
1840
- [true, initialAddOnMinQtyByKey, initialAddOnMinTotalByAddOnId]
1894
+ [isCustomerSelfServeChange, initialAddOnMinQtyByKey, initialAddOnMinTotalByAddOnId]
1841
1895
  );
1842
1896
 
1843
1897
  const updateAddOnSelections = useCallback(
@@ -2451,14 +2505,14 @@ export function ChangeBookingFlow({
2451
2505
  const options = selectedAvailability?.returnOptions ?? [];
2452
2506
  const serverReturnMap = latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId;
2453
2507
  const floor = effectiveChangeFlowReturnUnitFloorPerPerson;
2454
- const applyReturnFloor = floor != null && true;
2508
+ const applyReturnFloor = floor != null && isCustomerSelfServeChange;
2455
2509
  return options.map((opt) => {
2456
2510
  const vacancyCredit = changeFlowSeatCreditForReturnAvailabilityId(opt.returnAvailabilityId);
2457
2511
  const adjustedVacancies = Math.max(0, opt.vacancies ?? 0) + vacancyCredit;
2458
2512
  const rawPerPerson = opt.priceAdjustmentByCurrency?.[currency] ?? 0;
2459
2513
  const flooredPerPerson = applyReturnFloor ? Math.max(rawPerPerson, floor) : rawPerPerson;
2460
2514
  let perPerson = flooredPerPerson;
2461
- if (true && serverReturnMap && opt.returnAvailabilityId) {
2515
+ if (isCustomerSelfServeChange && serverReturnMap && opt.returnAvailabilityId) {
2462
2516
  const sid = opt.returnAvailabilityId.trim();
2463
2517
  const sp = serverReturnMap[sid];
2464
2518
  if (sp != null && Number.isFinite(sp)) {
@@ -2481,7 +2535,7 @@ export function ChangeBookingFlow({
2481
2535
  }, [
2482
2536
  selectedAvailability?.returnOptions,
2483
2537
  effectiveChangeFlowReturnUnitFloorPerPerson,
2484
- true,
2538
+ isCustomerSelfServeChange,
2485
2539
  currency,
2486
2540
  changeFlowSeatCreditForReturnAvailabilityId,
2487
2541
  latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId,
@@ -2490,11 +2544,11 @@ export function ChangeBookingFlow({
2490
2544
  const selectedReturnOptionWithFloor = useMemo(() => {
2491
2545
  if (!selectedReturnOption) return selectedReturnOption;
2492
2546
  const floor = effectiveChangeFlowReturnUnitFloorPerPerson;
2493
- const applyReturnFloor = floor != null && true;
2547
+ const applyReturnFloor = floor != null && isCustomerSelfServeChange;
2494
2548
  const rawPerPerson = selectedReturnOption.priceAdjustmentByCurrency?.[currency] ?? 0;
2495
2549
  let perPerson = rawPerPerson;
2496
2550
  const serverReturnMap = latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId;
2497
- if (true && serverReturnMap && selectedReturnOption.returnAvailabilityId) {
2551
+ if (isCustomerSelfServeChange && serverReturnMap && selectedReturnOption.returnAvailabilityId) {
2498
2552
  const sid = selectedReturnOption.returnAvailabilityId.trim();
2499
2553
  const sp = serverReturnMap[sid];
2500
2554
  if (sp != null && Number.isFinite(sp)) {
@@ -2515,7 +2569,7 @@ export function ChangeBookingFlow({
2515
2569
  }, [
2516
2570
  selectedReturnOption,
2517
2571
  effectiveChangeFlowReturnUnitFloorPerPerson,
2518
- true,
2572
+ isCustomerSelfServeChange,
2519
2573
  currency,
2520
2574
  latestChangeQuote?.serverPreview?.returnOptionPriceByReturnAvailabilityId,
2521
2575
  ]);
@@ -2556,7 +2610,7 @@ export function ChangeBookingFlow({
2556
2610
  /** Quote `ticketUnitPriceByCategory` overrides catalog units for ticket rows (customer self-serve only). */
2557
2611
  const pricingForTicketSelector = useMemo(() => {
2558
2612
  const m = latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory;
2559
- if (false || !m) return pricing;
2613
+ if (!isCustomerSelfServeChange || !m) return pricing;
2560
2614
  return pricing.map((r) => {
2561
2615
  const u = m[r.category.toUpperCase()];
2562
2616
  if (u == null || !Number.isFinite(u)) return r;
@@ -2567,7 +2621,7 @@ export function ChangeBookingFlow({
2567
2621
  baseInDisplayCurrency: u,
2568
2622
  };
2569
2623
  });
2570
- }, [pricing, latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory, true]);
2624
+ }, [pricing, latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory, isCustomerSelfServeChange]);
2571
2625
 
2572
2626
  // Price breakdown: mid-layer returns line items (base + one per rule/deal). UI renders each line; rate in brackets when used.
2573
2627
  const getPriceBreakdown = useCallback((category: string, priceCAD: number, baseInDisplayCurrency: number | undefined, appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }> = []): PriceBreakdownData | null => {
@@ -2672,7 +2726,7 @@ export function ChangeBookingFlow({
2672
2726
 
2673
2727
  // When return selection (or refreshed availabilities) caps the party below current ticket counts, trim tickets (non-admin).
2674
2728
  useEffect(() => {
2675
- if (false || !selectedAvailability) return;
2729
+ if (isAdmin || !selectedAvailability) return;
2676
2730
  const cap = effectivePartySizeCap;
2677
2731
  if (totalQuantity <= cap) return;
2678
2732
  const over = totalQuantity - cap;
@@ -2700,7 +2754,7 @@ export function ChangeBookingFlow({
2700
2754
  effectivePartySizeCap,
2701
2755
  totalQuantity,
2702
2756
  selectedAvailability,
2703
- false,
2757
+ isAdmin,
2704
2758
  changeBookingMinimumQuantities,
2705
2759
  ]);
2706
2760
 
@@ -2774,7 +2828,7 @@ export function ChangeBookingFlow({
2774
2828
  */
2775
2829
  const changeFlowProtectedReturnAdjustment = useMemo(() => {
2776
2830
  if (totalQuantity <= 0) return returnPriceAdjustment;
2777
- if (false) return returnPriceAdjustment;
2831
+ if (isProviderDashboardChange) return returnPriceAdjustment;
2778
2832
  if (effectiveChangeFlowReturnUnitFloorPerPerson == null) return returnPriceAdjustment;
2779
2833
  const livePerPerson =
2780
2834
  returnOptionCatalogPerPerson ?? (selectedReturnOption?.priceAdjustmentByCurrency?.[currency] ?? 0);
@@ -2788,7 +2842,7 @@ export function ChangeBookingFlow({
2788
2842
  }, [
2789
2843
  totalQuantity,
2790
2844
  returnPriceAdjustment,
2791
- false,
2845
+ isProviderDashboardChange,
2792
2846
  effectiveChangeFlowReturnUnitFloorPerPerson,
2793
2847
  selectedReturnOption,
2794
2848
  returnOptionCatalogPerPerson,
@@ -2799,7 +2853,7 @@ export function ChangeBookingFlow({
2799
2853
 
2800
2854
  /** Return row amount for PriceSummary, Stripe breakdown, and CheckoutModal (catalog vs protected same-product-option). */
2801
2855
  const checkoutReturnLineAmount = useMemo(() => {
2802
- if (true) {
2856
+ if (isCustomerSelfServeChange) {
2803
2857
  return changeFlowProtectedReturnAdjustment;
2804
2858
  }
2805
2859
  if (changeFlowApplyReceiptPaidFloors) {
@@ -2807,7 +2861,7 @@ export function ChangeBookingFlow({
2807
2861
  }
2808
2862
  return returnPriceAdjustment;
2809
2863
  }, [
2810
- true,
2864
+ isCustomerSelfServeChange,
2811
2865
  changeFlowApplyReceiptPaidFloors,
2812
2866
  changeFlowProtectedReturnAdjustment,
2813
2867
  returnPriceAdjustment,
@@ -2879,13 +2933,35 @@ export function ChangeBookingFlow({
2879
2933
  return [...feeLineItems, ...addOnLines];
2880
2934
  }, [feeLineItems, addOnSelections, addOns]);
2881
2935
 
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
+
2882
2960
  const checkoutPriceSummaryLines = useMemo((): PriceSummaryLine[] => {
2883
2961
  if (!selectedAvailability) return [];
2884
2962
  const returnLineAmount = checkoutReturnLineAmount;
2885
- /** Show row when a return is selected and either the priced amount is non-zero or a receipt return floor exists (catalog slot can be $0 while floored total is not). */
2886
2963
  const showReturnLine =
2887
- Boolean(selectedReturnOption) &&
2888
- (Math.abs(returnLineAmount) > 0.0005 || effectiveChangeFlowReturnUnitFloorPerPerson != null);
2964
+ Boolean(selectedReturnOption) && Math.abs(returnLineAmount) > 0.0005;
2889
2965
  return [
2890
2966
  ...ticketLineItemsForChangeFlowDisplay.map((line): PriceSummaryLine => {
2891
2967
  const rate = pricing.find((r) => r.category === line.category);
@@ -2897,8 +2973,13 @@ export function ChangeBookingFlow({
2897
2973
  );
2898
2974
  return {
2899
2975
  kind: 'ticket',
2900
- lineKey: undefined,
2901
- editable: false,
2976
+ lineKey:
2977
+ showProviderPricingInlineEditor
2978
+ ? providerEditableLineByNormalizedLabel.get(normalizeReceiptLabel(line.category))?.lineKey
2979
+ : undefined,
2980
+ editable:
2981
+ showProviderPricingInlineEditor &&
2982
+ providerEditableLineByNormalizedLabel.has(normalizeReceiptLabel(line.category)),
2902
2983
  category: line.category,
2903
2984
  qty: line.qty,
2904
2985
  itemTotal: line.itemTotal,
@@ -2933,8 +3014,25 @@ export function ChangeBookingFlow({
2933
3014
  fee.name.toLowerCase().includes('license'));
2934
3015
  return {
2935
3016
  kind: 'line' as const,
2936
- lineKey: undefined,
2937
- editable: false,
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
+ ),
2938
3036
  label: feeLineItems.some((f) => f.name === fee.name)
2939
3037
  ? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
2940
3038
  : fee.name,
@@ -2960,7 +3058,8 @@ export function ChangeBookingFlow({
2960
3058
  effectiveCancellationPolicyLabel,
2961
3059
  feeLineItemsWithAddOns,
2962
3060
  feeLineItems,
2963
- effectiveChangeFlowReturnUnitFloorPerPerson,
3061
+ showProviderPricingInlineEditor,
3062
+ providerEditableLineByNormalizedLabel,
2964
3063
  ]);
2965
3064
 
2966
3065
  const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
@@ -3131,7 +3230,45 @@ export function ChangeBookingFlow({
3131
3230
 
3132
3231
  const clientMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.clientLineItems);
3133
3232
  const useBeClientLines = clientMappedFromApi.length > 0;
3134
- const clientLinesForMerge = useBeClientLines ? clientMappedFromApi : checkoutPriceSummaryLines;
3233
+ /**
3234
+ * Checkout passes tax/discount via PriceSummary props, not always as `priceSummaryLines`. Include them here so
3235
+ * drift rows and their sums align with `changeFlowNewBookingTotal` (subtotal + tax − promo).
3236
+ */
3237
+ const clientLinesForMerge: PriceSummaryLine[] = (() => {
3238
+ if (useBeClientLines) return clientMappedFromApi;
3239
+ const lines: PriceSummaryLine[] = [...checkoutPriceSummaryLines];
3240
+ const hasTaxLine = lines.some(
3241
+ (l) => l.kind === 'line' && String(l.type ?? '').toUpperCase() === 'TAX',
3242
+ );
3243
+ if (!hasTaxLine && !isTaxIncludedInPrice && Math.abs(effectiveTax) >= 0.005) {
3244
+ lines.push({
3245
+ kind: 'line',
3246
+ label: t('booking.tax') !== 'booking.tax' ? t('booking.tax') : 'Taxes and fees',
3247
+ amount: effectiveTax,
3248
+ type: 'TAX',
3249
+ });
3250
+ }
3251
+ const hasPromoSummaryLine = lines.some(
3252
+ (l) =>
3253
+ l.kind === 'line' &&
3254
+ (/PROMO|DISCOUNT|VOUCHER|GIFT/i.test(String(l.type ?? '')) ||
3255
+ (l.amount < -0.005 && /promo|discount/i.test(l.label))),
3256
+ );
3257
+ if (!hasPromoSummaryLine && Math.abs(effectivePromoDiscountAmount) >= 0.005) {
3258
+ const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
3259
+ const promoLabel =
3260
+ trimmedPromoCode.length > 0
3261
+ ? `Promo: ${trimmedPromoCode}`
3262
+ : originalReceipt?.promoLabel?.trim() || 'Discount';
3263
+ lines.push({
3264
+ kind: 'line',
3265
+ label: promoLabel,
3266
+ amount: -effectivePromoDiscountAmount,
3267
+ type: 'PROMO_CODE',
3268
+ });
3269
+ }
3270
+ return lines;
3271
+ })();
3135
3272
 
3136
3273
  const serverMappedFromApi = mapQuoteLineItemsToPriceSummaryLines(api?.serverLineItems);
3137
3274
  const useBeServerLines = serverMappedFromApi.length > 0;
@@ -3175,14 +3312,35 @@ export function ChangeBookingFlow({
3175
3312
  }
3176
3313
 
3177
3314
  const mergedForDrift = mergePriceSummaryLinesForDrift(clientLinesForMerge, serverLinesForMerge);
3178
- let rows =
3315
+ const rows =
3179
3316
  api?.lineComparisons && api.lineComparisons.length > 0
3180
- ? enrichLineComparisonsWithMergedRows(api.lineComparisons, mergedForDrift)
3317
+ ? mergeLineComparisonsWithFullDrift(api.lineComparisons, mergedForDrift)
3181
3318
  : mergedForDrift;
3182
3319
 
3320
+ const receiptAnchoring = api?.receiptAnchoring;
3321
+ let driftRows = rows;
3322
+ if (receiptAnchoring) {
3323
+ const adj = roundMoney(
3324
+ receiptAnchoring.reconciledTotalMajorUnits -
3325
+ receiptAnchoring.requestedCatalogLineSumMajorUnits,
3326
+ );
3327
+ if (Math.abs(adj) >= 0.005) {
3328
+ driftRows = [
3329
+ ...rows,
3330
+ {
3331
+ key: 'meta:receipt-anchoring',
3332
+ label: 'Receipt anchoring adjustment (BE only — not included in app total)',
3333
+ clientAmount: null,
3334
+ serverAmount: adj,
3335
+ delta: computePricingDriftDelta(null, adj),
3336
+ },
3337
+ ];
3338
+ }
3339
+ }
3340
+
3183
3341
  const hasTotalDelta = totalDelta != null && Math.abs(totalDelta) >= 0.005;
3184
3342
 
3185
- if (rows.length === 0 && !hasTotalDelta) {
3343
+ if (driftRows.length === 0 && !hasTotalDelta) {
3186
3344
  return null;
3187
3345
  }
3188
3346
 
@@ -3211,7 +3369,7 @@ export function ChangeBookingFlow({
3211
3369
 
3212
3370
  return (
3213
3371
  <ChangeBookingPricingDriftPanel
3214
- rows={rows}
3372
+ rows={driftRows}
3215
3373
  clientTotal={clientTotalForDrift}
3216
3374
  serverTotal={serverTotalFromQuote}
3217
3375
  totalDelta={totalDelta}
@@ -3219,6 +3377,7 @@ export function ChangeBookingFlow({
3219
3377
  locale={locale}
3220
3378
  ticketCartDetail={ticketCartDetail.length > 0 ? ticketCartDetail : undefined}
3221
3379
  serverTicketPricingTrace={latestChangeQuote.ticketPricingTrace ?? undefined}
3380
+ receiptAnchoring={receiptAnchoring}
3222
3381
  footnote={
3223
3382
  usesBeLinePayload
3224
3383
  ? 'Lines use pricingDriftDetail.clientLineItems / serverLineItems when the quote includes them; totals prefer explicit major-unit fields, then sums of those lines, then the live cart / receipt preview.'
@@ -3237,6 +3396,12 @@ export function ChangeBookingFlow({
3237
3396
  locale,
3238
3397
  ticketLineItemsForChangeFlowDisplay,
3239
3398
  pricingForTicketSelector,
3399
+ isTaxIncludedInPrice,
3400
+ effectiveTax,
3401
+ effectivePromoDiscountAmount,
3402
+ appliedPromoCode,
3403
+ originalReceipt?.promoLabel,
3404
+ t,
3240
3405
  ]);
3241
3406
 
3242
3407
  /** Replaces PriceSummary with non-numeric status until quote returns authoritative totals (no FE dollar amounts). */
@@ -3426,31 +3591,62 @@ export function ChangeBookingFlow({
3426
3591
  addOnSelections,
3427
3592
  initialAddOnMinQtyByKey,
3428
3593
  ]);
3429
- const hasChangeSelection = changeSelectionDetails.hasChangesFromInitial;
3594
+ const hasChangeSelection =
3595
+ isProviderDashboardChange
3596
+ ? changeSelectionDetails.hasOperationalChangesFromInitial
3597
+ : changeSelectionDetails.hasChangesFromInitial;
3430
3598
 
3431
3599
  const changeFlowNeedsServerPrice =
3432
- true &&
3600
+ isCustomerSelfServeChange &&
3433
3601
  hasChangeSelection &&
3434
3602
  !!initialValues?.bookingReference?.trim() &&
3435
3603
  !!lastName.trim();
3436
3604
 
3437
- const isChangeQuoteBlocked = true && latestChangeQuote?.canProceed === false;
3438
- const requiresReturnInChangeFlow = true && !!initialValues?.returnAvailabilityId?.trim();
3605
+ const isChangeQuoteBlocked = isCustomerSelfServeChange && latestChangeQuote?.canProceed === false;
3606
+ const requiresReturnInChangeFlow = isCustomerSelfServeChange && !!initialValues?.returnAvailabilityId?.trim();
3439
3607
  const missingRequiredReturnSelection = requiresReturnInChangeFlow && !selectedReturnOption;
3440
3608
 
3441
3609
  const changeFlowSubmitDisabled =
3442
3610
  missingRequiredReturnSelection ||
3443
- (true &&
3611
+ (isCustomerSelfServeChange &&
3444
3612
  changeFlowNeedsServerPrice &&
3445
3613
  (changeQuoteLoading || (!latestChangeQuote && !changeQuoteFetchError)));
3446
3614
 
3447
- const providerTotalsPreview = null;
3448
- const hasEffectiveChangeSelection = hasChangeSelection;
3615
+ const providerTotalsPreview =
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;
3449
3645
 
3450
3646
  const displayedChangeAmounts = resolveChangeFlowDisplayedAmounts({
3451
3647
  providerPreview: providerTotalsPreview,
3452
3648
  serverQuotePreview:
3453
- true && latestChangeQuote?.serverDisplay
3649
+ isCustomerSelfServeChange && latestChangeQuote?.serverDisplay
3454
3650
  ? latestChangeQuote.serverDisplay
3455
3651
  : null,
3456
3652
  fromCart: {
@@ -3466,13 +3662,13 @@ export function ChangeBookingFlow({
3466
3662
  const changeFlowClientEstimateDue = (() => {
3467
3663
  if (!originalReceipt) return totalPrice;
3468
3664
  // Customer self-serve: amount due comes from POST .../change/quote (`amountDueCents` / priceDiff), not FE delta math.
3469
- if (true && latestChangeQuote != null && !changeQuoteFetchError) {
3665
+ if (isCustomerSelfServeChange && latestChangeQuote != null && !changeQuoteFetchError) {
3470
3666
  return normalizeNearZeroOwed(latestChangeQuote.priceDiff);
3471
3667
  }
3472
3668
  return changeFlowBalanceVsOriginal({
3473
3669
  newTotal: displayChangeFlowProposedTotal,
3474
3670
  originalReceiptTotal: originalReceipt.total,
3475
- audience: 'customer',
3671
+ audience: isProviderDashboardChange ? 'provider' : 'customer',
3476
3672
  });
3477
3673
  })();
3478
3674
 
@@ -3481,6 +3677,14 @@ export function ChangeBookingFlow({
3481
3677
 
3482
3678
  const changeCheckoutButtonLabel = (() => {
3483
3679
  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
+ }
3484
3688
  if (changeFlowNeedsServerPrice) {
3485
3689
  if (changeQuoteLoading) {
3486
3690
  const tr = t('booking.updatingPrice');
@@ -3514,8 +3718,39 @@ export function ChangeBookingFlow({
3514
3718
  const checkoutFormError =
3515
3719
  (error || '') ||
3516
3720
  (missingRequiredReturnSelection ? 'Removing return option in self-serve is not available. Please contact support.' : '') ||
3517
- (true && isChangeQuoteBlocked ? (latestChangeQuote?.reasonIfBlocked ?? '') : '') ||
3518
- (true ? changeQuoteFetchError ?? '' : '');
3721
+ (isCustomerSelfServeChange && isChangeQuoteBlocked ? (latestChangeQuote?.reasonIfBlocked ?? '') : '') ||
3722
+ (isCustomerSelfServeChange ? changeQuoteFetchError ?? '' : '');
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
+ : [];
3519
3754
 
3520
3755
  const changeFlowSelectionPreview = useMemo((): ChangeFlowSelectionPreview | null => {
3521
3756
  if (!selectedAvailability || totalQuantity <= 0) return null;
@@ -3621,7 +3856,7 @@ export function ChangeBookingFlow({
3621
3856
 
3622
3857
  /** Debounced server quote so CTA + “amount owed” match PaymentIntent; avoids free confirm when FE estimate ≠ BE. */
3623
3858
  useEffect(() => {
3624
- if (false) {
3859
+ if (!isCustomerSelfServeChange) {
3625
3860
  setChangeQuoteLoading(false);
3626
3861
  setChangeQuoteFetchError(null);
3627
3862
  setLatestChangeQuote(null);
@@ -3712,7 +3947,7 @@ export function ChangeBookingFlow({
3712
3947
  window.clearTimeout(timer);
3713
3948
  };
3714
3949
  }, [
3715
- true,
3950
+ isCustomerSelfServeChange,
3716
3951
  hasChangeSelection,
3717
3952
  selectedAvailability,
3718
3953
  selectedAvailability?.dateTime,
@@ -3763,7 +3998,8 @@ export function ChangeBookingFlow({
3763
3998
  timesForSelectedDate,
3764
3999
  anchor,
3765
4000
  companyTimezone,
3766
- optionsMap
4001
+ optionsMap,
4002
+ isAdmin
3767
4003
  );
3768
4004
  changeFlowOutboundAnchorRef.current = null;
3769
4005
  if (matched) {
@@ -3854,7 +4090,8 @@ export function ChangeBookingFlow({
3854
4090
  const fromAnchor = pickReturnMatchingPreviousSelection(
3855
4091
  sorted,
3856
4092
  returnAnchor,
3857
- companyTimezone
4093
+ companyTimezone,
4094
+ isAdmin
3858
4095
  );
3859
4096
  changeFlowReturnAnchorRef.current = null;
3860
4097
  if (fromAnchor) {
@@ -3900,7 +4137,7 @@ export function ChangeBookingFlow({
3900
4137
  selectedAvailability,
3901
4138
  selectedReturnOption,
3902
4139
  companyTimezone,
3903
- false,
4140
+ isAdmin,
3904
4141
  initialValues?.returnAvailabilityId,
3905
4142
  initialValues?.returnDateTime,
3906
4143
  ]);
@@ -3963,9 +4200,9 @@ export function ChangeBookingFlow({
3963
4200
  const firstWithInventory = dates.find((d) => {
3964
4201
  const rows = availabilitiesByDate[d] ?? [];
3965
4202
  if (rows.length === 0) return false;
3966
- if (false) return rows.some((a) => (a.vacancies ?? 0) > 0);
4203
+ if (isAdmin) return rows.some((a) => (a.vacancies ?? 0) > 0);
3967
4204
  if (
3968
- true &&
4205
+ isCustomerSelfServeChange &&
3969
4206
  changeFlowInitialTicketCount > 0
3970
4207
  ) {
3971
4208
  return rows.some(
@@ -3974,7 +4211,7 @@ export function ChangeBookingFlow({
3974
4211
  }
3975
4212
  return rows.some((a) => (a.vacancies ?? 0) > 0);
3976
4213
  });
3977
- const first = firstWithInventory ?? (false && dates[0] ? dates[0] : undefined);
4214
+ const first = firstWithInventory ?? (isAdmin && dates[0] ? dates[0] : undefined);
3978
4215
  if (!first) return;
3979
4216
 
3980
4217
  hasAutoSelectedPartnerDateRef.current = true;
@@ -3997,8 +4234,8 @@ export function ChangeBookingFlow({
3997
4234
  selectedDate,
3998
4235
  dates,
3999
4236
  availabilitiesByDate,
4000
- false,
4001
- true,
4237
+ isAdmin,
4238
+ isCustomerSelfServeChange,
4002
4239
  changeFlowInitialTicketCount,
4003
4240
  getCalendarEffectiveOutboundVacancies,
4004
4241
  useWindowScroll,
@@ -4036,7 +4273,7 @@ export function ChangeBookingFlow({
4036
4273
  };
4037
4274
 
4038
4275
  const handleQuantityChange = (category: string, delta: number) => {
4039
- const maxAvailable = false ? Number.MAX_SAFE_INTEGER : effectivePartySizeCap;
4276
+ const maxAvailable = isAdmin ? Number.MAX_SAFE_INTEGER : effectivePartySizeCap;
4040
4277
  const currentQty = quantities[category] || 0;
4041
4278
  const minQ =
4042
4279
  changeBookingMinimumQuantities != null
@@ -4044,7 +4281,7 @@ export function ChangeBookingFlow({
4044
4281
  : 0;
4045
4282
  const newQty = Math.max(minQ, currentQty + delta);
4046
4283
  // Admin can overbook; non-admin cannot exceed vacancies
4047
- if (delta > 0 && !false && orderSummary.totalQuantity >= maxAvailable) {
4284
+ if (delta > 0 && !isAdmin && orderSummary.totalQuantity >= maxAvailable) {
4048
4285
  return;
4049
4286
  }
4050
4287
  setQuantities(prev => ({
@@ -4177,27 +4414,30 @@ export function ChangeBookingFlow({
4177
4414
  return;
4178
4415
  }
4179
4416
 
4180
- // Validate email (required)
4181
- if (!email) {
4182
- setError(t('booking.enterEmail') || 'Please enter your email address');
4183
- return;
4184
- }
4417
+ const skipContactFields = isProviderDashboardChange;
4418
+ if (!skipContactFields) {
4419
+ // Validate email (required)
4420
+ if (!email) {
4421
+ setError(t('booking.enterEmail') || 'Please enter your email address');
4422
+ return;
4423
+ }
4185
4424
 
4186
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
4187
- setError(t('booking.invalidEmail') || 'Please enter a valid email address');
4188
- return;
4189
- }
4425
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
4426
+ setError(t('booking.invalidEmail') || 'Please enter a valid email address');
4427
+ return;
4428
+ }
4190
4429
 
4191
- // Validate first name (required)
4192
- if (!firstName?.trim()) {
4193
- setError(t('booking.enterFirstName') || 'Please enter your first name');
4194
- return;
4195
- }
4430
+ // Validate first name (required)
4431
+ if (!firstName?.trim()) {
4432
+ setError(t('booking.enterFirstName') || 'Please enter your first name');
4433
+ return;
4434
+ }
4196
4435
 
4197
- // Validate last name (required for manage booking lookup)
4198
- if (!lastName?.trim()) {
4199
- setError(t('booking.enterLastName') || 'Please enter your last name');
4200
- return;
4436
+ // Validate last name (required for manage booking lookup)
4437
+ if (!lastName?.trim()) {
4438
+ setError(t('booking.enterLastName') || 'Please enter your last name');
4439
+ return;
4440
+ }
4201
4441
  }
4202
4442
 
4203
4443
  // Allow checkout if pickup location is selected OR if user chose "I don't know"
@@ -4225,6 +4465,44 @@ export function ChangeBookingFlow({
4225
4465
  return;
4226
4466
  }
4227
4467
 
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
+ }
4228
4506
 
4229
4507
  const bookingSourceContext = buildBookingSourceContext(bookingSourceAttribution, {
4230
4508
  clientChannelSource: inferClientBookingSourceFromProductIds(
@@ -4232,17 +4510,18 @@ export function ChangeBookingFlow({
4232
4510
  availabilityProductOptionId,
4233
4511
  ),
4234
4512
  forcePartnerPortalChannel: partnerPortalBooking,
4235
- forceDashboardSource: false,
4513
+ forceDashboardSource: bookingAppMode === 'provider-dashboard',
4236
4514
  });
4237
4515
 
4238
4516
  // Get the hotel name if a pickup location was selected
4239
4517
  const selectedPickupLocation = pickupLocationId
4240
4518
  ? product.pickupLocations?.find(loc => loc.id === pickupLocationId)
4241
4519
  : null;
4520
+ let quotedPriceDiff: number | null = null;
4242
4521
  let changeIntentIdForCheckout: string | undefined;
4243
4522
  let changeBookingReferenceForPaidFlow: string | undefined;
4244
4523
 
4245
- {
4524
+ if (isCustomerSelfServeChange) {
4246
4525
  const changeBookingReference = initialValues?.bookingReference?.trim();
4247
4526
  const changeLastName = lastName.trim();
4248
4527
  if (!changeBookingReference || !changeLastName) {
@@ -4270,6 +4549,7 @@ export function ChangeBookingFlow({
4270
4549
  },
4271
4550
  currency
4272
4551
  );
4552
+ quotedPriceDiff = quoteSlice.priceDiff;
4273
4553
  changeBookingReferenceForPaidFlow = changeBookingReference;
4274
4554
  changeIntentIdForCheckout = quoteSlice.changeIntentId ?? undefined;
4275
4555
  setLatestChangeQuote(
@@ -4361,11 +4641,13 @@ export function ChangeBookingFlow({
4361
4641
  // Build checkout breakdown from the exact same values we show in the UI and Stripe modal.
4362
4642
  // Backend will charge totalAmount and store this as the receipt so /manage matches.
4363
4643
  const taxForBreakdown = effectivePromoDiscountAmount > 0 ? effectiveTax : tax;
4364
- const amountDueForCheckout = changeFlowBalanceVsOriginal({
4365
- newTotal: changeFlowNewBookingTotal,
4366
- originalReceiptTotal: originalReceipt?.total ?? 0,
4367
- audience: 'customer',
4368
- });
4644
+ const amountDueForCheckout = isCustomerSelfServeChange
4645
+ ? changeFlowBalanceVsOriginal({
4646
+ newTotal: changeFlowNewBookingTotal,
4647
+ originalReceiptTotal: originalReceipt?.total ?? 0,
4648
+ audience: 'customer',
4649
+ })
4650
+ : totalPrice;
4369
4651
  const lines = [
4370
4652
  ...ticketLineItemsForChangeFlowDisplay.map((line) => ({
4371
4653
  label: line.category,
@@ -4442,7 +4724,8 @@ export function ChangeBookingFlow({
4442
4724
  roundingLabel: t('booking.rounding') || 'Rounding',
4443
4725
  });
4444
4726
 
4445
- const paymentIntent = await createChangeBookingPaymentIntent(
4727
+ const paymentIntent = isCustomerSelfServeChange
4728
+ ? await createChangeBookingPaymentIntent(
4446
4729
  (() => {
4447
4730
  const id = changeIntentIdForCheckout ?? latestChangeQuote?.changeIntentId;
4448
4731
  if (!id) {
@@ -4450,8 +4733,76 @@ export function ChangeBookingFlow({
4450
4733
  }
4451
4734
  return id;
4452
4735
  })()
4453
- );
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
+ });
4454
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
+ }
4455
4806
 
4456
4807
  const ticketLinesForModal: CheckoutModalLineItem[] = ticketLineItemsForChangeFlowDisplay.map((line) => {
4457
4808
  const rate = pricing.find((r) => r.category === line.category);
@@ -4473,7 +4824,7 @@ export function ChangeBookingFlow({
4473
4824
  // Paid change: always return to stable ref+lastName + explicit intent (not reservationRef).
4474
4825
  // /manage-booking runs bounded refresh only when `from=change_payment` (see manage-booking page).
4475
4826
  successUrlOverride:
4476
- true && changeBookingReferenceForPaidFlow
4827
+ isCustomerSelfServeChange && changeBookingReferenceForPaidFlow
4477
4828
  ? (() => {
4478
4829
  const origin = typeof window !== 'undefined' ? window.location.origin : '';
4479
4830
  const ref = encodeURIComponent(
@@ -4501,7 +4852,7 @@ export function ChangeBookingFlow({
4501
4852
  promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
4502
4853
  discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
4503
4854
  changeTotals:
4504
- true && originalReceipt
4855
+ isCustomerSelfServeChange && originalReceipt
4505
4856
  ? {
4506
4857
  previousTotal: originalReceipt.total,
4507
4858
  newTotal: displayChangeFlowProposedTotal,
@@ -4570,7 +4921,85 @@ export function ChangeBookingFlow({
4570
4921
  }
4571
4922
  };
4572
4923
 
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
+ };
4573
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
+ };
4574
5003
 
4575
5004
  if (activeOptions.length === 0) {
4576
5005
  return (
@@ -4582,6 +5011,17 @@ export function ChangeBookingFlow({
4582
5011
 
4583
5012
  return (
4584
5013
  <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
+ />
4585
5025
  {checkoutModalData && (
4586
5026
  <CheckoutModal
4587
5027
  open={showCheckoutModal}
@@ -4655,12 +5095,12 @@ export function ChangeBookingFlow({
4655
5095
  syncVisibleWeekToSelectedDate={true}
4656
5096
  selectableSoldOutDate={changeFlowOriginalDate}
4657
5097
  partySizeRequiredForCalendarSelection={
4658
- true && !false && changeFlowInitialTicketCount > 0
5098
+ isCustomerSelfServeChange && !isAdmin && changeFlowInitialTicketCount > 0
4659
5099
  ? changeFlowInitialTicketCount
4660
5100
  : undefined
4661
5101
  }
4662
5102
  getEffectiveVacancies={
4663
- true && !false && changeFlowInitialTicketCount > 0
5103
+ isCustomerSelfServeChange && !isAdmin && changeFlowInitialTicketCount > 0
4664
5104
  ? getCalendarEffectiveOutboundVacancies
4665
5105
  : undefined
4666
5106
  }
@@ -4684,7 +5124,7 @@ export function ChangeBookingFlow({
4684
5124
  earliestDate={earliestAvailabilityDate}
4685
5125
  onVisibleRangeChange={handleVisibleRangeChange}
4686
5126
  currency={currency}
4687
- showCapacity={false}
5127
+ showCapacity={isAdmin}
4688
5128
  extraDiscountPercent={calendarDiscountPercent}
4689
5129
  capDiscountBadgesToBookingDate={changeFlowOriginalDate}
4690
5130
  />
@@ -4740,7 +5180,7 @@ export function ChangeBookingFlow({
4740
5180
  selectedTicketCount={totalQuantity}
4741
5181
  optionsMap={optionsMap}
4742
5182
  hasAnyMostPopular={hasAnyMostPopular}
4743
- isAdmin={false}
5183
+ isAdmin={isAdmin}
4744
5184
  pickupLocationSkipped={pickupLocationSkipped}
4745
5185
  t={t}
4746
5186
  onTimeSelect={handleTimeSelect}
@@ -4756,7 +5196,7 @@ export function ChangeBookingFlow({
4756
5196
  companyTimezone={companyTimezone}
4757
5197
  currency={currency}
4758
5198
  locale={locale}
4759
- isAdmin={false}
5199
+ isAdmin={isAdmin}
4760
5200
  t={t}
4761
5201
  onReturnSelect={(option) => {
4762
5202
  const raw = selectedAvailability.returnOptions?.find(
@@ -4784,7 +5224,7 @@ export function ChangeBookingFlow({
4784
5224
  resourceCount={selectedReturnOption ? null : (selectedAvailability.resourceCount ?? null)}
4785
5225
  currency={currency}
4786
5226
  locale={locale}
4787
- isAdmin={false}
5227
+ isAdmin={isAdmin}
4788
5228
  isSimplifiedPricingView={isSimplifiedPricingView}
4789
5229
  t={t}
4790
5230
  onQuantityChange={handleQuantityChange}
@@ -4806,7 +5246,7 @@ export function ChangeBookingFlow({
4806
5246
  currency={currency}
4807
5247
  locale={locale}
4808
5248
  onSelectionsChange={updateAddOnSelections}
4809
- minimumTotalByAddOnId={initialAddOnMinTotalByAddOnId}
5249
+ minimumTotalByAddOnId={isCustomerSelfServeChange ? initialAddOnMinTotalByAddOnId : undefined}
4810
5250
  suppressPrices={false}
4811
5251
  />
4812
5252
  )}
@@ -4836,15 +5276,99 @@ export function ChangeBookingFlow({
4836
5276
  currency={currency}
4837
5277
  locale={locale}
4838
5278
  t={t}
4839
- extraBetweenTaxAndTotal={undefined}
4840
- extraBeforeSubtotal={undefined}
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
+ }
4841
5365
  firstName={firstName}
4842
5366
  lastName={lastName}
4843
5367
  email={email}
4844
5368
  onFirstNameChange={(v) => { setFirstName(v); setError(''); }}
4845
5369
  onLastNameChange={(v) => { setLastName(v); setError(''); }}
4846
5370
  onEmailChange={(v) => { setEmail(v); setError(''); }}
4847
- readOnlyContactFields={false}
5371
+ readOnlyContactFields
4848
5372
  pickupLocations={
4849
5373
  selectedDate && product.pickupLocations && product.pickupLocations.length > 0
4850
5374
  ? product.pickupLocations
@@ -4878,29 +5402,38 @@ export function ChangeBookingFlow({
4878
5402
  setTermsAccepted(checked);
4879
5403
  setTermsAcceptedAt(checked ? new Date().toISOString() : null);
4880
5404
  }}
4881
- isAdmin={false}
5405
+ isAdmin={isAdmin}
4882
5406
  showCommunicationAdminSection={false}
4883
- skipConfirmationCommunications={false}
4884
- disableAutoCommunications={false}
4885
- onSkipConfirmationChange={() => {}}
4886
- onDisableCommunicationsChange={() => {}}
5407
+ skipConfirmationCommunications={skipConfirmationCommunications}
5408
+ disableAutoCommunications={disableAutoCommunications}
5409
+ onSkipConfirmationChange={setSkipConfirmationCommunications}
5410
+ onDisableCommunicationsChange={setDisableAutoCommunications}
4887
5411
  error={checkoutFormError}
4888
5412
  loading={loading}
4889
5413
  totalQuantity={totalQuantity}
4890
5414
  onCheckout={handleCheckout}
4891
5415
  submitLabel={changeCheckoutButtonLabel ?? deferredInvoiceSubmitLabel}
4892
5416
  hideSubmitButton={
4893
- showCheckoutModal || !hasEffectiveChangeSelection || isChangeQuoteBlocked
5417
+ showCheckoutModal ||
5418
+ showAdminPaymentChoice ||
5419
+ !hasEffectiveChangeSelection ||
5420
+ isChangeQuoteBlocked
4894
5421
  }
4895
5422
  submitDisabled={changeFlowSubmitDisabled}
4896
5423
  attributionSummary={flowUi?.partnerAttributionSummary}
4897
5424
  attributionConfirmLabel={flowUi?.partnerAttributionConfirmLabel}
4898
5425
  attributionConfirmed={partnerAttributionConfirmed}
4899
5426
  onAttributionConfirmedChange={setPartnerAttributionConfirmed}
4900
- lineAmountInputs={undefined}
4901
- onLineAmountInputChange={undefined}
4902
- onLineAmountInputBlur={undefined}
4903
- onLineAmountReset={undefined}
5427
+ lineAmountInputs={showProviderPricingInlineEditor ? providerPricingUi?.lineAmountInputs : undefined}
5428
+ onLineAmountInputChange={
5429
+ showProviderPricingInlineEditor ? providerPricingUi?.onLineAmountInputChange : undefined
5430
+ }
5431
+ onLineAmountInputBlur={
5432
+ showProviderPricingInlineEditor ? providerPricingUi?.onLineAmountInputBlur : undefined
5433
+ }
5434
+ onLineAmountReset={
5435
+ showProviderPricingInlineEditor ? providerPricingUi?.onLineAmountReset : undefined
5436
+ }
4904
5437
  />
4905
5438
  </>
4906
5439
  )}