@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
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from '../../lib/booking/map-utils';
|
|
34
34
|
import type { SearchedLocation, NearbyLocation } from '../../lib/booking/pickup-location-types';
|
|
35
35
|
import { useTranslations } from '../../lib/booking/i18n';
|
|
36
|
+
import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
|
|
36
37
|
import { useBookingApp } from '../../contexts/BookingAppContext';
|
|
37
38
|
import styles from './PickupLocationSelector.module.css';
|
|
38
39
|
|
|
@@ -77,9 +78,6 @@ const SUGGESTION_HIDE_DELAY = 200; // ms
|
|
|
77
78
|
const DEFAULT_MAP_ZOOM = 10;
|
|
78
79
|
const SEARCHED_LOCATION_ZOOM = 14;
|
|
79
80
|
const MAX_ZOOM = 15;
|
|
80
|
-
const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
|
|
81
|
-
const SAMSON_MALL_PARKING_DISCLAIMER =
|
|
82
|
-
'No public parking at this pickup location. This pickup location is meant to serve guests staying at the HI Lake Louise Alpine Hostel, Lake Louise Inn, or Lake Louise Campground and can walk to Samson Mall for pickup due to parking restrictions in the town of Lake Louise.';
|
|
83
81
|
|
|
84
82
|
// Filter definitions
|
|
85
83
|
const FILTERS = [
|
|
@@ -114,8 +112,21 @@ function hasFreeParking(location: PickupLocation): boolean {
|
|
|
114
112
|
return location.name.toLowerCase().includes('free parking');
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
function
|
|
118
|
-
|
|
115
|
+
function PickupLocationDisclaimerNote({
|
|
116
|
+
location,
|
|
117
|
+
className,
|
|
118
|
+
}: {
|
|
119
|
+
location: Pick<PickupLocation, 'id' | 'name' | 'disclaimer'>;
|
|
120
|
+
className: string;
|
|
121
|
+
}) {
|
|
122
|
+
const disclaimer = getPickupLocationDisclaimer(location);
|
|
123
|
+
if (!disclaimer) return null;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<p className={className} role="note">
|
|
127
|
+
{disclaimer.message}
|
|
128
|
+
</p>
|
|
129
|
+
);
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
// Filter locations based on selected filters (AND logic)
|
|
@@ -1213,11 +1224,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1213
1224
|
)}
|
|
1214
1225
|
</div>
|
|
1215
1226
|
<p className="text-xs text-stone-600 truncate">{location.address}</p>
|
|
1216
|
-
{
|
|
1217
|
-
<p className={styles.parkingDisclaimer} role="note">
|
|
1218
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1219
|
-
</p>
|
|
1220
|
-
)}
|
|
1227
|
+
<PickupLocationDisclaimerNote location={location} className={styles.parkingDisclaimer} />
|
|
1221
1228
|
{isNearby && searchedLocation && !exactMatchLocationId && !cityFilter && (
|
|
1222
1229
|
<div className="flex items-center gap-2 mt-1 text-xs text-stone-500">
|
|
1223
1230
|
<span>📍 {formatDistance((location as NearbyLocation).distance, useImperial)}</span>
|
|
@@ -1318,11 +1325,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1318
1325
|
<p className="text-sm text-stone-600 mb-2">
|
|
1319
1326
|
{location.address}
|
|
1320
1327
|
</p>
|
|
1321
|
-
{
|
|
1322
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1323
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1324
|
-
</p>
|
|
1325
|
-
)}
|
|
1328
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1326
1329
|
<div className="text-xs text-stone-500 space-y-1">
|
|
1327
1330
|
<p>
|
|
1328
1331
|
📍 {formatDistance(location.distance, useImperial)} {t('pickup.away')}
|
|
@@ -1376,11 +1379,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1376
1379
|
<p className="text-sm text-stone-600 mb-2">
|
|
1377
1380
|
{location.address}
|
|
1378
1381
|
</p>
|
|
1379
|
-
{
|
|
1380
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1381
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1382
|
-
</p>
|
|
1383
|
-
)}
|
|
1382
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1384
1383
|
{location.notes && (
|
|
1385
1384
|
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
|
|
1386
1385
|
{location.notes}
|
|
@@ -1438,11 +1437,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1438
1437
|
<p className="text-sm text-stone-600 mb-2">
|
|
1439
1438
|
{location.address}
|
|
1440
1439
|
</p>
|
|
1441
|
-
{
|
|
1442
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1443
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1444
|
-
</p>
|
|
1445
|
-
)}
|
|
1440
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1446
1441
|
{location.notes && (
|
|
1447
1442
|
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
|
|
1448
1443
|
{location.notes}
|
|
@@ -5,3 +5,22 @@
|
|
|
5
5
|
.ruleAbove {
|
|
6
6
|
border-top: 1px solid var(--booking-stone-200, #e7e5e4);
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
@media (max-width: 639px) {
|
|
10
|
+
.compactMobileTotal .totalRow {
|
|
11
|
+
align-items: flex-start;
|
|
12
|
+
font-size: 1rem !important;
|
|
13
|
+
line-height: 1.3 !important;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.compactMobileTotal .totalLabel {
|
|
17
|
+
overflow: visible !important;
|
|
18
|
+
white-space: normal !important;
|
|
19
|
+
text-overflow: clip !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.compactMobileTotal .totalAmount {
|
|
23
|
+
font-size: 1rem !important;
|
|
24
|
+
line-height: 1.3 !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -84,6 +84,8 @@ export interface PriceSummaryProps {
|
|
|
84
84
|
totalLabel?: string;
|
|
85
85
|
/** Prefix a positive total with + when the row represents a signed change rather than an absolute total. */
|
|
86
86
|
showPositiveTotalSign?: boolean;
|
|
87
|
+
/** Keep the total row compact and allow its label to wrap on narrow mobile screens. */
|
|
88
|
+
compactMobileTotal?: boolean;
|
|
87
89
|
/** Render after the total row (e.g. provider price delta + keep-original-price) */
|
|
88
90
|
extraAfterTotal?: ReactNode;
|
|
89
91
|
/** Optional map of editable line input values by line key. */
|
|
@@ -188,6 +190,7 @@ export function PriceSummary({
|
|
|
188
190
|
hideSubtotal = false,
|
|
189
191
|
totalLabel,
|
|
190
192
|
showPositiveTotalSign = false,
|
|
193
|
+
compactMobileTotal = false,
|
|
191
194
|
extraAfterTotal,
|
|
192
195
|
lineAmountInputs,
|
|
193
196
|
lineLabelInputs,
|
|
@@ -218,7 +221,7 @@ export function PriceSummary({
|
|
|
218
221
|
!embedSubtotalBeforeTaxInLines && extraBeforeSubtotal ? extraBeforeSubtotal : null;
|
|
219
222
|
|
|
220
223
|
return (
|
|
221
|
-
<div className={`space-y-2 min-w-0 ${className}`}>
|
|
224
|
+
<div className={`space-y-2 min-w-0 ${compactMobileTotal ? styles.compactMobileTotal : ''} ${className}`}>
|
|
222
225
|
{lines.map((row, index) => {
|
|
223
226
|
const isBeforeSubtotalBoundary = firstTaxLineIndex < 0 || index < firstTaxLineIndex;
|
|
224
227
|
if (row.kind === 'ticket') {
|
|
@@ -375,11 +378,11 @@ export function PriceSummary({
|
|
|
375
378
|
<div className="space-y-0">
|
|
376
379
|
{depositMode ? (
|
|
377
380
|
<>
|
|
378
|
-
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
|
|
379
|
-
<span className=
|
|
381
|
+
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
|
|
382
|
+
<span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
|
|
380
383
|
{t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total'}
|
|
381
384
|
</span>
|
|
382
|
-
<span className=
|
|
385
|
+
<span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
|
|
383
386
|
{formatCurrencyAmount(depositMode.fullTotalAmount, currency, locale)}
|
|
384
387
|
</span>
|
|
385
388
|
</div>
|
|
@@ -399,12 +402,12 @@ export function PriceSummary({
|
|
|
399
402
|
)}
|
|
400
403
|
</>
|
|
401
404
|
) : (
|
|
402
|
-
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
|
|
403
|
-
<span className=
|
|
405
|
+
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
|
|
406
|
+
<span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
|
|
404
407
|
{totalLabel ??
|
|
405
408
|
(t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total')}
|
|
406
409
|
</span>
|
|
407
|
-
<span className=
|
|
410
|
+
<span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
|
|
408
411
|
{showPositiveTotalSign && total > 0.005 ? '+' : ''}
|
|
409
412
|
{formatCurrencyAmount(total, currency, locale)}
|
|
410
413
|
</span>
|
|
@@ -39,7 +39,13 @@ export function PrivateShuttlePassengerSection({
|
|
|
39
39
|
Number of Passengers
|
|
40
40
|
</label>
|
|
41
41
|
<div className={styles.passengerBox}>
|
|
42
|
-
<div
|
|
42
|
+
<div
|
|
43
|
+
className={
|
|
44
|
+
isSpecialRequestMode
|
|
45
|
+
? 'flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between'
|
|
46
|
+
: 'flex items-center justify-between gap-4'
|
|
47
|
+
}
|
|
48
|
+
>
|
|
43
49
|
<div>
|
|
44
50
|
<div className="font-medium text-stone-900">Passengers</div>
|
|
45
51
|
<div className="text-sm text-stone-600">
|
|
@@ -100,13 +100,17 @@
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
.content {
|
|
103
|
-
display:
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
105
|
+
grid-template-rows: auto auto;
|
|
104
106
|
align-items: center;
|
|
105
|
-
|
|
107
|
+
column-gap: 1rem;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
.details {
|
|
109
|
-
|
|
111
|
+
min-width: 0;
|
|
112
|
+
grid-column: 1;
|
|
113
|
+
grid-row: 1;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
.time {
|
|
@@ -141,6 +145,8 @@
|
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
.staySummary {
|
|
148
|
+
grid-column: 1;
|
|
149
|
+
grid-row: 2;
|
|
144
150
|
font-size: 0.75rem;
|
|
145
151
|
color: var(--booking-stone-500, #78716c);
|
|
146
152
|
margin-top: 0.25rem;
|
|
@@ -155,9 +161,22 @@
|
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
.price {
|
|
164
|
+
grid-column: 2;
|
|
165
|
+
grid-row: 1 / 3;
|
|
166
|
+
align-self: center;
|
|
158
167
|
font-size: 0.875rem;
|
|
159
168
|
font-weight: 600;
|
|
160
|
-
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@media (max-width: 639px) {
|
|
173
|
+
.staySummary {
|
|
174
|
+
grid-column: 1 / -1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.price {
|
|
178
|
+
grid-row: 1;
|
|
179
|
+
}
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
.priceIncluded {
|
|
@@ -115,11 +115,6 @@ export function ReturnTimeSelector({
|
|
|
115
115
|
{t('calendar.spotsAvailable', { count: returnVacancies })}
|
|
116
116
|
</div>
|
|
117
117
|
))}
|
|
118
|
-
{staySummary && (
|
|
119
|
-
<div className={styles.staySummary}>
|
|
120
|
-
{staySummary}
|
|
121
|
-
</div>
|
|
122
|
-
)}
|
|
123
118
|
{isSoldOut && (
|
|
124
119
|
<div className={styles.soldOut}>
|
|
125
120
|
{t('booking.soldOut')}
|
|
@@ -142,6 +137,11 @@ export function ReturnTimeSelector({
|
|
|
142
137
|
`${formatCurrencyAmount(priceAdjustmentPerPerson, currency, locale as 'en' | 'fr')} per person`
|
|
143
138
|
)}
|
|
144
139
|
</div>
|
|
140
|
+
{staySummary && (
|
|
141
|
+
<div className={styles.staySummary}>
|
|
142
|
+
{staySummary}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
145
|
</div>
|
|
146
146
|
</button>
|
|
147
147
|
);
|
|
@@ -14,6 +14,7 @@ import { ItineraryPlaceholder } from './ItineraryPlaceholder';
|
|
|
14
14
|
type TranslationFn = (key: string, params?: Record<string, string>) => string;
|
|
15
15
|
|
|
16
16
|
export interface StandardBookingItineraryPanelProps {
|
|
17
|
+
productId: string;
|
|
17
18
|
selectedDate: string;
|
|
18
19
|
hideItineraryBox: boolean;
|
|
19
20
|
activeOptions: ProductOption[];
|
|
@@ -42,6 +43,7 @@ export interface StandardBookingItineraryPanelProps {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function StandardBookingItineraryPanel({
|
|
46
|
+
productId,
|
|
45
47
|
selectedDate,
|
|
46
48
|
hideItineraryBox,
|
|
47
49
|
activeOptions,
|
|
@@ -104,6 +106,7 @@ export function StandardBookingItineraryPanel({
|
|
|
104
106
|
|
|
105
107
|
return (
|
|
106
108
|
<ItineraryBox
|
|
109
|
+
productId={productId}
|
|
107
110
|
selectedDate={selectedDate}
|
|
108
111
|
formattedDate={formattedDate}
|
|
109
112
|
itineraryItems={itineraryItems}
|
|
@@ -24,14 +24,22 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.row {
|
|
27
|
-
display:
|
|
27
|
+
display: grid;
|
|
28
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
29
|
+
grid-template-rows: auto auto;
|
|
28
30
|
align-items: center;
|
|
29
|
-
|
|
31
|
+
column-gap: 1rem;
|
|
30
32
|
padding: 1rem;
|
|
31
33
|
background: var(--booking-stone-50, #fafaf9);
|
|
32
34
|
border-radius: 0.5rem;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
.summary {
|
|
38
|
+
grid-column: 1;
|
|
39
|
+
grid-row: 1;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
.category {
|
|
36
44
|
font-weight: 500;
|
|
37
45
|
color: var(--booking-stone-900, #1c1917);
|
|
@@ -50,11 +58,12 @@
|
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
.finePrint {
|
|
61
|
+
grid-column: 1 / -1;
|
|
62
|
+
grid-row: 2;
|
|
53
63
|
margin-top: 0.25rem;
|
|
54
64
|
font-size: 0.7rem;
|
|
55
65
|
line-height: 1.3;
|
|
56
66
|
color: var(--booking-stone-500, #78716c);
|
|
57
|
-
max-width: 16rem;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
.priceStrikethrough {
|
|
@@ -68,11 +77,29 @@
|
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
.controls {
|
|
80
|
+
grid-column: 2;
|
|
81
|
+
grid-row: 1;
|
|
71
82
|
display: flex;
|
|
72
83
|
align-items: center;
|
|
73
84
|
gap: 0.75rem;
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
@media (max-width: 639px) {
|
|
88
|
+
.row {
|
|
89
|
+
row-gap: 0.75rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.finePrint {
|
|
93
|
+
margin-top: 0;
|
|
94
|
+
font-size: 0.75rem;
|
|
95
|
+
line-height: 1.375;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.controls {
|
|
99
|
+
grid-row: 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
.qtyBtn {
|
|
77
104
|
width: 2.5rem;
|
|
78
105
|
height: 2.5rem;
|
|
@@ -142,7 +142,7 @@ export function TicketSelector({
|
|
|
142
142
|
|
|
143
143
|
return (
|
|
144
144
|
<div key={rate.category} className={styles.row}>
|
|
145
|
-
<div>
|
|
145
|
+
<div className={styles.summary}>
|
|
146
146
|
<p className={styles.category}>
|
|
147
147
|
{rate.category}
|
|
148
148
|
{rate.category === 'CHILD' && (
|
|
@@ -169,11 +169,6 @@ export function TicketSelector({
|
|
|
169
169
|
formatCurrencyAmount(effectiveUnitPrice, currency, locale as 'en' | 'fr')
|
|
170
170
|
)}
|
|
171
171
|
</p>
|
|
172
|
-
{ticketFinePrintByCategory?.[rate.category] && (
|
|
173
|
-
<p className={styles.finePrint}>
|
|
174
|
-
{ticketFinePrintByCategory[rate.category]}
|
|
175
|
-
</p>
|
|
176
|
-
)}
|
|
177
172
|
</div>
|
|
178
173
|
<div className={styles.controls}>
|
|
179
174
|
<button
|
|
@@ -195,6 +190,11 @@ export function TicketSelector({
|
|
|
195
190
|
+
|
|
196
191
|
</button>
|
|
197
192
|
</div>
|
|
193
|
+
{ticketFinePrintByCategory?.[rate.category] && (
|
|
194
|
+
<p className={styles.finePrint}>
|
|
195
|
+
{ticketFinePrintByCategory[rate.category]}
|
|
196
|
+
</p>
|
|
197
|
+
)}
|
|
198
198
|
</div>
|
|
199
199
|
);
|
|
200
200
|
})}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
border-radius: 0.5rem;
|
|
18
18
|
border: none;
|
|
19
19
|
cursor: pointer;
|
|
20
|
-
font-family: '
|
|
20
|
+
font-family: 'Figtree', sans-serif !important;
|
|
21
21
|
font-size: 1.125rem;
|
|
22
22
|
font-weight: 700;
|
|
23
23
|
text-transform: lowercase;
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
transition: opacity 0.2s;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
.root .mainToggleStatic {
|
|
30
|
+
position: relative;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
.root .mainToggle:hover {
|
|
30
34
|
opacity: 0.9;
|
|
31
35
|
}
|
|
@@ -168,6 +172,7 @@
|
|
|
168
172
|
background: none;
|
|
169
173
|
border: none;
|
|
170
174
|
cursor: pointer;
|
|
175
|
+
font-family: 'Figtree', sans-serif !important;
|
|
171
176
|
font-size: 0.9375rem;
|
|
172
177
|
font-weight: 700 !important;
|
|
173
178
|
color: var(--accent-orange) !important;
|
|
@@ -41,6 +41,10 @@ export interface TourDescriptionProps {
|
|
|
41
41
|
sections?: TourDescriptionSection[];
|
|
42
42
|
/** Start expanded (e.g. when in partial/pre-launch state) */
|
|
43
43
|
defaultExpanded?: boolean;
|
|
44
|
+
/** Keep the main toggle visible while scrolling inside booking dialogs. */
|
|
45
|
+
stickyToggle?: boolean;
|
|
46
|
+
/** Show the description content without a collapse control. */
|
|
47
|
+
alwaysExpanded?: boolean;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/** Items starting with • - * are bullet list; otherwise join as HTML (multi-line format) */
|
|
@@ -123,6 +127,8 @@ export function TourDescription({
|
|
|
123
127
|
review: reviewProp,
|
|
124
128
|
sections: sectionsProp,
|
|
125
129
|
defaultExpanded = false,
|
|
130
|
+
stickyToggle = true,
|
|
131
|
+
alwaysExpanded = false,
|
|
126
132
|
}: TourDescriptionProps) {
|
|
127
133
|
const { catalog, slots } = useBookingHost();
|
|
128
134
|
const PlusIcon = slots.PlusIcon;
|
|
@@ -154,6 +160,7 @@ export function TourDescription({
|
|
|
154
160
|
const displayReview = review ?? null;
|
|
155
161
|
const displaySections =
|
|
156
162
|
sections.length > 0 ? sections : [];
|
|
163
|
+
const contentIsExpanded = alwaysExpanded || isExpanded;
|
|
157
164
|
|
|
158
165
|
/** Group paragraphs: consecutive bullet items become a single <ul> block */
|
|
159
166
|
const paragraphBlocks = useMemo(() => {
|
|
@@ -178,23 +185,25 @@ export function TourDescription({
|
|
|
178
185
|
|
|
179
186
|
return (
|
|
180
187
|
<div className={styles.root}>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{isExpanded ?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
{!alwaysExpanded && (
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className={`${styles.mainToggle} ${stickyToggle ? '' : styles.mainToggleStatic}`}
|
|
192
|
+
onClick={() => setIsExpanded((e) => !e)}
|
|
193
|
+
aria-expanded={isExpanded}
|
|
194
|
+
>
|
|
195
|
+
<span>{toggleLabel ?? t('booking.seeFullTourDescription')}</span>
|
|
196
|
+
<span className={`${styles.toggleIcon} ${isExpanded ? styles.toggleIconExpanded : ''}`} aria-hidden>
|
|
197
|
+
{isExpanded ? (
|
|
198
|
+
<MinusIcon />
|
|
199
|
+
) : (
|
|
200
|
+
<PlusIcon />
|
|
201
|
+
)}
|
|
202
|
+
</span>
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
196
205
|
|
|
197
|
-
<div className={`${styles.contentWrapper} ${
|
|
206
|
+
<div className={`${styles.contentWrapper} ${contentIsExpanded ? styles.contentExpanded : ''}`}>
|
|
198
207
|
<div className={styles.content}>
|
|
199
208
|
{paragraphBlocks.map((block, i) =>
|
|
200
209
|
block.type === 'paragraph' ? (
|
|
@@ -95,3 +95,157 @@
|
|
|
95
95
|
line-height: 1.2;
|
|
96
96
|
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
.mobileDigest {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (max-width: 767px) {
|
|
104
|
+
.kickerMobileHidden {
|
|
105
|
+
display: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.mobileDigest {
|
|
109
|
+
display: block;
|
|
110
|
+
padding: 0 0.25rem;
|
|
111
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.mobileDigest .mobileDigestToggle {
|
|
115
|
+
display: block;
|
|
116
|
+
width: 100%;
|
|
117
|
+
min-height: 2.75rem;
|
|
118
|
+
margin: 0;
|
|
119
|
+
padding: 0.625rem 0.75rem;
|
|
120
|
+
border: 0;
|
|
121
|
+
border-radius: 0.75rem;
|
|
122
|
+
background: transparent;
|
|
123
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
124
|
+
font: inherit;
|
|
125
|
+
text-align: left;
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
appearance: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.mobileDigest .mobileDigestToggle:focus-visible {
|
|
131
|
+
outline: 2px solid var(--accent-orange, #ff4d00);
|
|
132
|
+
outline-offset: 2px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mobileDigestExpanded .mobileDigestToggle {
|
|
136
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.mobileDigestHeading,
|
|
140
|
+
.mobileDigestAction,
|
|
141
|
+
.mobileDigestLine,
|
|
142
|
+
.mobileDigestTotalLine {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: baseline;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.mobileDigestHeading {
|
|
148
|
+
justify-content: space-between;
|
|
149
|
+
gap: 0.75rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.mobileDigestKicker {
|
|
153
|
+
font-size: 0.625rem;
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
letter-spacing: 0.07em;
|
|
156
|
+
line-height: 1.2;
|
|
157
|
+
text-transform: uppercase;
|
|
158
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.mobileDigestAction {
|
|
162
|
+
flex-shrink: 0;
|
|
163
|
+
gap: 0.125rem;
|
|
164
|
+
font-size: 0.75rem;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
line-height: 1.2;
|
|
167
|
+
color: var(--accent-orange, #ff4d00);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.mobileDigestChevron {
|
|
171
|
+
flex-shrink: 0;
|
|
172
|
+
align-self: center;
|
|
173
|
+
transition: transform 160ms ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.mobileDigestChevronExpanded {
|
|
177
|
+
transform: rotate(180deg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mobileDigestChanges {
|
|
181
|
+
display: grid;
|
|
182
|
+
gap: 0.125rem;
|
|
183
|
+
margin-top: 0.375rem;
|
|
184
|
+
font-size: 0.8125rem;
|
|
185
|
+
line-height: 1.3;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.mobileDigestLine {
|
|
189
|
+
min-width: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mobileDigestArrow {
|
|
193
|
+
margin: 0 0.35rem;
|
|
194
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.mobileDigestNewValue {
|
|
198
|
+
font-weight: 700;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.mobileDigestMuted {
|
|
202
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.mobileDigestTotalLine {
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
gap: 0.75rem;
|
|
208
|
+
padding-top: 0.125rem;
|
|
209
|
+
font-variant-numeric: tabular-nums;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.deltaIncrease,
|
|
213
|
+
.deltaDecrease,
|
|
214
|
+
.deltaSame {
|
|
215
|
+
flex-shrink: 0;
|
|
216
|
+
font-weight: 700;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.deltaIncrease {
|
|
220
|
+
color: var(--accent-orange, #ff4d00);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.deltaDecrease {
|
|
224
|
+
color: var(--booking-emerald-700, #047857);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.deltaSame {
|
|
228
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.mobileDigestBaseline {
|
|
232
|
+
display: block;
|
|
233
|
+
margin-top: 0.375rem;
|
|
234
|
+
font-size: 0.8125rem;
|
|
235
|
+
font-weight: 500;
|
|
236
|
+
line-height: 1.3;
|
|
237
|
+
font-variant-numeric: tabular-nums;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.visuallyHidden {
|
|
241
|
+
position: absolute;
|
|
242
|
+
width: 1px;
|
|
243
|
+
height: 1px;
|
|
244
|
+
padding: 0;
|
|
245
|
+
margin: -1px;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
clip: rect(0, 0, 0, 0);
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
border: 0;
|
|
250
|
+
}
|
|
251
|
+
}
|