@resira/ui 0.4.16 → 0.4.18
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 +129 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -29
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -304,6 +304,45 @@ function writeCache(cache, key, value, ttlMs) {
|
|
|
304
304
|
expiresAt: Date.now() + Math.max(1e3, ttlMs)
|
|
305
305
|
});
|
|
306
306
|
}
|
|
307
|
+
function normalizePositiveInt(value) {
|
|
308
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
309
|
+
const rounded = Math.round(value);
|
|
310
|
+
return rounded > 0 ? rounded : null;
|
|
311
|
+
}
|
|
312
|
+
function toIsoOrNull(value) {
|
|
313
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
314
|
+
const date = new Date(value);
|
|
315
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
316
|
+
return date.toISOString();
|
|
317
|
+
}
|
|
318
|
+
function withCanonicalPaymentWindow(payload) {
|
|
319
|
+
const next = { ...payload };
|
|
320
|
+
const durationMinutes = normalizePositiveInt(next.durationMinutes);
|
|
321
|
+
if (next.durationMinutes != null && !durationMinutes) {
|
|
322
|
+
throw new Error("Invalid duration selected.");
|
|
323
|
+
}
|
|
324
|
+
if (durationMinutes) {
|
|
325
|
+
next.durationMinutes = durationMinutes;
|
|
326
|
+
next.durationHours = Number((durationMinutes / 60).toFixed(6));
|
|
327
|
+
}
|
|
328
|
+
const startIso = toIsoOrNull(next.startTime);
|
|
329
|
+
const endIso = toIsoOrNull(next.endTime);
|
|
330
|
+
if (startIso) next.startTime = startIso;
|
|
331
|
+
if (durationMinutes && startIso) {
|
|
332
|
+
const computedEnd = new Date(
|
|
333
|
+
new Date(startIso).getTime() + durationMinutes * 6e4
|
|
334
|
+
).toISOString();
|
|
335
|
+
if (!endIso) {
|
|
336
|
+
next.endTime = computedEnd;
|
|
337
|
+
} else {
|
|
338
|
+
const matchesDuration = new Date(endIso).getTime() - new Date(startIso).getTime() === durationMinutes * 6e4;
|
|
339
|
+
next.endTime = matchesDuration ? endIso : computedEnd;
|
|
340
|
+
}
|
|
341
|
+
} else if (endIso) {
|
|
342
|
+
next.endTime = endIso;
|
|
343
|
+
}
|
|
344
|
+
return next;
|
|
345
|
+
}
|
|
307
346
|
async function getDishCompat(client, dishId) {
|
|
308
347
|
const maybeClient = client;
|
|
309
348
|
if (typeof maybeClient.getDish === "function") {
|
|
@@ -616,7 +655,8 @@ function usePaymentIntent() {
|
|
|
616
655
|
setCreating(true);
|
|
617
656
|
setError(null);
|
|
618
657
|
try {
|
|
619
|
-
const
|
|
658
|
+
const payload = withCanonicalPaymentWindow(data);
|
|
659
|
+
const response = await client.createPaymentIntent(payload);
|
|
620
660
|
setPaymentIntent(response);
|
|
621
661
|
return response;
|
|
622
662
|
} catch (err) {
|
|
@@ -2955,6 +2995,15 @@ function formatTime(isoStr) {
|
|
|
2955
2995
|
return isoStr;
|
|
2956
2996
|
}
|
|
2957
2997
|
}
|
|
2998
|
+
function renderTimeRange(start, end) {
|
|
2999
|
+
if (start && end) {
|
|
3000
|
+
return `${formatTime(start)} \u2013 ${formatTime(end)}`;
|
|
3001
|
+
}
|
|
3002
|
+
if (start || end) {
|
|
3003
|
+
return "Time unavailable";
|
|
3004
|
+
}
|
|
3005
|
+
return "Time unavailable";
|
|
3006
|
+
}
|
|
2958
3007
|
function formatPrice4(cents, currency) {
|
|
2959
3008
|
return new Intl.NumberFormat("default", {
|
|
2960
3009
|
style: "currency",
|
|
@@ -3008,14 +3057,11 @@ function SummaryPreview({
|
|
|
3008
3057
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-value", children: formatDate(selection.startDate) })
|
|
3009
3058
|
] })
|
|
3010
3059
|
] }),
|
|
3011
|
-
!isDateBased && selection.startTime && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
3060
|
+
!isDateBased && (selection.startTime || selection.endTime) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
3012
3061
|
/* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size: 14 }),
|
|
3013
3062
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item-content", children: [
|
|
3014
3063
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-label", children: "Time" }),
|
|
3015
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3016
|
-
formatTime(selection.startTime),
|
|
3017
|
-
selection.endTime && ` \u2013 ${formatTime(selection.endTime)}`
|
|
3018
|
-
] })
|
|
3064
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-value", children: renderTimeRange(selection.startTime, selection.endTime) })
|
|
3019
3065
|
] })
|
|
3020
3066
|
] }),
|
|
3021
3067
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
@@ -3085,14 +3131,11 @@ function ConfirmationView({ reservation }) {
|
|
|
3085
3131
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-value", children: formatDate(reservation.endDate) })
|
|
3086
3132
|
] })
|
|
3087
3133
|
] }),
|
|
3088
|
-
reservation.startTime && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
3134
|
+
(reservation.startTime || reservation.endTime) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
3089
3135
|
/* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size: 14 }),
|
|
3090
3136
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item-content", children: [
|
|
3091
3137
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-label", children: "Time" }),
|
|
3092
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3093
|
-
formatTime(reservation.startTime),
|
|
3094
|
-
reservation.endTime && ` \u2013 ${formatTime(reservation.endTime)}`
|
|
3095
|
-
] })
|
|
3138
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "resira-summary-item-value", children: renderTimeRange(reservation.startTime, reservation.endTime) })
|
|
3096
3139
|
] })
|
|
3097
3140
|
] }),
|
|
3098
3141
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-summary-item", children: [
|
|
@@ -3280,6 +3323,9 @@ function ResiraBookingWidget() {
|
|
|
3280
3323
|
duration: domainConfig.defaultDuration
|
|
3281
3324
|
});
|
|
3282
3325
|
const [partySizeByProductId, setPartySizeByProductId] = react.useState({});
|
|
3326
|
+
const slotByProductRef = react.useRef({});
|
|
3327
|
+
const selectionRef = react.useRef(selection);
|
|
3328
|
+
selectionRef.current = selection;
|
|
3283
3329
|
const [guest, setGuest] = react.useState({
|
|
3284
3330
|
guestName: "",
|
|
3285
3331
|
guestEmail: "",
|
|
@@ -3296,6 +3342,25 @@ function ResiraBookingWidget() {
|
|
|
3296
3342
|
setPromoValidation(result);
|
|
3297
3343
|
}, []);
|
|
3298
3344
|
const [slotDate, setSlotDate] = react.useState(todayStr());
|
|
3345
|
+
const handleSlotDateChange = react.useCallback(
|
|
3346
|
+
(date) => {
|
|
3347
|
+
setSlotDate(date);
|
|
3348
|
+
setSelection((s) => {
|
|
3349
|
+
const next = { ...s, startDate: date, endDate: date, startTime: void 0, endTime: void 0 };
|
|
3350
|
+
if (selectedProduct?.id) {
|
|
3351
|
+
slotByProductRef.current[selectedProduct.id] = {
|
|
3352
|
+
...slotByProductRef.current[selectedProduct.id],
|
|
3353
|
+
startDate: date,
|
|
3354
|
+
endDate: date,
|
|
3355
|
+
startTime: void 0,
|
|
3356
|
+
endTime: void 0
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
return next;
|
|
3360
|
+
});
|
|
3361
|
+
},
|
|
3362
|
+
[selectedProduct?.id]
|
|
3363
|
+
);
|
|
3299
3364
|
const activeDurationMinutes = normalizeDurationMinutes(selection.duration);
|
|
3300
3365
|
const selectedProductPartyBounds = react.useMemo(
|
|
3301
3366
|
() => getServicePartySizeBounds(selectedProduct, domainConfig),
|
|
@@ -3481,13 +3546,22 @@ function ResiraBookingWidget() {
|
|
|
3481
3546
|
);
|
|
3482
3547
|
const handleSlotSelect = react.useCallback(
|
|
3483
3548
|
(start, end) => {
|
|
3484
|
-
setSelection((prev) =>
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3549
|
+
setSelection((prev) => {
|
|
3550
|
+
const next = {
|
|
3551
|
+
...prev,
|
|
3552
|
+
startDate: slotDate,
|
|
3553
|
+
endDate: slotDate,
|
|
3554
|
+
startTime: start,
|
|
3555
|
+
endTime: end
|
|
3556
|
+
};
|
|
3557
|
+
if (selectedProduct?.id) {
|
|
3558
|
+
slotByProductRef.current[selectedProduct.id] = {
|
|
3559
|
+
...slotByProductRef.current[selectedProduct.id],
|
|
3560
|
+
...next
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
return next;
|
|
3564
|
+
});
|
|
3491
3565
|
if (promoterEnabled && promoterMode.autoAdvanceAvailability && step === "availability") {
|
|
3492
3566
|
const nextIdx = stepIndex(step, STEPS) + 1;
|
|
3493
3567
|
if (nextIdx < STEPS.length) {
|
|
@@ -3495,7 +3569,7 @@ function ResiraBookingWidget() {
|
|
|
3495
3569
|
}
|
|
3496
3570
|
}
|
|
3497
3571
|
},
|
|
3498
|
-
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS]
|
|
3572
|
+
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS, selectedProduct?.id]
|
|
3499
3573
|
);
|
|
3500
3574
|
const handlePartySizeChange = react.useCallback((size) => {
|
|
3501
3575
|
const clamped = clampPartySize(size, selectedProductPartyBounds);
|
|
@@ -3505,15 +3579,40 @@ function ResiraBookingWidget() {
|
|
|
3505
3579
|
}
|
|
3506
3580
|
}, [selectedProduct?.id, selectedProductPartyBounds]);
|
|
3507
3581
|
const handleDurationChange = react.useCallback((minutes) => {
|
|
3508
|
-
setSelection((prev) =>
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3582
|
+
setSelection((prev) => {
|
|
3583
|
+
const next = {
|
|
3584
|
+
...prev,
|
|
3585
|
+
duration: minutes,
|
|
3586
|
+
startTime: void 0,
|
|
3587
|
+
endTime: void 0
|
|
3588
|
+
};
|
|
3589
|
+
if (selectedProduct?.id) {
|
|
3590
|
+
slotByProductRef.current[selectedProduct.id] = {
|
|
3591
|
+
...slotByProductRef.current[selectedProduct.id],
|
|
3592
|
+
duration: minutes,
|
|
3593
|
+
startTime: void 0,
|
|
3594
|
+
endTime: void 0
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
return next;
|
|
3598
|
+
});
|
|
3599
|
+
}, [selectedProduct?.id]);
|
|
3515
3600
|
const handleProductSelect = react.useCallback(
|
|
3516
3601
|
(product) => {
|
|
3602
|
+
if (selectedProduct?.id) {
|
|
3603
|
+
const s = selectionRef.current;
|
|
3604
|
+
slotByProductRef.current[selectedProduct.id] = {
|
|
3605
|
+
startTime: s.startTime,
|
|
3606
|
+
endTime: s.endTime,
|
|
3607
|
+
startDate: s.startDate,
|
|
3608
|
+
endDate: s.endDate,
|
|
3609
|
+
duration: s.duration
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
const saved = slotByProductRef.current[product.id] ?? {};
|
|
3613
|
+
if (saved.startDate && typeof saved.startDate === "string" && saved.startDate.length >= 10) {
|
|
3614
|
+
setSlotDate(saved.startDate.slice(0, 10));
|
|
3615
|
+
}
|
|
3517
3616
|
setSelectedProduct(product);
|
|
3518
3617
|
if (product.equipmentIds?.length) {
|
|
3519
3618
|
setActiveResourceId(product.equipmentIds[0]);
|
|
@@ -3525,9 +3624,10 @@ function ResiraBookingWidget() {
|
|
|
3525
3624
|
persistedPartySize ?? prev.partySize,
|
|
3526
3625
|
bounds
|
|
3527
3626
|
);
|
|
3528
|
-
const defaultDuration = product.durationPricing?.[0]?.durationMinutes ?? product.durationMinutes ?? prev.duration;
|
|
3627
|
+
const defaultDuration = saved.duration ?? product.durationPricing?.[0]?.durationMinutes ?? product.durationMinutes ?? prev.duration;
|
|
3529
3628
|
return {
|
|
3530
3629
|
...prev,
|
|
3630
|
+
...saved,
|
|
3531
3631
|
productId: product.id,
|
|
3532
3632
|
duration: defaultDuration,
|
|
3533
3633
|
partySize: nextPartySize
|
|
@@ -3549,7 +3649,7 @@ function ResiraBookingWidget() {
|
|
|
3549
3649
|
}
|
|
3550
3650
|
}
|
|
3551
3651
|
},
|
|
3552
|
-
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS]
|
|
3652
|
+
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS, selectedProduct?.id]
|
|
3553
3653
|
);
|
|
3554
3654
|
const handleResourceSelect = react.useCallback(
|
|
3555
3655
|
(resourceId) => {
|
|
@@ -3870,7 +3970,7 @@ function ResiraBookingWidget() {
|
|
|
3870
3970
|
{
|
|
3871
3971
|
timeSlots: availability?.timeSlots ?? [],
|
|
3872
3972
|
selectedDate: slotDate,
|
|
3873
|
-
onDateChange:
|
|
3973
|
+
onDateChange: handleSlotDateChange,
|
|
3874
3974
|
selectedSlot: selection.startTime,
|
|
3875
3975
|
onSlotSelect: handleSlotSelect,
|
|
3876
3976
|
partySize: selection.partySize,
|