@ticketboothapp/booking 1.2.99 → 1.2.101
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/booking/BookingDialog.tsx +29 -16
- package/src/components/booking/ChangeBookingDialog.tsx +10 -2
- package/src/components/booking/CheckoutModal.tsx +4 -1
- package/src/components/booking/NewBookingFlow.tsx +494 -134
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +208 -6
- package/src/components/booking/booking-flow-types.ts +50 -2
- package/src/components/booking/booking-flow-ui.ts +2 -0
- package/src/contexts/AvailabilitiesCacheContext.tsx +2 -2
- package/src/lib/booking/pricing.ts +1 -0
- package/src/lib/booking-api.ts +205 -2
- package/src/runtime/types.ts +2 -0
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
EARLIEST_AVAILABILITY_DATE,
|
|
29
29
|
LATEST_AVAILABILITY_DATE,
|
|
30
30
|
INITIAL_FETCH_WEEKS,
|
|
31
|
+
VISIBLE_RANGE_BUFFER_WEEKS,
|
|
31
32
|
} from '../../lib/booking-constants';
|
|
32
33
|
import { formatCurrencyAmount } from '../../lib/currency';
|
|
33
34
|
import { formatBookingRefForDisplay } from '../../lib/booking-ref';
|
|
@@ -447,11 +448,86 @@ export function PrivateShuttleBookingFlow({
|
|
|
447
448
|
availabilitiesCache,
|
|
448
449
|
]);
|
|
449
450
|
|
|
451
|
+
/** Fresh selected-day check before reserve so stale private shuttle vacancies are corrected before payment. */
|
|
452
|
+
const refreshSelectedDateDetailsForCheckout = useCallback(async (): Promise<Availability[]> => {
|
|
453
|
+
if (!selectedDate || !product.productId) return [];
|
|
454
|
+
|
|
455
|
+
const result = await getAvailabilities(product.productId, selectedDate, selectedDate, {
|
|
456
|
+
allOptions: true,
|
|
457
|
+
promoCode: activePromoCode || undefined,
|
|
458
|
+
...(pricingProfileIdForAvailabilities
|
|
459
|
+
? { pricingProfileId: pricingProfileIdForAvailabilities }
|
|
460
|
+
: {}),
|
|
461
|
+
...(cancellationPolicyProfileIdForAvailabilities
|
|
462
|
+
? { cancellationPolicyProfileId: cancellationPolicyProfileIdForAvailabilities }
|
|
463
|
+
: {}),
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (result.pricingConfig && !pricingConfigSetRef.current) {
|
|
467
|
+
setPricingConfig(result.pricingConfig);
|
|
468
|
+
pricingConfigSetRef.current = true;
|
|
469
|
+
}
|
|
470
|
+
if (result.precomputedPrices) setPrecomputedPrices(result.precomputedPrices);
|
|
471
|
+
if (result.resourcePriceByCurrency) setResourcePriceByCurrency(result.resourcePriceByCurrency);
|
|
472
|
+
if (result.resourcePriceByOption) setResourcePriceByOption(result.resourcePriceByOption);
|
|
473
|
+
|
|
474
|
+
let mergedAvailabilities: Availability[] = [];
|
|
475
|
+
setAvailabilities((prev) => {
|
|
476
|
+
const existingMap = new Map(
|
|
477
|
+
prev.map((avail) => [`${avail.dateTime}-${avail.productId || avail.productOptionId}`, avail])
|
|
478
|
+
);
|
|
479
|
+
result.availabilities.forEach((avail) => {
|
|
480
|
+
existingMap.set(`${avail.dateTime}-${avail.productId || avail.productOptionId}`, avail);
|
|
481
|
+
});
|
|
482
|
+
mergedAvailabilities = Array.from(existingMap.values());
|
|
483
|
+
return mergedAvailabilities;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const cacheKey = availabilitiesCache
|
|
487
|
+
? buildAvailabilitiesCacheKey(
|
|
488
|
+
product.productId,
|
|
489
|
+
activeOptionIdsKey,
|
|
490
|
+
activePromoCode,
|
|
491
|
+
pricingProfileIdForAvailabilities,
|
|
492
|
+
)
|
|
493
|
+
: null;
|
|
494
|
+
if (cacheKey && availabilitiesCache) {
|
|
495
|
+
const existingCache = availabilitiesCache.get(cacheKey);
|
|
496
|
+
const mergedAvailabilitiesMap = new Map(
|
|
497
|
+
(existingCache?.availabilities ?? []).map((availability) => [
|
|
498
|
+
`${availability.dateTime}-${availability.productId || availability.productOptionId}`,
|
|
499
|
+
availability,
|
|
500
|
+
])
|
|
501
|
+
);
|
|
502
|
+
result.availabilities.forEach((availability) => {
|
|
503
|
+
mergedAvailabilitiesMap.set(`${availability.dateTime}-${availability.productId || availability.productOptionId}`, availability);
|
|
504
|
+
});
|
|
505
|
+
availabilitiesCache.merge(cacheKey, {
|
|
506
|
+
fetchedRanges: existingCache?.fetchedRanges ?? fetchedRangesRef.current,
|
|
507
|
+
availabilities: Array.from(mergedAvailabilitiesMap.values()),
|
|
508
|
+
pricingConfig: result.pricingConfig ?? existingCache?.pricingConfig ?? null,
|
|
509
|
+
precomputedPrices: result.precomputedPrices ?? existingCache?.precomputedPrices ?? null,
|
|
510
|
+
resourcePriceByCurrency: result.resourcePriceByCurrency ?? existingCache?.resourcePriceByCurrency ?? null,
|
|
511
|
+
resourcePriceByOption: result.resourcePriceByOption ?? existingCache?.resourcePriceByOption ?? null,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return result.availabilities;
|
|
516
|
+
}, [
|
|
517
|
+
selectedDate,
|
|
518
|
+
product.productId,
|
|
519
|
+
activePromoCode,
|
|
520
|
+
pricingProfileIdForAvailabilities,
|
|
521
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
522
|
+
availabilitiesCache,
|
|
523
|
+
activeOptionIdsKey,
|
|
524
|
+
]);
|
|
525
|
+
|
|
450
526
|
useEffect(() => {
|
|
451
527
|
if (!visibleRange) {
|
|
452
528
|
setVisibleRange({
|
|
453
529
|
start: EARLIEST_AVAILABILITY_DATE,
|
|
454
|
-
end: addWeeks(EARLIEST_AVAILABILITY_DATE, INITIAL_FETCH_WEEKS),
|
|
530
|
+
end: addWeeks(EARLIEST_AVAILABILITY_DATE, INITIAL_FETCH_WEEKS + VISIBLE_RANGE_BUFFER_WEEKS),
|
|
455
531
|
});
|
|
456
532
|
}
|
|
457
533
|
}, [visibleRange]);
|
|
@@ -534,6 +610,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
534
610
|
)
|
|
535
611
|
: null;
|
|
536
612
|
const cached = cacheKey ? availabilitiesCache!.get(cacheKey) : undefined;
|
|
613
|
+
let shouldRevalidateCachedRange = false;
|
|
537
614
|
if (cached && cached.availabilities.length > 0) {
|
|
538
615
|
const cacheCoversRange = cached.fetchedRanges.some(
|
|
539
616
|
(r) =>
|
|
@@ -541,6 +618,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
541
618
|
);
|
|
542
619
|
const isStale = availabilitiesCache?.isStale(cached) ?? false;
|
|
543
620
|
if (cacheCoversRange) {
|
|
621
|
+
shouldRevalidateCachedRange = true;
|
|
544
622
|
setAvailabilities(cached.availabilities);
|
|
545
623
|
if (cached.pricingConfig) {
|
|
546
624
|
setPricingConfig(cached.pricingConfig);
|
|
@@ -553,8 +631,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
553
631
|
setIsFetchingMoreAvailabilities(false);
|
|
554
632
|
if (!isStale) {
|
|
555
633
|
fetchedRangesRef.current = [...cached.fetchedRanges];
|
|
556
|
-
|
|
557
|
-
return;
|
|
634
|
+
// Show cached availability instantly, then quietly revalidate the visible range.
|
|
558
635
|
}
|
|
559
636
|
}
|
|
560
637
|
// Partial cache: show cached data immediately, then fetch missing range below
|
|
@@ -570,7 +647,7 @@ export function PrivateShuttleBookingFlow({
|
|
|
570
647
|
setLoadingAvailabilities(false);
|
|
571
648
|
}
|
|
572
649
|
|
|
573
|
-
if (!needsFetch(clampedStart, clampedEnd)) {
|
|
650
|
+
if (!shouldRevalidateCachedRange && !needsFetch(clampedStart, clampedEnd)) {
|
|
574
651
|
setLoadingAvailabilities(false);
|
|
575
652
|
setIsFetchingMoreAvailabilities(false);
|
|
576
653
|
fetchingRef.current = false;
|
|
@@ -579,12 +656,13 @@ export function PrivateShuttleBookingFlow({
|
|
|
579
656
|
const hasPartialCache = cached && cached.availabilities.length > 0;
|
|
580
657
|
fetchingRef.current = true;
|
|
581
658
|
if (!hasPartialCache) setLoadingAvailabilities(true);
|
|
582
|
-
else setIsFetchingMoreAvailabilities(true);
|
|
659
|
+
else if (!shouldRevalidateCachedRange) setIsFetchingMoreAvailabilities(true);
|
|
583
660
|
try {
|
|
584
661
|
const startDate = format(startOfDay(clampedStart), 'yyyy-MM-dd');
|
|
585
662
|
const endDate = format(endOfDay(clampedEnd), 'yyyy-MM-dd');
|
|
586
663
|
const result = await getAvailabilities(product.productId, startDate, endDate, {
|
|
587
664
|
allOptions: true,
|
|
665
|
+
summary: true,
|
|
588
666
|
promoCode: activePromoCode || undefined,
|
|
589
667
|
...(pricingProfileIdForAvailabilities
|
|
590
668
|
? { pricingProfileId: pricingProfileIdForAvailabilities }
|
|
@@ -643,7 +721,11 @@ export function PrivateShuttleBookingFlow({
|
|
|
643
721
|
});
|
|
644
722
|
}
|
|
645
723
|
} catch (err) {
|
|
646
|
-
|
|
724
|
+
if (shouldRevalidateCachedRange && cached?.availabilities.length) {
|
|
725
|
+
console.warn('Background private shuttle availability refresh failed; keeping cached availability visible.', err);
|
|
726
|
+
} else {
|
|
727
|
+
setError(err instanceof Error ? err.message : 'Failed to load availabilities');
|
|
728
|
+
}
|
|
647
729
|
} finally {
|
|
648
730
|
setLoadingAvailabilities(false);
|
|
649
731
|
setIsFetchingMoreAvailabilities(false);
|
|
@@ -702,6 +784,100 @@ export function PrivateShuttleBookingFlow({
|
|
|
702
784
|
|
|
703
785
|
const dates = useMemo(() => Object.keys(availabilitiesByDate).sort(), [availabilitiesByDate]);
|
|
704
786
|
|
|
787
|
+
useEffect(() => {
|
|
788
|
+
if (!selectedDate || !product.productId) return;
|
|
789
|
+
const selectedDateAvailabilities = availabilitiesByDate[selectedDate] ?? [];
|
|
790
|
+
if (!selectedDateAvailabilities.some((availability) => availability.isSummary)) return;
|
|
791
|
+
|
|
792
|
+
let cancelled = false;
|
|
793
|
+
async function hydrateSelectedDateDetails() {
|
|
794
|
+
setIsFetchingMoreAvailabilities(true);
|
|
795
|
+
try {
|
|
796
|
+
const result = await getAvailabilities(product.productId, selectedDate, selectedDate, {
|
|
797
|
+
allOptions: true,
|
|
798
|
+
promoCode: activePromoCode || undefined,
|
|
799
|
+
...(pricingProfileIdForAvailabilities
|
|
800
|
+
? { pricingProfileId: pricingProfileIdForAvailabilities }
|
|
801
|
+
: {}),
|
|
802
|
+
...(cancellationPolicyProfileIdForAvailabilities
|
|
803
|
+
? { cancellationPolicyProfileId: cancellationPolicyProfileIdForAvailabilities }
|
|
804
|
+
: {}),
|
|
805
|
+
});
|
|
806
|
+
if (cancelled) return;
|
|
807
|
+
|
|
808
|
+
if (result.pricingConfig && !pricingConfigSetRef.current) {
|
|
809
|
+
setPricingConfig(result.pricingConfig);
|
|
810
|
+
pricingConfigSetRef.current = true;
|
|
811
|
+
}
|
|
812
|
+
if (result.precomputedPrices) setPrecomputedPrices(result.precomputedPrices);
|
|
813
|
+
if (result.resourcePriceByCurrency) setResourcePriceByCurrency(result.resourcePriceByCurrency);
|
|
814
|
+
if (result.resourcePriceByOption) setResourcePriceByOption(result.resourcePriceByOption);
|
|
815
|
+
|
|
816
|
+
setAvailabilities((prev) => {
|
|
817
|
+
const merged = new Map(
|
|
818
|
+
prev.map((availability) => [
|
|
819
|
+
`${availability.dateTime}-${availability.productId || availability.productOptionId}`,
|
|
820
|
+
availability,
|
|
821
|
+
])
|
|
822
|
+
);
|
|
823
|
+
result.availabilities.forEach((availability) => {
|
|
824
|
+
merged.set(`${availability.dateTime}-${availability.productId || availability.productOptionId}`, availability);
|
|
825
|
+
});
|
|
826
|
+
return Array.from(merged.values());
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const cacheKey = availabilitiesCache
|
|
830
|
+
? buildAvailabilitiesCacheKey(
|
|
831
|
+
product.productId,
|
|
832
|
+
activeOptionIdsKey,
|
|
833
|
+
activePromoCode,
|
|
834
|
+
pricingProfileIdForAvailabilities,
|
|
835
|
+
)
|
|
836
|
+
: null;
|
|
837
|
+
if (cacheKey && availabilitiesCache) {
|
|
838
|
+
const existingCache = availabilitiesCache.get(cacheKey);
|
|
839
|
+
const mergedAvailabilities = new Map(
|
|
840
|
+
(existingCache?.availabilities ?? []).map((availability) => [
|
|
841
|
+
`${availability.dateTime}-${availability.productId || availability.productOptionId}`,
|
|
842
|
+
availability,
|
|
843
|
+
])
|
|
844
|
+
);
|
|
845
|
+
result.availabilities.forEach((availability) => {
|
|
846
|
+
mergedAvailabilities.set(`${availability.dateTime}-${availability.productId || availability.productOptionId}`, availability);
|
|
847
|
+
});
|
|
848
|
+
availabilitiesCache.merge(cacheKey, {
|
|
849
|
+
fetchedRanges: existingCache?.fetchedRanges ?? fetchedRangesRef.current,
|
|
850
|
+
availabilities: Array.from(mergedAvailabilities.values()),
|
|
851
|
+
pricingConfig: result.pricingConfig ?? existingCache?.pricingConfig ?? null,
|
|
852
|
+
precomputedPrices: result.precomputedPrices ?? existingCache?.precomputedPrices ?? null,
|
|
853
|
+
resourcePriceByCurrency: result.resourcePriceByCurrency ?? existingCache?.resourcePriceByCurrency ?? null,
|
|
854
|
+
resourcePriceByOption: result.resourcePriceByOption ?? existingCache?.resourcePriceByOption ?? null,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
} catch (err) {
|
|
858
|
+
if (!cancelled) {
|
|
859
|
+
console.error('Error hydrating private shuttle availability details:', err);
|
|
860
|
+
}
|
|
861
|
+
} finally {
|
|
862
|
+
if (!cancelled) setIsFetchingMoreAvailabilities(false);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
hydrateSelectedDateDetails();
|
|
867
|
+
return () => {
|
|
868
|
+
cancelled = true;
|
|
869
|
+
};
|
|
870
|
+
}, [
|
|
871
|
+
selectedDate,
|
|
872
|
+
availabilitiesByDate,
|
|
873
|
+
product.productId,
|
|
874
|
+
activeOptionIdsKey,
|
|
875
|
+
activePromoCode,
|
|
876
|
+
pricingProfileIdForAvailabilities,
|
|
877
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
878
|
+
availabilitiesCache,
|
|
879
|
+
]);
|
|
880
|
+
|
|
705
881
|
const earliestAvailabilityDate = useMemo(() => {
|
|
706
882
|
if (dates.length === 0) return EARLIEST_AVAILABILITY_DATE;
|
|
707
883
|
// Build date in company timezone at noon to avoid cross-timezone day shifts.
|
|
@@ -1319,6 +1495,32 @@ export function PrivateShuttleBookingFlow({
|
|
|
1319
1495
|
const [hours, minutes] = selectedStartTime.split(':').map(Number);
|
|
1320
1496
|
const startTimeISO = `${selectedDate}T${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00-06:00`;
|
|
1321
1497
|
|
|
1498
|
+
try {
|
|
1499
|
+
const refreshed = await refreshSelectedDateDetailsForCheckout();
|
|
1500
|
+
const slot = findMergedPrivateShuttleAvailability(
|
|
1501
|
+
refreshed,
|
|
1502
|
+
selectedAvailability,
|
|
1503
|
+
selectedOption || null,
|
|
1504
|
+
selectedDate || null,
|
|
1505
|
+
companyTimezone
|
|
1506
|
+
);
|
|
1507
|
+
const passengersToReserve = isSpecialRequest
|
|
1508
|
+
? Math.min(passengerCount, maxVacancies)
|
|
1509
|
+
: passengerCount;
|
|
1510
|
+
if (slot?.vacancies != null && (slot.vacancies === 0 || slot.vacancies < passengersToReserve)) {
|
|
1511
|
+
setError(
|
|
1512
|
+
describePrivateShuttleCapacityConflictMessage({
|
|
1513
|
+
passengersRequested: passengerCount,
|
|
1514
|
+
vacancies: slot.vacancies,
|
|
1515
|
+
})
|
|
1516
|
+
);
|
|
1517
|
+
setLoading(false);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
} catch (refreshErr) {
|
|
1521
|
+
console.warn('Fresh private shuttle availability check failed before reserve; falling back to reserve validation.', refreshErr);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1322
1524
|
const specialRequestNote = isSpecialRequest
|
|
1323
1525
|
? `Special request: Customer requested ${passengerCount} passengers (${resourceCount} shuttles needed). We reserved ${billableResourceCount} at standard capacity. Team to add ${resourceCount - billableResourceCount} more shuttle(s) and verify fleet availability.`
|
|
1324
1526
|
: '';
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type { RefObject } from 'react';
|
|
2
|
-
import type {
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
Availability,
|
|
4
|
+
CheckoutDependentAddOnSelection,
|
|
5
|
+
ItineraryDisplayStep,
|
|
6
|
+
Product,
|
|
7
|
+
} from '../../lib/booking-api';
|
|
3
8
|
import type { BookingSourceMetadata } from '../../lib/booking/source-metadata';
|
|
4
9
|
import type { Currency } from './CurrencySwitcher';
|
|
10
|
+
import type { PriceSummaryLine } from './PriceSummary';
|
|
5
11
|
import type { BookingFlowUiOptions } from './booking-flow-ui';
|
|
6
12
|
import type { ProviderDashboardChangeBookingPayload } from './provider-dashboard-change-booking';
|
|
7
13
|
|
|
@@ -96,6 +102,25 @@ export interface BookingFlowBaseProps {
|
|
|
96
102
|
hideItineraryBox?: boolean;
|
|
97
103
|
/** Partner / embed-only tweaks; omit for default website behavior. */
|
|
98
104
|
flowUi?: BookingFlowUiOptions;
|
|
105
|
+
/**
|
|
106
|
+
* Optional inline extension rendered immediately below the Build Itinerary box once
|
|
107
|
+
* a date and shuttle time are selected. Used by photo-first dependent add-on flows.
|
|
108
|
+
*/
|
|
109
|
+
afterItinerary?: ReactNode | ((context: BookingFlowAfterItineraryContext) => ReactNode);
|
|
110
|
+
/**
|
|
111
|
+
* Optional hook to insert add-on rows into the Build Itinerary box. Receives the
|
|
112
|
+
* base itinerary from the selected shuttle and returns the display list to render.
|
|
113
|
+
*/
|
|
114
|
+
augmentItineraryItems?: (context: BookingFlowAfterItineraryContext) => ItineraryDisplayStep[] | null;
|
|
115
|
+
/** Optional hook for embed flows to show extra rows in the checkout receipt summary. */
|
|
116
|
+
augmentPriceSummary?: (context: BookingFlowPriceSummaryContext) => BookingFlowPriceSummaryAugmentation | null;
|
|
117
|
+
/**
|
|
118
|
+
* Optional dependent add-on to create with the primary booking after checkout.
|
|
119
|
+
* Used by photo-first flows where the add-on is selected before the shuttle booking exists.
|
|
120
|
+
*/
|
|
121
|
+
dependentAddOnSelection?: CheckoutDependentAddOnSelection | null;
|
|
122
|
+
/** Optional external validation before checkout submit (e.g. required add-on questionnaire answers). */
|
|
123
|
+
validateBeforeCheckout?: (context: BookingFlowAfterItineraryContext) => string | null;
|
|
99
124
|
/** Explicit reserve/checkout source metadata (browser URL layer + optional portal merge at call site). */
|
|
100
125
|
bookingSourceAttribution: Partial<BookingSourceMetadata>;
|
|
101
126
|
/** Dedicated partner portal app (`booking.*`): persist reserve `source` as PARTNER_PORTAL. */
|
|
@@ -108,6 +133,29 @@ export interface BookingFlowBaseProps {
|
|
|
108
133
|
changeProductOptions?: Product[];
|
|
109
134
|
}
|
|
110
135
|
|
|
136
|
+
export interface BookingFlowAfterItineraryContext {
|
|
137
|
+
selectedDate: string | null;
|
|
138
|
+
selectedAvailability: Availability | null;
|
|
139
|
+
itineraryItems: ItineraryDisplayStep[] | null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface BookingFlowPriceSummaryContext {
|
|
143
|
+
selectedDate: string | null;
|
|
144
|
+
selectedAvailability: Availability | null;
|
|
145
|
+
itineraryItems?: ItineraryDisplayStep[] | null;
|
|
146
|
+
priceSummaryLines: PriceSummaryLine[];
|
|
147
|
+
subtotal: number;
|
|
148
|
+
tax: number;
|
|
149
|
+
total: number;
|
|
150
|
+
currency: Currency;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface BookingFlowPriceSummaryAugmentation {
|
|
154
|
+
lines?: PriceSummaryLine[];
|
|
155
|
+
subtotalAdjustment?: number;
|
|
156
|
+
totalAdjustment?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
111
159
|
/** Standard (new) reservation flow — no change-booking receipt or callbacks. */
|
|
112
160
|
export interface NewBookingFlowProps extends BookingFlowBaseProps {}
|
|
113
161
|
|
|
@@ -45,6 +45,8 @@ export type ProviderDashboardChangePricingUi = {
|
|
|
45
45
|
export interface BookingFlowUiOptions {
|
|
46
46
|
showCollage?: boolean;
|
|
47
47
|
showTourDescription?: boolean;
|
|
48
|
+
/** When false, the flow still uses the selected/initial date but does not render the calendar picker. */
|
|
49
|
+
showCalendar?: boolean;
|
|
48
50
|
autoSelectFirstAvailableDate?: boolean;
|
|
49
51
|
autoSelectFirstHighlightedPickup?: boolean;
|
|
50
52
|
/**
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
import type { Availability, PricingConfig, PrecomputedPricesByCategory } from '../lib/booking-api';
|
|
11
11
|
|
|
12
|
-
/** Cache TTL in milliseconds (
|
|
13
|
-
export const AVAILABILITIES_CACHE_TTL_MS =
|
|
12
|
+
/** Cache TTL in milliseconds (1 minute). Entries older than this are considered stale. */
|
|
13
|
+
export const AVAILABILITIES_CACHE_TTL_MS = 60 * 1000;
|
|
14
14
|
|
|
15
15
|
export interface CachedAvailabilitiesData {
|
|
16
16
|
fetchedRanges: Array<{ start: Date; end: Date }>;
|
|
@@ -355,6 +355,7 @@ export interface OrderSummaryFeeLine {
|
|
|
355
355
|
name: string;
|
|
356
356
|
totalAmount: number;
|
|
357
357
|
description?: string;
|
|
358
|
+
showQuantityLabel?: boolean;
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
/** One ticket line for order summary (category, qty, price per unit, item total in display currency). */
|