@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,280 @@
1
+ import { useMemo } from 'react';
2
+ import type { AddOn, Availability, ReturnOption } from '../../lib/booking-api';
3
+ import {
4
+ changeFlowFeeLineTotalWithReceiptFloor,
5
+ changeFlowReturnLineTotalWithReceiptFloor,
6
+ changeFlowTicketLineTotalWithReceiptFloor,
7
+ roundMoney,
8
+ type ChangeFlowProtectedReceiptPricing,
9
+ } from '../../lib/booking/change-flow-pricing';
10
+ import type {
11
+ OrderSummaryFeeLine,
12
+ OrderSummaryTicketLine,
13
+ } from '../../lib/booking/pricing';
14
+ import type { Currency } from './CurrencySwitcher';
15
+ import { normalizeLineLabelForCompare } from './change-booking-flow-helpers';
16
+
17
+ type AddOnSelection = { addOnId: string; variantId?: string; quantity?: number };
18
+ type AdminCustomReceiptLine = { label: string; amountInput: string; amountSign?: number };
19
+
20
+ interface UseAdminChangeProtectedPricingParams {
21
+ selectedAvailability: Availability | null;
22
+ selectedReturnOption: ReturnOption | null;
23
+ currency: Currency;
24
+ totalQuantity: number;
25
+ returnPriceAdjustment: number;
26
+ isCustomerSelfServeChange: boolean;
27
+ effectiveChangeFlowReturnUnitFloorPerPerson: number | null | undefined;
28
+ changeFlowInitialTicketCount: number;
29
+ changeFlowReturnPricingRuleA: boolean;
30
+ changeFlowApplyReceiptPaidFloors: boolean;
31
+ ticketLineItems: OrderSummaryTicketLine[];
32
+ changeFlowTicketBookedUnitPriceByCategory: Map<string, number>;
33
+ changeFlowInitialTicketQtyByCategory: Map<string, number>;
34
+ changeFlowProtectedReceiptPricing: ChangeFlowProtectedReceiptPricing;
35
+ subtotal: number;
36
+ changeFlowProtectedTicketSubtotal: number;
37
+ feeLineItems: OrderSummaryFeeLine[];
38
+ changeFlowBookedFeeUnitByNormalizedLabel: Map<string, number>;
39
+ addOnSelections: AddOnSelection[];
40
+ addOns: AddOn[];
41
+ adminCustomReceiptLines: AdminCustomReceiptLine[];
42
+ }
43
+
44
+ export function useAdminChangeProtectedPricing({
45
+ selectedAvailability,
46
+ selectedReturnOption,
47
+ currency,
48
+ totalQuantity,
49
+ returnPriceAdjustment,
50
+ isCustomerSelfServeChange,
51
+ effectiveChangeFlowReturnUnitFloorPerPerson,
52
+ changeFlowInitialTicketCount,
53
+ changeFlowReturnPricingRuleA,
54
+ changeFlowApplyReceiptPaidFloors,
55
+ ticketLineItems,
56
+ changeFlowTicketBookedUnitPriceByCategory,
57
+ changeFlowInitialTicketQtyByCategory,
58
+ changeFlowProtectedReceiptPricing,
59
+ subtotal,
60
+ changeFlowProtectedTicketSubtotal,
61
+ feeLineItems,
62
+ changeFlowBookedFeeUnitByNormalizedLabel,
63
+ addOnSelections,
64
+ addOns,
65
+ adminCustomReceiptLines,
66
+ }: UseAdminChangeProtectedPricingParams) {
67
+ const addOnTotal = useMemo(() => {
68
+ let sum = 0;
69
+ for (const sel of addOnSelections) {
70
+ const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
71
+ if (!addOn) continue;
72
+ const basePrice = addOn.price ?? 0;
73
+ const hasVariant =
74
+ (addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
75
+ sel.variantId;
76
+ const variantAdjustment = hasVariant
77
+ ? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
78
+ : 0;
79
+ sum += (basePrice + variantAdjustment) * (sel.quantity ?? 1);
80
+ }
81
+ return sum;
82
+ }, [addOnSelections, addOns]);
83
+
84
+ const currentTicketSubtotal = useMemo(
85
+ () => ticketLineItems.reduce((sum, line) => sum + Math.max(0, Number(line.itemTotal) || 0), 0),
86
+ [ticketLineItems],
87
+ );
88
+
89
+ const currentFeeSubtotal = useMemo(
90
+ () => feeLineItems.reduce((sum, line) => sum + Math.max(0, Number(line.totalAmount) || 0), 0),
91
+ [feeLineItems],
92
+ );
93
+
94
+ const changeFlowProtectedFeeSubtotal = useMemo(() => {
95
+ if (!changeFlowApplyReceiptPaidFloors || totalQuantity <= 0) return currentFeeSubtotal;
96
+ const initialParty = changeFlowInitialTicketCount;
97
+ return feeLineItems.reduce((sum, line) => {
98
+ const key = normalizeLineLabelForCompare(line.name || '');
99
+ const bookedFeePerPerson = key ? changeFlowBookedFeeUnitByNormalizedLabel.get(key) : undefined;
100
+ return (
101
+ sum +
102
+ changeFlowFeeLineTotalWithReceiptFloor({
103
+ totalQuantity,
104
+ initialTicketCount: initialParty,
105
+ bookedFeePerPerson,
106
+ liveFeeLineTotal: Number(line.totalAmount) || 0,
107
+ protectedReceiptPricing: changeFlowProtectedReceiptPricing,
108
+ })
109
+ );
110
+ }, 0);
111
+ }, [
112
+ changeFlowApplyReceiptPaidFloors,
113
+ totalQuantity,
114
+ currentFeeSubtotal,
115
+ feeLineItems,
116
+ changeFlowBookedFeeUnitByNormalizedLabel,
117
+ changeFlowInitialTicketCount,
118
+ changeFlowProtectedReceiptPricing,
119
+ ]);
120
+
121
+ const returnOptionCatalogPerPerson = useMemo(() => {
122
+ if (!selectedReturnOption?.returnAvailabilityId || !selectedAvailability?.returnOptions?.length) {
123
+ return null;
124
+ }
125
+ const raw = selectedAvailability.returnOptions.find(
126
+ (o) => o.returnAvailabilityId === selectedReturnOption.returnAvailabilityId,
127
+ );
128
+ const v = raw?.priceAdjustmentByCurrency?.[currency];
129
+ return typeof v === 'number' && Number.isFinite(v) ? v : null;
130
+ }, [selectedReturnOption?.returnAvailabilityId, selectedAvailability?.returnOptions, currency]);
131
+
132
+ const changeFlowProtectedReturnAdjustment = useMemo(() => {
133
+ if (totalQuantity <= 0) return returnPriceAdjustment;
134
+ if (!isCustomerSelfServeChange) return returnPriceAdjustment;
135
+ if (effectiveChangeFlowReturnUnitFloorPerPerson == null) return returnPriceAdjustment;
136
+ const livePerPerson =
137
+ returnOptionCatalogPerPerson ?? (selectedReturnOption?.priceAdjustmentByCurrency?.[currency] ?? 0);
138
+ return changeFlowReturnLineTotalWithReceiptFloor({
139
+ totalPersons: totalQuantity,
140
+ baselineParty: changeFlowInitialTicketCount,
141
+ livePerPerson,
142
+ receiptFloorPerPerson: effectiveChangeFlowReturnUnitFloorPerPerson,
143
+ returnRuleA: changeFlowReturnPricingRuleA,
144
+ });
145
+ }, [
146
+ totalQuantity,
147
+ returnPriceAdjustment,
148
+ effectiveChangeFlowReturnUnitFloorPerPerson,
149
+ selectedReturnOption,
150
+ returnOptionCatalogPerPerson,
151
+ currency,
152
+ changeFlowInitialTicketCount,
153
+ changeFlowReturnPricingRuleA,
154
+ isCustomerSelfServeChange,
155
+ ]);
156
+
157
+ const checkoutReturnLineAmount = useMemo(() => {
158
+ if (isCustomerSelfServeChange) return changeFlowProtectedReturnAdjustment;
159
+ if (changeFlowApplyReceiptPaidFloors) return changeFlowProtectedReturnAdjustment;
160
+ return returnPriceAdjustment;
161
+ }, [
162
+ isCustomerSelfServeChange,
163
+ changeFlowApplyReceiptPaidFloors,
164
+ changeFlowProtectedReturnAdjustment,
165
+ returnPriceAdjustment,
166
+ ]);
167
+
168
+ const ticketLineItemsForChangeFlowDisplay = useMemo(() => {
169
+ if (!changeFlowApplyReceiptPaidFloors) return ticketLineItems;
170
+ return ticketLineItems.map((line) => {
171
+ const category = line.category?.trim().toUpperCase();
172
+ const qty = Math.max(0, Number(line.qty) || 0);
173
+ if (!category || qty <= 0) return line;
174
+ const newTotal = changeFlowTicketLineTotalWithReceiptFloor({
175
+ qty,
176
+ baselineQtyForCategory: changeFlowInitialTicketQtyByCategory.get(category) ?? 0,
177
+ receiptUnitFloor: changeFlowTicketBookedUnitPriceByCategory.get(category),
178
+ liveLineTotal: Number(line.itemTotal) || 0,
179
+ protectedReceiptPricing: changeFlowProtectedReceiptPricing,
180
+ });
181
+ return { ...line, itemTotal: newTotal };
182
+ });
183
+ }, [
184
+ changeFlowApplyReceiptPaidFloors,
185
+ ticketLineItems,
186
+ changeFlowTicketBookedUnitPriceByCategory,
187
+ changeFlowInitialTicketQtyByCategory,
188
+ changeFlowProtectedReceiptPricing,
189
+ ]);
190
+
191
+ const effectiveSubtotalBeforeAddOns =
192
+ changeFlowApplyReceiptPaidFloors
193
+ ? subtotal -
194
+ currentTicketSubtotal -
195
+ currentFeeSubtotal -
196
+ returnPriceAdjustment +
197
+ changeFlowProtectedTicketSubtotal +
198
+ changeFlowProtectedFeeSubtotal +
199
+ changeFlowProtectedReturnAdjustment
200
+ : subtotal;
201
+ const effectiveSubtotal = effectiveSubtotalBeforeAddOns + addOnTotal;
202
+
203
+ const adminCustomAdjustmentTotal = useMemo(
204
+ () =>
205
+ roundMoney(
206
+ adminCustomReceiptLines.reduce((sum, line) => {
207
+ const raw = line.amountInput.trim();
208
+ if (raw === '' || raw === '+' || raw === '-') return sum;
209
+ const n = Number(raw);
210
+ if (!Number.isFinite(n)) return sum;
211
+ const sign = line.amountSign ?? 1;
212
+ return sum + sign * Math.abs(n);
213
+ }, 0),
214
+ ),
215
+ [adminCustomReceiptLines],
216
+ );
217
+ const effectiveSubtotalForCheckout = roundMoney(effectiveSubtotal + adminCustomAdjustmentTotal);
218
+
219
+ const feeLineItemsForChangeFlowDisplay = useMemo(() => {
220
+ if (!changeFlowApplyReceiptPaidFloors || totalQuantity <= 0) return feeLineItems;
221
+ const initialParty = changeFlowInitialTicketCount;
222
+ return feeLineItems.map((line) => {
223
+ const key = normalizeLineLabelForCompare(line.name || '');
224
+ const bookedFeePerPerson = key ? changeFlowBookedFeeUnitByNormalizedLabel.get(key) : undefined;
225
+ return {
226
+ ...line,
227
+ totalAmount: changeFlowFeeLineTotalWithReceiptFloor({
228
+ totalQuantity,
229
+ initialTicketCount: initialParty,
230
+ bookedFeePerPerson,
231
+ liveFeeLineTotal: Number(line.totalAmount) || 0,
232
+ protectedReceiptPricing: changeFlowProtectedReceiptPricing,
233
+ }),
234
+ };
235
+ });
236
+ }, [
237
+ changeFlowApplyReceiptPaidFloors,
238
+ totalQuantity,
239
+ feeLineItems,
240
+ changeFlowInitialTicketCount,
241
+ changeFlowBookedFeeUnitByNormalizedLabel,
242
+ changeFlowProtectedReceiptPricing,
243
+ ]);
244
+
245
+ const feeLineItemsWithAddOns = useMemo(() => {
246
+ const addOnLines = addOnSelections
247
+ .map((sel) => {
248
+ const addOn = addOns.find((a) => a.addOnId === sel.addOnId);
249
+ if (!addOn) return null;
250
+ const base = addOn.price ?? 0;
251
+ const hasVariant =
252
+ (addOn.variantType === 'single_choice' || addOn.variantType === 'multi_quantity') &&
253
+ sel.variantId;
254
+ const adj = hasVariant
255
+ ? (addOn.variants?.find((v) => v.id === sel.variantId)?.priceAdjustment ?? 0)
256
+ : 0;
257
+ const qty = sel.quantity ?? 1;
258
+ const amt = (base + adj) * qty;
259
+ const variantLabel = hasVariant
260
+ ? addOn.variants?.find((v) => v.id === sel.variantId)?.label
261
+ : null;
262
+ const name = variantLabel
263
+ ? `${addOn.name} (${variantLabel})${qty > 1 ? ` \u00d7 ${qty}` : ''}`
264
+ : addOn.name;
265
+ return { name, totalAmount: amt, description: addOn.description ?? undefined };
266
+ })
267
+ .filter((x): x is NonNullable<typeof x> => x != null);
268
+ return [...feeLineItemsForChangeFlowDisplay, ...addOnLines];
269
+ }, [feeLineItemsForChangeFlowDisplay, addOnSelections, addOns]);
270
+
271
+ return {
272
+ checkoutReturnLineAmount,
273
+ ticketLineItemsForChangeFlowDisplay,
274
+ effectiveSubtotal,
275
+ adminCustomAdjustmentTotal,
276
+ effectiveSubtotalForCheckout,
277
+ feeLineItemsForChangeFlowDisplay,
278
+ feeLineItemsWithAddOns,
279
+ };
280
+ }