@ticketboothapp/booking 1.2.123 → 1.2.125

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.
Files changed (112) hide show
  1. package/CHANGE_BOOKING_BE_HANDOFF.md +15 -4
  2. package/package.json +1 -1
  3. package/src/components/booking/AdminChangeBookingContent.tsx +276 -0
  4. package/src/components/booking/AdminChangeBookingFlow.tsx +651 -6202
  5. package/src/components/booking/AdminChangeCheckoutDialogs.tsx +105 -0
  6. package/src/components/booking/AdminChangeCheckoutPanel.tsx +307 -0
  7. package/src/components/booking/AdminChangePricingPanel.tsx +244 -0
  8. package/src/components/booking/AdminChangeProductSwitcher.tsx +50 -0
  9. package/src/components/booking/AdminChangeReceiptComparison.tsx +167 -0
  10. package/src/components/booking/ChangeBookingCalendarPanel.tsx +126 -0
  11. package/src/components/booking/ChangeBookingCheckoutPanel.tsx +204 -0
  12. package/src/components/booking/ChangeBookingFlow.tsx +576 -4648
  13. package/src/components/booking/ChangeBookingItineraryPanel.tsx +115 -0
  14. package/src/components/booking/ChangeBookingQuoteStatusPlaceholder.tsx +57 -0
  15. package/src/components/booking/ChangeBookingSelectionControlsPanel.tsx +157 -0
  16. package/src/components/booking/ChangeBookingTicketsAndAddOnsPanel.tsx +102 -0
  17. package/src/components/booking/CheckoutModal.tsx +68 -49
  18. package/src/components/booking/NewBookingFlow.tsx +485 -3505
  19. package/src/components/booking/PriceBreakdown.tsx +17 -3
  20. package/src/components/booking/PriceSummary.tsx +21 -3
  21. package/src/components/booking/PrivateShuttleAddOnsSection.tsx +102 -0
  22. package/src/components/booking/PrivateShuttleBookingFlow.tsx +407 -2788
  23. package/src/components/booking/PrivateShuttleCheckoutDialogs.tsx +106 -0
  24. package/src/components/booking/PrivateShuttleCheckoutSection.tsx +333 -0
  25. package/src/components/booking/PrivateShuttleItinerarySection.tsx +95 -0
  26. package/src/components/booking/PrivateShuttleMediaIntro.tsx +62 -0
  27. package/src/components/booking/PrivateShuttleOptionSection.tsx +81 -0
  28. package/src/components/booking/PrivateShuttlePassengerSection.tsx +148 -0
  29. package/src/components/booking/PrivateShuttlePickupSection.tsx +91 -0
  30. package/src/components/booking/PrivateShuttlePreferencesSection.tsx +75 -0
  31. package/src/components/booking/PrivateShuttleStartTimeSection.tsx +88 -0
  32. package/src/components/booking/StandardBookingCheckoutDialogs.tsx +106 -0
  33. package/src/components/booking/StandardBookingCheckoutSection.tsx +185 -0
  34. package/src/components/booking/StandardBookingItineraryPanel.tsx +125 -0
  35. package/src/components/booking/StandardBookingMediaIntro.tsx +69 -0
  36. package/src/components/booking/StandardBookingSelectionControlsPanel.tsx +236 -0
  37. package/src/components/booking/admin-change-customer-quote-runner.ts +119 -0
  38. package/src/components/booking/admin-change-flow-state-helpers.ts +124 -0
  39. package/src/components/booking/admin-change-payment-choice-runner.ts +172 -0
  40. package/src/components/booking/admin-change-provider-payload.ts +101 -0
  41. package/src/components/booking/change-booking-checkout-builders.ts +185 -0
  42. package/src/components/booking/change-booking-checkout-quote-runner.ts +207 -0
  43. package/src/components/booking/change-booking-flow-helpers.ts +706 -0
  44. package/src/components/booking/change-booking-payment-modal-builders.ts +151 -0
  45. package/src/components/booking/change-booking-quote-guards.ts +223 -0
  46. package/src/components/booking/change-booking-quote-state.ts +64 -0
  47. package/src/components/booking/pricing-v2-checkout-summary.ts +144 -0
  48. package/src/components/booking/private-shuttle-availability.ts +111 -0
  49. package/src/components/booking/private-shuttle-checkout-builders.ts +264 -0
  50. package/src/components/booking/private-shuttle-checkout-controller.ts +744 -0
  51. package/src/components/booking/private-shuttle-payment-choice-runner.ts +271 -0
  52. package/src/components/booking/private-shuttle-provider-change-runner.ts +73 -0
  53. package/src/components/booking/private-shuttle-reservation-runner.ts +260 -0
  54. package/src/components/booking/standard-booking-availability.ts +305 -0
  55. package/src/components/booking/standard-booking-checkout-builders.ts +289 -0
  56. package/src/components/booking/standard-booking-checkout-controller.ts +659 -0
  57. package/src/components/booking/standard-booking-payment-choice-runner.ts +274 -0
  58. package/src/components/booking/standard-booking-reservation-runner.ts +229 -0
  59. package/src/components/booking/use-private-shuttle-availability.ts +602 -0
  60. package/src/components/booking/use-standard-booking-availability.ts +806 -0
  61. package/src/components/booking/useActiveProductOptions.ts +26 -0
  62. package/src/components/booking/useAdminChangeCheckoutController.ts +806 -0
  63. package/src/components/booking/useAdminChangeOrderSummaryModel.ts +246 -0
  64. package/src/components/booking/useAdminChangePriceSummary.ts +416 -0
  65. package/src/components/booking/useAdminChangePricingDebugPanel.tsx +178 -0
  66. package/src/components/booking/useAdminChangeProductReset.ts +60 -0
  67. package/src/components/booking/useAdminChangeProducts.ts +102 -0
  68. package/src/components/booking/useAdminChangePromoDiscount.ts +235 -0
  69. package/src/components/booking/useAdminChangeProtectedPricing.ts +280 -0
  70. package/src/components/booking/useAdminChangeQuoteDisplayState.tsx +437 -0
  71. package/src/components/booking/useAdminChangeQuotePreview.ts +506 -0
  72. package/src/components/booking/useAdminChangeReceiptDerivation.ts +203 -0
  73. package/src/components/booking/useAdminCustomReceiptLines.ts +57 -0
  74. package/src/components/booking/useAdminProviderPricingAdjustments.ts +146 -0
  75. package/src/components/booking/useBookingAvailabilityAddOns.ts +54 -0
  76. package/src/components/booking/useBookingPromoAndQuantityController.ts +163 -0
  77. package/src/components/booking/useBookingQuantityCapTrim.ts +56 -0
  78. package/src/components/booking/useBookingViewItemAnalytics.ts +30 -0
  79. package/src/components/booking/useChangeBookingAddOnFloorController.ts +109 -0
  80. package/src/components/booking/useChangeBookingAutoSelections.ts +148 -0
  81. package/src/components/booking/useChangeBookingAvailabilities.ts +692 -0
  82. package/src/components/booking/useChangeBookingCheckoutController.ts +461 -0
  83. package/src/components/booking/useChangeBookingInitialHydration.ts +204 -0
  84. package/src/components/booking/useChangeBookingItineraryUiState.ts +133 -0
  85. package/src/components/booking/useChangeBookingPickupItinerary.ts +472 -0
  86. package/src/components/booking/useChangeBookingPriceSummary.ts +199 -0
  87. package/src/components/booking/useChangeBookingPromoDiscount.ts +218 -0
  88. package/src/components/booking/useChangeBookingProtectedPricing.ts +256 -0
  89. package/src/components/booking/useChangeBookingQuotePreview.ts +442 -0
  90. package/src/components/booking/useChangeBookingReceiptDerivation.ts +128 -0
  91. package/src/components/booking/useChangeBookingReceiptFloorController.ts +449 -0
  92. package/src/components/booking/useChangeBookingSelection.ts +362 -0
  93. package/src/components/booking/useChangeBookingSelectionDetails.ts +200 -0
  94. package/src/components/booking/useChangeBookingSummaryLines.ts +169 -0
  95. package/src/components/booking/usePrivateShuttlePriceSummary.ts +513 -0
  96. package/src/components/booking/usePrivateShuttlePromoController.ts +143 -0
  97. package/src/components/booking/usePrivateShuttlePromoDiscount.ts +124 -0
  98. package/src/components/booking/useSeedPromoCodeInput.ts +13 -0
  99. package/src/components/booking/useStandardBookingAutoSelections.ts +224 -0
  100. package/src/components/booking/useStandardBookingPickupItinerary.ts +473 -0
  101. package/src/components/booking/useStandardBookingPriceSummary.ts +438 -0
  102. package/src/components/booking/useStandardBookingPromoController.ts +258 -0
  103. package/src/components/booking/useStandardBookingPromoDiscount.ts +203 -0
  104. package/src/data/products-config.json +1 -1
  105. package/src/lib/booking/change-booking-server-preview.ts +134 -20
  106. package/src/lib/booking/change-flow-pricing.ts +31 -11
  107. package/src/lib/booking/checkout-breakdown.ts +66 -1
  108. package/src/lib/booking/pricing-config.ts +10 -0
  109. package/src/lib/booking/source-metadata.ts +58 -0
  110. package/src/lib/booking-api.ts +155 -3
  111. package/src/lib/pricing-v2-shadow.ts +272 -0
  112. package/test/change-booking-helpers.test.ts +206 -0
