@resira/ui 0.4.14 → 0.4.16
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/dist/index.cjs +132 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +132 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -519,6 +519,7 @@ interface TimeSlotPickerProps {
|
|
|
519
519
|
showDuration?: boolean;
|
|
520
520
|
selectedDuration?: number;
|
|
521
521
|
onDurationChange?: (minutes: number) => void;
|
|
522
|
+
minPartySizeOverride?: number;
|
|
522
523
|
maxPartySizeOverride?: number;
|
|
523
524
|
durationPricing?: DurationPrice[];
|
|
524
525
|
currency?: string;
|
|
@@ -527,7 +528,7 @@ interface TimeSlotPickerProps {
|
|
|
527
528
|
/** Whether to show remaining-capacity labels under available slots. */
|
|
528
529
|
showRemainingSpots?: boolean;
|
|
529
530
|
}
|
|
530
|
-
declare function TimeSlotPicker({ timeSlots, selectedDate, onDateChange, selectedSlot, onSlotSelect, partySize, onPartySizeChange, showPartySize, showDuration, selectedDuration, onDurationChange, maxPartySizeOverride, durationPricing, currency, fullyBookedDates, showRemainingSpots, }: TimeSlotPickerProps): react_jsx_runtime.JSX.Element;
|
|
531
|
+
declare function TimeSlotPicker({ timeSlots, selectedDate, onDateChange, selectedSlot, onSlotSelect, partySize, onPartySizeChange, showPartySize, showDuration, selectedDuration, onDurationChange, minPartySizeOverride, maxPartySizeOverride, durationPricing, currency, fullyBookedDates, showRemainingSpots, }: TimeSlotPickerProps): react_jsx_runtime.JSX.Element;
|
|
531
532
|
|
|
532
533
|
interface ResourcePickerProps {
|
|
533
534
|
/** List of resources to display. */
|
|
@@ -873,6 +874,25 @@ interface Service {
|
|
|
873
874
|
/** Raw product object from the API. */
|
|
874
875
|
raw: Product;
|
|
875
876
|
}
|
|
877
|
+
interface ResolveServicePriceParams {
|
|
878
|
+
/** Selected duration in minutes (number or numeric string like "180"). */
|
|
879
|
+
durationMinutes?: number | string | null;
|
|
880
|
+
/** Party size used for per_person pricing. */
|
|
881
|
+
partySize?: number;
|
|
882
|
+
}
|
|
883
|
+
interface ResolvedServicePrice {
|
|
884
|
+
/** Unit price (one session) in cents after duration selection. */
|
|
885
|
+
unitPriceCents: number;
|
|
886
|
+
/** Final total in cents, including per_person multiplier if applicable. */
|
|
887
|
+
totalPriceCents: number;
|
|
888
|
+
/** Duration actually used to resolve the option (if any). */
|
|
889
|
+
matchedDurationMinutes?: number;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Resolve cart/summary price safely from selected duration + party size.
|
|
893
|
+
* Prevents falling back to base price when duration comes as a string.
|
|
894
|
+
*/
|
|
895
|
+
declare function resolveServicePrice(service: Pick<Service, "pricingModel" | "priceCents" | "durationMinutes" | "options">, params?: ResolveServicePriceParams): ResolvedServicePrice;
|
|
876
896
|
interface UseServicesReturn {
|
|
877
897
|
/** Enriched service list. */
|
|
878
898
|
services: Service[];
|
|
@@ -969,4 +989,4 @@ declare function TagIcon({ size, className }: IconProps): react_jsx_runtime.JSX.
|
|
|
969
989
|
declare function CubeIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
|
|
970
990
|
declare function ViewfinderIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
|
|
971
991
|
|
|
972
|
-
export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, type PromoterModeConfig, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type Service, type ServiceLayout, type ServiceOption, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, type UseServicesReturn, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
|
992
|
+
export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, type PromoterModeConfig, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, type ResolveServicePriceParams, type ResolvedServicePrice, ResourcePicker, type Service, type ServiceLayout, type ServiceOption, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, type UseServicesReturn, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveServicePrice, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -519,6 +519,7 @@ interface TimeSlotPickerProps {
|
|
|
519
519
|
showDuration?: boolean;
|
|
520
520
|
selectedDuration?: number;
|
|
521
521
|
onDurationChange?: (minutes: number) => void;
|
|
522
|
+
minPartySizeOverride?: number;
|
|
522
523
|
maxPartySizeOverride?: number;
|
|
523
524
|
durationPricing?: DurationPrice[];
|
|
524
525
|
currency?: string;
|
|
@@ -527,7 +528,7 @@ interface TimeSlotPickerProps {
|
|
|
527
528
|
/** Whether to show remaining-capacity labels under available slots. */
|
|
528
529
|
showRemainingSpots?: boolean;
|
|
529
530
|
}
|
|
530
|
-
declare function TimeSlotPicker({ timeSlots, selectedDate, onDateChange, selectedSlot, onSlotSelect, partySize, onPartySizeChange, showPartySize, showDuration, selectedDuration, onDurationChange, maxPartySizeOverride, durationPricing, currency, fullyBookedDates, showRemainingSpots, }: TimeSlotPickerProps): react_jsx_runtime.JSX.Element;
|
|
531
|
+
declare function TimeSlotPicker({ timeSlots, selectedDate, onDateChange, selectedSlot, onSlotSelect, partySize, onPartySizeChange, showPartySize, showDuration, selectedDuration, onDurationChange, minPartySizeOverride, maxPartySizeOverride, durationPricing, currency, fullyBookedDates, showRemainingSpots, }: TimeSlotPickerProps): react_jsx_runtime.JSX.Element;
|
|
531
532
|
|
|
532
533
|
interface ResourcePickerProps {
|
|
533
534
|
/** List of resources to display. */
|
|
@@ -873,6 +874,25 @@ interface Service {
|
|
|
873
874
|
/** Raw product object from the API. */
|
|
874
875
|
raw: Product;
|
|
875
876
|
}
|
|
877
|
+
interface ResolveServicePriceParams {
|
|
878
|
+
/** Selected duration in minutes (number or numeric string like "180"). */
|
|
879
|
+
durationMinutes?: number | string | null;
|
|
880
|
+
/** Party size used for per_person pricing. */
|
|
881
|
+
partySize?: number;
|
|
882
|
+
}
|
|
883
|
+
interface ResolvedServicePrice {
|
|
884
|
+
/** Unit price (one session) in cents after duration selection. */
|
|
885
|
+
unitPriceCents: number;
|
|
886
|
+
/** Final total in cents, including per_person multiplier if applicable. */
|
|
887
|
+
totalPriceCents: number;
|
|
888
|
+
/** Duration actually used to resolve the option (if any). */
|
|
889
|
+
matchedDurationMinutes?: number;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Resolve cart/summary price safely from selected duration + party size.
|
|
893
|
+
* Prevents falling back to base price when duration comes as a string.
|
|
894
|
+
*/
|
|
895
|
+
declare function resolveServicePrice(service: Pick<Service, "pricingModel" | "priceCents" | "durationMinutes" | "options">, params?: ResolveServicePriceParams): ResolvedServicePrice;
|
|
876
896
|
interface UseServicesReturn {
|
|
877
897
|
/** Enriched service list. */
|
|
878
898
|
services: Service[];
|
|
@@ -969,4 +989,4 @@ declare function TagIcon({ size, className }: IconProps): react_jsx_runtime.JSX.
|
|
|
969
989
|
declare function CubeIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
|
|
970
990
|
declare function ViewfinderIcon({ size, className }: IconProps): react_jsx_runtime.JSX.Element;
|
|
971
991
|
|
|
972
|
-
export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, type PromoterModeConfig, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, ResourcePicker, type Service, type ServiceLayout, type ServiceOption, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, type UseServicesReturn, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
|
992
|
+
export { AlertCircleIcon, BookingCalendar, BookingModal, type BookingSelection, type BookingStep, CalendarIcon, CheckCircleIcon, CheckIcon, type CheckoutSessionErrorCode, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, type DeeplinkGuest, type DeeplinkSelection, DishShowcase, type DishShowcaseProps, type DomainConfig, GuestForm, type GuestFormErrors, type GuestFormValues, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, type PromoterModeConfig, ResiraBookingWidget, type ResiraClassNames, type ResiraContextValue, type ResiraDomain, type ResiraLocale, ResiraProvider, type ResiraProviderConfig, type ResiraProviderProps, type ResiraTheme, type ResolveServicePriceParams, type ResolvedServicePrice, ResourcePicker, type Service, type ServiceLayout, type ServiceOption, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, type UseServicesReturn, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveServicePrice, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
package/dist/index.js
CHANGED
|
@@ -781,6 +781,28 @@ function useCheckoutSession(token) {
|
|
|
781
781
|
}, [client, token]);
|
|
782
782
|
return { session, loading, error, errorCode };
|
|
783
783
|
}
|
|
784
|
+
function normalizeMinutes(value) {
|
|
785
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
786
|
+
if (typeof value === "string" && value.trim()) {
|
|
787
|
+
const parsed = Number(value);
|
|
788
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
789
|
+
}
|
|
790
|
+
return void 0;
|
|
791
|
+
}
|
|
792
|
+
function resolveServicePrice(service, params = {}) {
|
|
793
|
+
const partySize = Math.max(1, Number(params.partySize ?? 1));
|
|
794
|
+
const selectedMinutes = normalizeMinutes(params.durationMinutes);
|
|
795
|
+
const options = Array.isArray(service.options) ? service.options : [];
|
|
796
|
+
const matchedOption = selectedMinutes ? options.find((option) => option.durationMinutes === selectedMinutes) : void 0;
|
|
797
|
+
const fallbackOption = options.find((option) => option.durationMinutes === service.durationMinutes) ?? options[0];
|
|
798
|
+
const unitPriceCents = matchedOption?.priceCents ?? fallbackOption?.priceCents ?? service.priceCents;
|
|
799
|
+
const totalPriceCents = service.pricingModel === "per_person" ? unitPriceCents * partySize : unitPriceCents;
|
|
800
|
+
return {
|
|
801
|
+
unitPriceCents,
|
|
802
|
+
totalPriceCents,
|
|
803
|
+
matchedDurationMinutes: matchedOption?.durationMinutes ?? fallbackOption?.durationMinutes
|
|
804
|
+
};
|
|
805
|
+
}
|
|
784
806
|
function formatPriceCentsUtil(cents, currency) {
|
|
785
807
|
return new Intl.NumberFormat("default", { style: "currency", currency }).format(cents / 100);
|
|
786
808
|
}
|
|
@@ -1677,6 +1699,7 @@ function TimeSlotPicker({
|
|
|
1677
1699
|
showDuration = false,
|
|
1678
1700
|
selectedDuration,
|
|
1679
1701
|
onDurationChange,
|
|
1702
|
+
minPartySizeOverride,
|
|
1680
1703
|
maxPartySizeOverride,
|
|
1681
1704
|
durationPricing,
|
|
1682
1705
|
currency,
|
|
@@ -1684,7 +1707,7 @@ function TimeSlotPicker({
|
|
|
1684
1707
|
showRemainingSpots = false
|
|
1685
1708
|
}) {
|
|
1686
1709
|
const { locale, domainConfig, domain, classNames } = useResira();
|
|
1687
|
-
const minParty = domainConfig.minPartySize ?? 1;
|
|
1710
|
+
const minParty = minPartySizeOverride ?? domainConfig.minPartySize ?? 1;
|
|
1688
1711
|
const maxParty = maxPartySizeOverride ?? domainConfig.maxPartySize ?? 12;
|
|
1689
1712
|
const durations = useMemo(() => {
|
|
1690
1713
|
if (durationPricing && durationPricing.length > 0) {
|
|
@@ -3085,6 +3108,56 @@ function ConfirmationView({ reservation }) {
|
|
|
3085
3108
|
] })
|
|
3086
3109
|
] });
|
|
3087
3110
|
}
|
|
3111
|
+
|
|
3112
|
+
// src/service-rules.ts
|
|
3113
|
+
function isPositiveInt(value) {
|
|
3114
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3115
|
+
}
|
|
3116
|
+
function getRiderTierBounds(product) {
|
|
3117
|
+
const tiers = product.riderTierPricing ?? [];
|
|
3118
|
+
if (!tiers.length) return {};
|
|
3119
|
+
let min;
|
|
3120
|
+
let max;
|
|
3121
|
+
for (const tier of tiers) {
|
|
3122
|
+
const tierMin = isPositiveInt(tier.minRiders) ? tier.minRiders : isPositiveInt(tier.riders) ? tier.riders : void 0;
|
|
3123
|
+
const tierMax = isPositiveInt(tier.maxRiders) ? tier.maxRiders : isPositiveInt(tier.riders) ? tier.riders : isPositiveInt(tier.minRiders) ? tier.minRiders : void 0;
|
|
3124
|
+
if (isPositiveInt(tierMin)) {
|
|
3125
|
+
min = min == null ? tierMin : Math.min(min, tierMin);
|
|
3126
|
+
}
|
|
3127
|
+
if (isPositiveInt(tierMax)) {
|
|
3128
|
+
max = max == null ? tierMax : Math.max(max, tierMax);
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
return { min, max };
|
|
3132
|
+
}
|
|
3133
|
+
function getServicePartySizeBounds(product, domainConfig) {
|
|
3134
|
+
const baseMin = isPositiveInt(domainConfig?.minPartySize) ? domainConfig.minPartySize : 1;
|
|
3135
|
+
const baseMax = isPositiveInt(domainConfig?.maxPartySize) ? domainConfig.maxPartySize : 12;
|
|
3136
|
+
let min = baseMin;
|
|
3137
|
+
let max = baseMax;
|
|
3138
|
+
if (product) {
|
|
3139
|
+
if (isPositiveInt(product.maxPartySize)) {
|
|
3140
|
+
max = Math.min(max, product.maxPartySize);
|
|
3141
|
+
}
|
|
3142
|
+
if (product.pricingModel === "per_rider" && product.riderTierPricing?.length) {
|
|
3143
|
+
const tierBounds = getRiderTierBounds(product);
|
|
3144
|
+
if (isPositiveInt(tierBounds.min)) {
|
|
3145
|
+
min = Math.max(min, tierBounds.min);
|
|
3146
|
+
}
|
|
3147
|
+
if (isPositiveInt(tierBounds.max)) {
|
|
3148
|
+
max = Math.min(max, tierBounds.max);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (max < min) {
|
|
3153
|
+
return { min, max: min };
|
|
3154
|
+
}
|
|
3155
|
+
return { min, max };
|
|
3156
|
+
}
|
|
3157
|
+
function clampPartySize(value, bounds) {
|
|
3158
|
+
if (!Number.isFinite(value)) return bounds.min;
|
|
3159
|
+
return Math.min(bounds.max, Math.max(bounds.min, Math.round(value)));
|
|
3160
|
+
}
|
|
3088
3161
|
function todayStr() {
|
|
3089
3162
|
const d = /* @__PURE__ */ new Date();
|
|
3090
3163
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
@@ -3095,6 +3168,14 @@ function formatPrice5(cents, currency) {
|
|
|
3095
3168
|
currency
|
|
3096
3169
|
}).format(cents / 100);
|
|
3097
3170
|
}
|
|
3171
|
+
function normalizeDurationMinutes(value) {
|
|
3172
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
3173
|
+
if (typeof value === "string" && value.trim()) {
|
|
3174
|
+
const parsed = Number(value);
|
|
3175
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
3176
|
+
}
|
|
3177
|
+
return void 0;
|
|
3178
|
+
}
|
|
3098
3179
|
function buildSteps(domain, hasPayment, catalogMode) {
|
|
3099
3180
|
const steps = [];
|
|
3100
3181
|
if (domain === "watersport" || domain === "service" || catalogMode && domain !== "restaurant") {
|
|
@@ -3196,6 +3277,7 @@ function ResiraBookingWidget() {
|
|
|
3196
3277
|
partySize: domainConfig.defaultPartySize ?? 2,
|
|
3197
3278
|
duration: domainConfig.defaultDuration
|
|
3198
3279
|
});
|
|
3280
|
+
const [partySizeByProductId, setPartySizeByProductId] = useState({});
|
|
3199
3281
|
const [guest, setGuest] = useState({
|
|
3200
3282
|
guestName: "",
|
|
3201
3283
|
guestEmail: "",
|
|
@@ -3212,6 +3294,11 @@ function ResiraBookingWidget() {
|
|
|
3212
3294
|
setPromoValidation(result);
|
|
3213
3295
|
}, []);
|
|
3214
3296
|
const [slotDate, setSlotDate] = useState(todayStr());
|
|
3297
|
+
const activeDurationMinutes = normalizeDurationMinutes(selection.duration);
|
|
3298
|
+
const selectedProductPartyBounds = useMemo(
|
|
3299
|
+
() => getServicePartySizeBounds(selectedProduct, domainConfig),
|
|
3300
|
+
[selectedProduct, domainConfig]
|
|
3301
|
+
);
|
|
3215
3302
|
const availabilityParams = useMemo(() => {
|
|
3216
3303
|
if (isDateBased) {
|
|
3217
3304
|
if (selection.startDate && selection.endDate) {
|
|
@@ -3222,9 +3309,9 @@ function ResiraBookingWidget() {
|
|
|
3222
3309
|
return {
|
|
3223
3310
|
date: slotDate,
|
|
3224
3311
|
partySize: selection.partySize,
|
|
3225
|
-
durationMinutes:
|
|
3312
|
+
durationMinutes: activeDurationMinutes
|
|
3226
3313
|
};
|
|
3227
|
-
}, [isDateBased, selection.startDate, selection.endDate, slotDate, selection.partySize,
|
|
3314
|
+
}, [isDateBased, selection.startDate, selection.endDate, slotDate, selection.partySize, activeDurationMinutes]);
|
|
3228
3315
|
const availabilityProductId = isServiceBased ? selectedProduct?.id : void 0;
|
|
3229
3316
|
const { data: availability, loading, error, refetch } = useAvailability(
|
|
3230
3317
|
availabilityParams,
|
|
@@ -3268,7 +3355,7 @@ function ResiraBookingWidget() {
|
|
|
3268
3355
|
productId: selectedProduct?.id ?? "",
|
|
3269
3356
|
resourceId,
|
|
3270
3357
|
partySize: selection.partySize,
|
|
3271
|
-
durationMinutes:
|
|
3358
|
+
durationMinutes: activeDurationMinutes,
|
|
3272
3359
|
startDate: selection.startDate,
|
|
3273
3360
|
startTime: selection.startTime,
|
|
3274
3361
|
endTime: selection.endTime,
|
|
@@ -3280,7 +3367,7 @@ function ResiraBookingWidget() {
|
|
|
3280
3367
|
termsAccepted: termsAccepted || void 0,
|
|
3281
3368
|
waiverAccepted: waiverAccepted || void 0
|
|
3282
3369
|
};
|
|
3283
|
-
}, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted, waiverAccepted, isCheckoutMode, checkoutSession, checkoutSessionToken]);
|
|
3370
|
+
}, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted, waiverAccepted, isCheckoutMode, checkoutSession, checkoutSessionToken, activeDurationMinutes]);
|
|
3284
3371
|
const blockedDates = useMemo(() => {
|
|
3285
3372
|
const dates = calendarData?.dates?.blockedDates ?? availability?.dates?.blockedDates ?? [];
|
|
3286
3373
|
return new Set(dates);
|
|
@@ -3300,11 +3387,11 @@ function ResiraBookingWidget() {
|
|
|
3300
3387
|
if (selectedProduct) {
|
|
3301
3388
|
let base = selectedProduct.priceCents;
|
|
3302
3389
|
if (selectedProduct.pricingModel === "per_rider" && activeRiderDurationPricing?.length) {
|
|
3303
|
-
const match =
|
|
3390
|
+
const match = activeDurationMinutes ? activeRiderDurationPricing.find((dp) => dp.durationMinutes === activeDurationMinutes) : activeRiderDurationPricing[0];
|
|
3304
3391
|
if (match) base = match.priceCents;
|
|
3305
|
-
} else if (selectedProduct.durationPricing?.length &&
|
|
3392
|
+
} else if (selectedProduct.durationPricing?.length && activeDurationMinutes) {
|
|
3306
3393
|
const match = selectedProduct.durationPricing.find(
|
|
3307
|
-
(dp) => dp.durationMinutes ===
|
|
3394
|
+
(dp) => dp.durationMinutes === activeDurationMinutes
|
|
3308
3395
|
);
|
|
3309
3396
|
if (match) base = match.priceCents;
|
|
3310
3397
|
}
|
|
@@ -3322,7 +3409,7 @@ function ResiraBookingWidget() {
|
|
|
3322
3409
|
return { total, amountNow, amountAtVenue };
|
|
3323
3410
|
}
|
|
3324
3411
|
return null;
|
|
3325
|
-
}, [selectedProduct, availability, selection.partySize,
|
|
3412
|
+
}, [selectedProduct, availability, selection.partySize, activeDurationMinutes, depositPercent, activeRiderDurationPricing]);
|
|
3326
3413
|
const stepTitle = useMemo(() => {
|
|
3327
3414
|
switch (step) {
|
|
3328
3415
|
case "resource":
|
|
@@ -3409,8 +3496,12 @@ function ResiraBookingWidget() {
|
|
|
3409
3496
|
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS]
|
|
3410
3497
|
);
|
|
3411
3498
|
const handlePartySizeChange = useCallback((size) => {
|
|
3412
|
-
|
|
3413
|
-
|
|
3499
|
+
const clamped = clampPartySize(size, selectedProductPartyBounds);
|
|
3500
|
+
setSelection((prev) => ({ ...prev, partySize: clamped }));
|
|
3501
|
+
if (selectedProduct?.id) {
|
|
3502
|
+
setPartySizeByProductId((prev) => ({ ...prev, [selectedProduct.id]: clamped }));
|
|
3503
|
+
}
|
|
3504
|
+
}, [selectedProduct?.id, selectedProductPartyBounds]);
|
|
3414
3505
|
const handleDurationChange = useCallback((minutes) => {
|
|
3415
3506
|
setSelection((prev) => ({
|
|
3416
3507
|
...prev,
|
|
@@ -3426,16 +3517,29 @@ function ResiraBookingWidget() {
|
|
|
3426
3517
|
setActiveResourceId(product.equipmentIds[0]);
|
|
3427
3518
|
}
|
|
3428
3519
|
setSelection((prev) => {
|
|
3429
|
-
const
|
|
3430
|
-
const
|
|
3520
|
+
const bounds = getServicePartySizeBounds(product, domainConfig);
|
|
3521
|
+
const persistedPartySize = partySizeByProductId[product.id];
|
|
3522
|
+
const nextPartySize = clampPartySize(
|
|
3523
|
+
persistedPartySize ?? prev.partySize,
|
|
3524
|
+
bounds
|
|
3525
|
+
);
|
|
3431
3526
|
const defaultDuration = product.durationPricing?.[0]?.durationMinutes ?? product.durationMinutes ?? prev.duration;
|
|
3432
3527
|
return {
|
|
3433
3528
|
...prev,
|
|
3434
3529
|
productId: product.id,
|
|
3435
3530
|
duration: defaultDuration,
|
|
3436
|
-
partySize:
|
|
3531
|
+
partySize: nextPartySize
|
|
3437
3532
|
};
|
|
3438
3533
|
});
|
|
3534
|
+
setPartySizeByProductId((prev) => {
|
|
3535
|
+
const bounds = getServicePartySizeBounds(product, domainConfig);
|
|
3536
|
+
const persistedPartySize = prev[product.id];
|
|
3537
|
+
const nextPartySize = clampPartySize(
|
|
3538
|
+
persistedPartySize ?? selection.partySize,
|
|
3539
|
+
bounds
|
|
3540
|
+
);
|
|
3541
|
+
return { ...prev, [product.id]: nextPartySize };
|
|
3542
|
+
});
|
|
3439
3543
|
if (step === "resource" && isServiceBased) {
|
|
3440
3544
|
const nextIdx = stepIndex("resource", STEPS) + 1;
|
|
3441
3545
|
if (nextIdx < STEPS.length) {
|
|
@@ -3443,7 +3547,7 @@ function ResiraBookingWidget() {
|
|
|
3443
3547
|
}
|
|
3444
3548
|
}
|
|
3445
3549
|
},
|
|
3446
|
-
[setActiveResourceId, domainConfig.
|
|
3550
|
+
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS]
|
|
3447
3551
|
);
|
|
3448
3552
|
const handleResourceSelect = useCallback(
|
|
3449
3553
|
(resourceId) => {
|
|
@@ -3643,6 +3747,11 @@ function ResiraBookingWidget() {
|
|
|
3643
3747
|
...deeplink.duration ? { duration: deeplink.duration } : {},
|
|
3644
3748
|
...deeplink.date ? { startDate: deeplink.date } : {}
|
|
3645
3749
|
}));
|
|
3750
|
+
if (deeplink.productId && deeplink.partySize) {
|
|
3751
|
+
const deeplinkProductId = deeplink.productId;
|
|
3752
|
+
const deeplinkPartySize = deeplink.partySize;
|
|
3753
|
+
setPartySizeByProductId((prev) => ({ ...prev, [deeplinkProductId]: deeplinkPartySize }));
|
|
3754
|
+
}
|
|
3646
3755
|
if (deeplink.date) setSlotDate(deeplink.date);
|
|
3647
3756
|
}
|
|
3648
3757
|
if (deeplinkGuest) {
|
|
@@ -3766,9 +3875,10 @@ function ResiraBookingWidget() {
|
|
|
3766
3875
|
onPartySizeChange: handlePartySizeChange,
|
|
3767
3876
|
showPartySize: true,
|
|
3768
3877
|
showDuration: domain === "watersport" || domain === "service",
|
|
3769
|
-
selectedDuration:
|
|
3878
|
+
selectedDuration: activeDurationMinutes,
|
|
3770
3879
|
onDurationChange: handleDurationChange,
|
|
3771
|
-
|
|
3880
|
+
minPartySizeOverride: selectedProductPartyBounds.min,
|
|
3881
|
+
maxPartySizeOverride: selectedProductPartyBounds.max,
|
|
3772
3882
|
durationPricing: activeRiderDurationPricing ?? selectedProduct?.durationPricing,
|
|
3773
3883
|
currency,
|
|
3774
3884
|
showRemainingSpots
|
|
@@ -3778,19 +3888,19 @@ function ResiraBookingWidget() {
|
|
|
3778
3888
|
/* @__PURE__ */ jsxs("div", { className: "resira-price-preview-row", children: [
|
|
3779
3889
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
3780
3890
|
selectedProduct.name,
|
|
3781
|
-
|
|
3891
|
+
activeDurationMinutes && selectedProduct.durationPricing && selectedProduct.durationPricing.length > 1 && /* @__PURE__ */ jsxs("span", { className: "resira-price-preview-label", children: [
|
|
3782
3892
|
" ",
|
|
3783
3893
|
"(",
|
|
3784
|
-
|
|
3894
|
+
activeDurationMinutes < 60 ? `${activeDurationMinutes} min` : `${Math.floor(activeDurationMinutes / 60)}h${activeDurationMinutes % 60 ? activeDurationMinutes % 60 : ""}`,
|
|
3785
3895
|
")"
|
|
3786
3896
|
] })
|
|
3787
3897
|
] }),
|
|
3788
3898
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
3789
3899
|
(() => {
|
|
3790
3900
|
let unitPrice = selectedProduct.priceCents;
|
|
3791
|
-
if (selectedProduct.durationPricing?.length &&
|
|
3901
|
+
if (selectedProduct.durationPricing?.length && activeDurationMinutes) {
|
|
3792
3902
|
const match = selectedProduct.durationPricing.find(
|
|
3793
|
-
(dp) => dp.durationMinutes ===
|
|
3903
|
+
(dp) => dp.durationMinutes === activeDurationMinutes
|
|
3794
3904
|
);
|
|
3795
3905
|
if (match) unitPrice = match.priceCents;
|
|
3796
3906
|
}
|
|
@@ -4630,6 +4740,6 @@ function DishShowcase({
|
|
|
4630
4740
|
] });
|
|
4631
4741
|
}
|
|
4632
4742
|
|
|
4633
|
-
export { AlertCircleIcon, BookingCalendar, BookingModal, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, DishShowcase, GuestForm, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, ResiraProvider, ResourcePicker, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
|
4743
|
+
export { AlertCircleIcon, BookingCalendar, BookingModal, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, ClockIcon, ConfirmationView, CreditCardIcon, CubeIcon, DEFAULT_LOCALE, DEFAULT_THEME, DishShowcase, GuestForm, LockIcon, MailIcon, MinusIcon, NoteIcon, PaymentForm, PhoneIcon, PlusIcon, ProductSelector, ResiraBookingWidget, ResiraProvider, ResourcePicker, ShieldIcon, SummaryPreview, TagIcon, TimeSlotPicker, UserIcon, UsersIcon, ViewfinderIcon, WaiverConsent, XIcon, fetchServices, resolveServicePrice, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, validateGuestForm };
|
|
4634
4744
|
//# sourceMappingURL=index.js.map
|
|
4635
4745
|
//# sourceMappingURL=index.js.map
|