@resira/ui 0.4.15 → 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 +110 -8
- 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 +110 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -783,6 +783,28 @@ function useCheckoutSession(token) {
|
|
|
783
783
|
}, [client, token]);
|
|
784
784
|
return { session, loading, error, errorCode };
|
|
785
785
|
}
|
|
786
|
+
function normalizeMinutes(value) {
|
|
787
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
788
|
+
if (typeof value === "string" && value.trim()) {
|
|
789
|
+
const parsed = Number(value);
|
|
790
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
791
|
+
}
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
function resolveServicePrice(service, params = {}) {
|
|
795
|
+
const partySize = Math.max(1, Number(params.partySize ?? 1));
|
|
796
|
+
const selectedMinutes = normalizeMinutes(params.durationMinutes);
|
|
797
|
+
const options = Array.isArray(service.options) ? service.options : [];
|
|
798
|
+
const matchedOption = selectedMinutes ? options.find((option) => option.durationMinutes === selectedMinutes) : void 0;
|
|
799
|
+
const fallbackOption = options.find((option) => option.durationMinutes === service.durationMinutes) ?? options[0];
|
|
800
|
+
const unitPriceCents = matchedOption?.priceCents ?? fallbackOption?.priceCents ?? service.priceCents;
|
|
801
|
+
const totalPriceCents = service.pricingModel === "per_person" ? unitPriceCents * partySize : unitPriceCents;
|
|
802
|
+
return {
|
|
803
|
+
unitPriceCents,
|
|
804
|
+
totalPriceCents,
|
|
805
|
+
matchedDurationMinutes: matchedOption?.durationMinutes ?? fallbackOption?.durationMinutes
|
|
806
|
+
};
|
|
807
|
+
}
|
|
786
808
|
function formatPriceCentsUtil(cents, currency) {
|
|
787
809
|
return new Intl.NumberFormat("default", { style: "currency", currency }).format(cents / 100);
|
|
788
810
|
}
|
|
@@ -1679,6 +1701,7 @@ function TimeSlotPicker({
|
|
|
1679
1701
|
showDuration = false,
|
|
1680
1702
|
selectedDuration,
|
|
1681
1703
|
onDurationChange,
|
|
1704
|
+
minPartySizeOverride,
|
|
1682
1705
|
maxPartySizeOverride,
|
|
1683
1706
|
durationPricing,
|
|
1684
1707
|
currency,
|
|
@@ -1686,7 +1709,7 @@ function TimeSlotPicker({
|
|
|
1686
1709
|
showRemainingSpots = false
|
|
1687
1710
|
}) {
|
|
1688
1711
|
const { locale, domainConfig, domain, classNames } = useResira();
|
|
1689
|
-
const minParty = domainConfig.minPartySize ?? 1;
|
|
1712
|
+
const minParty = minPartySizeOverride ?? domainConfig.minPartySize ?? 1;
|
|
1690
1713
|
const maxParty = maxPartySizeOverride ?? domainConfig.maxPartySize ?? 12;
|
|
1691
1714
|
const durations = react.useMemo(() => {
|
|
1692
1715
|
if (durationPricing && durationPricing.length > 0) {
|
|
@@ -3087,6 +3110,56 @@ function ConfirmationView({ reservation }) {
|
|
|
3087
3110
|
] })
|
|
3088
3111
|
] });
|
|
3089
3112
|
}
|
|
3113
|
+
|
|
3114
|
+
// src/service-rules.ts
|
|
3115
|
+
function isPositiveInt(value) {
|
|
3116
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3117
|
+
}
|
|
3118
|
+
function getRiderTierBounds(product) {
|
|
3119
|
+
const tiers = product.riderTierPricing ?? [];
|
|
3120
|
+
if (!tiers.length) return {};
|
|
3121
|
+
let min;
|
|
3122
|
+
let max;
|
|
3123
|
+
for (const tier of tiers) {
|
|
3124
|
+
const tierMin = isPositiveInt(tier.minRiders) ? tier.minRiders : isPositiveInt(tier.riders) ? tier.riders : void 0;
|
|
3125
|
+
const tierMax = isPositiveInt(tier.maxRiders) ? tier.maxRiders : isPositiveInt(tier.riders) ? tier.riders : isPositiveInt(tier.minRiders) ? tier.minRiders : void 0;
|
|
3126
|
+
if (isPositiveInt(tierMin)) {
|
|
3127
|
+
min = min == null ? tierMin : Math.min(min, tierMin);
|
|
3128
|
+
}
|
|
3129
|
+
if (isPositiveInt(tierMax)) {
|
|
3130
|
+
max = max == null ? tierMax : Math.max(max, tierMax);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return { min, max };
|
|
3134
|
+
}
|
|
3135
|
+
function getServicePartySizeBounds(product, domainConfig) {
|
|
3136
|
+
const baseMin = isPositiveInt(domainConfig?.minPartySize) ? domainConfig.minPartySize : 1;
|
|
3137
|
+
const baseMax = isPositiveInt(domainConfig?.maxPartySize) ? domainConfig.maxPartySize : 12;
|
|
3138
|
+
let min = baseMin;
|
|
3139
|
+
let max = baseMax;
|
|
3140
|
+
if (product) {
|
|
3141
|
+
if (isPositiveInt(product.maxPartySize)) {
|
|
3142
|
+
max = Math.min(max, product.maxPartySize);
|
|
3143
|
+
}
|
|
3144
|
+
if (product.pricingModel === "per_rider" && product.riderTierPricing?.length) {
|
|
3145
|
+
const tierBounds = getRiderTierBounds(product);
|
|
3146
|
+
if (isPositiveInt(tierBounds.min)) {
|
|
3147
|
+
min = Math.max(min, tierBounds.min);
|
|
3148
|
+
}
|
|
3149
|
+
if (isPositiveInt(tierBounds.max)) {
|
|
3150
|
+
max = Math.min(max, tierBounds.max);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (max < min) {
|
|
3155
|
+
return { min, max: min };
|
|
3156
|
+
}
|
|
3157
|
+
return { min, max };
|
|
3158
|
+
}
|
|
3159
|
+
function clampPartySize(value, bounds) {
|
|
3160
|
+
if (!Number.isFinite(value)) return bounds.min;
|
|
3161
|
+
return Math.min(bounds.max, Math.max(bounds.min, Math.round(value)));
|
|
3162
|
+
}
|
|
3090
3163
|
function todayStr() {
|
|
3091
3164
|
const d = /* @__PURE__ */ new Date();
|
|
3092
3165
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
@@ -3206,6 +3279,7 @@ function ResiraBookingWidget() {
|
|
|
3206
3279
|
partySize: domainConfig.defaultPartySize ?? 2,
|
|
3207
3280
|
duration: domainConfig.defaultDuration
|
|
3208
3281
|
});
|
|
3282
|
+
const [partySizeByProductId, setPartySizeByProductId] = react.useState({});
|
|
3209
3283
|
const [guest, setGuest] = react.useState({
|
|
3210
3284
|
guestName: "",
|
|
3211
3285
|
guestEmail: "",
|
|
@@ -3223,6 +3297,10 @@ function ResiraBookingWidget() {
|
|
|
3223
3297
|
}, []);
|
|
3224
3298
|
const [slotDate, setSlotDate] = react.useState(todayStr());
|
|
3225
3299
|
const activeDurationMinutes = normalizeDurationMinutes(selection.duration);
|
|
3300
|
+
const selectedProductPartyBounds = react.useMemo(
|
|
3301
|
+
() => getServicePartySizeBounds(selectedProduct, domainConfig),
|
|
3302
|
+
[selectedProduct, domainConfig]
|
|
3303
|
+
);
|
|
3226
3304
|
const availabilityParams = react.useMemo(() => {
|
|
3227
3305
|
if (isDateBased) {
|
|
3228
3306
|
if (selection.startDate && selection.endDate) {
|
|
@@ -3420,8 +3498,12 @@ function ResiraBookingWidget() {
|
|
|
3420
3498
|
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS]
|
|
3421
3499
|
);
|
|
3422
3500
|
const handlePartySizeChange = react.useCallback((size) => {
|
|
3423
|
-
|
|
3424
|
-
|
|
3501
|
+
const clamped = clampPartySize(size, selectedProductPartyBounds);
|
|
3502
|
+
setSelection((prev) => ({ ...prev, partySize: clamped }));
|
|
3503
|
+
if (selectedProduct?.id) {
|
|
3504
|
+
setPartySizeByProductId((prev) => ({ ...prev, [selectedProduct.id]: clamped }));
|
|
3505
|
+
}
|
|
3506
|
+
}, [selectedProduct?.id, selectedProductPartyBounds]);
|
|
3425
3507
|
const handleDurationChange = react.useCallback((minutes) => {
|
|
3426
3508
|
setSelection((prev) => ({
|
|
3427
3509
|
...prev,
|
|
@@ -3437,16 +3519,29 @@ function ResiraBookingWidget() {
|
|
|
3437
3519
|
setActiveResourceId(product.equipmentIds[0]);
|
|
3438
3520
|
}
|
|
3439
3521
|
setSelection((prev) => {
|
|
3440
|
-
const
|
|
3441
|
-
const
|
|
3522
|
+
const bounds = getServicePartySizeBounds(product, domainConfig);
|
|
3523
|
+
const persistedPartySize = partySizeByProductId[product.id];
|
|
3524
|
+
const nextPartySize = clampPartySize(
|
|
3525
|
+
persistedPartySize ?? prev.partySize,
|
|
3526
|
+
bounds
|
|
3527
|
+
);
|
|
3442
3528
|
const defaultDuration = product.durationPricing?.[0]?.durationMinutes ?? product.durationMinutes ?? prev.duration;
|
|
3443
3529
|
return {
|
|
3444
3530
|
...prev,
|
|
3445
3531
|
productId: product.id,
|
|
3446
3532
|
duration: defaultDuration,
|
|
3447
|
-
partySize:
|
|
3533
|
+
partySize: nextPartySize
|
|
3448
3534
|
};
|
|
3449
3535
|
});
|
|
3536
|
+
setPartySizeByProductId((prev) => {
|
|
3537
|
+
const bounds = getServicePartySizeBounds(product, domainConfig);
|
|
3538
|
+
const persistedPartySize = prev[product.id];
|
|
3539
|
+
const nextPartySize = clampPartySize(
|
|
3540
|
+
persistedPartySize ?? selection.partySize,
|
|
3541
|
+
bounds
|
|
3542
|
+
);
|
|
3543
|
+
return { ...prev, [product.id]: nextPartySize };
|
|
3544
|
+
});
|
|
3450
3545
|
if (step === "resource" && isServiceBased) {
|
|
3451
3546
|
const nextIdx = stepIndex("resource", STEPS) + 1;
|
|
3452
3547
|
if (nextIdx < STEPS.length) {
|
|
@@ -3454,7 +3549,7 @@ function ResiraBookingWidget() {
|
|
|
3454
3549
|
}
|
|
3455
3550
|
}
|
|
3456
3551
|
},
|
|
3457
|
-
[setActiveResourceId, domainConfig.
|
|
3552
|
+
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS]
|
|
3458
3553
|
);
|
|
3459
3554
|
const handleResourceSelect = react.useCallback(
|
|
3460
3555
|
(resourceId) => {
|
|
@@ -3654,6 +3749,11 @@ function ResiraBookingWidget() {
|
|
|
3654
3749
|
...deeplink.duration ? { duration: deeplink.duration } : {},
|
|
3655
3750
|
...deeplink.date ? { startDate: deeplink.date } : {}
|
|
3656
3751
|
}));
|
|
3752
|
+
if (deeplink.productId && deeplink.partySize) {
|
|
3753
|
+
const deeplinkProductId = deeplink.productId;
|
|
3754
|
+
const deeplinkPartySize = deeplink.partySize;
|
|
3755
|
+
setPartySizeByProductId((prev) => ({ ...prev, [deeplinkProductId]: deeplinkPartySize }));
|
|
3756
|
+
}
|
|
3657
3757
|
if (deeplink.date) setSlotDate(deeplink.date);
|
|
3658
3758
|
}
|
|
3659
3759
|
if (deeplinkGuest) {
|
|
@@ -3779,7 +3879,8 @@ function ResiraBookingWidget() {
|
|
|
3779
3879
|
showDuration: domain === "watersport" || domain === "service",
|
|
3780
3880
|
selectedDuration: activeDurationMinutes,
|
|
3781
3881
|
onDurationChange: handleDurationChange,
|
|
3782
|
-
|
|
3882
|
+
minPartySizeOverride: selectedProductPartyBounds.min,
|
|
3883
|
+
maxPartySizeOverride: selectedProductPartyBounds.max,
|
|
3783
3884
|
durationPricing: activeRiderDurationPricing ?? selectedProduct?.durationPricing,
|
|
3784
3885
|
currency,
|
|
3785
3886
|
showRemainingSpots
|
|
@@ -4678,6 +4779,7 @@ exports.ViewfinderIcon = ViewfinderIcon;
|
|
|
4678
4779
|
exports.WaiverConsent = WaiverConsent;
|
|
4679
4780
|
exports.XIcon = XIcon;
|
|
4680
4781
|
exports.fetchServices = fetchServices;
|
|
4782
|
+
exports.resolveServicePrice = resolveServicePrice;
|
|
4681
4783
|
exports.resolveTheme = resolveTheme;
|
|
4682
4784
|
exports.themeToCSS = themeToCSS;
|
|
4683
4785
|
exports.useAvailability = useAvailability;
|