@@ -0,0 +1,246 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import {
3
+ type Availability,
4
+ type PricingConfig,
5
+ type Product,
6
+ type ReturnOption,
7
+ } from '../../lib/booking-api';
8
+ import {
9
+ changeFlowTicketLineTotalWithReceiptFloor,
10
+ type ChangeFlowProtectedReceiptPricing,
11
+ } from '../../lib/booking/change-flow-pricing';
12
+ import {
13
+ computeOrderSummary,
14
+ computePriceBreakdown,
15
+ type OrderSummary,
16
+ type PriceBreakdown as PriceBreakdownData,
17
+ } from '../../lib/booking/pricing';
18
+ import type { Currency } from './CurrencySwitcher';
19
+ import {
20
+ buildPricingFromAvailability,
21
+ getAvailabilityOptionId,
22
+ } from './change-booking-flow-helpers';
23
+ import type { AdminChangeLatestQuote } from './useAdminChangeCheckoutController';
24
+
25
+ type ProductOption = NonNullable<Product['options']>[number];
26
+ type PrecomputedPricesByOption = Parameters<typeof buildPricingFromAvailability>[2];
27
+
28
+ export interface UseAdminChangeOrderSummaryModelParams {
29
+ selectedAvailability: Availability | null;
30
+ activeOptions: ProductOption[];
31
+ precomputedPricesByOption: PrecomputedPricesByOption;
32
+ currency: Currency;
33
+ pricingConfig: PricingConfig | null;
34
+ isSimplifiedPricingView: boolean;
35
+ latestChangeQuote: AdminChangeLatestQuote | null;
36
+ useAdminFeAuthoritativeQuote: boolean;
37
+ isCustomerSelfServeChange: boolean;
38
+ quantities: Record<string, number>;
39
+ returnOptionForOrderSummary: ReturnOption | null;
40
+ cancellationPolicyId: string | null;
41
+ changeFlowApplyReceiptPaidFloors: boolean;
42
+ changeFlowTicketBookedUnitPriceByCategory: Map<string, number>;
43
+ changeFlowInitialTicketQtyByCategory: Map<string, number>;
44
+ changeFlowProtectedReceiptPricing: ChangeFlowProtectedReceiptPricing;
45
+ t: (key: string) => string;
46
+ }
47
+
48
+ export function useAdminChangeOrderSummaryModel({
49
+ selectedAvailability,
50
+ activeOptions,
51
+ precomputedPricesByOption,
52
+ currency,
53
+ pricingConfig,
54
+ isSimplifiedPricingView,
55
+ latestChangeQuote,
56
+ useAdminFeAuthoritativeQuote,
57
+ isCustomerSelfServeChange,
58
+ quantities,
59
+ returnOptionForOrderSummary,
60
+ cancellationPolicyId,
61
+ changeFlowApplyReceiptPaidFloors,
62
+ changeFlowTicketBookedUnitPriceByCategory,
63
+ changeFlowInitialTicketQtyByCategory,
64
+ changeFlowProtectedReceiptPricing,
65
+ t,
66
+ }: UseAdminChangeOrderSummaryModelParams) {
67
+ const hasFees = useMemo(
68
+ () =>
69
+ Boolean(
70
+ pricingConfig?.fees &&
71
+ Object.keys(pricingConfig.fees).length > 0 &&
72
+ Object.values(pricingConfig.fees).some((fee) => (fee?.feePerPerson ?? 0) > 0),
73
+ ),
74
+ [pricingConfig?.fees],
75
+ );
76
+
77
+ const pricing = useMemo(
78
+ () =>
79
+ buildPricingFromAvailability(
80
+ selectedAvailability,
81
+ activeOptions,
82
+ precomputedPricesByOption,
83
+ currency,
84
+ pricingConfig,
85
+ hasFees,
86
+ isSimplifiedPricingView,
87
+ ),
88
+ [
89
+ selectedAvailability,
90
+ currency,
91
+ hasFees,
92
+ pricingConfig,
93
+ precomputedPricesByOption,
94
+ activeOptions,
95
+ isSimplifiedPricingView,
96
+ ],
97
+ );
98
+
99
+ const pricingForTicketSelector = useMemo(() => {
100
+ const ticketUnitPriceByCategory = latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory;
101
+ if (
102
+ useAdminFeAuthoritativeQuote ||
103
+ !isCustomerSelfServeChange ||
104
+ !ticketUnitPriceByCategory
105
+ ) {
106
+ return pricing;
107
+ }
108
+ return pricing.map((rate) => {
109
+ const unitPrice = ticketUnitPriceByCategory[rate.category.toUpperCase()];
110
+ if (unitPrice == null || !Number.isFinite(unitPrice)) return rate;
111
+ return {
112
+ ...rate,
113
+ price: unitPrice,
114
+ priceCAD: unitPrice,
115
+ baseInDisplayCurrency: unitPrice,
116
+ };
117
+ });
118
+ }, [
119
+ pricing,
120
+ latestChangeQuote?.serverPreview?.ticketUnitPriceByCategory,
121
+ isCustomerSelfServeChange,
122
+ useAdminFeAuthoritativeQuote,
123
+ ]);
124
+
125
+ const getPriceBreakdown = useCallback(
126
+ (
127
+ category: string,
128
+ priceCAD: number,
129
+ baseInDisplayCurrency: number | undefined,
130
+ appliedAdjustments: Array<{
131
+ type: string;
132
+ id: string;
133
+ name: string;
134
+ changeByCurrency?: Record<string, number>;
135
+ }> = [],
136
+ ): PriceBreakdownData | null => {
137
+ if (!pricingConfig) return null;
138
+ const selectedOption = activeOptions.find(
139
+ (option) =>
140
+ option.optionId ===
141
+ (selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : ''),
142
+ );
143
+ const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
144
+ return computePriceBreakdown(
145
+ pricingConfig,
146
+ currency,
147
+ priceCAD,
148
+ basePriceCAD,
149
+ hasFees,
150
+ appliedAdjustments,
151
+ undefined,
152
+ baseInDisplayCurrency,
153
+ isSimplifiedPricingView,
154
+ );
155
+ },
156
+ [pricingConfig, currency, hasFees, activeOptions, selectedAvailability, isSimplifiedPricingView],
157
+ );
158
+
159
+ const orderSummary: OrderSummary = useMemo(
160
+ () =>
161
+ computeOrderSummary(
162
+ quantities,
163
+ pricingForTicketSelector,
164
+ returnOptionForOrderSummary,
165
+ pricingConfig ?? null,
166
+ currency,
167
+ hasFees,
168
+ cancellationPolicyId,
169
+ ),
170
+ [
171
+ quantities,
172
+ pricingForTicketSelector,
173
+ returnOptionForOrderSummary,
174
+ pricingConfig,
175
+ currency,
176
+ hasFees,
177
+ cancellationPolicyId,
178
+ ],
179
+ );
180
+
181
+ const {
182
+ totalQuantity,
183
+ subtotal,
184
+ tax,
185
+ feeLineItems,
186
+ returnPriceAdjustment,
187
+ cancellationPolicyFee,
188
+ isTaxIncludedInPrice,
189
+ ticketLineItems,
190
+ } = orderSummary;
191
+
192
+ const changeFlowProtectedTicketSubtotal = useMemo(() => {
193
+ const currentTicketSubtotal = ticketLineItems.reduce(
194
+ (sum, line) => sum + Math.max(0, Number(line.itemTotal) || 0),
195
+ 0,
196
+ );
197
+ if (!changeFlowApplyReceiptPaidFloors) return currentTicketSubtotal;
198
+ return ticketLineItems.reduce((sum, line) => {
199
+ const category = line.category?.trim().toUpperCase();
200
+ const qty = Math.max(0, Number(line.qty) || 0);
201
+ if (!category || qty <= 0) return sum;
202
+ return (
203
+ sum +
204
+ changeFlowTicketLineTotalWithReceiptFloor({
205
+ qty,
206
+ baselineQtyForCategory: changeFlowInitialTicketQtyByCategory.get(category) ?? 0,
207
+ receiptUnitFloor: changeFlowTicketBookedUnitPriceByCategory.get(category),
208
+ liveLineTotal: Number(line.itemTotal) || 0,
209
+ protectedReceiptPricing: changeFlowProtectedReceiptPricing,
210
+ })
211
+ );
212
+ }, 0);
213
+ }, [
214
+ changeFlowApplyReceiptPaidFloors,
215
+ ticketLineItems,
216
+ changeFlowTicketBookedUnitPriceByCategory,
217
+ changeFlowInitialTicketQtyByCategory,
218
+ changeFlowProtectedReceiptPricing,
219
+ ]);
220
+
221
+ const selectedCancellationPolicy = pricingConfig?.cancellationPolicies?.find(
222
+ (policy) => policy.id === cancellationPolicyId,
223
+ );
224
+ const effectiveCancellationPolicyLabel =
225
+ selectedCancellationPolicy?.label ??
226
+ (t('booking.flexibleCancellation') || 'Flexible cancellation');
227
+
228
+ return {
229
+ hasFees,
230
+ pricing,
231
+ pricingForTicketSelector,
232
+ getPriceBreakdown,
233
+ orderSummary,
234
+ totalQuantity,
235
+ subtotal,
236
+ tax,
237
+ feeLineItems,
238
+ returnPriceAdjustment,
239
+ cancellationPolicyFee,
240
+ isTaxIncludedInPrice,
241
+ ticketLineItems,
242
+ changeFlowProtectedTicketSubtotal,
243
+ selectedCancellationPolicy,
244
+ effectiveCancellationPolicyLabel,
245
+ };
246
+ }
@@ -0,0 +1,416 @@
1
+ import { useMemo } from 'react';
2
+ import {
3
+ type AddOn,
4
+ type Availability,
5
+ type PricingConfig,
6
+ type ReturnOption,
7
+ } from '../../lib/booking-api';
8
+ import {
9
+ resolveChangeFlowNewBookingTotal,
10
+ type ChangeFlowProtectedReceiptPricing,
11
+ } from '../../lib/booking/change-flow-pricing';
12
+ import type {
13
+ OrderSummaryFeeLine,
14
+ OrderSummaryTicketLine,
15
+ PriceBreakdown as PriceBreakdownData,
16
+ } from '../../lib/booking/pricing';
17
+ import type { ChangeBookingFlowProps } from './booking-flow-types';
18
+ import type { Currency } from './CurrencySwitcher';
19
+ import type { PriceSummaryLine } from './PriceSummary';
20
+ import { useEditableSummaryLines } from './useEditableSummaryLines';
21
+ import {
22
+ isPromoDiscountGiftCardOrVoucherLine,
23
+ omitZeroAmountPromoDiscountSummaryLines,
24
+ type PricingRate,
25
+ } from './change-booking-flow-helpers';
26
+ import { useAdminChangeProtectedPricing } from './useAdminChangeProtectedPricing';
27
+ import { useAdminChangePromoDiscount } from './useAdminChangePromoDiscount';
28
+
29
+ type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
30
+ type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
31
+ type ProviderPricingUi = NonNullable<
32
+ NonNullable<ChangeBookingFlowProps['flowUi']>['providerDashboardChangePricingUi']
33
+ >;
34
+ type ProviderQuotedLine = NonNullable<ProviderPricingUi['quotedLines']>[number];
35
+ type GetPriceBreakdown = (
36
+ category: string,
37
+ priceCAD: number,
38
+ baseInDisplayCurrency: number | undefined,
39
+ appliedAdjustments?: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }>,
40
+ ) => PriceBreakdownData | null;
41
+
42
+ export interface UseAdminChangePriceSummaryParams {
43
+ selectedAvailability: Availability | null;
44
+ selectedReturnOption: ReturnOption | null;
45
+ currency: Currency;
46
+ totalQuantity: number;
47
+ returnPriceAdjustment: number;
48
+ isCustomerSelfServeChange: boolean;
49
+ effectiveChangeFlowReturnUnitFloorPerPerson: number | null | undefined;
50
+ changeFlowInitialTicketCount: number;
51
+ changeFlowReturnPricingRuleA: boolean;
52
+ changeFlowApplyReceiptPaidFloors: boolean;
53
+ ticketLineItems: OrderSummaryTicketLine[];
54
+ changeFlowTicketBookedUnitPriceByCategory: Map<string, number>;
55
+ changeFlowInitialTicketQtyByCategory: Map<string, number>;
56
+ changeFlowProtectedReceiptPricing: ChangeFlowProtectedReceiptPricing;
57
+ subtotal: number;
58
+ changeFlowProtectedTicketSubtotal: number;
59
+ feeLineItems: OrderSummaryFeeLine[];
60
+ changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
61
+ addOnSelections: AddOnSelection[];
62
+ addOns: AddOn[];
63
+ adminCustomReceiptLines: AdminCustomReceiptLine[];
64
+ quantities: Record<string, number>;
65
+ flowUi: ChangeBookingFlowProps['flowUi'];
66
+ isProviderDashboardChange: boolean;
67
+ isAdmin: boolean;
68
+ isManualOverrideEligibleLine: (line: { editable: boolean; type?: string; label?: string }) => boolean;
69
+ pricing: PricingRate[];
70
+ getPriceBreakdown: GetPriceBreakdown;
71
+ cancellationPolicyFee: number;
72
+ hasSelectedCancellationPolicy: boolean;
73
+ effectiveCancellationPolicyLabel: string;
74
+ t: (key: string) => string;
75
+ appliedPromoCode: string | null;
76
+ productCompanyId?: string | null;
77
+ envCompanyId?: string | null;
78
+ productId: string;
79
+ lockedPromoCode: string | null;
80
+ originalReceipt: ChangeBookingFlowProps['originalReceipt'];
81
+ pricingConfig: PricingConfig | null;
82
+ isTaxIncludedInPrice: boolean;
83
+ useAdminFeAuthoritativeQuote: boolean;
84
+ suppressSelfServeCurrencyUi: boolean;
85
+ selfServePricingConfirmed: boolean;
86
+ latestServerPriceSummaryLines?: PriceSummaryLine[];
87
+ }
88
+
89
+ export function useAdminChangePriceSummary({
90
+ selectedAvailability,
91
+ selectedReturnOption,
92
+ currency,
93
+ totalQuantity,
94
+ returnPriceAdjustment,
95
+ isCustomerSelfServeChange,
96
+ effectiveChangeFlowReturnUnitFloorPerPerson,
97
+ changeFlowInitialTicketCount,
98
+ changeFlowReturnPricingRuleA,
99
+ changeFlowApplyReceiptPaidFloors,
100
+ ticketLineItems,
101
+ changeFlowTicketBookedUnitPriceByCategory,
102
+ changeFlowInitialTicketQtyByCategory,
103
+ changeFlowProtectedReceiptPricing,
104
+ subtotal,
105
+ changeFlowProtectedTicketSubtotal,
106
+ feeLineItems,
107
+ changeFlowBookedFeeUnitByNormalizedLabel,
108
+ addOnSelections,
109
+ addOns,
110
+ adminCustomReceiptLines,
111
+ quantities,
112
+ flowUi,
113
+ isProviderDashboardChange,
114
+ isAdmin,
115
+ isManualOverrideEligibleLine,
116
+ pricing,
117
+ getPriceBreakdown,
118
+ cancellationPolicyFee,
119
+ hasSelectedCancellationPolicy,
120
+ effectiveCancellationPolicyLabel,
121
+ t,
122
+ appliedPromoCode,
123
+ productCompanyId,
124
+ envCompanyId,
125
+ productId,
126
+ lockedPromoCode,
127
+ originalReceipt,
128
+ pricingConfig,
129
+ isTaxIncludedInPrice,
130
+ useAdminFeAuthoritativeQuote,
131
+ suppressSelfServeCurrencyUi,
132
+ selfServePricingConfirmed,
133
+ latestServerPriceSummaryLines,
134
+ }: UseAdminChangePriceSummaryParams) {
135
+ const {
136
+ checkoutReturnLineAmount,
137
+ ticketLineItemsForChangeFlowDisplay,
138
+ effectiveSubtotal,
139
+ adminCustomAdjustmentTotal,
140
+ effectiveSubtotalForCheckout,
141
+ feeLineItemsForChangeFlowDisplay,
142
+ feeLineItemsWithAddOns,
143
+ } = useAdminChangeProtectedPricing({
144
+ selectedAvailability,
145
+ selectedReturnOption,
146
+ currency,
147
+ totalQuantity,
148
+ returnPriceAdjustment,
149
+ isCustomerSelfServeChange,
150
+ effectiveChangeFlowReturnUnitFloorPerPerson,
151
+ changeFlowInitialTicketCount,
152
+ changeFlowReturnPricingRuleA,
153
+ changeFlowApplyReceiptPaidFloors,
154
+ ticketLineItems,
155
+ changeFlowTicketBookedUnitPriceByCategory,
156
+ changeFlowInitialTicketQtyByCategory,
157
+ changeFlowProtectedReceiptPricing,
158
+ subtotal,
159
+ changeFlowProtectedTicketSubtotal,
160
+ feeLineItems,
161
+ changeFlowBookedFeeUnitByNormalizedLabel,
162
+ addOnSelections,
163
+ addOns,
164
+ adminCustomReceiptLines,
165
+ });
166
+
167
+ const providerPricingUi = flowUi?.providerDashboardChangePricingUi;
168
+ const providerQuotedLines = useMemo(
169
+ () => providerPricingUi?.quotedLines ?? [],
170
+ [providerPricingUi?.quotedLines],
171
+ );
172
+ const providerEditableLines = useMemo(
173
+ () => providerQuotedLines.filter((line) => isManualOverrideEligibleLine(line)),
174
+ [isManualOverrideEligibleLine, providerQuotedLines],
175
+ );
176
+ const showProviderPricingInlineEditor =
177
+ isProviderDashboardChange &&
178
+ isAdmin &&
179
+ (providerPricingUi?.loading || providerPricingUi?.error != null || providerQuotedLines.length > 0);
180
+
181
+ const normalizeReceiptLabel = (value: string): string =>
182
+ value
183
+ .toLowerCase()
184
+ .replace(/\([^)]*\)/g, '')
185
+ .replace(/[^a-z0-9]+/g, '')
186
+ .trim();
187
+
188
+ const providerEditableLineByNormalizedLabel = useMemo(() => {
189
+ const m = new Map<string, ProviderQuotedLine>();
190
+ for (const line of providerEditableLines) {
191
+ const key = normalizeReceiptLabel(line.label ?? '');
192
+ if (key) m.set(key, line);
193
+ }
194
+ return m;
195
+ }, [providerEditableLines]);
196
+
197
+ const checkoutPriceSummaryLines = useMemo((): PriceSummaryLine[] => {
198
+ if (!selectedAvailability) return [];
199
+ const returnLineAmount = checkoutReturnLineAmount;
200
+ const showReturnLine =
201
+ Boolean(selectedReturnOption) && Math.abs(returnLineAmount) > 0.0005;
202
+ return [
203
+ ...ticketLineItemsForChangeFlowDisplay.map((line): PriceSummaryLine => {
204
+ const rate = pricing.find((r) => r.category === line.category);
205
+ const breakdown = getPriceBreakdown(
206
+ line.category,
207
+ rate?.priceCAD ?? 0,
208
+ rate?.baseInDisplayCurrency,
209
+ rate?.appliedAdjustments ?? [],
210
+ );
211
+ const normalizedCategory = normalizeReceiptLabel(line.category);
212
+ return {
213
+ kind: 'ticket',
214
+ lineKey: showProviderPricingInlineEditor
215
+ ? providerEditableLineByNormalizedLabel.get(normalizedCategory)?.lineKey
216
+ : undefined,
217
+ editable:
218
+ showProviderPricingInlineEditor &&
219
+ providerEditableLineByNormalizedLabel.has(normalizedCategory),
220
+ category: line.category,
221
+ qty: line.qty,
222
+ itemTotal: line.itemTotal,
223
+ breakdown,
224
+ };
225
+ }),
226
+ ...(showReturnLine
227
+ ? [
228
+ {
229
+ kind: 'line' as const,
230
+ label: `${t('booking.returnOption')} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`,
231
+ amount: returnLineAmount,
232
+ type: 'return',
233
+ },
234
+ ]
235
+ : []),
236
+ ...(cancellationPolicyFee > 0 && hasSelectedCancellationPolicy
237
+ ? [
238
+ {
239
+ kind: 'line' as const,
240
+ label: effectiveCancellationPolicyLabel,
241
+ amount: cancellationPolicyFee,
242
+ type: 'cancellation',
243
+ },
244
+ ]
245
+ : []),
246
+ ...feeLineItemsWithAddOns.map((fee) => {
247
+ const isMoraineLakeRoadAccessFee =
248
+ fee.name.toLowerCase().includes('moraine') &&
249
+ (fee.name.toLowerCase().includes('access') ||
250
+ fee.name.toLowerCase().includes('road') ||
251
+ fee.name.toLowerCase().includes('license'));
252
+ const isProductFee = feeLineItemsForChangeFlowDisplay.some((f) => f.name === fee.name);
253
+ const label = isProductFee
254
+ ? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
255
+ : fee.name;
256
+ const normalizedLabel = normalizeReceiptLabel(label);
257
+ return {
258
+ kind: 'line' as const,
259
+ lineKey: showProviderPricingInlineEditor
260
+ ? providerEditableLineByNormalizedLabel.get(normalizedLabel)?.lineKey
261
+ : undefined,
262
+ editable:
263
+ showProviderPricingInlineEditor &&
264
+ providerEditableLineByNormalizedLabel.has(normalizedLabel),
265
+ label,
266
+ amount: fee.totalAmount,
267
+ type: 'fee',
268
+ tooltip: isMoraineLakeRoadAccessFee
269
+ ? "Since 2025, Parks Canada charges a per-trip fee for License of Occupation. Based on our capacity, this per-person fee contributes towards Parks Canada's Moraine Lake Road operations."
270
+ : undefined,
271
+ };
272
+ }),
273
+ ];
274
+ }, [
275
+ selectedAvailability,
276
+ ticketLineItemsForChangeFlowDisplay,
277
+ pricing,
278
+ getPriceBreakdown,
279
+ selectedReturnOption,
280
+ checkoutReturnLineAmount,
281
+ totalQuantity,
282
+ t,
283
+ cancellationPolicyFee,
284
+ hasSelectedCancellationPolicy,
285
+ effectiveCancellationPolicyLabel,
286
+ feeLineItemsWithAddOns,
287
+ feeLineItemsForChangeFlowDisplay,
288
+ showProviderPricingInlineEditor,
289
+ providerEditableLineByNormalizedLabel,
290
+ ]);
291
+
292
+ const {
293
+ originalReceiptPromoAdjustment,
294
+ effectivePromoDiscountAmount,
295
+ effectivePromoLineType,
296
+ effectiveTax,
297
+ totalPrice,
298
+ } = useAdminChangePromoDiscount({
299
+ appliedPromoCode,
300
+ selectedAvailability,
301
+ totalQuantity,
302
+ productCompanyId,
303
+ envCompanyId,
304
+ productId,
305
+ currency,
306
+ quantities,
307
+ ticketLineItemsForChangeFlowDisplay,
308
+ feeLineItemsWithAddOns,
309
+ effectiveSubtotalForCheckout,
310
+ lockedPromoCode,
311
+ originalReceipt,
312
+ isTaxIncludedInPrice,
313
+ pricingConfig,
314
+ });
315
+
316
+ const checkoutPriceSummaryLinesForCheckout = useMemo(() => {
317
+ let raw: PriceSummaryLine[];
318
+ if (!useAdminFeAuthoritativeQuote && suppressSelfServeCurrencyUi && selfServePricingConfirmed) {
319
+ raw =
320
+ latestServerPriceSummaryLines && latestServerPriceSummaryLines.length > 0
321
+ ? latestServerPriceSummaryLines
322
+ : checkoutPriceSummaryLines;
323
+ } else {
324
+ raw = checkoutPriceSummaryLines;
325
+ }
326
+ const lines = omitZeroAmountPromoDiscountSummaryLines(raw);
327
+ const hasPromoSummaryLine = lines.some((line) =>
328
+ line.kind === 'line' && isPromoDiscountGiftCardOrVoucherLine(line),
329
+ );
330
+ if (hasPromoSummaryLine || effectivePromoDiscountAmount < 0.005) return lines;
331
+ const trimmedPromoCode = appliedPromoCode?.trim() ?? '';
332
+ const promoLabel =
333
+ trimmedPromoCode.length > 0
334
+ ? `Promo: ${trimmedPromoCode}`
335
+ : originalReceiptPromoAdjustment.label || (t('booking.discount') || 'Discount');
336
+ return [
337
+ ...lines,
338
+ {
339
+ kind: 'line' as const,
340
+ label: promoLabel,
341
+ amount: -effectivePromoDiscountAmount,
342
+ type: effectivePromoLineType,
343
+ },
344
+ ];
345
+ }, [
346
+ suppressSelfServeCurrencyUi,
347
+ selfServePricingConfirmed,
348
+ useAdminFeAuthoritativeQuote,
349
+ checkoutPriceSummaryLines,
350
+ latestServerPriceSummaryLines,
351
+ effectivePromoDiscountAmount,
352
+ effectivePromoLineType,
353
+ appliedPromoCode,
354
+ originalReceiptPromoAdjustment.label,
355
+ t,
356
+ ]);
357
+
358
+ const priceSummaryLinesIncludeTaxRow = useMemo(
359
+ () =>
360
+ checkoutPriceSummaryLinesForCheckout.some(
361
+ (line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
362
+ ),
363
+ [checkoutPriceSummaryLinesForCheckout],
364
+ );
365
+
366
+ const {
367
+ editableCheckoutPriceSummaryLines,
368
+ editableSummaryLineAmountInputs,
369
+ editableSummaryLineLabelInputs,
370
+ editableSummaryPreSubtotalDelta,
371
+ editableSummaryPreSubtotalDebugRows,
372
+ onLineAmountInputChange: handleEditableLineAmountInputChange,
373
+ onLineAmountInputBlur: handleEditableLineAmountInputBlur,
374
+ onLineAmountReset: handleEditableLineAmountReset,
375
+ } = useEditableSummaryLines(checkoutPriceSummaryLinesForCheckout, {
376
+ enabled: showProviderPricingInlineEditor,
377
+ onChange: providerPricingUi?.onLineAmountInputChange,
378
+ onBlur: providerPricingUi?.onLineAmountInputBlur,
379
+ onReset: providerPricingUi?.onLineAmountReset,
380
+ });
381
+
382
+ const changeFlowNewBookingTotal = resolveChangeFlowNewBookingTotal({
383
+ cartTotal: totalPrice,
384
+ originalReceiptTotal: originalReceipt?.total,
385
+ });
386
+
387
+ return {
388
+ checkoutReturnLineAmount,
389
+ ticketLineItemsForChangeFlowDisplay,
390
+ effectiveSubtotal,
391
+ adminCustomAdjustmentTotal,
392
+ effectiveSubtotalForCheckout,
393
+ feeLineItemsForChangeFlowDisplay,
394
+ feeLineItemsWithAddOns,
395
+ providerPricingUi,
396
+ providerQuotedLines,
397
+ showProviderPricingInlineEditor,
398
+ checkoutPriceSummaryLines,
399
+ originalReceiptPromoAdjustment,
400
+ effectivePromoDiscountAmount,
401
+ effectivePromoLineType,
402
+ effectiveTax,
403
+ totalPrice,
404
+ checkoutPriceSummaryLinesForCheckout,
405
+ priceSummaryLinesIncludeTaxRow,
406
+ editableCheckoutPriceSummaryLines,
407
+ editableSummaryLineAmountInputs,
408
+ editableSummaryLineLabelInputs,
409
+ editableSummaryPreSubtotalDelta,
410
+ editableSummaryPreSubtotalDebugRows,
411
+ handleEditableLineAmountInputChange,
412
+ handleEditableLineAmountInputBlur,
413
+ handleEditableLineAmountReset,
414
+ changeFlowNewBookingTotal,
415
+ };
416
+ }