@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,148 @@
1
+ 'use client';
2
+
3
+ import {
4
+ PRIVATE_SHUTTLE_RESOURCE_CAPACITY,
5
+ } from './usePrivateShuttlePriceSummary';
6
+ import styles from './PrivateShuttleBookingFlow.module.css';
7
+
8
+ interface PrivateShuttlePassengerSectionProps {
9
+ passengerCount: number;
10
+ resourceCount: number;
11
+ billableResourceCount: number;
12
+ maxVacancies: number;
13
+ isSpecialRequestMode: boolean;
14
+ specialRequestInputValue: string;
15
+ onPassengerCountChange: (count: number) => void;
16
+ onPassengerCountOverride: (count: number) => void;
17
+ onSpecialRequestModeChange: (enabled: boolean) => void;
18
+ onSpecialRequestInputValueChange: (value: string) => void;
19
+ }
20
+
21
+ export function PrivateShuttlePassengerSection({
22
+ passengerCount,
23
+ resourceCount,
24
+ billableResourceCount,
25
+ maxVacancies,
26
+ isSpecialRequestMode,
27
+ specialRequestInputValue,
28
+ onPassengerCountChange,
29
+ onPassengerCountOverride,
30
+ onSpecialRequestModeChange,
31
+ onSpecialRequestInputValueChange,
32
+ }: PrivateShuttlePassengerSectionProps) {
33
+ const passengerSelectMin = 1;
34
+ const specialRequestMin = maxVacancies + 1;
35
+
36
+ return (
37
+ <div className={styles.section}>
38
+ <label className={`${styles.sectionLabel} private-shuttle-section-label`}>
39
+ Number of Passengers
40
+ </label>
41
+ <div className={styles.passengerBox}>
42
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
43
+ <div>
44
+ <div className="font-medium text-stone-900">Passengers</div>
45
+ <div className="text-sm text-stone-600">
46
+ {resourceCount} {resourceCount === 1 ? 'shuttle' : 'shuttles'} needed
47
+ </div>
48
+ </div>
49
+ {isSpecialRequestMode ? (
50
+ <div className="flex flex-col sm:flex-row sm:items-center gap-2">
51
+ <input
52
+ type="number"
53
+ min={specialRequestMin}
54
+ max={100}
55
+ value={specialRequestInputValue || String(passengerCount)}
56
+ onChange={(e) => {
57
+ const raw = e.target.value;
58
+ onSpecialRequestInputValueChange(raw);
59
+ const val = parseInt(raw, 10);
60
+ if (!Number.isNaN(val) && val >= specialRequestMin && val <= 100) {
61
+ onPassengerCountOverride(val);
62
+ }
63
+ }}
64
+ onBlur={() => {
65
+ const val = parseInt(specialRequestInputValue, 10);
66
+ const clamped = Math.max(
67
+ specialRequestMin,
68
+ Math.min(100, Number.isNaN(val) ? specialRequestMin : val)
69
+ );
70
+ onPassengerCountOverride(clamped);
71
+ onSpecialRequestInputValueChange(String(clamped));
72
+ }}
73
+ className={`${styles.input} ${styles.passengerInput}`}
74
+ style={{ minWidth: '4.5rem', textAlign: 'center' }}
75
+ />
76
+ <button
77
+ type="button"
78
+ onClick={() => {
79
+ onSpecialRequestModeChange(false);
80
+ onSpecialRequestInputValueChange('');
81
+ onPassengerCountOverride(Math.max(1, maxVacancies));
82
+ }}
83
+ className="text-sm text-stone-600 underline hover:text-stone-800"
84
+ >
85
+ Back to standard ({maxVacancies} max)
86
+ </button>
87
+ </div>
88
+ ) : passengerSelectMin <= maxVacancies ? (
89
+ <select
90
+ value={passengerCount}
91
+ onChange={(e) => onPassengerCountChange(Number(e.target.value))}
92
+ className={`${styles.input} ${styles.passengerSelect}`}
93
+ style={{ minWidth: '4.5rem', textAlign: 'center' }}
94
+ >
95
+ {Array.from(
96
+ {
97
+ length: Math.max(1, maxVacancies - passengerSelectMin + 1),
98
+ },
99
+ (_, i) => i + passengerSelectMin
100
+ ).map((n) => (
101
+ <option key={n} value={n}>
102
+ {n}
103
+ </option>
104
+ ))}
105
+ </select>
106
+ ) : null}
107
+ </div>
108
+ <div className="text-xs text-stone-500 mt-2">
109
+ Each shuttle can accommodate up to {PRIVATE_SHUTTLE_RESOURCE_CAPACITY} passengers.
110
+ {resourceCount > 1 && (
111
+ <span>
112
+ {' '}
113
+ You&apos;ll need {resourceCount} shuttles for {passengerCount}{' '}
114
+ passengers.
115
+ </span>
116
+ )}
117
+ </div>
118
+ {maxVacancies > 0 && !isSpecialRequestMode ? (
119
+ <div className="mt-3 pt-3 border-t border-stone-200 text-right">
120
+ <button
121
+ type="button"
122
+ onClick={() => {
123
+ const initial = maxVacancies + 1;
124
+ onSpecialRequestModeChange(true);
125
+ onPassengerCountOverride(initial);
126
+ onSpecialRequestInputValueChange(String(initial));
127
+ }}
128
+ className={`${styles.specialRequestLink} text-emerald-600 hover:text-emerald-700 underline`}
129
+ >
130
+ Special request for more than {maxVacancies} passengers
131
+ </button>
132
+ </div>
133
+ ) : null}
134
+ {isSpecialRequestMode && passengerCount > maxVacancies ? (
135
+ <div className="mt-3 pt-3 border-t border-stone-200">
136
+ <p className="text-sm text-amber-700 bg-amber-50 p-3 rounded-lg">
137
+ The team will review your request to ensure we can pull{' '}
138
+ {resourceCount} {resourceCount === 1 ? 'shuttle' : 'shuttles'}{' '}
139
+ from our fleet. You&apos;ll pay the full amount for {resourceCount}{' '}
140
+ {resourceCount === 1 ? 'shuttle' : 'shuttles'}; we&apos;ve reserved{' '}
141
+ {billableResourceCount} at standard capacity and the team will add the rest.
142
+ </p>
143
+ </div>
144
+ ) : null}
145
+ </div>
146
+ </div>
147
+ );
148
+ }
@@ -0,0 +1,91 @@
1
+ import type { Destination, PickupLocation } from '../../lib/booking-api';
2
+ import { PickupLocationSelector } from './PickupLocationSelector';
3
+ import styles from './PrivateShuttleBookingFlow.module.css';
4
+
5
+ type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
6
+
7
+ export interface PrivateShuttlePickupSectionProps {
8
+ passengerCount: number;
9
+ pickupLocations?: PickupLocation[];
10
+ destinations?: Destination[];
11
+ pickupLocationId: string | null;
12
+ customPickupAddress: string | null;
13
+ pickupLocationSkipped: boolean;
14
+ selectedPickupLocation?: PickupLocation | null;
15
+ highlightedPickupLocationIds?: string[];
16
+ t: TranslationFn;
17
+ onLocationSelect: (locationId: string | null, customAddress: string | null) => void;
18
+ onSkip: () => void;
19
+ onReset: () => void;
20
+ }
21
+
22
+ export function PrivateShuttlePickupSection({
23
+ passengerCount,
24
+ pickupLocations,
25
+ destinations,
26
+ pickupLocationId,
27
+ customPickupAddress,
28
+ pickupLocationSkipped,
29
+ selectedPickupLocation,
30
+ highlightedPickupLocationIds,
31
+ t,
32
+ onLocationSelect,
33
+ onSkip,
34
+ onReset,
35
+ }: PrivateShuttlePickupSectionProps) {
36
+ if (passengerCount <= 0 || !pickupLocations?.length) return null;
37
+
38
+ return (
39
+ <div id="pickup-location-section" className={styles.section}>
40
+ {pickupLocationId || customPickupAddress || pickupLocationSkipped ? (
41
+ <div className="space-y-4">
42
+ <div className="flex items-center justify-between">
43
+ <div>
44
+ <label className={`${styles.sectionLabel} private-shuttle-section-label`}>
45
+ {t('pickup.pickupLocation') || 'Pickup Location'}
46
+ </label>
47
+ {pickupLocationSkipped ? (
48
+ <p className="text-sm text-stone-900">
49
+ {t('booking.pickupLocationUnknown')}
50
+ </p>
51
+ ) : customPickupAddress ? (
52
+ <p className="text-sm text-stone-900">{customPickupAddress}</p>
53
+ ) : (
54
+ <>
55
+ <p className="text-sm text-stone-900">
56
+ {selectedPickupLocation?.name}
57
+ </p>
58
+ <p className="text-xs text-stone-500">
59
+ {selectedPickupLocation?.address}
60
+ </p>
61
+ </>
62
+ )}
63
+ </div>
64
+ <button
65
+ type="button"
66
+ onClick={onReset}
67
+ className={styles.changeBtn}
68
+ >
69
+ {t('common.change') || 'Change'}
70
+ </button>
71
+ </div>
72
+ </div>
73
+ ) : (
74
+ <PickupLocationSelector
75
+ pickupLocations={pickupLocations}
76
+ selectedLocationId={pickupLocationId}
77
+ selectedCustomAddress={customPickupAddress}
78
+ highlightedPickupLocationIds={highlightedPickupLocationIds}
79
+ allowCustomLocation
80
+ restrictCustomLocationToServiceArea
81
+ isSkipped={pickupLocationSkipped}
82
+ destinations={destinations}
83
+ onLocationSelect={(locationId, customLocation) => {
84
+ onLocationSelect(locationId, customLocation?.address ?? null);
85
+ }}
86
+ onSkip={onSkip}
87
+ />
88
+ )}
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,75 @@
1
+ import styles from './PrivateShuttleBookingFlow.module.css';
2
+
3
+ export interface PrivateShuttlePreferencesSectionProps {
4
+ passengerCount: number;
5
+ childSafetySeatsCount: number;
6
+ foodRestrictions: string;
7
+ onChildSafetySeatsCountChange: (count: number) => void;
8
+ onFoodRestrictionsChange: (value: string) => void;
9
+ }
10
+
11
+ export function PrivateShuttlePreferencesSection({
12
+ passengerCount,
13
+ childSafetySeatsCount,
14
+ foodRestrictions,
15
+ onChildSafetySeatsCountChange,
16
+ onFoodRestrictionsChange,
17
+ }: PrivateShuttlePreferencesSectionProps) {
18
+ if (passengerCount <= 0) return null;
19
+
20
+ return (
21
+ <>
22
+ <div className={styles.section}>
23
+ <label className={`${styles.sectionLabel} private-shuttle-section-label`}>
24
+ Safety seats for kids
25
+ </label>
26
+ <p className="text-sm text-stone-500 mb-2">
27
+ How many child safety seats do you need?
28
+ </p>
29
+ <div className="flex items-center gap-2">
30
+ <button
31
+ type="button"
32
+ onClick={() => onChildSafetySeatsCountChange(Math.max(0, childSafetySeatsCount - 1))}
33
+ disabled={childSafetySeatsCount <= 0}
34
+ className={styles.qtyBtn}
35
+ >
36
+ -
37
+ </button>
38
+ <span className="w-8 text-center font-medium tabular-nums">
39
+ {childSafetySeatsCount}
40
+ </span>
41
+ <button
42
+ type="button"
43
+ onClick={() =>
44
+ onChildSafetySeatsCountChange(Math.min(passengerCount, childSafetySeatsCount + 1))
45
+ }
46
+ disabled={childSafetySeatsCount >= passengerCount}
47
+ className={styles.qtyBtn}
48
+ >
49
+ +
50
+ </button>
51
+ </div>
52
+ </div>
53
+
54
+ <div className={styles.section}>
55
+ <label
56
+ htmlFor="food-restrictions"
57
+ className={`${styles.sectionLabel} private-shuttle-section-label`}
58
+ >
59
+ Food restrictions
60
+ </label>
61
+ <p className="text-sm text-stone-500 mb-2">
62
+ Shuttle includes croissants, coffee, tea, hot chocolate, trail snacks.
63
+ </p>
64
+ <textarea
65
+ id="food-restrictions"
66
+ value={foodRestrictions}
67
+ onChange={(event) => onFoodRestrictionsChange(event.target.value)}
68
+ placeholder="Any dietary restrictions or allergies?"
69
+ rows={2}
70
+ className={styles.input}
71
+ />
72
+ </div>
73
+ </>
74
+ );
75
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
4
+ import styles from './PrivateShuttleBookingFlow.module.css';
5
+
6
+ type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
7
+
8
+ interface PrivateShuttleStartTimeSectionProps {
9
+ suggestedStartTimes: string[];
10
+ selectedStartTime: string;
11
+ isCustomTimeMode: boolean;
12
+ companyTimezone: string;
13
+ t: TranslationFn;
14
+ onStartTimeSelect: (time: string) => void;
15
+ onCustomTimeRequest: () => void;
16
+ onCustomTimeChange: (time: string) => void;
17
+ }
18
+
19
+ export function PrivateShuttleStartTimeSection({
20
+ suggestedStartTimes,
21
+ selectedStartTime,
22
+ isCustomTimeMode,
23
+ companyTimezone,
24
+ t,
25
+ onStartTimeSelect,
26
+ onCustomTimeRequest,
27
+ onCustomTimeChange,
28
+ }: PrivateShuttleStartTimeSectionProps) {
29
+ return (
30
+ <div className={styles.section}>
31
+ <label className={`${styles.sectionLabel} private-shuttle-section-label`}>
32
+ {t('booking.selectPickupTime')}
33
+ </label>
34
+ {(suggestedStartTimes.length > 0 || isCustomTimeMode) && (
35
+ <div className="flex flex-wrap gap-2">
36
+ {suggestedStartTimes.map((time) => {
37
+ const isSelected = !isCustomTimeMode && selectedStartTime === time;
38
+ const [hours, minutes] = time.split(':').map(Number);
39
+ const timeInTz = fromZonedTime(new Date(2000, 0, 1, hours, minutes, 0), companyTimezone);
40
+ const displayTime = formatInTimeZone(timeInTz, companyTimezone, 'h:mm a');
41
+ return (
42
+ <button
43
+ key={time}
44
+ type="button"
45
+ onClick={() => onStartTimeSelect(time)}
46
+ className={`${styles.btnTime} private-shuttle-btn-time ${
47
+ isSelected ? styles.btnTimeSelected : styles.btnTimeDefault
48
+ }`}
49
+ >
50
+ {displayTime}
51
+ </button>
52
+ );
53
+ })}
54
+ {suggestedStartTimes.length > 0 && (
55
+ <button
56
+ type="button"
57
+ onClick={onCustomTimeRequest}
58
+ className={`${styles.btnTime} private-shuttle-btn-time ${
59
+ isCustomTimeMode ? styles.btnTimeSelected : styles.btnTimeDefault
60
+ }`}
61
+ >
62
+ {t('booking.requestDifferentTime')}
63
+ </button>
64
+ )}
65
+ </div>
66
+ )}
67
+ {(isCustomTimeMode || suggestedStartTimes.length === 0) && (
68
+ <div className="mt-3">
69
+ <label
70
+ htmlFor="custom-pickup-time"
71
+ className={styles.labelSecondary}
72
+ >
73
+ {suggestedStartTimes.length === 0
74
+ ? t('booking.preferredPickupTime')
75
+ : t('booking.requestDifferentTime')}
76
+ </label>
77
+ <input
78
+ id="custom-pickup-time"
79
+ type="time"
80
+ value={selectedStartTime}
81
+ onChange={(e) => onCustomTimeChange(e.target.value)}
82
+ className={styles.inputTime}
83
+ />
84
+ </div>
85
+ )}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,106 @@
1
+ import { AdminPaymentChoiceModal } from './AdminPaymentChoiceModal';
2
+ import { CheckoutModal } from './CheckoutModal';
3
+ import type {
4
+ StandardAdminChoiceData,
5
+ StandardCheckoutModalData,
6
+ } from './standard-booking-payment-choice-runner';
7
+ import type { ChangeBookingSuccessUrlFactory } from './change-booking-payment-modal-builders';
8
+ import type { Currency } from './CurrencySwitcher';
9
+ import type { Locale } from '../../lib/booking/i18n/config';
10
+
11
+ type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
12
+
13
+ export interface StandardBookingCheckoutDialogsProps {
14
+ showAdminPaymentChoice: boolean;
15
+ adminChoiceData: StandardAdminChoiceData | null;
16
+ checkoutModalData: StandardCheckoutModalData | null;
17
+ showCheckoutModal: boolean;
18
+ checkoutClientSecret: string;
19
+ currency: Currency;
20
+ locale: Locale;
21
+ loading: boolean;
22
+ error: string;
23
+ getSuccessUrl?: ChangeBookingSuccessUrlFactory;
24
+ t: TranslationFn;
25
+ onPayNow: () => void;
26
+ onConfirmWithoutPayment: () => void;
27
+ onAdminPaymentChoiceCancel: () => void;
28
+ onCheckoutClose: () => void;
29
+ onPaymentSubmitStart: () => void;
30
+ onPaymentSubmitError: () => void;
31
+ }
32
+
33
+ export function StandardBookingCheckoutDialogs({
34
+ showAdminPaymentChoice,
35
+ adminChoiceData,
36
+ checkoutModalData,
37
+ showCheckoutModal,
38
+ checkoutClientSecret,
39
+ currency,
40
+ locale,
41
+ loading,
42
+ error,
43
+ getSuccessUrl,
44
+ t,
45
+ onPayNow,
46
+ onConfirmWithoutPayment,
47
+ onAdminPaymentChoiceCancel,
48
+ onCheckoutClose,
49
+ onPaymentSubmitStart,
50
+ onPaymentSubmitError,
51
+ }: StandardBookingCheckoutDialogsProps) {
52
+ return (
53
+ <>
54
+ <AdminPaymentChoiceModal
55
+ open={!!(showAdminPaymentChoice && adminChoiceData)}
56
+ totalAmount={adminChoiceData?.totalAmount ?? 0}
57
+ currency={currency}
58
+ loading={loading}
59
+ error={error}
60
+ onPayNow={onPayNow}
61
+ onConfirmWithoutPayment={onConfirmWithoutPayment}
62
+ onCancel={onAdminPaymentChoiceCancel}
63
+ />
64
+ {checkoutModalData && (
65
+ <CheckoutModal
66
+ open={showCheckoutModal}
67
+ onClose={onCheckoutClose}
68
+ onPaymentSubmitStart={onPaymentSubmitStart}
69
+ onPaymentSubmitError={onPaymentSubmitError}
70
+ clientSecret={checkoutClientSecret}
71
+ reservationReference={checkoutModalData.reservationReference}
72
+ reservationExpiration={checkoutModalData.reservationExpiration}
73
+ customerLastName={checkoutModalData.customerLastName}
74
+ successUrlOverride={
75
+ checkoutModalData.successUrlOverride ??
76
+ (getSuccessUrl
77
+ ? getSuccessUrl({
78
+ reservationRef: checkoutModalData.reservationReference,
79
+ lastName: checkoutModalData.customerLastName ?? '',
80
+ focusDate: checkoutModalData.bookingDate,
81
+ })
82
+ : undefined)
83
+ }
84
+ ticketLines={checkoutModalData.ticketLines}
85
+ feeLineItems={checkoutModalData.feeLineItems}
86
+ returnPriceAdjustment={checkoutModalData.returnPriceAdjustment}
87
+ cancellationPolicyFee={checkoutModalData.cancellationPolicyFee}
88
+ cancellationPolicyLabel={checkoutModalData.cancellationPolicyLabel}
89
+ subtotal={checkoutModalData.subtotal}
90
+ tax={checkoutModalData.tax}
91
+ total={checkoutModalData.total}
92
+ promoDiscountAmount={checkoutModalData.promoDiscountAmount ?? 0}
93
+ discountLabel={checkoutModalData.discountLabel}
94
+ summaryOverride={checkoutModalData.summaryOverride}
95
+ totalQuantity={checkoutModalData.totalQuantity}
96
+ isTaxIncludedInPrice={checkoutModalData.isTaxIncludedInPrice}
97
+ taxRate={checkoutModalData.taxRate}
98
+ changeTotals={checkoutModalData.changeTotals}
99
+ currency={currency}
100
+ locale={locale}
101
+ t={t}
102
+ />
103
+ )}
104
+ </>
105
+ );
106
+ }
@@ -0,0 +1,185 @@
1
+ import type { Destination, PickupLocation } from '../../lib/booking-api';
2
+ import type { NewBookingFlowProps } from './booking-flow-types';
3
+ import { CheckoutForm } from './CheckoutForm';
4
+ import type { Currency } from './CurrencySwitcher';
5
+ import { PromoCodeInput } from './PromoCodeInput';
6
+ import type { PriceSummaryLine } from './PriceSummary';
7
+
8
+ type TranslationFn = (key: string, params?: Record<string, string>) => string;
9
+
10
+ export interface StandardBookingCheckoutSectionProps {
11
+ priceSummaryLines: PriceSummaryLine[];
12
+ totalPrice: number;
13
+ subtotal?: number;
14
+ taxAmount: number;
15
+ taxRate?: number;
16
+ currency: Currency;
17
+ locale: string;
18
+ t: TranslationFn;
19
+ promoCodeInput: string;
20
+ appliedPromoCode: string | null;
21
+ promoCodeError: string;
22
+ promoCodeValidating: boolean;
23
+ promoDiscountAmount: number;
24
+ onPromoInputChange: (value: string) => void;
25
+ onPromoApply: () => void;
26
+ onPromoRemove: () => void;
27
+ firstName: string;
28
+ lastName: string;
29
+ email: string;
30
+ phoneNumber: string;
31
+ onFirstNameChange: (value: string) => void;
32
+ onLastNameChange: (value: string) => void;
33
+ onEmailChange: (value: string) => void;
34
+ onPhoneNumberChange: (value: string) => void;
35
+ selectedDate: string;
36
+ pickupLocations?: PickupLocation[];
37
+ destinations?: Destination[];
38
+ pickupLocationId: string | null;
39
+ pickupLocationSkipped: boolean;
40
+ selectedPickupLocation?: PickupLocation | null;
41
+ highlightedPickupLocationIds?: NewBookingFlowProps['highlightedPickupLocationIds'];
42
+ hidePickupSkipOption: boolean;
43
+ onLocationSelect: (locationId: string | null) => void;
44
+ onSkipPickup: () => void;
45
+ onChangePickup: () => void;
46
+ termsAccepted: boolean;
47
+ onTermsChange: (checked: boolean) => void;
48
+ isAdmin: boolean;
49
+ skipConfirmationCommunications: boolean;
50
+ disableAutoCommunications: boolean;
51
+ onSkipConfirmationChange: (checked: boolean) => void;
52
+ onDisableCommunicationsChange: (checked: boolean) => void;
53
+ error: string;
54
+ loading: boolean;
55
+ totalQuantity: number;
56
+ onCheckout: () => void;
57
+ submitLabel?: string;
58
+ hideSubmitButton: boolean;
59
+ attributionSummary?: string;
60
+ attributionConfirmLabel?: string;
61
+ attributionConfirmed: boolean;
62
+ onAttributionConfirmedChange: (checked: boolean) => void;
63
+ }
64
+
65
+ export function StandardBookingCheckoutSection({
66
+ priceSummaryLines,
67
+ totalPrice,
68
+ subtotal,
69
+ taxAmount,
70
+ taxRate,
71
+ currency,
72
+ locale,
73
+ t,
74
+ promoCodeInput,
75
+ appliedPromoCode,
76
+ promoCodeError,
77
+ promoCodeValidating,
78
+ promoDiscountAmount,
79
+ onPromoInputChange,
80
+ onPromoApply,
81
+ onPromoRemove,
82
+ firstName,
83
+ lastName,
84
+ email,
85
+ phoneNumber,
86
+ onFirstNameChange,
87
+ onLastNameChange,
88
+ onEmailChange,
89
+ onPhoneNumberChange,
90
+ selectedDate,
91
+ pickupLocations,
92
+ destinations,
93
+ pickupLocationId,
94
+ pickupLocationSkipped,
95
+ selectedPickupLocation,
96
+ highlightedPickupLocationIds,
97
+ hidePickupSkipOption,
98
+ onLocationSelect,
99
+ onSkipPickup,
100
+ onChangePickup,
101
+ termsAccepted,
102
+ onTermsChange,
103
+ isAdmin,
104
+ skipConfirmationCommunications,
105
+ disableAutoCommunications,
106
+ onSkipConfirmationChange,
107
+ onDisableCommunicationsChange,
108
+ error,
109
+ loading,
110
+ totalQuantity,
111
+ onCheckout,
112
+ submitLabel,
113
+ hideSubmitButton,
114
+ attributionSummary,
115
+ attributionConfirmLabel,
116
+ attributionConfirmed,
117
+ onAttributionConfirmedChange,
118
+ }: StandardBookingCheckoutSectionProps) {
119
+ return (
120
+ <CheckoutForm
121
+ priceSummaryLines={priceSummaryLines}
122
+ totalPrice={totalPrice}
123
+ totalSummaryLabel={undefined}
124
+ subtotal={subtotal}
125
+ taxAmount={taxAmount}
126
+ taxRate={taxRate}
127
+ currency={currency}
128
+ locale={locale}
129
+ t={t}
130
+ extraBetweenTaxAndTotal={
131
+ <PromoCodeInput
132
+ promoCodeInput={promoCodeInput}
133
+ appliedPromoCode={appliedPromoCode}
134
+ promoCodeError={promoCodeError}
135
+ promoCodeValidating={promoCodeValidating}
136
+ promoDiscountAmount={promoDiscountAmount}
137
+ currency={currency}
138
+ locale={locale}
139
+ t={t}
140
+ onInputChange={onPromoInputChange}
141
+ onApply={onPromoApply}
142
+ onRemove={onPromoRemove}
143
+ />
144
+ }
145
+ firstName={firstName}
146
+ lastName={lastName}
147
+ email={email}
148
+ onFirstNameChange={onFirstNameChange}
149
+ onLastNameChange={onLastNameChange}
150
+ onEmailChange={onEmailChange}
151
+ phoneNumber={phoneNumber}
152
+ onPhoneNumberChange={onPhoneNumberChange}
153
+ readOnlyContactFields={false}
154
+ pickupLocations={selectedDate && pickupLocations && pickupLocations.length > 0 ? pickupLocations : undefined}
155
+ destinations={destinations}
156
+ pickupLocationId={pickupLocationId}
157
+ pickupLocationSkipped={pickupLocationSkipped}
158
+ selectedPickupLocation={selectedPickupLocation}
159
+ highlightedPickupLocationIds={highlightedPickupLocationIds}
160
+ hidePickupSkipOption={hidePickupSkipOption}
161
+ onLocationSelect={onLocationSelect}
162
+ onSkip={onSkipPickup}
163
+ onChangePickup={onChangePickup}
164
+ termsAccepted={termsAccepted}
165
+ onTermsChange={onTermsChange}
166
+ isAdmin={isAdmin}
167
+ showCommunicationAdminSection
168
+ skipConfirmationCommunications={skipConfirmationCommunications}
169
+ disableAutoCommunications={disableAutoCommunications}
170
+ onSkipConfirmationChange={onSkipConfirmationChange}
171
+ onDisableCommunicationsChange={onDisableCommunicationsChange}
172
+ error={error}
173
+ loading={loading}
174
+ totalQuantity={totalQuantity}
175
+ onCheckout={onCheckout}
176
+ submitLabel={submitLabel}
177
+ hideSubmitButton={hideSubmitButton}
178
+ submitDisabled={false}
179
+ attributionSummary={attributionSummary}
180
+ attributionConfirmLabel={attributionConfirmLabel}
181
+ attributionConfirmed={attributionConfirmed}
182
+ onAttributionConfirmedChange={onAttributionConfirmedChange}
183
+ />
184
+ );
185
+ }