@ticketboothapp/booking 1.2.122 → 1.2.124

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 +647 -6157
  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 +154 -3
  111. package/src/lib/pricing-v2-shadow.ts +272 -0
  112. package/test/change-booking-helpers.test.ts +206 -0
@@ -0,0 +1,143 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ validatePromoCode,
4
+ type Availability,
5
+ } from '../../lib/booking-api';
6
+
7
+ type TranslationFn = (key: string) => string;
8
+
9
+ export interface UsePrivateShuttlePromoControllerParams {
10
+ initialPromoCode?: string | null;
11
+ isAdmin: boolean;
12
+ selectedAvailability: Availability | null;
13
+ selectedOption: string;
14
+ selectedStartTime: string;
15
+ resourceCount: number;
16
+ productCompanyId?: string | null;
17
+ productId: string;
18
+ onPromoChangesAvailability: () => void;
19
+ t: TranslationFn;
20
+ }
21
+
22
+ export function usePrivateShuttlePromoController({
23
+ initialPromoCode,
24
+ isAdmin,
25
+ selectedAvailability,
26
+ selectedOption,
27
+ selectedStartTime,
28
+ resourceCount,
29
+ productCompanyId,
30
+ productId,
31
+ onPromoChangesAvailability,
32
+ t,
33
+ }: UsePrivateShuttlePromoControllerParams) {
34
+ const [promoCodeInput, setPromoCodeInput] = useState(() => initialPromoCode?.trim() ?? '');
35
+ const [appliedPromoCode, setAppliedPromoCode] = useState<string | null>(
36
+ () => initialPromoCode?.trim() || null,
37
+ );
38
+ const [promoCodeError, setPromoCodeError] = useState('');
39
+ const [promoCodeValidating, setPromoCodeValidating] = useState(false);
40
+ const lastValidatedInputRef = useRef<string | null>(null);
41
+
42
+ const activePromoCode = isAdmin ? appliedPromoCode : null;
43
+ const hasOngoingDiscount = useMemo(
44
+ () =>
45
+ selectedAvailability?.rates?.some((rate) =>
46
+ (rate.appliedAdjustments ?? rate.applied_adjustments ?? []).some(
47
+ (adjustment) => (adjustment.type ?? '').toLowerCase() === 'deal',
48
+ ),
49
+ ) ?? false,
50
+ [selectedAvailability],
51
+ );
52
+
53
+ const handleApplyPromo = useCallback(async () => {
54
+ const code = promoCodeInput.trim().toUpperCase();
55
+ if (!isAdmin) return;
56
+ if (!code || appliedPromoCode === code) return;
57
+ if (!productCompanyId) return;
58
+ lastValidatedInputRef.current = code;
59
+ setPromoCodeError('');
60
+ setPromoCodeValidating(true);
61
+ try {
62
+ const result = await validatePromoCode(
63
+ code,
64
+ productCompanyId,
65
+ productId,
66
+ hasOngoingDiscount,
67
+ );
68
+ if (result.valid) {
69
+ setAppliedPromoCode(code);
70
+ onPromoChangesAvailability();
71
+ } else {
72
+ setPromoCodeError(
73
+ result.error === 'Promo codes cannot be stacked with deals'
74
+ ? (t('booking.promoCodesCannotStackWithDiscounts') || result.error)
75
+ : (result.error || t('booking.invalidPromoCode') || 'Invalid or expired promo code'),
76
+ );
77
+ }
78
+ } catch (err) {
79
+ setPromoCodeError(err instanceof Error ? err.message : 'Failed to validate promo code');
80
+ } finally {
81
+ setPromoCodeValidating(false);
82
+ }
83
+ }, [
84
+ promoCodeInput,
85
+ appliedPromoCode,
86
+ isAdmin,
87
+ productCompanyId,
88
+ productId,
89
+ hasOngoingDiscount,
90
+ t,
91
+ onPromoChangesAvailability,
92
+ ]);
93
+
94
+ const handleApplyPromoRef = useRef(handleApplyPromo);
95
+ handleApplyPromoRef.current = handleApplyPromo;
96
+
97
+ const handlePromoInputChange = useCallback((value: string) => {
98
+ setPromoCodeInput(value);
99
+ setPromoCodeError('');
100
+ }, []);
101
+
102
+ const handleRemovePromo = useCallback(() => {
103
+ lastValidatedInputRef.current = null;
104
+ setAppliedPromoCode(null);
105
+ setPromoCodeInput('');
106
+ setPromoCodeError('');
107
+ onPromoChangesAvailability();
108
+ }, [onPromoChangesAvailability]);
109
+
110
+ useEffect(() => {
111
+ if (!isAdmin) return;
112
+ const trimmed = promoCodeInput.trim().toUpperCase();
113
+ if (!trimmed) return;
114
+ if (!selectedOption || !selectedStartTime || resourceCount === 0) return;
115
+ if (appliedPromoCode === trimmed) return;
116
+ if (lastValidatedInputRef.current === trimmed && promoCodeError) return;
117
+
118
+ const timer = setTimeout(() => {
119
+ handleApplyPromoRef.current();
120
+ }, 600);
121
+
122
+ return () => clearTimeout(timer);
123
+ }, [
124
+ isAdmin,
125
+ promoCodeInput,
126
+ appliedPromoCode,
127
+ promoCodeError,
128
+ selectedOption,
129
+ selectedStartTime,
130
+ resourceCount,
131
+ ]);
132
+
133
+ return {
134
+ promoCodeInput,
135
+ appliedPromoCode,
136
+ activePromoCode,
137
+ promoCodeError,
138
+ promoCodeValidating,
139
+ handlePromoInputChange,
140
+ handleApplyPromo,
141
+ handleRemovePromo,
142
+ };
143
+ }
@@ -0,0 +1,124 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { getPromoDiscount } from '../../lib/booking-api';
3
+ import type { Currency } from './CurrencySwitcher';
4
+
5
+ interface UsePrivateShuttlePromoDiscountParams {
6
+ activePromoCode: string | null;
7
+ selectedOption: string;
8
+ selectedDate: string;
9
+ selectedStartTime: string;
10
+ resourceCount: number;
11
+ productCompanyId?: string | null;
12
+ productId: string;
13
+ currency: Currency;
14
+ subtotal: number;
15
+ }
16
+
17
+ export function usePrivateShuttlePromoDiscount({
18
+ activePromoCode,
19
+ selectedOption,
20
+ selectedDate,
21
+ selectedStartTime,
22
+ resourceCount,
23
+ productCompanyId,
24
+ productId,
25
+ currency,
26
+ subtotal,
27
+ }: UsePrivateShuttlePromoDiscountParams) {
28
+ const [promoDiscountAmount, setPromoDiscountAmount] = useState(0);
29
+ const [isGiftCard, setIsGiftCard] = useState(false);
30
+ const [isVoucher, setIsVoucher] = useState(false);
31
+
32
+ const resetPromoDiscount = useCallback(() => {
33
+ setPromoDiscountAmount(0);
34
+ setIsGiftCard(false);
35
+ setIsVoucher(false);
36
+ }, []);
37
+
38
+ const promoDiscountFetchKey = useMemo(() => {
39
+ if (
40
+ !activePromoCode ||
41
+ !selectedOption ||
42
+ !selectedDate ||
43
+ !selectedStartTime ||
44
+ resourceCount === 0
45
+ ) {
46
+ return '';
47
+ }
48
+ if (!productCompanyId) return '';
49
+ return [
50
+ activePromoCode,
51
+ productCompanyId,
52
+ productId,
53
+ selectedOption,
54
+ selectedDate,
55
+ selectedStartTime,
56
+ currency,
57
+ resourceCount,
58
+ Math.round(subtotal * 100),
59
+ ].join('::');
60
+ }, [
61
+ activePromoCode,
62
+ selectedOption,
63
+ selectedDate,
64
+ selectedStartTime,
65
+ resourceCount,
66
+ productCompanyId,
67
+ productId,
68
+ currency,
69
+ subtotal,
70
+ ]);
71
+
72
+ useEffect(() => {
73
+ if (!promoDiscountFetchKey) {
74
+ resetPromoDiscount();
75
+ return;
76
+ }
77
+
78
+ let cancelled = false;
79
+ getPromoDiscount(
80
+ activePromoCode!,
81
+ productCompanyId!,
82
+ productId,
83
+ selectedOption,
84
+ currency,
85
+ [{ category: 'RESOURCE', qty: resourceCount }],
86
+ selectedDate,
87
+ subtotal,
88
+ )
89
+ .then((res) => {
90
+ if (cancelled) return;
91
+ setPromoDiscountAmount(res.discount ?? 0);
92
+ setIsGiftCard(res.isGiftCard ?? false);
93
+ setIsVoucher(res.isVoucher ?? false);
94
+ })
95
+ .catch(() => {
96
+ if (cancelled) return;
97
+ resetPromoDiscount();
98
+ });
99
+ return () => {
100
+ cancelled = true;
101
+ };
102
+ }, [
103
+ promoDiscountFetchKey,
104
+ activePromoCode,
105
+ productCompanyId,
106
+ productId,
107
+ selectedOption,
108
+ currency,
109
+ resourceCount,
110
+ selectedDate,
111
+ subtotal,
112
+ resetPromoDiscount,
113
+ ]);
114
+
115
+ const effectivePromoDiscountAmount = promoDiscountAmount > 0 ? promoDiscountAmount : 0;
116
+
117
+ return {
118
+ promoDiscountAmount,
119
+ isGiftCard,
120
+ isVoucher,
121
+ effectivePromoDiscountAmount,
122
+ resetPromoDiscount,
123
+ };
124
+ }
@@ -0,0 +1,13 @@
1
+ import { useEffect, type Dispatch, type SetStateAction } from 'react';
2
+
3
+ export function useSeedPromoCodeInput(
4
+ autoAppliedPromoCode: string | undefined,
5
+ setPromoCodeInput: Dispatch<SetStateAction<string>>,
6
+ ) {
7
+ useEffect(() => {
8
+ if (!autoAppliedPromoCode) return;
9
+ const normalizedPromo = autoAppliedPromoCode.trim().toUpperCase();
10
+ if (!normalizedPromo) return;
11
+ setPromoCodeInput((current) => current || normalizedPromo);
12
+ }, [autoAppliedPromoCode, setPromoCodeInput]);
13
+ }
@@ -0,0 +1,224 @@
1
+ import { useEffect, useRef, type RefObject } from 'react';
2
+ import { parseISO } from 'date-fns';
3
+ import type { Availability, Product, ReturnOption } from '../../lib/booking-api';
4
+ import {
5
+ availabilityMatchesSelectedDate,
6
+ parseAvailabilityDateTime,
7
+ pickDefaultAvailabilityForTimes,
8
+ } from './standard-booking-availability';
9
+
10
+ interface UseStandardBookingAutoSelectionsParams {
11
+ selectedDate: string;
12
+ selectedAvailability: Availability | null;
13
+ selectedReturnOption: ReturnOption | null;
14
+ timesForSelectedDateLength: number;
15
+ timesForSelectedDateSelectionKey: string;
16
+ activeOptions: Product['options'];
17
+ companyTimezone: string;
18
+ getPickupCutoffEligibleTimesForDate: (date: string) => Availability[];
19
+ setSelectedAvailability: (availability: Availability | null) => void;
20
+ setSelectedReturnOption: (option: ReturnOption | null) => void;
21
+ setSelectedDate: (date: string) => void;
22
+ setError: (error: string) => void;
23
+ initialReturnAvailabilityId?: string | null;
24
+ initialReturnDateTime?: string | null;
25
+ autoSelectFirstAvailableDate?: boolean;
26
+ isPartialLaunch: boolean;
27
+ initialDateTime?: string | null;
28
+ dates: string[];
29
+ isAdmin: boolean;
30
+ suppressCalendarDateScroll?: boolean;
31
+ contentRef?: RefObject<HTMLDivElement | null>;
32
+ useWindowScroll: boolean;
33
+ autoSelectFirstHighlightedPickup?: boolean;
34
+ highlightedPickupLocationIds?: string[];
35
+ pickupLocationId: string | null;
36
+ pickupLocationSkipped: boolean;
37
+ initialPickupLocationId?: string | null;
38
+ pickupLocations?: Product['pickupLocations'];
39
+ setPickupLocationId: (locationId: string | null) => void;
40
+ setPickupLocationSkipped: (skipped: boolean) => void;
41
+ }
42
+
43
+ function scrollAfterCalendarSelection(
44
+ contentRef: RefObject<HTMLDivElement | null> | undefined,
45
+ useWindowScroll: boolean,
46
+ ) {
47
+ setTimeout(() => {
48
+ const container = contentRef?.current;
49
+ if (!useWindowScroll && container && container.scrollHeight > container.clientHeight + 16) {
50
+ container.scrollBy({ top: 400, behavior: 'smooth' });
51
+ } else if (typeof window !== 'undefined') {
52
+ window.scrollBy({ top: 400, behavior: 'smooth' });
53
+ }
54
+ }, 100);
55
+ }
56
+
57
+ export function useStandardBookingAutoSelections({
58
+ selectedDate,
59
+ selectedAvailability,
60
+ selectedReturnOption,
61
+ timesForSelectedDateLength,
62
+ timesForSelectedDateSelectionKey,
63
+ activeOptions,
64
+ companyTimezone,
65
+ getPickupCutoffEligibleTimesForDate,
66
+ setSelectedAvailability,
67
+ setSelectedReturnOption,
68
+ setSelectedDate,
69
+ setError,
70
+ initialReturnAvailabilityId,
71
+ initialReturnDateTime,
72
+ autoSelectFirstAvailableDate = false,
73
+ isPartialLaunch,
74
+ initialDateTime,
75
+ dates,
76
+ isAdmin,
77
+ suppressCalendarDateScroll,
78
+ contentRef,
79
+ useWindowScroll,
80
+ autoSelectFirstHighlightedPickup = false,
81
+ highlightedPickupLocationIds,
82
+ pickupLocationId,
83
+ pickupLocationSkipped,
84
+ initialPickupLocationId,
85
+ pickupLocations,
86
+ setPickupLocationId,
87
+ setPickupLocationSkipped,
88
+ }: UseStandardBookingAutoSelectionsParams) {
89
+ const hasAutoSelectedPartnerDateRef = useRef(false);
90
+ const hasAutoSelectedPartnerPickupRef = useRef(false);
91
+
92
+ useEffect(() => {
93
+ if (!selectedDate || timesForSelectedDateLength === 0) return;
94
+ if (
95
+ selectedAvailability &&
96
+ availabilityMatchesSelectedDate(selectedAvailability, selectedDate, companyTimezone)
97
+ ) {
98
+ return;
99
+ }
100
+ const toSelect = pickDefaultAvailabilityForTimes(
101
+ getPickupCutoffEligibleTimesForDate(selectedDate),
102
+ activeOptions,
103
+ );
104
+ if (toSelect) {
105
+ setSelectedAvailability(toSelect);
106
+ setError('');
107
+ }
108
+ }, [
109
+ selectedDate,
110
+ timesForSelectedDateLength,
111
+ timesForSelectedDateSelectionKey,
112
+ activeOptions,
113
+ selectedAvailability,
114
+ companyTimezone,
115
+ getPickupCutoffEligibleTimesForDate,
116
+ setSelectedAvailability,
117
+ setError,
118
+ ]);
119
+
120
+ useEffect(() => {
121
+ if (!selectedAvailability?.returnOptions?.length || selectedReturnOption) return;
122
+
123
+ const sorted = [...selectedAvailability.returnOptions].sort(
124
+ (a, b) => parseISO(a.dateTime).getTime() - parseISO(b.dateTime).getTime(),
125
+ );
126
+ const initialReturnIdForSelect = initialReturnAvailabilityId?.trim();
127
+ const initialReturnDtForSelect = initialReturnDateTime?.trim();
128
+ const preferBooked = initialReturnIdForSelect
129
+ ? sorted.find((option) => option.returnAvailabilityId === initialReturnIdForSelect)
130
+ : undefined;
131
+ const preferByDateTime =
132
+ initialReturnDtForSelect && !preferBooked
133
+ ? sorted.find((option) => {
134
+ try {
135
+ return (
136
+ parseAvailabilityDateTime(option.dateTime).getTime() ===
137
+ parseAvailabilityDateTime(initialReturnDtForSelect).getTime()
138
+ );
139
+ } catch {
140
+ return false;
141
+ }
142
+ })
143
+ : undefined;
144
+ const firstAvailable = sorted.find((option) => option.vacancies > 0);
145
+ const toSelect = preferBooked ?? preferByDateTime ?? firstAvailable;
146
+ if (toSelect) {
147
+ setSelectedReturnOption(toSelect);
148
+ }
149
+ }, [
150
+ selectedAvailability,
151
+ selectedReturnOption,
152
+ initialReturnAvailabilityId,
153
+ initialReturnDateTime,
154
+ setSelectedReturnOption,
155
+ ]);
156
+
157
+ useEffect(() => {
158
+ if (!autoSelectFirstAvailableDate) return;
159
+ if (isPartialLaunch) return;
160
+ if (initialDateTime?.trim()) return;
161
+ if (selectedDate !== '') return;
162
+ if (dates.length === 0) return;
163
+ if (hasAutoSelectedPartnerDateRef.current) return;
164
+
165
+ const firstWithInventory = dates.find((date) =>
166
+ getPickupCutoffEligibleTimesForDate(date).some((availability) => (availability.vacancies ?? 0) > 0),
167
+ );
168
+ const first = firstWithInventory ?? (isAdmin && dates[0] ? dates[0] : undefined);
169
+ if (!first) return;
170
+
171
+ hasAutoSelectedPartnerDateRef.current = true;
172
+ setSelectedDate(first);
173
+ setSelectedReturnOption(null);
174
+ setSelectedAvailability(
175
+ pickDefaultAvailabilityForTimes(getPickupCutoffEligibleTimesForDate(first), activeOptions),
176
+ );
177
+ if (!suppressCalendarDateScroll) {
178
+ scrollAfterCalendarSelection(contentRef, useWindowScroll);
179
+ }
180
+ }, [
181
+ autoSelectFirstAvailableDate,
182
+ isPartialLaunch,
183
+ initialDateTime,
184
+ selectedDate,
185
+ dates,
186
+ getPickupCutoffEligibleTimesForDate,
187
+ isAdmin,
188
+ activeOptions,
189
+ setSelectedAvailability,
190
+ setSelectedDate,
191
+ setSelectedReturnOption,
192
+ suppressCalendarDateScroll,
193
+ contentRef,
194
+ useWindowScroll,
195
+ ]);
196
+
197
+ useEffect(() => {
198
+ if (!autoSelectFirstHighlightedPickup) return;
199
+ if (hasAutoSelectedPartnerPickupRef.current) return;
200
+ if (!highlightedPickupLocationIds?.length) return;
201
+ if (pickupLocationId) return;
202
+ if (pickupLocationSkipped) return;
203
+ if (initialPickupLocationId?.trim()) return;
204
+ if (!pickupLocations?.length) return;
205
+
206
+ const match = highlightedPickupLocationIds.find((id) =>
207
+ pickupLocations.some((location) => location.id === id),
208
+ );
209
+ if (!match) return;
210
+
211
+ hasAutoSelectedPartnerPickupRef.current = true;
212
+ setPickupLocationId(match);
213
+ setPickupLocationSkipped(false);
214
+ }, [
215
+ autoSelectFirstHighlightedPickup,
216
+ highlightedPickupLocationIds,
217
+ pickupLocationId,
218
+ pickupLocationSkipped,
219
+ initialPickupLocationId,
220
+ pickupLocations,
221
+ setPickupLocationId,
222
+ setPickupLocationSkipped,
223
+ ]);
224
+ }