@ticketboothapp/booking 1.2.189 → 1.2.191
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 +3 -1
- package/src/components/BackgroundVideo.tsx +6 -3
- package/src/components/booking/AddOnsSection.module.css +129 -1
- package/src/components/booking/AddOnsSection.tsx +21 -25
- package/src/components/booking/AdminChangeBookingContent.tsx +1 -0
- package/src/components/booking/AdminChangeBookingFlow.tsx +1 -0
- package/src/components/booking/AdminChangeReceiptComparison.module.css +16 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +14 -4
- package/src/components/booking/BookingDialog.module.css +43 -0
- package/src/components/booking/BookingFlowCollage.tsx +43 -16
- package/src/components/booking/Calendar.module.css +132 -13
- package/src/components/booking/Calendar.tsx +46 -20
- package/src/components/booking/CancellationPolicySelector.module.css +33 -4
- package/src/components/booking/CancellationPolicySelector.tsx +30 -28
- package/src/components/booking/ChangeBookingDialog.tsx +116 -55
- package/src/components/booking/ChangeBookingFlow.tsx +2 -0
- package/src/components/booking/ChangeBookingItineraryPanel.tsx +3 -0
- package/src/components/booking/CheckoutForm.module.css +7 -0
- package/src/components/booking/CollapsibleAddOnItem.module.css +4 -3
- package/src/components/booking/ItineraryBox.module.css +14 -0
- package/src/components/booking/ItineraryBox.tsx +75 -7
- package/src/components/booking/MealDrinkAddOnSelector.tsx +14 -16
- package/src/components/booking/NewBookingFlow.tsx +1 -0
- package/src/components/booking/PickupLocationDialog.tsx +19 -23
- package/src/components/booking/PickupLocationSelector.tsx +20 -25
- package/src/components/booking/PriceSummary.module.css +19 -0
- package/src/components/booking/PriceSummary.tsx +10 -7
- package/src/components/booking/PrivateShuttlePassengerSection.tsx +7 -1
- package/src/components/booking/ReturnTimeSelector.module.css +23 -4
- package/src/components/booking/ReturnTimeSelector.tsx +5 -5
- package/src/components/booking/StandardBookingItineraryPanel.tsx +3 -0
- package/src/components/booking/TicketSelector.module.css +30 -3
- package/src/components/booking/TicketSelector.tsx +6 -6
- package/src/components/booking/TourDescription.module.css +6 -1
- package/src/components/booking/TourDescription.tsx +25 -16
- package/src/components/booking/change-booking-compare.module.css +154 -0
- package/src/components/booking/change-booking-compare.tsx +141 -1
- package/src/constants/images.ts +1 -1
- package/src/hooks/useIsBookingLaunchLive.ts +4 -1
- package/src/index.ts +11 -0
- package/src/lib/booking/i18n/messages/en.json +2 -0
- package/src/lib/booking/i18n/messages/fr.json +2 -0
- package/src/lib/booking/pickup-location-disclaimer.ts +67 -0
- package/src/lib/booking-api.ts +1 -0
- package/src/lib/booking-constants.ts +1 -1
- package/src/strings/en.json +140 -68
- package/src/strings/es.json +181 -68
- package/src/strings/fr.json +181 -68
- package/test/pickup-location-disclaimer.test.ts +44 -0
- package/CHANGE_BOOKING_BE_HANDOFF.md +0 -160
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ticketboothapp/booking",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.191",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./src/index.ts",
|
|
14
14
|
"./background-video": "./src/components/BackgroundVideo.tsx",
|
|
15
|
+
"./terms-content": "./src/components/booking/DefaultTermsContent.tsx",
|
|
15
16
|
"./booking-flow.css": "./src/components/booking/booking-flow.css",
|
|
16
17
|
"./runtime": "./src/runtime/index.ts",
|
|
17
18
|
"./contexts/booking-app-context": "./src/contexts/BookingAppContext.tsx",
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
"./hooks/useBookingSourceMetadataFromLocation": "./src/hooks/useBookingSourceMetadataFromLocation.ts",
|
|
22
23
|
"./hooks/useIsBookingLaunchLive": "./src/hooks/useIsBookingLaunchLive.ts",
|
|
23
24
|
"./hooks/usePlacesAutocompleteSearch": "./src/hooks/usePlacesAutocompleteSearch.ts",
|
|
25
|
+
"./pickup-location-disclaimer": "./src/lib/booking/pickup-location-disclaimer.ts",
|
|
24
26
|
"./viavia/catalog": "./src/constants/products.ts",
|
|
25
27
|
"./viavia/images": "./src/constants/images.ts",
|
|
26
28
|
"./viavia/product-descriptions": "./src/lib/product-descriptions.ts",
|
|
@@ -20,6 +20,7 @@ export interface BackgroundVideoProps
|
|
|
20
20
|
poster?: string;
|
|
21
21
|
preload?: 'auto' | 'metadata' | 'none';
|
|
22
22
|
loading?: BackgroundVideoLoading;
|
|
23
|
+
viewportMargin?: string;
|
|
23
24
|
className?: string;
|
|
24
25
|
style?: CSSProperties;
|
|
25
26
|
}
|
|
@@ -41,6 +42,7 @@ export const BackgroundVideo = forwardRef<HTMLVideoElement, BackgroundVideoProps
|
|
|
41
42
|
poster,
|
|
42
43
|
preload,
|
|
43
44
|
loading = 'viewport',
|
|
45
|
+
viewportMargin = '300px 0px',
|
|
44
46
|
className,
|
|
45
47
|
style,
|
|
46
48
|
autoPlay = true,
|
|
@@ -80,11 +82,11 @@ export const BackgroundVideo = forwardRef<HTMLVideoElement, BackgroundVideoProps
|
|
|
80
82
|
if (entry.isIntersecting) setShouldLoad(true);
|
|
81
83
|
setShouldPlay(entry.isIntersecting);
|
|
82
84
|
},
|
|
83
|
-
{ rootMargin:
|
|
85
|
+
{ rootMargin: viewportMargin },
|
|
84
86
|
);
|
|
85
87
|
observer.observe(container);
|
|
86
88
|
return () => observer.disconnect();
|
|
87
|
-
}, [loading]);
|
|
89
|
+
}, [loading, viewportMargin]);
|
|
88
90
|
|
|
89
91
|
useEffect(() => {
|
|
90
92
|
const video = videoRef.current;
|
|
@@ -100,6 +102,7 @@ export const BackgroundVideo = forwardRef<HTMLVideoElement, BackgroundVideoProps
|
|
|
100
102
|
style={{ width: '100%', height: '100%', display: 'grid', ...style }}
|
|
101
103
|
>
|
|
102
104
|
<video
|
|
105
|
+
key={shouldLoad ? `${webm ?? ''}|${src}` : 'deferred'}
|
|
103
106
|
{...videoProps}
|
|
104
107
|
ref={setVideoRef}
|
|
105
108
|
className="next-video-bg-video"
|
|
@@ -107,7 +110,7 @@ export const BackgroundVideo = forwardRef<HTMLVideoElement, BackgroundVideoProps
|
|
|
107
110
|
muted={muted}
|
|
108
111
|
loop={loop}
|
|
109
112
|
playsInline={playsInline}
|
|
110
|
-
poster={poster}
|
|
113
|
+
poster={shouldLoad ? poster : undefined}
|
|
111
114
|
preload={shouldLoad ? (preload ?? (loading === 'eager' ? 'auto' : 'metadata')) : 'none'}
|
|
112
115
|
style={{
|
|
113
116
|
gridArea: '1 / 1',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.section {
|
|
2
2
|
padding-top: 1.5rem;
|
|
3
3
|
margin-top: 1.5rem;
|
|
4
|
-
border-top: 1px solid var(--booking-stone-200, #e7e5e4);
|
|
4
|
+
border-top: 1px solid var(--booking-stone-200, #e7e5e4) !important;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.items {
|
|
@@ -39,3 +39,131 @@
|
|
|
39
39
|
line-height: 1.5;
|
|
40
40
|
color: var(--booking-stone-500, #78716c);
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
.optionRow {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
46
|
+
grid-template-rows: auto auto;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.25rem 0.75rem;
|
|
49
|
+
padding: 0.75rem;
|
|
50
|
+
background: var(--booking-stone-50, #fafaf9);
|
|
51
|
+
border-radius: 0.5rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.optionLabel {
|
|
55
|
+
grid-column: 1;
|
|
56
|
+
grid-row: 1;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
font-size: 0.875rem;
|
|
59
|
+
color: var(--booking-stone-800, #292524);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.optionPrice {
|
|
63
|
+
grid-column: 2;
|
|
64
|
+
grid-row: 1 / 3;
|
|
65
|
+
align-self: center;
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
font-size: 0.875rem;
|
|
68
|
+
color: var(--booking-stone-500, #78716c);
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.optionControls {
|
|
73
|
+
grid-column: 3;
|
|
74
|
+
grid-row: 1 / 3;
|
|
75
|
+
align-self: center;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: 0.5rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.quantityButton {
|
|
83
|
+
border: 1px solid var(--booking-stone-300, #d6d3d1) !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.optionDescription {
|
|
87
|
+
grid-column: 1;
|
|
88
|
+
grid-row: 2;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
font-size: 0.75rem;
|
|
91
|
+
font-weight: 400;
|
|
92
|
+
line-height: 1.375;
|
|
93
|
+
color: var(--booking-stone-500, #78716c);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.choiceRow {
|
|
97
|
+
display: grid;
|
|
98
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
99
|
+
grid-template-rows: auto auto;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 0.25rem 0.75rem;
|
|
102
|
+
width: 100%;
|
|
103
|
+
padding: 0.75rem;
|
|
104
|
+
border-width: 2px !important;
|
|
105
|
+
border-radius: 0.5rem;
|
|
106
|
+
text-align: left;
|
|
107
|
+
transition: border-color 0.2s, background-color 0.2s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.choiceLabel {
|
|
111
|
+
grid-column: 1;
|
|
112
|
+
grid-row: 1;
|
|
113
|
+
min-width: 0;
|
|
114
|
+
font-size: 0.875rem;
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
color: var(--booking-stone-800, #292524);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.choicePrice {
|
|
120
|
+
grid-column: 2;
|
|
121
|
+
grid-row: 1 / 3;
|
|
122
|
+
align-self: center;
|
|
123
|
+
font-size: 0.875rem;
|
|
124
|
+
color: var(--booking-stone-500, #78716c);
|
|
125
|
+
white-space: nowrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.choiceDescription {
|
|
129
|
+
grid-column: 1;
|
|
130
|
+
grid-row: 2;
|
|
131
|
+
min-width: 0;
|
|
132
|
+
font-size: 0.75rem;
|
|
133
|
+
font-weight: 400;
|
|
134
|
+
line-height: 1.375;
|
|
135
|
+
color: var(--booking-stone-500, #78716c);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (max-width: 639px) {
|
|
139
|
+
.optionRow {
|
|
140
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
141
|
+
grid-template-rows: auto auto auto;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.optionPrice {
|
|
145
|
+
grid-column: 1;
|
|
146
|
+
grid-row: 2;
|
|
147
|
+
justify-self: start;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.optionControls {
|
|
151
|
+
grid-column: 2;
|
|
152
|
+
grid-row: 1 / 3;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.optionDescription {
|
|
156
|
+
grid-column: 1 / -1;
|
|
157
|
+
grid-row: 3;
|
|
158
|
+
margin-top: 0.125rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.choiceDescription {
|
|
162
|
+
grid-column: 1 / -1;
|
|
163
|
+
margin-top: 0.125rem;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.choicePrice {
|
|
167
|
+
grid-row: 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -94,7 +94,7 @@ export function AddOnsSection({
|
|
|
94
94
|
});
|
|
95
95
|
}}
|
|
96
96
|
disabled={!canDecrement}
|
|
97
|
-
className=
|
|
97
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-sm text-stone-600 hover:bg-stone-50 disabled:cursor-not-allowed disabled:opacity-50`}
|
|
98
98
|
>
|
|
99
99
|
−
|
|
100
100
|
</button>
|
|
@@ -107,7 +107,7 @@ export function AddOnsSection({
|
|
|
107
107
|
return [...rest, { addOnId: addOn.addOnId, quantity: quantity + 1 }];
|
|
108
108
|
});
|
|
109
109
|
}}
|
|
110
|
-
className=
|
|
110
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-sm text-stone-600 hover:bg-stone-50`}
|
|
111
111
|
>
|
|
112
112
|
+
|
|
113
113
|
</button>
|
|
@@ -159,19 +159,12 @@ export function AddOnsSection({
|
|
|
159
159
|
const canDecrement = qty > 0 && currentTotalForAddOn > minTotalForAddOn;
|
|
160
160
|
const price = (addOn.price ?? 0) + (variant.priceAdjustment ?? 0);
|
|
161
161
|
return (
|
|
162
|
-
<div key={variant.id} className=
|
|
163
|
-
<span className=
|
|
164
|
-
|
|
165
|
-
{variant.description?.trim() ? (
|
|
166
|
-
<span className="mt-0.5 block text-xs leading-snug text-stone-500">
|
|
167
|
-
{variant.description.trim()}
|
|
168
|
-
</span>
|
|
169
|
-
) : null}
|
|
170
|
-
</span>
|
|
171
|
-
<span className="text-sm text-stone-500 shrink-0">
|
|
162
|
+
<div key={variant.id} className={styles.optionRow}>
|
|
163
|
+
<span className={styles.optionLabel}>{variant.label}</span>
|
|
164
|
+
<span className={styles.optionPrice}>
|
|
172
165
|
{suppressPrices ? '—' : `${formatCurrencyAmount(price, currency, locale as 'en' | 'fr')} ea`}
|
|
173
166
|
</span>
|
|
174
|
-
<div className=
|
|
167
|
+
<div className={styles.optionControls}>
|
|
175
168
|
<button
|
|
176
169
|
type="button"
|
|
177
170
|
onClick={() => {
|
|
@@ -193,7 +186,7 @@ export function AddOnsSection({
|
|
|
193
186
|
});
|
|
194
187
|
}}
|
|
195
188
|
disabled={!canDecrement}
|
|
196
|
-
className=
|
|
189
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
|
|
197
190
|
>
|
|
198
191
|
−
|
|
199
192
|
</button>
|
|
@@ -215,11 +208,16 @@ export function AddOnsSection({
|
|
|
215
208
|
];
|
|
216
209
|
});
|
|
217
210
|
}}
|
|
218
|
-
className=
|
|
211
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 text-sm`}
|
|
219
212
|
>
|
|
220
213
|
+
|
|
221
214
|
</button>
|
|
222
215
|
</div>
|
|
216
|
+
{variant.description?.trim() ? (
|
|
217
|
+
<span className={styles.optionDescription}>
|
|
218
|
+
{variant.description.trim()}
|
|
219
|
+
</span>
|
|
220
|
+
) : null}
|
|
223
221
|
</div>
|
|
224
222
|
);
|
|
225
223
|
})}
|
|
@@ -261,23 +259,21 @@ export function AddOnsSection({
|
|
|
261
259
|
];
|
|
262
260
|
});
|
|
263
261
|
}}
|
|
264
|
-
className={
|
|
262
|
+
className={`${styles.choiceRow} ${
|
|
265
263
|
isSelected
|
|
266
264
|
? 'border-emerald-500 bg-emerald-50'
|
|
267
265
|
: 'border-stone-200 bg-stone-50 hover:border-stone-300'
|
|
268
266
|
}`}
|
|
269
267
|
>
|
|
270
|
-
<span className=
|
|
271
|
-
|
|
272
|
-
{variant.description?.trim() ? (
|
|
273
|
-
<span className="mt-0.5 block text-xs font-normal leading-snug text-stone-500">
|
|
274
|
-
{variant.description.trim()}
|
|
275
|
-
</span>
|
|
276
|
-
) : null}
|
|
277
|
-
</span>
|
|
278
|
-
<span className="text-sm text-stone-500">
|
|
268
|
+
<span className={styles.choiceLabel}>{variant.label}</span>
|
|
269
|
+
<span className={styles.choicePrice}>
|
|
279
270
|
{suppressPrices ? '—' : `+${formatCurrencyAmount(price, currency, locale as 'en' | 'fr')}`}
|
|
280
271
|
</span>
|
|
272
|
+
{variant.description?.trim() ? (
|
|
273
|
+
<span className={styles.choiceDescription}>
|
|
274
|
+
{variant.description.trim()}
|
|
275
|
+
</span>
|
|
276
|
+
) : null}
|
|
281
277
|
</button>
|
|
282
278
|
);
|
|
283
279
|
})}
|
|
@@ -1238,6 +1238,7 @@ export function AdminChangeBookingFlow({
|
|
|
1238
1238
|
? getCalendarEffectiveOutboundVacancies
|
|
1239
1239
|
: undefined,
|
|
1240
1240
|
hideItineraryBox, activeOptions,
|
|
1241
|
+
productId: product.productId,
|
|
1241
1242
|
destinationsCount: product.destinations?.length ?? 0, pickupLocationsCount: product.pickupLocations?.length ?? 0,
|
|
1242
1243
|
getItineraryItems: computeItineraryDisplay,
|
|
1243
1244
|
...{ isItinerarySticky, isMobile, showTooltip, selectedPickupLocation, itineraryRef },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@media (max-width: 639px) {
|
|
2
|
+
.receiptTitle {
|
|
3
|
+
font-size: 0.875rem !important;
|
|
4
|
+
line-height: 1.25rem !important;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.amountDueLabel {
|
|
8
|
+
font-size: 0.875rem !important;
|
|
9
|
+
line-height: 1.25rem !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.amountDueValue {
|
|
13
|
+
font-size: 1rem !important;
|
|
14
|
+
line-height: 1.25rem !important;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { Locale } from '../../lib/booking/i18n/config';
|
|
|
9
9
|
import type { ChangeBookingFlowProps } from './booking-flow-types';
|
|
10
10
|
import type { Currency } from './CurrencySwitcher';
|
|
11
11
|
import { PriceSummary, type PriceSummaryLine } from './PriceSummary';
|
|
12
|
+
import styles from './AdminChangeReceiptComparison.module.css';
|
|
12
13
|
import { collapseAdminPromoLines } from './admin-change-receipt-lines';
|
|
13
14
|
import type { AdminCustomReceiptLine } from './useAdminCustomReceiptLines';
|
|
14
15
|
import {
|
|
@@ -74,6 +75,8 @@ export interface AdminChangeReceiptComparisonProps {
|
|
|
74
75
|
}) => void;
|
|
75
76
|
preserveExistingReceipt?: boolean;
|
|
76
77
|
onPreserveExistingReceiptChange?: (preserve: boolean) => void;
|
|
78
|
+
/** Use denser, wrapping receipt typography in the public mobile change flow. */
|
|
79
|
+
compactMobile?: boolean;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
function normalizeLocale(locale: string): Locale {
|
|
@@ -132,6 +135,7 @@ export function AdminChangeReceiptComparison({
|
|
|
132
135
|
onSetReceiptLineOverride,
|
|
133
136
|
preserveExistingReceipt = false,
|
|
134
137
|
onPreserveExistingReceiptChange,
|
|
138
|
+
compactMobile = false,
|
|
135
139
|
}: AdminChangeReceiptComparisonProps) {
|
|
136
140
|
const [overrideDrafts, setOverrideDrafts] = useState<Record<string, string>>({});
|
|
137
141
|
useEffect(() => {
|
|
@@ -344,7 +348,9 @@ export function AdminChangeReceiptComparison({
|
|
|
344
348
|
<div className="grid gap-4 md:grid-cols-2">
|
|
345
349
|
<section className="min-w-0 overflow-visible rounded-lg border border-stone-200 bg-stone-50/70 p-3">
|
|
346
350
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
347
|
-
<h3 className=
|
|
351
|
+
<h3 className={`${compactMobile ? styles.receiptTitle : ''} text-sm font-semibold text-stone-900`}>
|
|
352
|
+
Existing purchase
|
|
353
|
+
</h3>
|
|
348
354
|
<span className="shrink-0 text-xs font-medium text-stone-500">
|
|
349
355
|
{formatCurrencyAmount(originalReceipt.total, originalCurrency, displayLocale)}
|
|
350
356
|
</span>
|
|
@@ -358,6 +364,7 @@ export function AdminChangeReceiptComparison({
|
|
|
358
364
|
taxAmount={hasTaxLine(existingLines) ? 0 : originalReceipt.tax}
|
|
359
365
|
taxRate={taxRate}
|
|
360
366
|
size="sm"
|
|
367
|
+
compactMobileTotal={compactMobile}
|
|
361
368
|
t={t}
|
|
362
369
|
/>
|
|
363
370
|
</section>
|
|
@@ -365,7 +372,7 @@ export function AdminChangeReceiptComparison({
|
|
|
365
372
|
<section className="min-w-0 overflow-visible rounded-lg border border-sky-200 bg-sky-50/50 p-3">
|
|
366
373
|
<div className="mb-3">
|
|
367
374
|
<div>
|
|
368
|
-
<h3 className=
|
|
375
|
+
<h3 className={`${compactMobile ? styles.receiptTitle : ''} text-sm font-semibold text-stone-900`}>
|
|
369
376
|
{isProductFamilyConversion ? 'New purchase' : 'Current change'}
|
|
370
377
|
</h3>
|
|
371
378
|
{isProductFamilyConversion ? (
|
|
@@ -436,6 +443,7 @@ export function AdminChangeReceiptComparison({
|
|
|
436
443
|
locale={displayLocale}
|
|
437
444
|
subtotal={summarySubtotal}
|
|
438
445
|
size="sm"
|
|
446
|
+
compactMobileTotal={compactMobile}
|
|
439
447
|
t={t}
|
|
440
448
|
lineAmountInputs={preserveExistingReceipt ? undefined : { ...quotedOverrideAmountInputs, ...overrideDrafts }}
|
|
441
449
|
onLineAmountInputChange={preserveExistingReceipt ? undefined : handleOverrideChange}
|
|
@@ -538,8 +546,10 @@ export function AdminChangeReceiptComparison({
|
|
|
538
546
|
) : null}
|
|
539
547
|
|
|
540
548
|
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-stone-200 bg-stone-50 px-3 py-2">
|
|
541
|
-
<span className=
|
|
542
|
-
|
|
549
|
+
<span className={`${compactMobile ? styles.amountDueLabel : ''} text-sm font-semibold text-stone-900`}>
|
|
550
|
+
{amountDueLabel}
|
|
551
|
+
</span>
|
|
552
|
+
<span className={`${compactMobile ? styles.amountDueValue : ''} text-lg font-semibold tabular-nums ${amountDueClass}`}>
|
|
543
553
|
{!selectionChanged || displayedSettlementAmount == null
|
|
544
554
|
? '—'
|
|
545
555
|
: formatCurrencyAmount(displayedSettlementAmount, currency, displayLocale)}
|
|
@@ -109,6 +109,49 @@
|
|
|
109
109
|
padding: 1.25rem;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
.bookingCompareSticky {
|
|
113
|
+
position: sticky;
|
|
114
|
+
top: 0;
|
|
115
|
+
z-index: 10;
|
|
116
|
+
margin-bottom: 0.75rem;
|
|
117
|
+
width: 100%;
|
|
118
|
+
border-radius: 1rem;
|
|
119
|
+
padding: 0.5rem 0;
|
|
120
|
+
background: var(--light-orange-background);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.mobileCompareDigestContainer {
|
|
124
|
+
display: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@media (max-width: 767px) {
|
|
128
|
+
.bookingCompareSticky {
|
|
129
|
+
z-index: 20;
|
|
130
|
+
max-height: calc(90vh - 4.5rem);
|
|
131
|
+
max-height: calc(90dvh - 4.5rem);
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mobileCompareDigestContainer {
|
|
136
|
+
display: block;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.bookingCompareDetails {
|
|
140
|
+
max-height: calc(90vh - 11rem);
|
|
141
|
+
max-height: calc(90dvh - 11rem);
|
|
142
|
+
overflow-y: auto;
|
|
143
|
+
overscroll-behavior: contain;
|
|
144
|
+
border-radius: 0 0 1rem 1rem;
|
|
145
|
+
padding: 0 0.25rem 0.25rem;
|
|
146
|
+
-webkit-overflow-scrolling: touch;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.bookingCompareDetails :global(.booking-change-summary-column) {
|
|
150
|
+
border-radius: 0 0 0.75rem 0.75rem;
|
|
151
|
+
background: transparent;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
112
155
|
@media (min-width: 768px) {
|
|
113
156
|
.dialog {
|
|
114
157
|
max-width: 960px;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
5
5
|
import { BackgroundVideo } from '../BackgroundVideo';
|
|
6
6
|
import type { VideoSources } from '../../constants/products';
|
|
7
|
+
import type { BackgroundVideoLoading } from '../BackgroundVideo';
|
|
7
8
|
import { resolvePublicAssetUrl, useBookingHost } from '../../runtime';
|
|
8
9
|
import { useTranslations } from '../../lib/booking/i18n';
|
|
9
10
|
import styles from './BookingFlowCollage.module.css';
|
|
@@ -17,6 +18,17 @@ export interface BookingFlowCollageProps {
|
|
|
17
18
|
imageIds: string[];
|
|
18
19
|
/** Alt text prefix for images (e.g. product name) */
|
|
19
20
|
altPrefix?: string;
|
|
21
|
+
/** Descriptive alt text matching imageIds. Falls back to numbered labels. */
|
|
22
|
+
imageAlts?: string[];
|
|
23
|
+
/** Responsive slot widths for the one- and two-column gallery cells. */
|
|
24
|
+
imageSizes?: {
|
|
25
|
+
narrow: string;
|
|
26
|
+
wide: string;
|
|
27
|
+
};
|
|
28
|
+
/** Defer loading when the collage appears below the fold. */
|
|
29
|
+
videoLoading?: BackgroundVideoLoading;
|
|
30
|
+
/** Browser preload hint once the video is eligible to load. */
|
|
31
|
+
videoPreload?: 'auto' | 'metadata' | 'none';
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
const DEFAULT_VIDEO: VideoSources = {
|
|
@@ -24,25 +36,36 @@ const DEFAULT_VIDEO: VideoSources = {
|
|
|
24
36
|
webm: '/videos/via-via-moraine-lake-tour-video.webm',
|
|
25
37
|
};
|
|
26
38
|
|
|
39
|
+
// The first and fourth cells occupy one grid column; the middle cells occupy
|
|
40
|
+
// two. Accurate slot hints prevent small thumbnails from selecting full-width
|
|
41
|
+
// gallery assets while retaining enough resolution for high-density displays.
|
|
42
|
+
const NARROW_IMAGE_SIZES = '(max-width: 640px) 29vw, 300px';
|
|
43
|
+
const WIDE_IMAGE_SIZES = '(max-width: 640px) 59vw, 560px';
|
|
44
|
+
const DEFAULT_IMAGE_SIZES = {
|
|
45
|
+
narrow: NARROW_IMAGE_SIZES,
|
|
46
|
+
wide: WIDE_IMAGE_SIZES,
|
|
47
|
+
};
|
|
48
|
+
|
|
27
49
|
export function BookingFlowCollage({
|
|
28
50
|
video,
|
|
29
51
|
videoPosterImageId,
|
|
30
52
|
imageIds,
|
|
31
53
|
altPrefix = 'Tour',
|
|
54
|
+
imageAlts,
|
|
55
|
+
imageSizes = DEFAULT_IMAGE_SIZES,
|
|
56
|
+
videoLoading = 'eager',
|
|
57
|
+
videoPreload = 'auto',
|
|
32
58
|
}: BookingFlowCollageProps) {
|
|
33
59
|
const { catalog, slots, env } = useBookingHost();
|
|
34
60
|
const ViaViaImage = slots.Image;
|
|
35
61
|
const ImageModal = slots.ImageModal;
|
|
36
62
|
const videoSrc = video ?? DEFAULT_VIDEO;
|
|
37
|
-
// Use
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
src: resolvePublicAssetUrl(env.STATIC_ASSET_ORIGIN, videoForCollageRaw.src),
|
|
44
|
-
webm: resolvePublicAssetUrl(env.STATIC_ASSET_ORIGIN, videoForCollageRaw.webm),
|
|
45
|
-
};
|
|
63
|
+
// Use the MP4 here because these videos include an audio track, while the
|
|
64
|
+
// WebM variants are silent background-video encodes.
|
|
65
|
+
const videoForCollageSrc = resolvePublicAssetUrl(
|
|
66
|
+
env.STATIC_ASSET_ORIGIN,
|
|
67
|
+
videoSrc.longSrc ?? videoSrc.src,
|
|
68
|
+
);
|
|
46
69
|
const posterUrl =
|
|
47
70
|
videoPosterImageId && catalog.getImageUrl
|
|
48
71
|
? String(catalog.getImageUrl(videoPosterImageId))
|
|
@@ -91,10 +114,13 @@ export function BookingFlowCollage({
|
|
|
91
114
|
const handleToggleMute = useCallback(() => {
|
|
92
115
|
const el = videoRef.current;
|
|
93
116
|
if (!el) return;
|
|
94
|
-
|
|
117
|
+
const nextMuted = !el.muted;
|
|
118
|
+
el.muted = nextMuted;
|
|
119
|
+
setIsMuted(nextMuted);
|
|
95
120
|
}, []);
|
|
96
121
|
|
|
97
|
-
const
|
|
122
|
+
const getAlt = (i: number) => imageAlts?.[i] ?? `${altPrefix} - ${i + 1}`;
|
|
123
|
+
const imageItems = gridImages.map((id, i) => ({ id, alt: getAlt(i) }));
|
|
98
124
|
|
|
99
125
|
return (
|
|
100
126
|
<div className={`${styles.collage} ${!hasGridImages ? styles.videoOnly : ''}`}>
|
|
@@ -102,11 +128,11 @@ export function BookingFlowCollage({
|
|
|
102
128
|
<div className={styles.videoWrapper}>
|
|
103
129
|
<BackgroundVideo
|
|
104
130
|
ref={videoRef}
|
|
105
|
-
{
|
|
106
|
-
loading=
|
|
107
|
-
preload=
|
|
131
|
+
src={videoForCollageSrc}
|
|
132
|
+
loading={videoLoading}
|
|
133
|
+
preload={videoPreload}
|
|
108
134
|
autoPlay
|
|
109
|
-
muted
|
|
135
|
+
muted={isMuted}
|
|
110
136
|
loop
|
|
111
137
|
playsInline
|
|
112
138
|
poster={posterUrl}
|
|
@@ -163,8 +189,9 @@ export function BookingFlowCollage({
|
|
|
163
189
|
>
|
|
164
190
|
<ViaViaImage
|
|
165
191
|
imageId={id}
|
|
166
|
-
alt={
|
|
192
|
+
alt={getAlt(i)}
|
|
167
193
|
context="GALLERY"
|
|
194
|
+
sizes={i === 0 || i === 3 ? imageSizes.narrow : imageSizes.wide}
|
|
168
195
|
/>
|
|
169
196
|
</button>
|
|
170
197
|
))}
|