@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
|
@@ -71,10 +71,11 @@
|
|
|
71
71
|
--cal-soldout-selected-border: rgba(248, 113, 113, 0.5);
|
|
72
72
|
|
|
73
73
|
/* Discount badge */
|
|
74
|
-
--cal-discount-bg: #
|
|
75
|
-
--cal-discount-text: #
|
|
76
|
-
--cal-discount-tag-bg:
|
|
77
|
-
--cal-discount-tag-text: #
|
|
74
|
+
--cal-discount-bg: #fff0e8;
|
|
75
|
+
--cal-discount-text: #a93600;
|
|
76
|
+
--cal-discount-tag-bg: #fff0e8;
|
|
77
|
+
--cal-discount-tag-text: #a93600;
|
|
78
|
+
--cal-discount-tag-border: var(--accent-orange, #ff4d00);
|
|
78
79
|
|
|
79
80
|
/* Today marker */
|
|
80
81
|
--cal-today-ring: var(--booking-orange-600, #ff4d00);
|
|
@@ -374,7 +375,12 @@
|
|
|
374
375
|
transform: translateX(-50%);
|
|
375
376
|
}
|
|
376
377
|
|
|
377
|
-
|
|
378
|
+
/*
|
|
379
|
+
* Keep the frame more specific than the scoped booking preflight
|
|
380
|
+
* (`.booking-flow-preflight *`), which otherwise wins when its stylesheet is
|
|
381
|
+
* emitted later and resets the border width to zero on some mobile builds.
|
|
382
|
+
*/
|
|
383
|
+
.calendar .calendarGrid {
|
|
378
384
|
border: 1px solid var(--cal-outer-border);
|
|
379
385
|
border-radius: 0.5rem;
|
|
380
386
|
overflow: hidden;
|
|
@@ -394,7 +400,7 @@
|
|
|
394
400
|
border-bottom: 1px solid var(--cal-header-border);
|
|
395
401
|
}
|
|
396
402
|
|
|
397
|
-
.calendarHeaderCell {
|
|
403
|
+
.calendar .calendarHeaderCell {
|
|
398
404
|
padding: 0.25rem 0;
|
|
399
405
|
text-align: center;
|
|
400
406
|
font-size: 0.75rem;
|
|
@@ -404,7 +410,7 @@
|
|
|
404
410
|
letter-spacing: 0.05em;
|
|
405
411
|
border-right: 1px solid var(--cal-header-border);
|
|
406
412
|
}
|
|
407
|
-
.calendarHeaderCell:last-child {
|
|
413
|
+
.calendar .calendarHeaderCell:last-child {
|
|
408
414
|
border-right: none;
|
|
409
415
|
}
|
|
410
416
|
|
|
@@ -505,6 +511,19 @@
|
|
|
505
511
|
padding: 0.5rem 0.5rem 0.125rem;
|
|
506
512
|
}
|
|
507
513
|
|
|
514
|
+
/* Minimum-passenger tours need a second line below the departure time on mobile. */
|
|
515
|
+
.calendarDayCell.calendarDayCellMobilePax {
|
|
516
|
+
min-height: 4.75rem;
|
|
517
|
+
padding-right: 0.125rem;
|
|
518
|
+
padding-left: 0.125rem;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* Status badges need the cell width; the standard mobile gutters are sized for dot/time lists. */
|
|
522
|
+
.calendarDayCell.calendarDayCellMobileStatus {
|
|
523
|
+
padding-right: 0.125rem;
|
|
524
|
+
padding-left: 0.125rem;
|
|
525
|
+
}
|
|
526
|
+
|
|
508
527
|
.calendarDayCell:disabled {
|
|
509
528
|
cursor: not-allowed;
|
|
510
529
|
}
|
|
@@ -586,19 +605,55 @@
|
|
|
586
605
|
right: 0.25rem;
|
|
587
606
|
display: flex;
|
|
588
607
|
align-items: center;
|
|
589
|
-
gap: 0.125rem;
|
|
590
608
|
background-color: var(--cal-discount-tag-bg);
|
|
591
609
|
color: var(--cal-discount-tag-text);
|
|
610
|
+
border: 1px solid var(--cal-discount-tag-border);
|
|
611
|
+
box-shadow: 0 1px 3px rgba(255, 77, 0, 0.18);
|
|
592
612
|
font-size: 9px;
|
|
593
|
-
font-weight:
|
|
594
|
-
|
|
613
|
+
font-weight: 800;
|
|
614
|
+
line-height: 1.1;
|
|
615
|
+
padding: 0.25rem 0.375rem;
|
|
595
616
|
border-radius: 0.25rem;
|
|
617
|
+
white-space: nowrap;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.calendarDiscountOff {
|
|
621
|
+
margin-left: 0.1875rem;
|
|
596
622
|
}
|
|
623
|
+
|
|
597
624
|
/* Mobile: position discount below day number (top-left) so it doesn't overlap */
|
|
598
625
|
.calendarDiscountTagMobile {
|
|
599
626
|
top: 1rem;
|
|
600
627
|
left: 0.25rem;
|
|
601
|
-
right:
|
|
628
|
+
right: 0.25rem;
|
|
629
|
+
flex-direction: column;
|
|
630
|
+
justify-content: center;
|
|
631
|
+
padding: 0.25rem;
|
|
632
|
+
font-size: 9px;
|
|
633
|
+
line-height: 1;
|
|
634
|
+
text-align: center;
|
|
635
|
+
white-space: normal;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.calendarDiscountTagMobile .calendarDiscountOff {
|
|
639
|
+
margin-left: 0;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
@media (min-width: 640px) {
|
|
643
|
+
.calendarDiscountTag {
|
|
644
|
+
border-width: 1.5px;
|
|
645
|
+
font-size: 10px;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.calendarAvailabilityIndicators {
|
|
650
|
+
min-width: 0;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
@media (min-width: 640px) {
|
|
654
|
+
.calendarAvailabilityIndicatorsWithDiscount {
|
|
655
|
+
padding-top: 1.625rem;
|
|
656
|
+
}
|
|
602
657
|
}
|
|
603
658
|
|
|
604
659
|
.calendarSoldOutBadge {
|
|
@@ -613,6 +668,14 @@
|
|
|
613
668
|
color: var(--cal-soldout-text);
|
|
614
669
|
border: 1px solid var(--cal-soldout-border);
|
|
615
670
|
}
|
|
671
|
+
|
|
672
|
+
@media (max-width: 639px) {
|
|
673
|
+
.calendarDayCellMobile .calendarSoldOutBadge,
|
|
674
|
+
.calendarDayCellMobileTall .calendarSoldOutBadge {
|
|
675
|
+
margin-top: 1rem;
|
|
676
|
+
margin-bottom: 0.25rem;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
616
679
|
.calendarDayCellSelected .calendarSoldOutBadge {
|
|
617
680
|
background-color: var(--cal-soldout-selected-bg);
|
|
618
681
|
color: var(--cal-cell-selected-text);
|
|
@@ -636,6 +699,34 @@
|
|
|
636
699
|
color: var(--cal-cell-selected-text);
|
|
637
700
|
}
|
|
638
701
|
|
|
702
|
+
@media (max-width: 639px) {
|
|
703
|
+
.calendarStatusPillMobile {
|
|
704
|
+
max-width: 100%;
|
|
705
|
+
min-width: 0;
|
|
706
|
+
margin-top: 1rem;
|
|
707
|
+
margin-bottom: 0.25rem;
|
|
708
|
+
padding: 0.25rem 0.5rem;
|
|
709
|
+
border: 0;
|
|
710
|
+
background-color: var(--cal-time-available-bg);
|
|
711
|
+
color: var(--cal-time-available-text);
|
|
712
|
+
font-size: 9px;
|
|
713
|
+
font-weight: 500;
|
|
714
|
+
line-height: 1;
|
|
715
|
+
letter-spacing: 0;
|
|
716
|
+
white-space: nowrap;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.calendarStatusPillMobileWithDiscount {
|
|
720
|
+
margin-top: 2.25rem;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.calendarDayCellSelected .calendarStatusPillMobile {
|
|
724
|
+
border-color: rgba(255, 255, 255, 0.8);
|
|
725
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
726
|
+
color: var(--cal-cell-selected-text);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
639
730
|
.calendarTimePillLow {
|
|
640
731
|
background-color: var(--cal-time-low-bg);
|
|
641
732
|
color: var(--cal-time-low-text);
|
|
@@ -703,10 +794,12 @@
|
|
|
703
794
|
align-items: center;
|
|
704
795
|
gap: 0.0625rem;
|
|
705
796
|
width: 100%;
|
|
797
|
+
margin-top: 1rem;
|
|
798
|
+
margin-bottom: 0.25rem;
|
|
706
799
|
}
|
|
707
800
|
/* Mobile: push time slots lower when discount tag is present */
|
|
708
801
|
.calendarMobileTimeListWithDiscount {
|
|
709
|
-
margin-top:
|
|
802
|
+
margin-top: 2.25rem;
|
|
710
803
|
}
|
|
711
804
|
|
|
712
805
|
.calendarMobileTimeDot {
|
|
@@ -719,14 +812,40 @@
|
|
|
719
812
|
line-height: 1.2;
|
|
720
813
|
}
|
|
721
814
|
|
|
815
|
+
.calendarMobileTimeDotWithPax {
|
|
816
|
+
flex-direction: column;
|
|
817
|
+
align-items: center;
|
|
818
|
+
width: 100%;
|
|
819
|
+
row-gap: 0.125rem;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.calendarMobileTimeRow {
|
|
823
|
+
display: flex;
|
|
824
|
+
align-items: center;
|
|
825
|
+
justify-content: center;
|
|
826
|
+
gap: 0.25rem;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.calendarMobileTimeLabel {
|
|
830
|
+
min-width: 0;
|
|
831
|
+
white-space: nowrap;
|
|
832
|
+
}
|
|
833
|
+
|
|
722
834
|
.calendarMobilePaxProgress {
|
|
835
|
+
align-self: center;
|
|
723
836
|
color: var(--booking-stone-600, #57534e);
|
|
724
|
-
|
|
837
|
+
max-width: 100%;
|
|
838
|
+
font-size: 7px;
|
|
725
839
|
font-weight: 700;
|
|
840
|
+
line-height: 1;
|
|
841
|
+
text-align: center;
|
|
726
842
|
white-space: nowrap;
|
|
727
843
|
}
|
|
728
844
|
.calendarMobilePaxProgressReached {
|
|
729
845
|
color: #047857;
|
|
846
|
+
max-width: 2.75rem;
|
|
847
|
+
line-height: 1.05;
|
|
848
|
+
white-space: normal;
|
|
730
849
|
}
|
|
731
850
|
.calendarMobilePaxProgressReached::before {
|
|
732
851
|
content: "✓ ";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useMemo, useRef, useEffect, useCallback, memo } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
-
import {
|
|
5
|
+
import { addDays, addWeeks, subWeeks, isSameDay, parseISO, startOfMonth, endOfMonth, addMonths, subMonths } from 'date-fns';
|
|
6
6
|
import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
|
|
7
7
|
import { enUS, fr } from 'date-fns/locale';
|
|
8
8
|
import type { Availability } from '../../lib/booking-api';
|
|
@@ -205,6 +205,7 @@ const DateCell = memo(function DateCell({
|
|
|
205
205
|
const { locale } = useLocale();
|
|
206
206
|
const dateFnsLocale = dateFnsLocales[locale] || enUS;
|
|
207
207
|
const dayNumber = formatInTimeZone(date, timezone, 'd');
|
|
208
|
+
const fullDateLabel = formatInTimeZone(date, timezone, 'EEEE, MMMM d, yyyy', { locale: dateFnsLocale });
|
|
208
209
|
const allowPinnedSoldOutDate = Boolean(selectableSoldOutDate && selectableSoldOutDate === dateStr);
|
|
209
210
|
const hasAvailabilityData = availability !== null;
|
|
210
211
|
// When showCapacity (admin), allow selecting sold-out slots for overbooking.
|
|
@@ -233,12 +234,16 @@ const DateCell = memo(function DateCell({
|
|
|
233
234
|
// Admin capacity line under each time pill needs extra vertical room (provider dashboard only).
|
|
234
235
|
const slotCount = availability?.startTimes?.length ?? 0;
|
|
235
236
|
const needsTallerCell = !isMobile && slotCount > 2;
|
|
236
|
-
const
|
|
237
|
+
const hasMobilePaxProgress =
|
|
238
|
+
isMobile && Boolean(availability?.timeBookingProgress && Object.keys(availability.timeBookingProgress).length > 0);
|
|
239
|
+
const mobileNeedsTaller = isMobile && (slotCount > 2 || hasMobilePaxProgress);
|
|
237
240
|
const adminCapacityTallCells =
|
|
238
241
|
Boolean(showCapacity) && !isMobile && displayMode === 'times';
|
|
239
242
|
const buttonClassName = useMemo(() => cn(
|
|
240
243
|
styles.calendarDayCell,
|
|
241
244
|
isMobile && (mobileNeedsTaller ? styles.calendarDayCellMobileTall : styles.calendarDayCellMobile),
|
|
245
|
+
hasMobilePaxProgress && styles.calendarDayCellMobilePax,
|
|
246
|
+
isMobile && displayMode === 'status' && styles.calendarDayCellMobileStatus,
|
|
242
247
|
!isMobile && (
|
|
243
248
|
adminCapacityTallCells
|
|
244
249
|
? needsTallerCell
|
|
@@ -256,16 +261,17 @@ const DateCell = memo(function DateCell({
|
|
|
256
261
|
? styles.calendarDayCellSelected
|
|
257
262
|
: styles.calendarDayCellAvailable,
|
|
258
263
|
isToday && !isPast && !isSelected && styles.calendarDayCellToday
|
|
259
|
-
), [isPast, isDisabled, isSelected, isToday, needsTallerCell, isMobile, mobileNeedsTaller, adminCapacityTallCells]);
|
|
264
|
+
), [isPast, isDisabled, isSelected, isToday, needsTallerCell, isMobile, mobileNeedsTaller, hasMobilePaxProgress, adminCapacityTallCells, displayMode]);
|
|
260
265
|
|
|
261
266
|
const ariaLabel = useMemo(() => {
|
|
262
|
-
const parts = [
|
|
267
|
+
const parts = [fullDateLabel];
|
|
263
268
|
if (isPast) parts.push(' - Past date');
|
|
264
269
|
if (availability?.isSoldOut) parts.push(' - Sold out');
|
|
270
|
+
else if (availability) parts.push(` - ${t('calendar.available')}`);
|
|
265
271
|
const pct = availability?.totalDiscountPercent;
|
|
266
272
|
if (pct != null && pct > 0) parts.push(` - ${pct}% off`);
|
|
267
273
|
return parts.join('');
|
|
268
|
-
}, [
|
|
274
|
+
}, [fullDateLabel, isPast, availability, t]);
|
|
269
275
|
|
|
270
276
|
return (
|
|
271
277
|
<button
|
|
@@ -273,6 +279,7 @@ const DateCell = memo(function DateCell({
|
|
|
273
279
|
disabled={isDisabled}
|
|
274
280
|
className={buttonClassName}
|
|
275
281
|
aria-label={ariaLabel}
|
|
282
|
+
aria-pressed={isSelected}
|
|
276
283
|
>
|
|
277
284
|
<div className={cn(styles.calendarDayCellInner, isMobile && styles.calendarDayCellInnerMobile)}>
|
|
278
285
|
{/* Day Number - on mobile, in a top row with discount below to avoid overlap */}
|
|
@@ -281,12 +288,10 @@ const DateCell = memo(function DateCell({
|
|
|
281
288
|
</div>
|
|
282
289
|
|
|
283
290
|
{/* Discount Tag - top-right on desktop; below day number on mobile to avoid overlap */}
|
|
284
|
-
{availability?.totalDiscountPercent && availability.totalDiscountPercent > 0 && (
|
|
291
|
+
{availability?.totalDiscountPercent && availability.totalDiscountPercent > 0 && !availability.isSoldOut && (
|
|
285
292
|
<div className={cn(styles.calendarDiscountTag, isMobile && styles.calendarDiscountTagMobile)}>
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
</svg>
|
|
289
|
-
<span>-{availability.totalDiscountPercent}%</span>
|
|
293
|
+
<span>{availability.totalDiscountPercent}%</span>
|
|
294
|
+
<span className={styles.calendarDiscountOff}>OFF</span>
|
|
290
295
|
</div>
|
|
291
296
|
)}
|
|
292
297
|
|
|
@@ -294,7 +299,14 @@ const DateCell = memo(function DateCell({
|
|
|
294
299
|
{availability && (
|
|
295
300
|
<div
|
|
296
301
|
className={cn(
|
|
297
|
-
|
|
302
|
+
styles.calendarAvailabilityIndicators,
|
|
303
|
+
!isMobile &&
|
|
304
|
+
availability.totalDiscountPercent != null &&
|
|
305
|
+
availability.totalDiscountPercent > 0 &&
|
|
306
|
+
!availability.isSoldOut &&
|
|
307
|
+
styles.calendarAvailabilityIndicatorsWithDiscount,
|
|
308
|
+
'flex flex-col items-center space-y-0 w-full',
|
|
309
|
+
isMobile && displayMode === 'status' ? 'px-0' : 'px-0.5',
|
|
298
310
|
isMobile ? 'justify-start' : 'justify-end'
|
|
299
311
|
)}
|
|
300
312
|
>
|
|
@@ -307,8 +319,15 @@ const DateCell = memo(function DateCell({
|
|
|
307
319
|
<>
|
|
308
320
|
{/* Mobile: green dot + time (compact). Desktop: full time pills */}
|
|
309
321
|
{displayMode === 'status' ? (
|
|
310
|
-
<div className={
|
|
311
|
-
|
|
322
|
+
<div className={cn(
|
|
323
|
+
styles.calendarTimePill,
|
|
324
|
+
isMobile && styles.calendarStatusPillMobile,
|
|
325
|
+
isMobile &&
|
|
326
|
+
availability.totalDiscountPercent != null &&
|
|
327
|
+
availability.totalDiscountPercent > 0 &&
|
|
328
|
+
styles.calendarStatusPillMobileWithDiscount
|
|
329
|
+
)}>
|
|
330
|
+
{isMobile ? t('calendar.open') : t('calendar.available')}
|
|
312
331
|
</div>
|
|
313
332
|
) : availability.startTimes && availability.startTimes.length > 0 ? (
|
|
314
333
|
isMobile ? (
|
|
@@ -333,13 +352,18 @@ const DateCell = memo(function DateCell({
|
|
|
333
352
|
key={time}
|
|
334
353
|
className={cn(
|
|
335
354
|
styles.calendarMobileTimeDot,
|
|
355
|
+
paxProgress && !isTimeSoldOut && !isLowAvailability && styles.calendarMobileTimeDotWithPax,
|
|
336
356
|
isTimeSoldOut && styles.calendarMobileTimeDotSoldOut,
|
|
337
357
|
isLowAvailability && !isTimeSoldOut && styles.calendarMobileTimeDotLow,
|
|
338
358
|
showMinimumReached && !isTimeSoldOut && styles.calendarMobileTimeDotMinimumReached
|
|
339
359
|
)}
|
|
340
360
|
>
|
|
341
|
-
<span className={styles.
|
|
342
|
-
|
|
361
|
+
<span className={styles.calendarMobileTimeRow}>
|
|
362
|
+
<span className={styles.calendarMobileTimeDotBullet} aria-hidden />
|
|
363
|
+
<span className={styles.calendarMobileTimeLabel}>
|
|
364
|
+
{isDailyAvailability ? t('calendar.available') : formatTime(time)}
|
|
365
|
+
</span>
|
|
366
|
+
</span>
|
|
343
367
|
{paxProgress && !isTimeSoldOut && !isLowAvailability && (
|
|
344
368
|
<span
|
|
345
369
|
className={cn(
|
|
@@ -348,7 +372,7 @@ const DateCell = memo(function DateCell({
|
|
|
348
372
|
)}
|
|
349
373
|
>
|
|
350
374
|
{showMinimumReached
|
|
351
|
-
? t('calendar.
|
|
375
|
+
? t('calendar.minimumReachedShort')
|
|
352
376
|
: t('calendar.paxProgress', {
|
|
353
377
|
booked: paxProgress.booked,
|
|
354
378
|
minimum: paxProgress.minimum,
|
|
@@ -360,8 +384,10 @@ const DateCell = memo(function DateCell({
|
|
|
360
384
|
})}
|
|
361
385
|
{availability.startTimes.length > 3 && (
|
|
362
386
|
<div className={styles.calendarMobileTimeDot}>
|
|
363
|
-
<span className={styles.
|
|
364
|
-
|
|
387
|
+
<span className={styles.calendarMobileTimeRow}>
|
|
388
|
+
<span className={styles.calendarMobileTimeDotBullet} aria-hidden />
|
|
389
|
+
<span>+{availability.startTimes.length - 3}</span>
|
|
390
|
+
</span>
|
|
365
391
|
</div>
|
|
366
392
|
)}
|
|
367
393
|
</div>
|
|
@@ -696,7 +722,7 @@ export function Calendar({
|
|
|
696
722
|
const lastAvailableDate = availableDates[availableDates.length - 1];
|
|
697
723
|
const currentEndDate = addDays(currentStartDate, WEEKS_TO_SHOW * 7 - 1);
|
|
698
724
|
return lastAvailableDate > currentEndDate;
|
|
699
|
-
}, [availableDates, currentStartDate]);
|
|
725
|
+
}, [availableDates, currentStartDate, WEEKS_TO_SHOW]);
|
|
700
726
|
|
|
701
727
|
const lastReportedRangeRef = useRef<{ start: Date; end: Date } | null>(null);
|
|
702
728
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -758,7 +784,7 @@ export function Calendar({
|
|
|
758
784
|
days.push(addDays(currentStartDate, i));
|
|
759
785
|
}
|
|
760
786
|
return days;
|
|
761
|
-
}, [currentStartDate]);
|
|
787
|
+
}, [currentStartDate, WEEKS_TO_SHOW]);
|
|
762
788
|
|
|
763
789
|
// Notify parent of visible date range changes (with buffer weeks before/after)
|
|
764
790
|
// When dropdown is open, expand range to include the visible month so we fetch availability for it
|
|
@@ -30,10 +30,11 @@
|
|
|
30
30
|
|
|
31
31
|
.btn {
|
|
32
32
|
width: 100%;
|
|
33
|
-
display:
|
|
33
|
+
display: grid;
|
|
34
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
35
|
+
grid-template-rows: auto auto;
|
|
34
36
|
align-items: center;
|
|
35
|
-
|
|
36
|
-
gap: 0.75rem;
|
|
37
|
+
column-gap: 0.75rem;
|
|
37
38
|
padding: 1rem 1.25rem;
|
|
38
39
|
border-radius: 0.5rem;
|
|
39
40
|
border: 2px solid;
|
|
@@ -85,12 +86,20 @@
|
|
|
85
86
|
display: flex;
|
|
86
87
|
align-items: center;
|
|
87
88
|
gap: 0.75rem;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
grid-column: 1;
|
|
91
|
+
grid-row: 1;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
.
|
|
94
|
+
.policyDetails {
|
|
91
95
|
display: flex;
|
|
92
96
|
flex-direction: column;
|
|
93
97
|
gap: 0.125rem;
|
|
98
|
+
grid-column: 1;
|
|
99
|
+
grid-row: 2;
|
|
100
|
+
min-width: 0;
|
|
101
|
+
margin-top: 0.125rem;
|
|
102
|
+
margin-left: 2rem;
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
.policyLabel {
|
|
@@ -110,10 +119,30 @@
|
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
.fee {
|
|
122
|
+
grid-column: 2;
|
|
123
|
+
grid-row: 1 / 3;
|
|
124
|
+
align-self: center;
|
|
113
125
|
font-size: 0.875rem;
|
|
114
126
|
white-space: nowrap;
|
|
115
127
|
}
|
|
116
128
|
|
|
129
|
+
.rowSubtitle {
|
|
130
|
+
display: block;
|
|
131
|
+
margin-top: 0.25rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@media (max-width: 639px) {
|
|
135
|
+
.policyDetails {
|
|
136
|
+
grid-column: 1 / -1;
|
|
137
|
+
margin-left: 0;
|
|
138
|
+
margin-top: 0.375rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.fee {
|
|
142
|
+
grid-row: 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
117
146
|
.feeFree {
|
|
118
147
|
color: var(--booking-stone-500, #78716c);
|
|
119
148
|
}
|
|
@@ -81,26 +81,26 @@ export function CancellationPolicySelector({
|
|
|
81
81
|
<span className={`${styles.radio} ${styles.radioSelected}`}>
|
|
82
82
|
<Check className="h-3 w-3 text-white" />
|
|
83
83
|
</span>
|
|
84
|
-
<
|
|
85
|
-
<span className={styles.policyLabel}>{forcedPolicy.label}</span>
|
|
86
|
-
<span
|
|
87
|
-
className={styles.refundTiersText}
|
|
88
|
-
dangerouslySetInnerHTML={{
|
|
89
|
-
__html:
|
|
90
|
-
formatRefundTiersSummary(forcedPolicy.refundTiers ?? [], t) ||
|
|
91
|
-
formatNoRefundsMessage(forcedPolicy.changeWindowHoursBefore, t),
|
|
92
|
-
}}
|
|
93
|
-
/>
|
|
94
|
-
{rowSubtitle && (
|
|
95
|
-
<span className={`${styles.refundTiersText} mt-1 block`}>
|
|
96
|
-
{rowSubtitle}
|
|
97
|
-
</span>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
84
|
+
<span className={styles.policyLabel}>{forcedPolicy.label}</span>
|
|
100
85
|
</div>
|
|
101
|
-
|
|
86
|
+
<span className={`${styles.fee} ${styles.feeFree}`}>
|
|
102
87
|
{t('booking.included') ?? 'Included'}
|
|
103
88
|
</span>
|
|
89
|
+
<div className={styles.policyDetails}>
|
|
90
|
+
<span
|
|
91
|
+
className={styles.refundTiersText}
|
|
92
|
+
dangerouslySetInnerHTML={{
|
|
93
|
+
__html:
|
|
94
|
+
formatRefundTiersSummary(forcedPolicy.refundTiers ?? [], t) ||
|
|
95
|
+
formatNoRefundsMessage(forcedPolicy.changeWindowHoursBefore, t),
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
{rowSubtitle && (
|
|
99
|
+
<span className={`${styles.refundTiersText} ${styles.rowSubtitle}`}>
|
|
100
|
+
{rowSubtitle}
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
104
|
</div>
|
|
105
105
|
)}
|
|
106
106
|
{!forcedPolicy && policies.map((policy) => {
|
|
@@ -121,8 +121,17 @@ export function CancellationPolicySelector({
|
|
|
121
121
|
<span className={`${styles.radio} ${isSelected ? styles.radioSelected : styles.radioDefault}`}>
|
|
122
122
|
{isSelected && <Check className="h-3 w-3 text-white" />}
|
|
123
123
|
</span>
|
|
124
|
-
<
|
|
125
|
-
|
|
124
|
+
<span className={styles.policyLabel}>{policy.label}</span>
|
|
125
|
+
</div>
|
|
126
|
+
<span className={`${styles.fee} ${isFree ? styles.feeFree : styles.feePaid}`}>
|
|
127
|
+
{suppressFees
|
|
128
|
+
? '—'
|
|
129
|
+
: isFree
|
|
130
|
+
? (t('booking.included') ?? 'Included')
|
|
131
|
+
: `+${formatCurrencyAmount(fee, currency, locale as 'en' | 'fr')}`}
|
|
132
|
+
</span>
|
|
133
|
+
{(refundSummary || rowSubtitle) && (
|
|
134
|
+
<div className={styles.policyDetails}>
|
|
126
135
|
{refundSummary ? (
|
|
127
136
|
<span
|
|
128
137
|
className={styles.refundTiersText}
|
|
@@ -130,19 +139,12 @@ export function CancellationPolicySelector({
|
|
|
130
139
|
/>
|
|
131
140
|
) : null}
|
|
132
141
|
{rowSubtitle && (
|
|
133
|
-
<span className={`${styles.refundTiersText}
|
|
142
|
+
<span className={`${styles.refundTiersText} ${styles.rowSubtitle}`}>
|
|
134
143
|
{rowSubtitle}
|
|
135
144
|
</span>
|
|
136
145
|
)}
|
|
137
146
|
</div>
|
|
138
|
-
|
|
139
|
-
<span className={`${styles.fee} ${isFree ? styles.feeFree : styles.feePaid}`}>
|
|
140
|
-
{suppressFees
|
|
141
|
-
? '—'
|
|
142
|
-
: isFree
|
|
143
|
-
? (t('booking.included') ?? 'Included')
|
|
144
|
-
: `+${formatCurrencyAmount(fee, currency, locale as 'en' | 'fr')}`}
|
|
145
|
-
</span>
|
|
147
|
+
)}
|
|
146
148
|
</button>
|
|
147
149
|
);
|
|
148
150
|
})}
|