@ticketboothapp/booking 1.2.101 → 1.2.102
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.
- package/package.json +1 -1
- package/src/components/booking/BookingDialog.module.css +9 -0
- package/src/components/booking/BookingProductGrid.module.css +11 -0
- package/src/components/booking/BookingProductGrid.tsx +54 -28
- package/src/components/booking/CancellationPolicySelector.tsx +4 -1
- package/src/components/booking/CheckoutForm.module.css +108 -3
- package/src/components/booking/CheckoutForm.tsx +13 -1
- package/src/components/booking/CheckoutOptionalPhoneFields.tsx +58 -0
- package/src/components/booking/DapTourDescription.tsx +9 -7
- package/src/components/booking/DependentAddOnBookingDialog.tsx +42 -7
- package/src/components/booking/NewBookingFlow.tsx +137 -55
- package/src/components/booking/PrivateShuttleBookingFlow.module.css +7 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +21 -0
- package/src/components/booking/booking-flow-types.ts +2 -0
- package/src/components/booking/booking-flow-ui.ts +2 -0
- package/src/components/booking/booking-flow.css +72 -4
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -3
- package/src/data/dap-descriptions/session-elopements.en.json +12 -12
- package/src/data/dap-descriptions/session-proposals.en.json +6 -9
- package/src/data/products-config.json +20 -0
- package/src/lib/booking/checkout-contact.ts +8 -0
- package/src/lib/booking/i18n/messages/en.json +6 -0
- package/src/lib/booking/i18n/messages/fr.json +6 -0
- package/src/lib/booking/phone.ts +18 -0
- package/src/lib/booking-api.ts +131 -2
- package/src/lib/booking-types.ts +5 -0
- package/src/lib/dap-descriptions.ts +6 -0
- package/src/lib/dependent-add-on-api.ts +6 -0
- package/src/lib/photo-dap-config.ts +92 -15
- package/src/providers/dependent-add-on-dialog-provider.tsx +6 -0
- package/src/runtime/types.ts +2 -0
- package/src/strings/en.json +6 -6
package/package.json
CHANGED
|
@@ -363,6 +363,15 @@
|
|
|
363
363
|
padding: var(--spacing-small);
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
.dapCheckoutCheckboxQuestion label {
|
|
367
|
+
align-items: center;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.dapCheckoutCheckboxQuestion input[type="checkbox"] {
|
|
371
|
+
flex: 0 0 auto;
|
|
372
|
+
margin-top: 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
366
375
|
.dapField {
|
|
367
376
|
display: flex;
|
|
368
377
|
flex-direction: column;
|
|
@@ -357,3 +357,14 @@
|
|
|
357
357
|
left: 0.75rem;
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
+
|
|
361
|
+
/* Embedded pickers (photo-first dialog): 2 columns only — default tile sizes */
|
|
362
|
+
.compactRoot .grid {
|
|
363
|
+
grid-template-columns: 1fr 1fr;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@media (min-width: 768px) {
|
|
367
|
+
.compactRoot .grid {
|
|
368
|
+
grid-template-columns: 1fr 1fr;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -137,6 +137,7 @@ function BookingProductTileExpanded({
|
|
|
137
137
|
useLayoutId,
|
|
138
138
|
suppressLayoutAnimation,
|
|
139
139
|
isPartialLaunch,
|
|
140
|
+
selectProductLabel,
|
|
140
141
|
}: {
|
|
141
142
|
product: Product;
|
|
142
143
|
onBook: () => void;
|
|
@@ -145,6 +146,7 @@ function BookingProductTileExpanded({
|
|
|
145
146
|
useLayoutId: boolean;
|
|
146
147
|
suppressLayoutAnimation?: boolean;
|
|
147
148
|
isPartialLaunch: boolean;
|
|
149
|
+
selectProductLabel?: string;
|
|
148
150
|
}) {
|
|
149
151
|
const [isClosing, setIsClosing] = useState(false);
|
|
150
152
|
const { locale } = useLocale();
|
|
@@ -257,7 +259,10 @@ function BookingProductTileExpanded({
|
|
|
257
259
|
))}
|
|
258
260
|
</div>
|
|
259
261
|
<div className={styles.expandedActions}>
|
|
260
|
-
<Button className={styles.bookButton} hoverColor={ButtonHoverColor.Turquoise} onClick={onBook}>
|
|
262
|
+
<Button className={styles.bookButton} hoverColor={ButtonHoverColor.Turquoise} onClick={onBook}>
|
|
263
|
+
{selectProductLabel ??
|
|
264
|
+
(isPartialLaunch ? defaultStrings.common.moreInfo : defaultStrings.common.bookNow)}
|
|
265
|
+
</Button>
|
|
261
266
|
</div>
|
|
262
267
|
</div>
|
|
263
268
|
</div>
|
|
@@ -271,6 +276,8 @@ interface BookingProductGridProps {
|
|
|
271
276
|
onRestoreApplied?: () => void;
|
|
272
277
|
/** Pre-select a filter when opening the grid (e.g. from a theme page). */
|
|
273
278
|
initialFilterId?: FilterId;
|
|
279
|
+
/** When true, hides the theme filter pills (e.g. photo-first shuttle picker). */
|
|
280
|
+
hideFilterPills?: boolean;
|
|
274
281
|
/** When true, shows "More Info" instead of "Book Now" (pre-launch state). */
|
|
275
282
|
isPartialLaunch?: boolean;
|
|
276
283
|
/**
|
|
@@ -283,6 +290,12 @@ interface BookingProductGridProps {
|
|
|
283
290
|
* Default false keeps expand-in-place behavior for the main booking dialog.
|
|
284
291
|
*/
|
|
285
292
|
bookOnTileClick?: boolean;
|
|
293
|
+
/** Extra filter applied after theme filter (e.g. DAP-eligible shuttles only). */
|
|
294
|
+
productFilter?: (product: Product) => boolean;
|
|
295
|
+
/** Expanded tile CTA label when `onBookProduct` is set (default: book now / more info). */
|
|
296
|
+
selectProductLabel?: string;
|
|
297
|
+
/** Force a 2-column grid (e.g. photo-first add-on dialog); uses default tile sizing. */
|
|
298
|
+
compact?: boolean;
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
function filterProductsByFilterId(products: Product[], filterId: FilterId): Product[] {
|
|
@@ -297,9 +310,13 @@ export default function BookingProductGrid({
|
|
|
297
310
|
restoreState,
|
|
298
311
|
onRestoreApplied,
|
|
299
312
|
initialFilterId,
|
|
313
|
+
hideFilterPills = false,
|
|
300
314
|
isPartialLaunch = false,
|
|
301
315
|
onBookProduct,
|
|
302
316
|
bookOnTileClick = false,
|
|
317
|
+
productFilter,
|
|
318
|
+
selectProductLabel,
|
|
319
|
+
compact = false,
|
|
303
320
|
}: BookingProductGridProps = {}) {
|
|
304
321
|
const { catalog, strings: defaultStrings } = useBookingHost();
|
|
305
322
|
const { push } = useBookingDialog();
|
|
@@ -340,10 +357,10 @@ export default function BookingProductGrid({
|
|
|
340
357
|
[catalog, defaultStrings]
|
|
341
358
|
);
|
|
342
359
|
|
|
343
|
-
const products = useMemo(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
);
|
|
360
|
+
const products = useMemo(() => {
|
|
361
|
+
const filtered = filterProductsByFilterId(allProducts, selectedFilterId);
|
|
362
|
+
return productFilter ? filtered.filter(productFilter) : filtered;
|
|
363
|
+
}, [allProducts, selectedFilterId, productFilter]);
|
|
347
364
|
|
|
348
365
|
// Collapse expanded tile if it's no longer in the filtered list
|
|
349
366
|
useEffect(() => {
|
|
@@ -358,15 +375,21 @@ export default function BookingProductGrid({
|
|
|
358
375
|
const expandedProduct =
|
|
359
376
|
expandedIndex >= 0 ? products[expandedIndex] : null;
|
|
360
377
|
|
|
361
|
-
// Column count for expand-in-place logic (2 mobile, 3 desktop)
|
|
362
|
-
const [cols, setCols] = useState(3);
|
|
378
|
+
// Column count for expand-in-place logic (2 mobile, 3 desktop; compact always 2)
|
|
379
|
+
const [cols, setCols] = useState(compact ? 2 : 3);
|
|
363
380
|
useEffect(() => {
|
|
381
|
+
if (compact) {
|
|
382
|
+
setCols(2);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
364
385
|
const mq = window.matchMedia('(min-width: 768px)');
|
|
365
386
|
const update = () => setCols(mq.matches ? 3 : 2);
|
|
366
387
|
update();
|
|
367
388
|
mq.addEventListener('change', update);
|
|
368
389
|
return () => mq.removeEventListener('change', update);
|
|
369
|
-
}, []);
|
|
390
|
+
}, [compact]);
|
|
391
|
+
|
|
392
|
+
const useLayoutAnimations = !compact && cols === 3;
|
|
370
393
|
|
|
371
394
|
// Expand in place: keep expanded tile in its row, move same-row siblings below.
|
|
372
395
|
// This avoids holes and prevents the expanded tile from appearing far from the click.
|
|
@@ -487,24 +510,26 @@ export default function BookingProductGrid({
|
|
|
487
510
|
};
|
|
488
511
|
|
|
489
512
|
return (
|
|
490
|
-
<div>
|
|
491
|
-
|
|
492
|
-
<div className={styles.
|
|
493
|
-
{
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
513
|
+
<div className={compact ? styles.compactRoot : undefined}>
|
|
514
|
+
{hideFilterPills ? null : (
|
|
515
|
+
<div className={styles.filterPillsScroll}>
|
|
516
|
+
<div className={styles.filterPills}>
|
|
517
|
+
{FILTER_IDS.map((filterId) => {
|
|
518
|
+
const isApplied = selectedFilterId === filterId;
|
|
519
|
+
return (
|
|
520
|
+
<button
|
|
521
|
+
key={filterId}
|
|
522
|
+
type="button"
|
|
523
|
+
onClick={() => setSelectedFilterId(filterId)}
|
|
524
|
+
className={`${styles.filterPill} ${isApplied ? styles.filterPillApplied : ''}`}
|
|
525
|
+
>
|
|
526
|
+
{filterLabels[filterId]}
|
|
527
|
+
</button>
|
|
528
|
+
);
|
|
529
|
+
})}
|
|
530
|
+
</div>
|
|
506
531
|
</div>
|
|
507
|
-
|
|
532
|
+
)}
|
|
508
533
|
<div className={styles.grid}>
|
|
509
534
|
<AnimatePresence mode={isRestoring ? 'sync' : 'popLayout'}>
|
|
510
535
|
{productsAbove.map((product) => (
|
|
@@ -512,7 +537,7 @@ export default function BookingProductGrid({
|
|
|
512
537
|
key={product.id}
|
|
513
538
|
product={product}
|
|
514
539
|
onClick={() => onCollapsedTileClick(product)}
|
|
515
|
-
useLayoutId={
|
|
540
|
+
useLayoutId={useLayoutAnimations}
|
|
516
541
|
suppressLayoutAnimation={isRestoring}
|
|
517
542
|
/>
|
|
518
543
|
))}
|
|
@@ -524,9 +549,10 @@ export default function BookingProductGrid({
|
|
|
524
549
|
onBook={() => handleBook(expandedProduct)}
|
|
525
550
|
onCollapse={() => setExpandedId(null)}
|
|
526
551
|
onMount={scrollExpandedToTop}
|
|
527
|
-
useLayoutId={
|
|
552
|
+
useLayoutId={useLayoutAnimations}
|
|
528
553
|
suppressLayoutAnimation={isRestoring}
|
|
529
554
|
isPartialLaunch={isPartialLaunch}
|
|
555
|
+
selectProductLabel={onBookProduct ? selectProductLabel : undefined}
|
|
530
556
|
/>
|
|
531
557
|
)}
|
|
532
558
|
|
|
@@ -535,7 +561,7 @@ export default function BookingProductGrid({
|
|
|
535
561
|
key={product.id}
|
|
536
562
|
product={product}
|
|
537
563
|
onClick={() => onCollapsedTileClick(product)}
|
|
538
|
-
useLayoutId={
|
|
564
|
+
useLayoutId={useLayoutAnimations}
|
|
539
565
|
suppressLayoutAnimation={isRestoring}
|
|
540
566
|
/>
|
|
541
567
|
))}
|
|
@@ -50,6 +50,8 @@ interface CancellationPolicySelectorProps {
|
|
|
50
50
|
rowSubtitle?: React.ReactNode;
|
|
51
51
|
/** Hide +fee amounts (customer change flow until server-backed pricing). */
|
|
52
52
|
suppressFees?: boolean;
|
|
53
|
+
/** Override default `booking.cancellationPolicy` section heading. */
|
|
54
|
+
sectionLabel?: string;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
export function CancellationPolicySelector({
|
|
@@ -62,11 +64,12 @@ export function CancellationPolicySelector({
|
|
|
62
64
|
forcedPolicy,
|
|
63
65
|
rowSubtitle,
|
|
64
66
|
suppressFees = false,
|
|
67
|
+
sectionLabel,
|
|
65
68
|
}: CancellationPolicySelectorProps) {
|
|
66
69
|
return (
|
|
67
70
|
<div className={styles.wrapper}>
|
|
68
71
|
<div className={styles.label}>
|
|
69
|
-
{t('booking.cancellationPolicy')}
|
|
72
|
+
{sectionLabel ?? t('booking.cancellationPolicy')}
|
|
70
73
|
</div>
|
|
71
74
|
<div className={styles.list}>
|
|
72
75
|
{forcedPolicy && (
|
|
@@ -45,22 +45,127 @@
|
|
|
45
45
|
|
|
46
46
|
.input {
|
|
47
47
|
width: 100%;
|
|
48
|
-
padding: 0.
|
|
48
|
+
padding: 0.5rem 0.625rem;
|
|
49
49
|
border-radius: 0.5rem;
|
|
50
50
|
border: 1px solid var(--booking-stone-300, #d6d3d1);
|
|
51
|
-
font-size:
|
|
51
|
+
font-size: 0.875rem;
|
|
52
|
+
line-height: 1.25rem;
|
|
52
53
|
color: var(--booking-stone-900, #1c1917);
|
|
54
|
+
background: var(--booking-stone-50, #fafaf9);
|
|
55
|
+
box-sizing: border-box;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
.input:focus {
|
|
56
59
|
outline: none;
|
|
57
|
-
border-color: var(--booking-
|
|
60
|
+
border-color: var(--booking-emerald-600, #059669);
|
|
61
|
+
background: #fff;
|
|
62
|
+
box-shadow: 0 0 0 2px rgba(5, 150, 105, 0.2);
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
.inputError {
|
|
61
66
|
font-size: 0.75rem;
|
|
62
67
|
color: #b91c1c;
|
|
63
68
|
margin-top: 0.375rem;
|
|
69
|
+
border: none;
|
|
70
|
+
background: transparent;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.optionalPhoneField {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.contactFieldHint {
|
|
80
|
+
margin: 0.625rem 0 0;
|
|
81
|
+
font-size: 0.75rem;
|
|
82
|
+
line-height: 1.35;
|
|
83
|
+
color: var(--booking-stone-500, #78716c);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.optionalPhoneFieldHint {
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 0.375rem 0.5rem 0;
|
|
89
|
+
font-size: 0.75rem;
|
|
90
|
+
line-height: 1.4;
|
|
91
|
+
color: var(--booking-stone-500, #78716c);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.checkoutPhoneInput {
|
|
95
|
+
width: 100%;
|
|
96
|
+
--react-international-phone-height: auto;
|
|
97
|
+
--react-international-phone-border-radius: 0.5rem;
|
|
98
|
+
--react-international-phone-background-color: var(--booking-stone-50, #fafaf9);
|
|
99
|
+
--react-international-phone-border-color: var(--booking-stone-300, #d6d3d1);
|
|
100
|
+
--react-international-phone-font-size: 0.875rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Override site PhoneInputWithCountry (orange 2px border) — match promo / contact fields */
|
|
104
|
+
.checkoutPhoneInput :global(.react-international-phone-input-container) {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
width: 100%;
|
|
108
|
+
min-height: 0;
|
|
109
|
+
border: 1px solid var(--booking-stone-300, #d6d3d1) !important;
|
|
110
|
+
border-radius: 0.5rem !important;
|
|
111
|
+
background: var(--booking-stone-50, #fafaf9) !important;
|
|
112
|
+
box-shadow: none !important;
|
|
113
|
+
overflow: visible;
|
|
114
|
+
transition: border-color 0.15s, background-color 0.15s;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.checkoutPhoneInput:focus-within :global(.react-international-phone-input-container) {
|
|
118
|
+
border-color: var(--booking-emerald-600, #059669) !important;
|
|
119
|
+
border-width: 1px !important;
|
|
120
|
+
background: #fff !important;
|
|
121
|
+
outline: none !important;
|
|
122
|
+
box-shadow: 0 0 0 2px rgba(5, 150, 105, 0.2) !important;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.checkoutPhoneInput:focus-within :global(.react-international-phone-country-selector-button) {
|
|
126
|
+
background: transparent !important;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.checkoutPhoneInput :global(.react-international-phone-country-selector-button) {
|
|
130
|
+
border: none !important;
|
|
131
|
+
background: transparent !important;
|
|
132
|
+
padding: 0 0.375rem 0 0.5rem !important;
|
|
133
|
+
height: auto !important;
|
|
134
|
+
align-self: stretch;
|
|
135
|
+
border-radius: 0 !important;
|
|
136
|
+
box-shadow: none !important;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.checkoutPhoneInput :global(.react-international-phone-country-selector-button:hover) {
|
|
140
|
+
background: rgba(0, 0, 0, 0.03) !important;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.checkoutPhoneInput :global(.react-international-phone-input) {
|
|
144
|
+
flex: 1;
|
|
145
|
+
min-width: 0;
|
|
146
|
+
border: none !important;
|
|
147
|
+
padding: 0.5rem 0.625rem 0.5rem 0.25rem !important;
|
|
148
|
+
font-size: 0.875rem !important;
|
|
149
|
+
line-height: 1.25rem !important;
|
|
150
|
+
color: var(--booking-stone-900, #1c1917) !important;
|
|
151
|
+
background: transparent !important;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.checkoutPhoneInput :global(.react-international-phone-input:focus),
|
|
155
|
+
.checkoutPhoneInput :global(.react-international-phone-input:focus-visible) {
|
|
156
|
+
outline: none !important;
|
|
157
|
+
border: none !important;
|
|
158
|
+
box-shadow: none !important;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.checkoutPhoneInput :global(.react-international-phone-country-selector-button:focus),
|
|
162
|
+
.checkoutPhoneInput :global(.react-international-phone-country-selector-button:focus-visible) {
|
|
163
|
+
outline: none !important;
|
|
164
|
+
box-shadow: none !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.checkoutPhoneInput :global(.react-international-phone-country-selector-dropdown) {
|
|
168
|
+
z-index: 1000;
|
|
64
169
|
}
|
|
65
170
|
|
|
66
171
|
.contactStaticValue {
|
|
@@ -7,6 +7,7 @@ import { PickupLocationSelector } from './PickupLocationSelector';
|
|
|
7
7
|
import type { Currency } from './CurrencySwitcher';
|
|
8
8
|
import type { PickupLocation, Destination } from '../../lib/booking-api';
|
|
9
9
|
import styles from './CheckoutForm.module.css';
|
|
10
|
+
import { CheckoutOptionalPhoneFields } from './CheckoutOptionalPhoneFields';
|
|
10
11
|
import { useBookingHost } from '../../runtime';
|
|
11
12
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
12
13
|
|
|
@@ -41,9 +42,11 @@ interface CheckoutFormProps {
|
|
|
41
42
|
firstName: string;
|
|
42
43
|
lastName: string;
|
|
43
44
|
email: string;
|
|
45
|
+
phoneNumber?: string;
|
|
44
46
|
onFirstNameChange: (value: string) => void;
|
|
45
47
|
onLastNameChange: (value: string) => void;
|
|
46
48
|
onEmailChange: (value: string) => void;
|
|
49
|
+
onPhoneNumberChange?: (value: string) => void;
|
|
47
50
|
readOnlyContactFields?: boolean;
|
|
48
51
|
// Pickup location
|
|
49
52
|
pickupLocations?: PickupLocation[];
|
|
@@ -104,9 +107,11 @@ export function CheckoutForm({
|
|
|
104
107
|
firstName,
|
|
105
108
|
lastName,
|
|
106
109
|
email,
|
|
110
|
+
phoneNumber = '',
|
|
107
111
|
onFirstNameChange,
|
|
108
112
|
onLastNameChange,
|
|
109
113
|
onEmailChange,
|
|
114
|
+
onPhoneNumberChange,
|
|
110
115
|
readOnlyContactFields = false,
|
|
111
116
|
pickupLocations,
|
|
112
117
|
destinations = [],
|
|
@@ -255,9 +260,16 @@ export function CheckoutForm({
|
|
|
255
260
|
className={styles.input}
|
|
256
261
|
/>
|
|
257
262
|
{email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && (
|
|
258
|
-
<
|
|
263
|
+
<div className={styles.inputError} role="alert">
|
|
264
|
+
{t('booking.invalidEmail') || 'Please enter a valid email address'}
|
|
265
|
+
</div>
|
|
259
266
|
)}
|
|
260
267
|
</div>
|
|
268
|
+
<CheckoutOptionalPhoneFields
|
|
269
|
+
phoneNumber={phoneNumber}
|
|
270
|
+
onPhoneNumberChange={onPhoneNumberChange}
|
|
271
|
+
t={t}
|
|
272
|
+
/>
|
|
261
273
|
</>
|
|
262
274
|
)}
|
|
263
275
|
</div>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useBookingHost } from '../../runtime';
|
|
4
|
+
import styles from './CheckoutForm.module.css';
|
|
5
|
+
|
|
6
|
+
type TranslationFn = (key: string, params?: Record<string, string>) => string;
|
|
7
|
+
|
|
8
|
+
export interface CheckoutOptionalPhoneFieldsProps {
|
|
9
|
+
phoneNumber: string;
|
|
10
|
+
onPhoneNumberChange?: (value: string) => void;
|
|
11
|
+
t: TranslationFn;
|
|
12
|
+
idPrefix?: string;
|
|
13
|
+
labelClassName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CheckoutOptionalPhoneFields({
|
|
17
|
+
phoneNumber,
|
|
18
|
+
onPhoneNumberChange,
|
|
19
|
+
t,
|
|
20
|
+
idPrefix = 'booking',
|
|
21
|
+
labelClassName = styles.label,
|
|
22
|
+
}: CheckoutOptionalPhoneFieldsProps) {
|
|
23
|
+
const { slots } = useBookingHost();
|
|
24
|
+
const PhoneInput = slots.PhoneInput;
|
|
25
|
+
const phonePlaceholder =
|
|
26
|
+
t('booking.phoneOptionalPlaceholder') || 'e.g. +1 403 555 0123';
|
|
27
|
+
const fieldId = `${idPrefix}-phone`;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={styles.optionalPhoneField}>
|
|
31
|
+
<label className={labelClassName} htmlFor={fieldId}>
|
|
32
|
+
{t('booking.phoneOptional') || 'Phone'}
|
|
33
|
+
</label>
|
|
34
|
+
{PhoneInput ? (
|
|
35
|
+
<PhoneInput
|
|
36
|
+
id={fieldId}
|
|
37
|
+
value={phoneNumber}
|
|
38
|
+
onChange={(v: string | undefined) => onPhoneNumberChange?.(v ?? '')}
|
|
39
|
+
placeholder={phonePlaceholder}
|
|
40
|
+
className={styles.checkoutPhoneInput}
|
|
41
|
+
/>
|
|
42
|
+
) : (
|
|
43
|
+
<input
|
|
44
|
+
type="tel"
|
|
45
|
+
id={fieldId}
|
|
46
|
+
value={phoneNumber}
|
|
47
|
+
onChange={(e) => onPhoneNumberChange?.(e.target.value)}
|
|
48
|
+
placeholder={phonePlaceholder}
|
|
49
|
+
autoComplete="tel"
|
|
50
|
+
className={styles.input}
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
<div className={styles.optionalPhoneFieldHint} role="note">
|
|
54
|
+
{t('booking.phoneOptionalHint') || 'For pickup and day-of updates'}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -24,12 +24,14 @@ export function DapTourDescription({
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
<div>
|
|
28
|
+
<TourDescription
|
|
29
|
+
paragraphs={content.paragraphs}
|
|
30
|
+
review={content.review}
|
|
31
|
+
sections={content.sections}
|
|
32
|
+
defaultExpanded={false}
|
|
33
|
+
toggleLabel={t('booking.seeFullAddOnDescription')}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
34
36
|
);
|
|
35
37
|
}
|
|
@@ -467,13 +467,42 @@ export default function DependentAddOnBookingDialog() {
|
|
|
467
467
|
}, [isOpen]);
|
|
468
468
|
|
|
469
469
|
useEffect(() => {
|
|
470
|
-
if (isOpen)
|
|
471
|
-
|
|
470
|
+
if (!isOpen) return;
|
|
471
|
+
|
|
472
|
+
const inheritedScrollLock = document.body.dataset.vviaScrollLockScrollY;
|
|
473
|
+
if (inheritedScrollLock != null) {
|
|
472
474
|
return () => {
|
|
473
|
-
document.body.style.overflow = '
|
|
475
|
+
document.body.style.overflow = document.body.dataset.vviaScrollLockOverflow ?? '';
|
|
476
|
+
document.body.style.position = document.body.dataset.vviaScrollLockPosition ?? '';
|
|
477
|
+
document.body.style.top = document.body.dataset.vviaScrollLockTop ?? '';
|
|
478
|
+
document.body.style.width = document.body.dataset.vviaScrollLockWidth ?? '';
|
|
479
|
+
document.body.style.paddingRight = document.body.dataset.vviaScrollLockPaddingRight ?? '';
|
|
480
|
+
delete document.body.dataset.vviaScrollLockScrollY;
|
|
481
|
+
delete document.body.dataset.vviaScrollLockOverflow;
|
|
482
|
+
delete document.body.dataset.vviaScrollLockPosition;
|
|
483
|
+
delete document.body.dataset.vviaScrollLockTop;
|
|
484
|
+
delete document.body.dataset.vviaScrollLockWidth;
|
|
485
|
+
delete document.body.dataset.vviaScrollLockPaddingRight;
|
|
474
486
|
};
|
|
475
487
|
}
|
|
476
|
-
|
|
488
|
+
|
|
489
|
+
const previousOverflow = document.body.style.overflow;
|
|
490
|
+
const previousPosition = document.body.style.position;
|
|
491
|
+
const previousTop = document.body.style.top;
|
|
492
|
+
const previousWidth = document.body.style.width;
|
|
493
|
+
const previousPaddingRight = document.body.style.paddingRight;
|
|
494
|
+
const scrollbarWidth = Math.max(0, window.innerWidth - document.documentElement.clientWidth);
|
|
495
|
+
document.body.style.overflow = 'hidden';
|
|
496
|
+
if (scrollbarWidth > 0 && !previousPaddingRight) {
|
|
497
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
498
|
+
}
|
|
499
|
+
return () => {
|
|
500
|
+
document.body.style.overflow = previousOverflow;
|
|
501
|
+
document.body.style.position = previousPosition;
|
|
502
|
+
document.body.style.top = previousTop;
|
|
503
|
+
document.body.style.width = previousWidth;
|
|
504
|
+
document.body.style.paddingRight = previousPaddingRight;
|
|
505
|
+
};
|
|
477
506
|
}, [isOpen]);
|
|
478
507
|
|
|
479
508
|
const clearPaymentPrepForSlotChange = useCallback(() => {
|
|
@@ -836,9 +865,10 @@ export default function DependentAddOnBookingDialog() {
|
|
|
836
865
|
|
|
837
866
|
const dapCheckoutSummary = useMemo(() => {
|
|
838
867
|
if (!payload || !selectedSlot) return null;
|
|
868
|
+
const receiptTitle = payload.receiptDisplayTitle?.trim() || payload.productDisplayTitle;
|
|
839
869
|
const lineTitle = sessionLengthLabel
|
|
840
|
-
? `${
|
|
841
|
-
:
|
|
870
|
+
? `${receiptTitle} - ${sessionLengthLabel}`
|
|
871
|
+
: receiptTitle;
|
|
842
872
|
const subtotal =
|
|
843
873
|
paymentSubtotalAmount ??
|
|
844
874
|
(selectedSlot.price != null ? selectedSlot.price * DAP_SLOT_QUANTITY : 0);
|
|
@@ -1053,8 +1083,13 @@ export default function DependentAddOnBookingDialog() {
|
|
|
1053
1083
|
}
|
|
1054
1084
|
>
|
|
1055
1085
|
<span className={bookingStyles.dapSessionOptionTitle}>
|
|
1056
|
-
{opt.label}
|
|
1086
|
+
{opt.name ?? opt.label}
|
|
1057
1087
|
</span>
|
|
1088
|
+
{opt.name && opt.label ? (
|
|
1089
|
+
<span className={bookingStyles.dapSessionOptionMeta}>
|
|
1090
|
+
{opt.label}
|
|
1091
|
+
</span>
|
|
1092
|
+
) : null}
|
|
1058
1093
|
{opt.photosLabel ? (
|
|
1059
1094
|
<span className={bookingStyles.dapSessionOptionMeta}>
|
|
1060
1095
|
{opt.photosLabel}
|