@ticketboothapp/booking 1.2.63 → 1.2.65

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