@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,50 @@
1
+ import type { Product } from '../../lib/booking-api';
2
+
3
+ export interface AdminChangeProductSwitcherProps {
4
+ visible: boolean;
5
+ products: Product[];
6
+ selectedProductId: string;
7
+ disabled: boolean;
8
+ error?: string | null;
9
+ onProductChange: (productId: string) => void;
10
+ }
11
+
12
+ export function AdminChangeProductSwitcher({
13
+ visible,
14
+ products,
15
+ selectedProductId,
16
+ disabled,
17
+ error,
18
+ onProductChange,
19
+ }: AdminChangeProductSwitcherProps) {
20
+ if (!visible) return null;
21
+
22
+ return (
23
+ <div className="mb-6 rounded-lg border border-stone-200 bg-stone-50 p-4">
24
+ <label
25
+ className="mb-2 block text-sm font-medium text-stone-800"
26
+ htmlFor="admin-change-product-select"
27
+ >
28
+ Product
29
+ </label>
30
+ <select
31
+ id="admin-change-product-select"
32
+ className="w-full rounded-md border border-stone-300 bg-white px-3 py-2 text-sm text-stone-900 focus:outline-none focus:ring-2 focus:ring-stone-400/80"
33
+ value={selectedProductId}
34
+ disabled={disabled}
35
+ onChange={(e) => onProductChange(e.target.value)}
36
+ >
37
+ {products.map((product) => (
38
+ <option key={product.productId} value={product.productId}>
39
+ {product.name}
40
+ </option>
41
+ ))}
42
+ </select>
43
+ {error ? (
44
+ <div className="mt-2 text-xs text-amber-800">
45
+ Product list could not be refreshed. Showing the current product only.
46
+ </div>
47
+ ) : null}
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,167 @@
1
+ import { useMemo, type ReactNode } from 'react';
2
+ import type { AdminFeAuthoritativeReceipt } from '../../lib/booking-api';
3
+ import { roundMoney } from '../../lib/booking/change-flow-pricing';
4
+ import { mapQuoteLineItemsToPriceSummaryLines } from '../../lib/booking/change-booking-server-preview';
5
+ import type { PriceBreakdown as PriceBreakdownData } from '../../lib/booking/pricing';
6
+ import { formatCurrencyAmount } from '../../lib/currency';
7
+ import type { Locale } from '../../lib/booking/i18n/config';
8
+ import type { ChangeBookingFlowProps } from './booking-flow-types';
9
+ import type { Currency } from './CurrencySwitcher';
10
+ import { PriceSummary, type PriceSummaryLine } from './PriceSummary';
11
+
12
+ type TranslationFn = (key: string, params?: Record<string, string>) => string;
13
+ type OriginalReceipt = NonNullable<ChangeBookingFlowProps['originalReceipt']>;
14
+
15
+ export interface AdminChangeReceiptComparisonProps {
16
+ originalReceipt: OriginalReceipt;
17
+ newReceipt: Pick<AdminFeAuthoritativeReceipt, 'subtotal' | 'tax' | 'total'>;
18
+ newPriceSummaryLines: PriceSummaryLine[];
19
+ newPriceSummaryLinesIncludeTaxRow: boolean;
20
+ amountDue: number;
21
+ amountDueLabel: string;
22
+ currency: Currency;
23
+ locale: string;
24
+ t: TranslationFn;
25
+ taxRate?: number;
26
+ newReceiptAdjustments?: ReactNode;
27
+ lineAmountInputs?: Record<string, string>;
28
+ onLineAmountInputChange?: (lineKey: string, value: string) => void;
29
+ onLineAmountInputBlur?: (lineKey: string) => void;
30
+ onLineAmountReset?: (lineKey: string) => void;
31
+ lineLabelInputs?: Record<string, string>;
32
+ }
33
+
34
+ function normalizeLocale(locale: string): Locale {
35
+ return locale === 'fr' ? 'fr' : 'en';
36
+ }
37
+
38
+ function hasTaxLine(lines: PriceSummaryLine[]): boolean {
39
+ return lines.some(
40
+ (line) => line.kind === 'line' && String(line.type ?? '').toUpperCase() === 'TAX',
41
+ );
42
+ }
43
+
44
+ function storedReceiptTicketBreakdown(line: Extract<PriceSummaryLine, { kind: 'ticket' }>): PriceBreakdownData {
45
+ const qty = Math.max(1, line.qty);
46
+ const unitAmount = roundMoney(line.itemTotal / qty);
47
+ return {
48
+ lineItems: [
49
+ {
50
+ type: 'base',
51
+ name: 'Stored receipt unit',
52
+ amountInDisplayCurrency: unitAmount,
53
+ },
54
+ ],
55
+ subtotalAfterAdjustments: unitAmount,
56
+ feeLines: [],
57
+ taxRate: 0,
58
+ taxAmount: 0,
59
+ isTaxIncluded: false,
60
+ finalPrice: unitAmount,
61
+ };
62
+ }
63
+
64
+ function withStoredReceiptBreakdowns(lines: PriceSummaryLine[]): PriceSummaryLine[] {
65
+ return lines.map((line) => {
66
+ if (line.kind !== 'ticket' || line.breakdown) return line;
67
+ return {
68
+ ...line,
69
+ breakdown: storedReceiptTicketBreakdown(line),
70
+ };
71
+ });
72
+ }
73
+
74
+ function mapOriginalReceiptLines(originalReceipt: OriginalReceipt): PriceSummaryLine[] {
75
+ if (!originalReceipt.lineItems?.length) return [];
76
+ return withStoredReceiptBreakdowns(mapQuoteLineItemsToPriceSummaryLines(originalReceipt.lineItems));
77
+ }
78
+
79
+ export function AdminChangeReceiptComparison({
80
+ originalReceipt,
81
+ newReceipt,
82
+ newPriceSummaryLines,
83
+ newPriceSummaryLinesIncludeTaxRow,
84
+ amountDue,
85
+ amountDueLabel,
86
+ currency,
87
+ locale,
88
+ t,
89
+ taxRate,
90
+ newReceiptAdjustments,
91
+ lineAmountInputs,
92
+ onLineAmountInputChange,
93
+ onLineAmountInputBlur,
94
+ onLineAmountReset,
95
+ lineLabelInputs,
96
+ }: AdminChangeReceiptComparisonProps) {
97
+ const displayLocale = normalizeLocale(locale);
98
+ const originalReceiptLines = useMemo(
99
+ () => mapOriginalReceiptLines(originalReceipt),
100
+ [originalReceipt],
101
+ );
102
+ const originalLinesIncludeTaxRow = hasTaxLine(originalReceiptLines);
103
+ const originalCurrency = originalReceipt.currency ?? currency;
104
+ const amountDueClass =
105
+ amountDue < -0.005
106
+ ? 'text-emerald-700'
107
+ : amountDue > 0.005
108
+ ? 'text-stone-900'
109
+ : 'text-stone-700';
110
+
111
+ return (
112
+ <div className="space-y-3 min-w-0 overflow-visible">
113
+ <div className="grid gap-4 md:grid-cols-2">
114
+ <section className="min-w-0 overflow-visible rounded-lg border border-stone-200 bg-stone-50/70 p-3">
115
+ <div className="mb-3 flex items-center justify-between gap-3">
116
+ <h3 className="text-sm font-semibold text-stone-900">Current receipt</h3>
117
+ <span className="shrink-0 text-xs font-medium text-stone-500">
118
+ {formatCurrencyAmount(originalReceipt.total, originalCurrency, displayLocale)}
119
+ </span>
120
+ </div>
121
+ <PriceSummary
122
+ lines={originalReceiptLines}
123
+ total={originalReceipt.total}
124
+ currency={originalCurrency}
125
+ locale={displayLocale}
126
+ subtotal={originalReceipt.subtotal}
127
+ taxAmount={!originalLinesIncludeTaxRow ? originalReceipt.tax : 0}
128
+ taxRate={taxRate}
129
+ size="sm"
130
+ t={t}
131
+ />
132
+ </section>
133
+ <section className="min-w-0 overflow-visible rounded-lg border border-stone-300 bg-white p-3 shadow-sm">
134
+ <div className="mb-3 flex items-center justify-between gap-3">
135
+ <h3 className="text-sm font-semibold text-stone-900">New receipt</h3>
136
+ <span className="shrink-0 text-xs font-medium text-stone-500">
137
+ {formatCurrencyAmount(newReceipt.total, currency, displayLocale)}
138
+ </span>
139
+ </div>
140
+ <PriceSummary
141
+ lines={newPriceSummaryLines}
142
+ total={newReceipt.total}
143
+ currency={currency}
144
+ locale={displayLocale}
145
+ subtotal={newReceipt.subtotal}
146
+ taxAmount={!newPriceSummaryLinesIncludeTaxRow ? newReceipt.tax : 0}
147
+ taxRate={taxRate}
148
+ size="sm"
149
+ t={t}
150
+ extraBeforeSubtotal={newReceiptAdjustments}
151
+ lineAmountInputs={lineAmountInputs}
152
+ onLineAmountInputChange={onLineAmountInputChange}
153
+ onLineAmountInputBlur={onLineAmountInputBlur}
154
+ onLineAmountReset={onLineAmountReset}
155
+ lineLabelInputs={lineLabelInputs}
156
+ />
157
+ </section>
158
+ </div>
159
+ <div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-stone-200 bg-stone-50 px-3 py-2">
160
+ <span className="text-sm font-semibold text-stone-900">{amountDueLabel}</span>
161
+ <span className={`text-lg font-semibold tabular-nums ${amountDueClass}`}>
162
+ {formatCurrencyAmount(amountDue, currency, displayLocale)}
163
+ </span>
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode, RefObject } from 'react';
4
+ import type { Availability } from '../../lib/booking-api';
5
+ import { Calendar } from './Calendar';
6
+ import type { Currency } from './CurrencySwitcher';
7
+
8
+ type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
9
+
10
+ export interface ChangeBookingCalendarPanelProps {
11
+ visibleAvailabilityCount: number;
12
+ loadingAvailabilities: boolean;
13
+ isFetchingMoreAvailabilities: boolean;
14
+ availabilitiesByDate: Record<string, Availability[]>;
15
+ selectedDate: string;
16
+ onSelectedDateChange: (date: string) => void;
17
+ onDateSelect: (date: string) => void;
18
+ suppressCalendarDateScroll?: boolean;
19
+ contentRef?: RefObject<HTMLDivElement | null>;
20
+ useWindowScroll: boolean;
21
+ timezone: string;
22
+ earliestDate: Date | null;
23
+ onVisibleRangeChange: (startDate: Date, endDate: Date) => void;
24
+ currency: Currency;
25
+ showCapacity: boolean;
26
+ extraDiscountPercent?: number;
27
+ capDiscountBadgesToBookingDate?: string | null;
28
+ selectableSoldOutDate?: string | null;
29
+ partySizeRequiredForCalendarSelection?: number;
30
+ getEffectiveVacancies?: (availability: Availability) => number;
31
+ t: TranslationFn;
32
+ children: ReactNode;
33
+ }
34
+
35
+ export function ChangeBookingCalendarPanel({
36
+ visibleAvailabilityCount,
37
+ loadingAvailabilities,
38
+ isFetchingMoreAvailabilities,
39
+ availabilitiesByDate,
40
+ selectedDate,
41
+ onSelectedDateChange,
42
+ onDateSelect,
43
+ suppressCalendarDateScroll,
44
+ contentRef,
45
+ useWindowScroll,
46
+ timezone,
47
+ earliestDate,
48
+ onVisibleRangeChange,
49
+ currency,
50
+ showCapacity,
51
+ extraDiscountPercent,
52
+ capDiscountBadgesToBookingDate,
53
+ selectableSoldOutDate,
54
+ partySizeRequiredForCalendarSelection,
55
+ getEffectiveVacancies,
56
+ t,
57
+ children,
58
+ }: ChangeBookingCalendarPanelProps) {
59
+ const handleDateSelect = (date: string) => {
60
+ onSelectedDateChange(date);
61
+ onDateSelect(date);
62
+ if (suppressCalendarDateScroll) return;
63
+
64
+ setTimeout(() => {
65
+ const container = contentRef?.current;
66
+ if (!useWindowScroll && container && container.scrollHeight > container.clientHeight + 16) {
67
+ container.scrollBy({ top: 400, behavior: 'smooth' });
68
+ } else if (typeof window !== 'undefined') {
69
+ window.scrollBy({ top: 400, behavior: 'smooth' });
70
+ }
71
+ }, 100);
72
+ };
73
+
74
+ if (loadingAvailabilities && visibleAvailabilityCount === 0) {
75
+ return (
76
+ <div className="flex flex-col items-center justify-center py-12 gap-4">
77
+ <div className="booking-loading-spinner" aria-hidden />
78
+ <div className="text-stone-600">{t('booking.loadingTimes')}</div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ if (visibleAvailabilityCount === 0) {
84
+ return (
85
+ <div className="text-center py-8 text-stone-500">
86
+ {t('booking.noAvailability')}
87
+ </div>
88
+ );
89
+ }
90
+
91
+ return (
92
+ <>
93
+ <div>
94
+ <div className="relative">
95
+ {loadingAvailabilities ? (
96
+ <div className="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center z-10 rounded-lg">
97
+ <div className="flex flex-col items-center gap-3">
98
+ <div className="booking-loading-spinner" aria-hidden />
99
+ <div className="text-stone-600">{t('booking.loadingTimes')}</div>
100
+ </div>
101
+ </div>
102
+ ) : null}
103
+ <Calendar
104
+ availabilitiesByDate={availabilitiesByDate}
105
+ selectedDate={selectedDate}
106
+ syncVisibleWeekToSelectedDate
107
+ selectableSoldOutDate={selectableSoldOutDate}
108
+ partySizeRequiredForCalendarSelection={partySizeRequiredForCalendarSelection}
109
+ getEffectiveVacancies={getEffectiveVacancies}
110
+ isLoading={loadingAvailabilities || isFetchingMoreAvailabilities}
111
+ onDateSelect={handleDateSelect}
112
+ timezone={timezone}
113
+ earliestDate={earliestDate}
114
+ onVisibleRangeChange={onVisibleRangeChange}
115
+ currency={currency}
116
+ showCapacity={showCapacity}
117
+ extraDiscountPercent={extraDiscountPercent}
118
+ capDiscountBadgesToBookingDate={capDiscountBadgesToBookingDate}
119
+ />
120
+ </div>
121
+ </div>
122
+
123
+ {children}
124
+ </>
125
+ );
126
+ }
@@ -0,0 +1,204 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Destination, PickupLocation } from '../../lib/booking-api';
3
+ import { CheckoutForm } from './CheckoutForm';
4
+ import type { Currency } from './CurrencySwitcher';
5
+ import type { PriceSummaryLine } from './PriceSummary';
6
+
7
+ type TranslationFn = (key: string, params?: Record<string, string>) => string;
8
+
9
+ export interface ChangeBookingCheckoutPanelProps {
10
+ priceSummaryLines: PriceSummaryLine[];
11
+ replacePriceSummary?: ReactNode;
12
+ totalPrice: number;
13
+ subtotal: number;
14
+ taxAmount: number;
15
+ taxRate?: number;
16
+ currency: Currency;
17
+ locale: string;
18
+ t: TranslationFn;
19
+ firstName: string;
20
+ lastName: string;
21
+ email: string;
22
+ onFirstNameChange: (value: string) => void;
23
+ onLastNameChange: (value: string) => void;
24
+ onEmailChange: (value: string) => void;
25
+ onClearError: () => void;
26
+ selectedDate: string;
27
+ pickupLocations?: PickupLocation[];
28
+ destinations?: Destination[];
29
+ pickupLocationId: string | null;
30
+ pickupLocationSkipped: boolean;
31
+ selectedPickupLocation?: PickupLocation | null;
32
+ highlightedPickupLocationIds?: string[];
33
+ hidePickupSkipOption: boolean;
34
+ onPickupLocationIdChange: (locationId: string | null) => void;
35
+ onPickupLocationSkippedChange: (skipped: boolean) => void;
36
+ termsAccepted: boolean;
37
+ onTermsAcceptedChange: (checked: boolean) => void;
38
+ onTermsAcceptedAtChange: (value: string | null) => void;
39
+ error: string;
40
+ loading: boolean;
41
+ totalQuantity: number;
42
+ onCheckout: () => void;
43
+ submitLabel?: string;
44
+ showCheckoutModal: boolean;
45
+ hasEffectiveChangeSelection: boolean;
46
+ isChangeQuoteBlocked: boolean;
47
+ submitDisabled: boolean;
48
+ attributionSummary?: string;
49
+ attributionConfirmLabel?: string;
50
+ attributionConfirmed: boolean;
51
+ onAttributionConfirmedChange: (checked: boolean) => void;
52
+ priceSummaryLinesIncludeTaxRow: boolean;
53
+ isTaxIncludedInPrice: boolean;
54
+ displayChangeFlowTax: number;
55
+ }
56
+
57
+ export function ChangeBookingCheckoutPanel({
58
+ priceSummaryLines,
59
+ replacePriceSummary,
60
+ totalPrice,
61
+ subtotal,
62
+ taxAmount,
63
+ taxRate,
64
+ currency,
65
+ locale,
66
+ t,
67
+ firstName,
68
+ lastName,
69
+ email,
70
+ onFirstNameChange,
71
+ onLastNameChange,
72
+ onEmailChange,
73
+ onClearError,
74
+ selectedDate,
75
+ pickupLocations,
76
+ destinations,
77
+ pickupLocationId,
78
+ pickupLocationSkipped,
79
+ selectedPickupLocation,
80
+ highlightedPickupLocationIds,
81
+ hidePickupSkipOption,
82
+ onPickupLocationIdChange,
83
+ onPickupLocationSkippedChange,
84
+ termsAccepted,
85
+ onTermsAcceptedChange,
86
+ onTermsAcceptedAtChange,
87
+ error,
88
+ loading,
89
+ totalQuantity,
90
+ onCheckout,
91
+ submitLabel,
92
+ showCheckoutModal,
93
+ hasEffectiveChangeSelection,
94
+ isChangeQuoteBlocked,
95
+ submitDisabled,
96
+ attributionSummary,
97
+ attributionConfirmLabel,
98
+ attributionConfirmed,
99
+ onAttributionConfirmedChange,
100
+ priceSummaryLinesIncludeTaxRow,
101
+ isTaxIncludedInPrice,
102
+ displayChangeFlowTax,
103
+ }: ChangeBookingCheckoutPanelProps) {
104
+ const totalSummaryLabel =
105
+ t('booking.totalOwedForBookingChange') &&
106
+ t('booking.totalOwedForBookingChange') !== 'booking.totalOwedForBookingChange'
107
+ ? t('booking.totalOwedForBookingChange')
108
+ : 'Total owed for booking difference';
109
+
110
+ return (
111
+ <CheckoutForm
112
+ priceSummaryLines={priceSummaryLines}
113
+ replacePriceSummary={replacePriceSummary}
114
+ totalPrice={totalPrice}
115
+ totalSummaryLabel={totalSummaryLabel}
116
+ subtotal={subtotal}
117
+ taxAmount={
118
+ !isTaxIncludedInPrice &&
119
+ displayChangeFlowTax > 0 &&
120
+ !priceSummaryLinesIncludeTaxRow
121
+ ? taxAmount
122
+ : 0
123
+ }
124
+ taxRate={taxRate}
125
+ currency={currency}
126
+ locale={locale}
127
+ t={t}
128
+ extraBetweenTaxAndTotal={null}
129
+ extraBeforeSubtotal={undefined}
130
+ firstName={firstName}
131
+ lastName={lastName}
132
+ email={email}
133
+ onFirstNameChange={(value) => {
134
+ onFirstNameChange(value);
135
+ onClearError();
136
+ }}
137
+ onLastNameChange={(value) => {
138
+ onLastNameChange(value);
139
+ onClearError();
140
+ }}
141
+ onEmailChange={(value) => {
142
+ onEmailChange(value);
143
+ onClearError();
144
+ }}
145
+ readOnlyContactFields
146
+ pickupLocations={selectedDate && pickupLocations && pickupLocations.length > 0 ? pickupLocations : undefined}
147
+ destinations={destinations}
148
+ pickupLocationId={pickupLocationId}
149
+ pickupLocationSkipped={pickupLocationSkipped}
150
+ selectedPickupLocation={selectedPickupLocation}
151
+ highlightedPickupLocationIds={highlightedPickupLocationIds}
152
+ hidePickupSkipOption={hidePickupSkipOption}
153
+ onLocationSelect={(locationId) => {
154
+ onPickupLocationIdChange(locationId);
155
+ onClearError();
156
+ if (locationId === null && pickupLocationSkipped) {
157
+ onPickupLocationSkippedChange(false);
158
+ } else if (locationId !== null) {
159
+ onPickupLocationSkippedChange(false);
160
+ }
161
+ }}
162
+ onSkip={() => {
163
+ if (hidePickupSkipOption) return;
164
+ onPickupLocationSkippedChange(true);
165
+ onPickupLocationIdChange(null);
166
+ onClearError();
167
+ }}
168
+ onChangePickup={() => {
169
+ onPickupLocationIdChange(null);
170
+ onPickupLocationSkippedChange(false);
171
+ }}
172
+ termsAccepted={termsAccepted}
173
+ onTermsChange={(checked) => {
174
+ onTermsAcceptedChange(checked);
175
+ onTermsAcceptedAtChange(checked ? new Date().toISOString() : null);
176
+ }}
177
+ isAdmin={false}
178
+ showCommunicationAdminSection={false}
179
+ skipConfirmationCommunications={false}
180
+ disableAutoCommunications={false}
181
+ onSkipConfirmationChange={() => {}}
182
+ onDisableCommunicationsChange={() => {}}
183
+ error={error}
184
+ loading={loading}
185
+ totalQuantity={totalQuantity}
186
+ onCheckout={onCheckout}
187
+ submitLabel={submitLabel}
188
+ hideSubmitButton={
189
+ showCheckoutModal ||
190
+ !hasEffectiveChangeSelection ||
191
+ isChangeQuoteBlocked
192
+ }
193
+ submitDisabled={submitDisabled}
194
+ attributionSummary={attributionSummary}
195
+ attributionConfirmLabel={attributionConfirmLabel}
196
+ attributionConfirmed={attributionConfirmed}
197
+ onAttributionConfirmedChange={onAttributionConfirmedChange}
198
+ lineAmountInputs={undefined}
199
+ onLineAmountInputChange={undefined}
200
+ onLineAmountInputBlur={undefined}
201
+ onLineAmountReset={undefined}
202
+ />
203
+ );
204
+ }