@ticketboothapp/booking 1.2.189 → 1.2.190
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/AddOnsSection.module.css +124 -0
- package/src/components/booking/AddOnsSection.tsx +17 -21
- package/src/components/booking/AdminChangeBookingContent.tsx +1 -0
- package/src/components/booking/AdminChangeBookingFlow.tsx +1 -0
- package/src/components/booking/Calendar.module.css +124 -10
- 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/ChangeBookingFlow.tsx +1 -0
- package/src/components/booking/ChangeBookingItineraryPanel.tsx +3 -0
- package/src/components/booking/CollapsibleAddOnItem.module.css +1 -1
- package/src/components/booking/ItineraryBox.module.css +14 -0
- package/src/components/booking/ItineraryBox.tsx +75 -7
- package/src/components/booking/MealDrinkAddOnSelector.tsx +10 -12
- package/src/components/booking/NewBookingFlow.tsx +1 -0
- 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/hooks/useIsBookingLaunchLive.ts +4 -1
- package/src/lib/booking/i18n/messages/en.json +2 -0
- package/src/lib/booking/i18n/messages/fr.json +2 -0
- package/src/lib/booking-constants.ts +1 -1
- package/CHANGE_BOOKING_BE_HANDOFF.md +0 -160
package/package.json
CHANGED
|
@@ -39,3 +39,127 @@
|
|
|
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
|
+
.optionDescription {
|
|
83
|
+
grid-column: 1;
|
|
84
|
+
grid-row: 2;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
font-size: 0.75rem;
|
|
87
|
+
font-weight: 400;
|
|
88
|
+
line-height: 1.375;
|
|
89
|
+
color: var(--booking-stone-500, #78716c);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.choiceRow {
|
|
93
|
+
display: grid;
|
|
94
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
95
|
+
grid-template-rows: auto auto;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 0.25rem 0.75rem;
|
|
98
|
+
width: 100%;
|
|
99
|
+
padding: 0.75rem;
|
|
100
|
+
border-width: 2px;
|
|
101
|
+
border-radius: 0.5rem;
|
|
102
|
+
text-align: left;
|
|
103
|
+
transition: border-color 0.2s, background-color 0.2s;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.choiceLabel {
|
|
107
|
+
grid-column: 1;
|
|
108
|
+
grid-row: 1;
|
|
109
|
+
min-width: 0;
|
|
110
|
+
font-size: 0.875rem;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
color: var(--booking-stone-800, #292524);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.choicePrice {
|
|
116
|
+
grid-column: 2;
|
|
117
|
+
grid-row: 1 / 3;
|
|
118
|
+
align-self: center;
|
|
119
|
+
font-size: 0.875rem;
|
|
120
|
+
color: var(--booking-stone-500, #78716c);
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.choiceDescription {
|
|
125
|
+
grid-column: 1;
|
|
126
|
+
grid-row: 2;
|
|
127
|
+
min-width: 0;
|
|
128
|
+
font-size: 0.75rem;
|
|
129
|
+
font-weight: 400;
|
|
130
|
+
line-height: 1.375;
|
|
131
|
+
color: var(--booking-stone-500, #78716c);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@media (max-width: 639px) {
|
|
135
|
+
.optionRow {
|
|
136
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
137
|
+
grid-template-rows: auto auto auto;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.optionPrice {
|
|
141
|
+
grid-column: 1;
|
|
142
|
+
grid-row: 2;
|
|
143
|
+
justify-self: start;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.optionControls {
|
|
147
|
+
grid-column: 2;
|
|
148
|
+
grid-row: 1 / 3;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.optionDescription {
|
|
152
|
+
grid-column: 1 / -1;
|
|
153
|
+
grid-row: 3;
|
|
154
|
+
margin-top: 0.125rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.choiceDescription {
|
|
158
|
+
grid-column: 1 / -1;
|
|
159
|
+
margin-top: 0.125rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.choicePrice {
|
|
163
|
+
grid-row: 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -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={() => {
|
|
@@ -220,6 +213,11 @@ export function AddOnsSection({
|
|
|
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 },
|
|
@@ -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);
|
|
@@ -505,6 +506,19 @@
|
|
|
505
506
|
padding: 0.5rem 0.5rem 0.125rem;
|
|
506
507
|
}
|
|
507
508
|
|
|
509
|
+
/* Minimum-passenger tours need a second line below the departure time on mobile. */
|
|
510
|
+
.calendarDayCell.calendarDayCellMobilePax {
|
|
511
|
+
min-height: 4.75rem;
|
|
512
|
+
padding-right: 0.125rem;
|
|
513
|
+
padding-left: 0.125rem;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* Status badges need the cell width; the standard mobile gutters are sized for dot/time lists. */
|
|
517
|
+
.calendarDayCell.calendarDayCellMobileStatus {
|
|
518
|
+
padding-right: 0.125rem;
|
|
519
|
+
padding-left: 0.125rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
508
522
|
.calendarDayCell:disabled {
|
|
509
523
|
cursor: not-allowed;
|
|
510
524
|
}
|
|
@@ -586,19 +600,55 @@
|
|
|
586
600
|
right: 0.25rem;
|
|
587
601
|
display: flex;
|
|
588
602
|
align-items: center;
|
|
589
|
-
gap: 0.125rem;
|
|
590
603
|
background-color: var(--cal-discount-tag-bg);
|
|
591
604
|
color: var(--cal-discount-tag-text);
|
|
605
|
+
border: 1px solid var(--cal-discount-tag-border);
|
|
606
|
+
box-shadow: 0 1px 3px rgba(255, 77, 0, 0.18);
|
|
592
607
|
font-size: 9px;
|
|
593
|
-
font-weight:
|
|
594
|
-
|
|
608
|
+
font-weight: 800;
|
|
609
|
+
line-height: 1.1;
|
|
610
|
+
padding: 0.25rem 0.375rem;
|
|
595
611
|
border-radius: 0.25rem;
|
|
612
|
+
white-space: nowrap;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.calendarDiscountOff {
|
|
616
|
+
margin-left: 0.1875rem;
|
|
596
617
|
}
|
|
618
|
+
|
|
597
619
|
/* Mobile: position discount below day number (top-left) so it doesn't overlap */
|
|
598
620
|
.calendarDiscountTagMobile {
|
|
599
621
|
top: 1rem;
|
|
600
622
|
left: 0.25rem;
|
|
601
|
-
right:
|
|
623
|
+
right: 0.25rem;
|
|
624
|
+
flex-direction: column;
|
|
625
|
+
justify-content: center;
|
|
626
|
+
padding: 0.25rem;
|
|
627
|
+
font-size: 9px;
|
|
628
|
+
line-height: 1;
|
|
629
|
+
text-align: center;
|
|
630
|
+
white-space: normal;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.calendarDiscountTagMobile .calendarDiscountOff {
|
|
634
|
+
margin-left: 0;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@media (min-width: 640px) {
|
|
638
|
+
.calendarDiscountTag {
|
|
639
|
+
border-width: 1.5px;
|
|
640
|
+
font-size: 10px;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.calendarAvailabilityIndicators {
|
|
645
|
+
min-width: 0;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
@media (min-width: 640px) {
|
|
649
|
+
.calendarAvailabilityIndicatorsWithDiscount {
|
|
650
|
+
padding-top: 1.625rem;
|
|
651
|
+
}
|
|
602
652
|
}
|
|
603
653
|
|
|
604
654
|
.calendarSoldOutBadge {
|
|
@@ -613,6 +663,14 @@
|
|
|
613
663
|
color: var(--cal-soldout-text);
|
|
614
664
|
border: 1px solid var(--cal-soldout-border);
|
|
615
665
|
}
|
|
666
|
+
|
|
667
|
+
@media (max-width: 639px) {
|
|
668
|
+
.calendarDayCellMobile .calendarSoldOutBadge,
|
|
669
|
+
.calendarDayCellMobileTall .calendarSoldOutBadge {
|
|
670
|
+
margin-top: 1rem;
|
|
671
|
+
margin-bottom: 0.25rem;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
616
674
|
.calendarDayCellSelected .calendarSoldOutBadge {
|
|
617
675
|
background-color: var(--cal-soldout-selected-bg);
|
|
618
676
|
color: var(--cal-cell-selected-text);
|
|
@@ -636,6 +694,34 @@
|
|
|
636
694
|
color: var(--cal-cell-selected-text);
|
|
637
695
|
}
|
|
638
696
|
|
|
697
|
+
@media (max-width: 639px) {
|
|
698
|
+
.calendarStatusPillMobile {
|
|
699
|
+
max-width: 100%;
|
|
700
|
+
min-width: 0;
|
|
701
|
+
margin-top: 1rem;
|
|
702
|
+
margin-bottom: 0.25rem;
|
|
703
|
+
padding: 0.25rem 0.5rem;
|
|
704
|
+
border: 0;
|
|
705
|
+
background-color: var(--cal-time-available-bg);
|
|
706
|
+
color: var(--cal-time-available-text);
|
|
707
|
+
font-size: 9px;
|
|
708
|
+
font-weight: 500;
|
|
709
|
+
line-height: 1;
|
|
710
|
+
letter-spacing: 0;
|
|
711
|
+
white-space: nowrap;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.calendarStatusPillMobileWithDiscount {
|
|
715
|
+
margin-top: 2.25rem;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.calendarDayCellSelected .calendarStatusPillMobile {
|
|
719
|
+
border-color: rgba(255, 255, 255, 0.8);
|
|
720
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
721
|
+
color: var(--cal-cell-selected-text);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
639
725
|
.calendarTimePillLow {
|
|
640
726
|
background-color: var(--cal-time-low-bg);
|
|
641
727
|
color: var(--cal-time-low-text);
|
|
@@ -703,10 +789,12 @@
|
|
|
703
789
|
align-items: center;
|
|
704
790
|
gap: 0.0625rem;
|
|
705
791
|
width: 100%;
|
|
792
|
+
margin-top: 1rem;
|
|
793
|
+
margin-bottom: 0.25rem;
|
|
706
794
|
}
|
|
707
795
|
/* Mobile: push time slots lower when discount tag is present */
|
|
708
796
|
.calendarMobileTimeListWithDiscount {
|
|
709
|
-
margin-top:
|
|
797
|
+
margin-top: 2.25rem;
|
|
710
798
|
}
|
|
711
799
|
|
|
712
800
|
.calendarMobileTimeDot {
|
|
@@ -719,14 +807,40 @@
|
|
|
719
807
|
line-height: 1.2;
|
|
720
808
|
}
|
|
721
809
|
|
|
810
|
+
.calendarMobileTimeDotWithPax {
|
|
811
|
+
flex-direction: column;
|
|
812
|
+
align-items: center;
|
|
813
|
+
width: 100%;
|
|
814
|
+
row-gap: 0.125rem;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.calendarMobileTimeRow {
|
|
818
|
+
display: flex;
|
|
819
|
+
align-items: center;
|
|
820
|
+
justify-content: center;
|
|
821
|
+
gap: 0.25rem;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.calendarMobileTimeLabel {
|
|
825
|
+
min-width: 0;
|
|
826
|
+
white-space: nowrap;
|
|
827
|
+
}
|
|
828
|
+
|
|
722
829
|
.calendarMobilePaxProgress {
|
|
830
|
+
align-self: center;
|
|
723
831
|
color: var(--booking-stone-600, #57534e);
|
|
724
|
-
|
|
832
|
+
max-width: 100%;
|
|
833
|
+
font-size: 7px;
|
|
725
834
|
font-weight: 700;
|
|
835
|
+
line-height: 1;
|
|
836
|
+
text-align: center;
|
|
726
837
|
white-space: nowrap;
|
|
727
838
|
}
|
|
728
839
|
.calendarMobilePaxProgressReached {
|
|
729
840
|
color: #047857;
|
|
841
|
+
max-width: 2.75rem;
|
|
842
|
+
line-height: 1.05;
|
|
843
|
+
white-space: normal;
|
|
730
844
|
}
|
|
731
845
|
.calendarMobilePaxProgressReached::before {
|
|
732
846
|
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
|
}
|