@resira/ui 0.4.19 → 0.4.20
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 +313 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -2
- package/dist/index.d.ts +57 -2
- package/dist/index.js +313 -39
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import { Resira } from '@resira/sdk';
|
|
2
|
+
import { Resira, ResiraApiError } from '@resira/sdk';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/ResiraProvider.tsx
|
|
@@ -60,7 +60,10 @@ var DEFAULT_LOCALE = {
|
|
|
60
60
|
contactHint: "Please provide at least a phone number or email",
|
|
61
61
|
restaurantNotesPlaceholder: "Allergies, special requests...",
|
|
62
62
|
reserveTable: "Reserve a table",
|
|
63
|
-
reservationFor: "Reservation for"
|
|
63
|
+
reservationFor: "Reservation for",
|
|
64
|
+
slotHoldReserved: "Reserved for you",
|
|
65
|
+
slotSoldOut: "This slot just sold out. Pick another time.",
|
|
66
|
+
slotHoldExpired: "Your hold expired. Please choose a time again."
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
// src/theme.ts
|
|
@@ -238,6 +241,7 @@ function ResiraProvider({
|
|
|
238
241
|
() => ({ ...DEFAULT_PROMOTER_MODE, ...config?.promoterMode }),
|
|
239
242
|
[config?.promoterMode]
|
|
240
243
|
);
|
|
244
|
+
const slotHoldsEnabled = config?.slotHoldsEnabled ?? true;
|
|
241
245
|
const showStepIndicator = config?.showStepIndicator ?? (promoterMode.enabled ? promoterMode.showStepIndicator : true);
|
|
242
246
|
const deeplink = config?.deeplink;
|
|
243
247
|
const deeplinkGuest = config?.deeplinkGuest;
|
|
@@ -278,9 +282,10 @@ function ResiraProvider({
|
|
|
278
282
|
onBookingComplete,
|
|
279
283
|
onError,
|
|
280
284
|
checkoutSessionToken,
|
|
281
|
-
promoterMode
|
|
285
|
+
promoterMode,
|
|
286
|
+
slotHoldsEnabled
|
|
282
287
|
}),
|
|
283
|
-
[client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard, showStepIndicator, deeplink, deeplinkGuest, onStepChange, onBookingComplete, onError, checkoutSessionToken, promoterMode]
|
|
288
|
+
[client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard, showStepIndicator, deeplink, deeplinkGuest, onStepChange, onBookingComplete, onError, checkoutSessionToken, promoterMode, slotHoldsEnabled]
|
|
284
289
|
);
|
|
285
290
|
return /* @__PURE__ */ jsx(ResiraContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: "resira-root", style: cssVars, children }) });
|
|
286
291
|
}
|
|
@@ -3201,6 +3206,175 @@ function clampPartySize(value, bounds) {
|
|
|
3201
3206
|
if (!Number.isFinite(value)) return bounds.min;
|
|
3202
3207
|
return Math.min(bounds.max, Math.max(bounds.min, Math.round(value)));
|
|
3203
3208
|
}
|
|
3209
|
+
function parseSlotHoldError(err) {
|
|
3210
|
+
if (err instanceof ResiraApiError) {
|
|
3211
|
+
const raw = `${err.body.code ?? ""} ${err.body.error ?? ""}`.toUpperCase();
|
|
3212
|
+
const message2 = err.body.error || err.message;
|
|
3213
|
+
if (raw.includes("SLOT_UNAVAILABLE")) return { code: "SLOT_UNAVAILABLE", message: message2 };
|
|
3214
|
+
if (raw.includes("HOLD_EXPIRED")) return { code: "HOLD_EXPIRED", message: message2 };
|
|
3215
|
+
if (raw.includes("HOLD_MISMATCH")) return { code: "HOLD_MISMATCH", message: message2 };
|
|
3216
|
+
if (raw.includes("HOLD_NOT_FOUND")) return { code: "HOLD_NOT_FOUND", message: message2 };
|
|
3217
|
+
if (raw.includes("HOLD_NOT_ACTIVE")) return { code: "HOLD_NOT_ACTIVE", message: message2 };
|
|
3218
|
+
if (err.status === 409) return { code: "SLOT_UNAVAILABLE", message: message2 };
|
|
3219
|
+
return { code: "UNKNOWN", message: message2 };
|
|
3220
|
+
}
|
|
3221
|
+
const message = err instanceof Error ? err.message : "Slot hold failed";
|
|
3222
|
+
return { code: "UNKNOWN", message };
|
|
3223
|
+
}
|
|
3224
|
+
function useSlotHold(options) {
|
|
3225
|
+
const {
|
|
3226
|
+
client,
|
|
3227
|
+
enabled,
|
|
3228
|
+
productId,
|
|
3229
|
+
startTime,
|
|
3230
|
+
durationMinutes,
|
|
3231
|
+
partySize,
|
|
3232
|
+
resourceId,
|
|
3233
|
+
sessionToken,
|
|
3234
|
+
onSlotUnavailable,
|
|
3235
|
+
onHoldExpired
|
|
3236
|
+
} = options;
|
|
3237
|
+
const [lastHold, setLastHold] = useState(null);
|
|
3238
|
+
const [creating, setCreating] = useState(false);
|
|
3239
|
+
const [error, setError] = useState(null);
|
|
3240
|
+
const [parsedCode, setParsedCode] = useState(null);
|
|
3241
|
+
const [remainingSeconds, setRemainingSeconds] = useState(null);
|
|
3242
|
+
const holdIdRef = useRef(null);
|
|
3243
|
+
const serverOffsetMs = useRef(0);
|
|
3244
|
+
const expiredFired = useRef(false);
|
|
3245
|
+
const onHoldExpiredRef = useRef(onHoldExpired);
|
|
3246
|
+
onHoldExpiredRef.current = onHoldExpired;
|
|
3247
|
+
const onSlotUnavailableRef = useRef(onSlotUnavailable);
|
|
3248
|
+
onSlotUnavailableRef.current = onSlotUnavailable;
|
|
3249
|
+
const slotKey = useMemo(
|
|
3250
|
+
() => enabled && productId && startTime && durationMinutes && partySize ? `${productId}|${startTime}|${durationMinutes}|${partySize}|${resourceId ?? ""}|${sessionToken ?? ""}` : "",
|
|
3251
|
+
[enabled, productId, startTime, durationMinutes, partySize, resourceId, sessionToken]
|
|
3252
|
+
);
|
|
3253
|
+
const release = useCallback(async () => {
|
|
3254
|
+
const id = holdIdRef.current;
|
|
3255
|
+
if (!id) return;
|
|
3256
|
+
holdIdRef.current = null;
|
|
3257
|
+
try {
|
|
3258
|
+
await client.releaseSlotHold(id);
|
|
3259
|
+
} catch {
|
|
3260
|
+
}
|
|
3261
|
+
setLastHold(null);
|
|
3262
|
+
setRemainingSeconds(null);
|
|
3263
|
+
}, [client]);
|
|
3264
|
+
useEffect(() => {
|
|
3265
|
+
expiredFired.current = false;
|
|
3266
|
+
if (!enabled || !slotKey) {
|
|
3267
|
+
void release();
|
|
3268
|
+
setError(null);
|
|
3269
|
+
setParsedCode(null);
|
|
3270
|
+
setCreating(false);
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
let cancelled = false;
|
|
3274
|
+
async function run() {
|
|
3275
|
+
setCreating(true);
|
|
3276
|
+
setError(null);
|
|
3277
|
+
setParsedCode(null);
|
|
3278
|
+
try {
|
|
3279
|
+
if (holdIdRef.current) {
|
|
3280
|
+
const prev = holdIdRef.current;
|
|
3281
|
+
holdIdRef.current = null;
|
|
3282
|
+
try {
|
|
3283
|
+
await client.releaseSlotHold(prev);
|
|
3284
|
+
} catch {
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
const res = await client.createSlotHold(
|
|
3288
|
+
productId,
|
|
3289
|
+
{
|
|
3290
|
+
startTime,
|
|
3291
|
+
durationMinutes,
|
|
3292
|
+
partySize,
|
|
3293
|
+
...resourceId ? { resourceId } : {},
|
|
3294
|
+
...sessionToken ? { sessionToken } : {}
|
|
3295
|
+
}
|
|
3296
|
+
);
|
|
3297
|
+
if (cancelled) return;
|
|
3298
|
+
holdIdRef.current = res.holdId;
|
|
3299
|
+
serverOffsetMs.current = new Date(res.serverNow).getTime() - Date.now();
|
|
3300
|
+
setLastHold(res);
|
|
3301
|
+
const rem = Math.max(
|
|
3302
|
+
0,
|
|
3303
|
+
Math.floor(
|
|
3304
|
+
(new Date(res.expiresAt).getTime() - (Date.now() + serverOffsetMs.current)) / 1e3
|
|
3305
|
+
)
|
|
3306
|
+
);
|
|
3307
|
+
setRemainingSeconds(rem);
|
|
3308
|
+
} catch (err) {
|
|
3309
|
+
if (cancelled) return;
|
|
3310
|
+
holdIdRef.current = null;
|
|
3311
|
+
setLastHold(null);
|
|
3312
|
+
setRemainingSeconds(null);
|
|
3313
|
+
const parsed = parseSlotHoldError(err);
|
|
3314
|
+
setParsedCode(parsed.code);
|
|
3315
|
+
setError(parsed.message);
|
|
3316
|
+
if (parsed.code === "SLOT_UNAVAILABLE" || err instanceof ResiraApiError && err.status === 409) {
|
|
3317
|
+
onSlotUnavailableRef.current?.();
|
|
3318
|
+
}
|
|
3319
|
+
} finally {
|
|
3320
|
+
if (!cancelled) setCreating(false);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
void run();
|
|
3324
|
+
return () => {
|
|
3325
|
+
cancelled = true;
|
|
3326
|
+
};
|
|
3327
|
+
}, [client, enabled, slotKey, productId, startTime, durationMinutes, partySize, resourceId, sessionToken, release]);
|
|
3328
|
+
useEffect(() => {
|
|
3329
|
+
if (!lastHold?.expiresAt) {
|
|
3330
|
+
setRemainingSeconds(null);
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
const tick = () => {
|
|
3334
|
+
const end = new Date(lastHold.expiresAt).getTime();
|
|
3335
|
+
const now = Date.now() + serverOffsetMs.current;
|
|
3336
|
+
const rem = Math.max(0, Math.floor((end - now) / 1e3));
|
|
3337
|
+
setRemainingSeconds(rem);
|
|
3338
|
+
if (rem <= 0 && !expiredFired.current) {
|
|
3339
|
+
expiredFired.current = true;
|
|
3340
|
+
holdIdRef.current = null;
|
|
3341
|
+
onHoldExpiredRef.current?.();
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3344
|
+
tick();
|
|
3345
|
+
const id = window.setInterval(tick, 1e3);
|
|
3346
|
+
return () => window.clearInterval(id);
|
|
3347
|
+
}, [lastHold]);
|
|
3348
|
+
useEffect(() => {
|
|
3349
|
+
return () => {
|
|
3350
|
+
const id = holdIdRef.current;
|
|
3351
|
+
if (id) {
|
|
3352
|
+
holdIdRef.current = null;
|
|
3353
|
+
void client.releaseSlotHold(id).catch(() => {
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
}, [client]);
|
|
3358
|
+
const holdReady = useMemo(() => {
|
|
3359
|
+
if (!enabled) return true;
|
|
3360
|
+
if (creating) return false;
|
|
3361
|
+
if (error) return false;
|
|
3362
|
+
return !!lastHold?.holdId;
|
|
3363
|
+
}, [enabled, creating, error, lastHold?.holdId]);
|
|
3364
|
+
return {
|
|
3365
|
+
holdId: lastHold?.holdId ?? null,
|
|
3366
|
+
holdExpiresAt: lastHold?.expiresAt ?? null,
|
|
3367
|
+
heldResourceId: lastHold?.resourceId ?? null,
|
|
3368
|
+
remainingSeconds,
|
|
3369
|
+
creating,
|
|
3370
|
+
error,
|
|
3371
|
+
parsedErrorCode: parsedCode,
|
|
3372
|
+
holdReady,
|
|
3373
|
+
release,
|
|
3374
|
+
serverNow: lastHold?.serverNow ?? null,
|
|
3375
|
+
lastHold
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3204
3378
|
function todayStr() {
|
|
3205
3379
|
const d = /* @__PURE__ */ new Date();
|
|
3206
3380
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
@@ -3219,6 +3393,12 @@ function normalizeDurationMinutes(value) {
|
|
|
3219
3393
|
}
|
|
3220
3394
|
return void 0;
|
|
3221
3395
|
}
|
|
3396
|
+
function formatHoldCountdown(totalSeconds) {
|
|
3397
|
+
const s = Math.max(0, Math.floor(totalSeconds));
|
|
3398
|
+
const m = Math.floor(s / 60);
|
|
3399
|
+
const r = s % 60;
|
|
3400
|
+
return `${m}:${String(r).padStart(2, "0")}`;
|
|
3401
|
+
}
|
|
3222
3402
|
function buildSteps(domain, hasPayment, catalogMode) {
|
|
3223
3403
|
const steps = [];
|
|
3224
3404
|
if (domain === "watersport" || domain === "service" || catalogMode && domain !== "restaurant") {
|
|
@@ -3249,6 +3429,7 @@ var STEP_LABELS = {
|
|
|
3249
3429
|
};
|
|
3250
3430
|
function ResiraBookingWidget() {
|
|
3251
3431
|
const {
|
|
3432
|
+
client,
|
|
3252
3433
|
domain,
|
|
3253
3434
|
locale,
|
|
3254
3435
|
domainConfig,
|
|
@@ -3269,7 +3450,8 @@ function ResiraBookingWidget() {
|
|
|
3269
3450
|
onBookingComplete,
|
|
3270
3451
|
onError,
|
|
3271
3452
|
checkoutSessionToken,
|
|
3272
|
-
promoterMode
|
|
3453
|
+
promoterMode,
|
|
3454
|
+
slotHoldsEnabled
|
|
3273
3455
|
} = useResira();
|
|
3274
3456
|
const isDateBased = domain === "rental";
|
|
3275
3457
|
const isTimeBased = domain === "restaurant" || domain === "watersport" || domain === "service";
|
|
@@ -3401,6 +3583,26 @@ function ResiraBookingWidget() {
|
|
|
3401
3583
|
confirming: confirmingPayment,
|
|
3402
3584
|
reset: resetPaymentIntent
|
|
3403
3585
|
} = usePaymentIntent();
|
|
3586
|
+
const slotHold = useSlotHold({
|
|
3587
|
+
client,
|
|
3588
|
+
enabled: slotHoldsEnabled && isServiceBased && !isCheckoutMode && !!selectedProduct?.id && !!selection.startTime && !!selection.endTime && !!activeDurationMinutes,
|
|
3589
|
+
productId: selectedProduct?.id,
|
|
3590
|
+
startTime: selection.startTime,
|
|
3591
|
+
durationMinutes: activeDurationMinutes ?? void 0,
|
|
3592
|
+
partySize: selection.partySize,
|
|
3593
|
+
resourceId: activeResourceId ?? selectedProduct?.equipmentIds?.[0],
|
|
3594
|
+
onSlotUnavailable: () => {
|
|
3595
|
+
void refetch();
|
|
3596
|
+
setSelection((s) => ({ ...s, startTime: void 0, endTime: void 0 }));
|
|
3597
|
+
onError?.("slot_unavailable", locale.slotSoldOut);
|
|
3598
|
+
},
|
|
3599
|
+
onHoldExpired: () => {
|
|
3600
|
+
setStep("availability");
|
|
3601
|
+
resetPaymentIntent();
|
|
3602
|
+
void refetch();
|
|
3603
|
+
onError?.("hold_expired", locale.slotHoldExpired);
|
|
3604
|
+
}
|
|
3605
|
+
});
|
|
3404
3606
|
const [paymentSubmitRequestId, setPaymentSubmitRequestId] = useState(0);
|
|
3405
3607
|
const [paymentFormReady, setPaymentFormReady] = useState(false);
|
|
3406
3608
|
const [paymentFormSubmitting, setPaymentFormSubmitting] = useState(false);
|
|
@@ -3412,7 +3614,8 @@ function ResiraBookingWidget() {
|
|
|
3412
3614
|
guestEmail: guest.guestEmail.trim() || checkoutSession.guestEmail || void 0,
|
|
3413
3615
|
guestPhone: guest.guestPhone.trim() || void 0,
|
|
3414
3616
|
notes: guest.notes.trim() || void 0,
|
|
3415
|
-
termsAccepted: termsAccepted || void 0
|
|
3617
|
+
termsAccepted: termsAccepted || void 0,
|
|
3618
|
+
...checkoutSession.holdId ? { holdId: checkoutSession.holdId } : {}
|
|
3416
3619
|
};
|
|
3417
3620
|
}
|
|
3418
3621
|
const resourceId = activeResourceId ?? selectedProduct?.equipmentIds?.[0] ?? "";
|
|
@@ -3430,9 +3633,10 @@ function ResiraBookingWidget() {
|
|
|
3430
3633
|
notes: guest.notes.trim() || void 0,
|
|
3431
3634
|
promoCode: discountCode.trim() || void 0,
|
|
3432
3635
|
termsAccepted: termsAccepted || void 0,
|
|
3433
|
-
waiverAccepted: waiverAccepted || void 0
|
|
3636
|
+
waiverAccepted: waiverAccepted || void 0,
|
|
3637
|
+
...slotHold.holdId ? { holdId: slotHold.holdId } : {}
|
|
3434
3638
|
};
|
|
3435
|
-
}, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted, waiverAccepted, isCheckoutMode, checkoutSession, checkoutSessionToken, activeDurationMinutes]);
|
|
3639
|
+
}, [activeResourceId, selectedProduct, selection, guest, discountCode, termsAccepted, waiverAccepted, isCheckoutMode, checkoutSession, checkoutSessionToken, activeDurationMinutes, slotHold.holdId]);
|
|
3436
3640
|
const blockedDates = useMemo(() => {
|
|
3437
3641
|
const dates = calendarData?.dates?.blockedDates ?? availability?.dates?.blockedDates ?? [];
|
|
3438
3642
|
return new Set(dates);
|
|
@@ -3521,15 +3725,24 @@ function ResiraBookingWidget() {
|
|
|
3521
3725
|
}
|
|
3522
3726
|
if (step === "availability") {
|
|
3523
3727
|
if (isDateBased) return !!selection.startDate && !!selection.endDate;
|
|
3524
|
-
|
|
3728
|
+
const base = !!selection.startTime && !!selection.endTime;
|
|
3729
|
+
if (!base) return false;
|
|
3730
|
+
if (slotHoldsEnabled && isServiceBased) {
|
|
3731
|
+
return slotHold.holdReady && !slotHold.creating;
|
|
3732
|
+
}
|
|
3733
|
+
return true;
|
|
3525
3734
|
}
|
|
3526
3735
|
if (step === "terms") {
|
|
3527
3736
|
const needTerms = showTerms && !termsAccepted;
|
|
3528
3737
|
const needWaiver = showWaiver && !waiverAccepted;
|
|
3529
|
-
|
|
3738
|
+
if (needTerms || needWaiver) return false;
|
|
3739
|
+
if (slotHoldsEnabled && isServiceBased && !isCheckoutMode) {
|
|
3740
|
+
return !!slotHold.holdId && slotHold.remainingSeconds != null && slotHold.remainingSeconds > 0;
|
|
3741
|
+
}
|
|
3742
|
+
return true;
|
|
3530
3743
|
}
|
|
3531
3744
|
return true;
|
|
3532
|
-
}, [step, isDateBased, isServiceBased, selection, selectedResourceIds, selectedProduct, termsAccepted, waiverAccepted, showTerms, showWaiver]);
|
|
3745
|
+
}, [step, isDateBased, isServiceBased, selection, selectedResourceIds, selectedProduct, termsAccepted, waiverAccepted, showTerms, showWaiver, slotHoldsEnabled, slotHold, isCheckoutMode]);
|
|
3533
3746
|
const handleDateSelect = useCallback(
|
|
3534
3747
|
(start, end) => {
|
|
3535
3748
|
setSelection((prev) => ({ ...prev, startDate: start, endDate: end }));
|
|
@@ -3560,14 +3773,15 @@ function ResiraBookingWidget() {
|
|
|
3560
3773
|
}
|
|
3561
3774
|
return next;
|
|
3562
3775
|
});
|
|
3563
|
-
|
|
3776
|
+
const deferPromoterAdvance = slotHoldsEnabled && isServiceBased;
|
|
3777
|
+
if (promoterEnabled && promoterMode.autoAdvanceAvailability && !deferPromoterAdvance && step === "availability") {
|
|
3564
3778
|
const nextIdx = stepIndex(step, STEPS) + 1;
|
|
3565
3779
|
if (nextIdx < STEPS.length) {
|
|
3566
3780
|
setStep(STEPS[nextIdx]);
|
|
3567
3781
|
}
|
|
3568
3782
|
}
|
|
3569
3783
|
},
|
|
3570
|
-
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS, selectedProduct?.id]
|
|
3784
|
+
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS, selectedProduct?.id, slotHoldsEnabled, isServiceBased]
|
|
3571
3785
|
);
|
|
3572
3786
|
const handlePartySizeChange = useCallback((size) => {
|
|
3573
3787
|
const clamped = clampPartySize(size, selectedProductPartyBounds);
|
|
@@ -3622,11 +3836,14 @@ function ResiraBookingWidget() {
|
|
|
3622
3836
|
persistedPartySize ?? prev.partySize,
|
|
3623
3837
|
bounds
|
|
3624
3838
|
);
|
|
3839
|
+
const hasSavedSlot = !!saved.startTime && !!saved.endTime;
|
|
3625
3840
|
const defaultDuration = saved.duration ?? product.durationPricing?.[0]?.durationMinutes ?? product.durationMinutes ?? prev.duration;
|
|
3626
3841
|
return {
|
|
3627
|
-
...prev,
|
|
3628
|
-
...saved,
|
|
3629
3842
|
productId: product.id,
|
|
3843
|
+
startDate: saved.startDate ?? slotDate,
|
|
3844
|
+
endDate: saved.endDate ?? slotDate,
|
|
3845
|
+
startTime: hasSavedSlot ? saved.startTime : void 0,
|
|
3846
|
+
endTime: hasSavedSlot ? saved.endTime : void 0,
|
|
3630
3847
|
duration: defaultDuration,
|
|
3631
3848
|
partySize: nextPartySize
|
|
3632
3849
|
};
|
|
@@ -3647,7 +3864,7 @@ function ResiraBookingWidget() {
|
|
|
3647
3864
|
}
|
|
3648
3865
|
}
|
|
3649
3866
|
},
|
|
3650
|
-
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS, selectedProduct?.id]
|
|
3867
|
+
[setActiveResourceId, domainConfig, partySizeByProductId, selection.partySize, step, isServiceBased, STEPS, selectedProduct?.id, slotDate]
|
|
3651
3868
|
);
|
|
3652
3869
|
const handleResourceSelect = useCallback(
|
|
3653
3870
|
(resourceId) => {
|
|
@@ -3963,27 +4180,36 @@ function ResiraBookingWidget() {
|
|
|
3963
4180
|
minStay: domainConfig.minStay
|
|
3964
4181
|
}
|
|
3965
4182
|
),
|
|
3966
|
-
isTimeBased && /* @__PURE__ */
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
4183
|
+
isTimeBased && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4184
|
+
/* @__PURE__ */ jsx(
|
|
4185
|
+
TimeSlotPicker,
|
|
4186
|
+
{
|
|
4187
|
+
timeSlots: availability?.timeSlots ?? [],
|
|
4188
|
+
selectedDate: slotDate,
|
|
4189
|
+
onDateChange: handleSlotDateChange,
|
|
4190
|
+
selectedSlot: selection.startTime,
|
|
4191
|
+
onSlotSelect: handleSlotSelect,
|
|
4192
|
+
partySize: selection.partySize,
|
|
4193
|
+
onPartySizeChange: handlePartySizeChange,
|
|
4194
|
+
showPartySize: true,
|
|
4195
|
+
showDuration: domain === "watersport" || domain === "service",
|
|
4196
|
+
selectedDuration: activeDurationMinutes,
|
|
4197
|
+
onDurationChange: handleDurationChange,
|
|
4198
|
+
minPartySizeOverride: selectedProductPartyBounds.min,
|
|
4199
|
+
maxPartySizeOverride: selectedProductPartyBounds.max,
|
|
4200
|
+
durationPricing: activeRiderDurationPricing ?? selectedProduct?.durationPricing,
|
|
4201
|
+
currency,
|
|
4202
|
+
showRemainingSpots
|
|
4203
|
+
}
|
|
4204
|
+
),
|
|
4205
|
+
slotHoldsEnabled && isServiceBased && selection.startTime && selection.endTime && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4206
|
+
slotHold.creating && /* @__PURE__ */ jsx("p", { style: { marginTop: 10, fontSize: 14, opacity: 0.85 }, role: "status", children: locale.loading }),
|
|
4207
|
+
slotHold.error && !slotHold.creating && /* @__PURE__ */ jsxs("div", { className: "resira-error", style: { marginTop: 10 }, role: "alert", children: [
|
|
4208
|
+
/* @__PURE__ */ jsx(AlertCircleIcon, { size: 18 }),
|
|
4209
|
+
/* @__PURE__ */ jsx("p", { className: "resira-error-message", children: slotHold.error })
|
|
4210
|
+
] })
|
|
4211
|
+
] })
|
|
4212
|
+
] }),
|
|
3987
4213
|
isServiceBased && selectedProduct && computedPrice && /* @__PURE__ */ jsxs("div", { className: "resira-price-preview", children: [
|
|
3988
4214
|
/* @__PURE__ */ jsxs("div", { className: "resira-price-preview-row", children: [
|
|
3989
4215
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -4046,6 +4272,30 @@ function ResiraBookingWidget() {
|
|
|
4046
4272
|
] })
|
|
4047
4273
|
] }),
|
|
4048
4274
|
step === "terms" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4275
|
+
slotHoldsEnabled && isServiceBased && !isCheckoutMode && slotHold.holdId && slotHold.remainingSeconds != null && /* @__PURE__ */ jsxs(
|
|
4276
|
+
"div",
|
|
4277
|
+
{
|
|
4278
|
+
style: {
|
|
4279
|
+
marginBottom: 14,
|
|
4280
|
+
padding: "10px 12px",
|
|
4281
|
+
borderRadius: 8,
|
|
4282
|
+
fontSize: 14,
|
|
4283
|
+
background: slotHold.remainingSeconds <= 60 ? "rgba(220, 38, 38, 0.08)" : "rgba(59, 130, 246, 0.08)",
|
|
4284
|
+
border: `1px solid ${slotHold.remainingSeconds <= 60 ? "rgba(220, 38, 38, 0.22)" : "rgba(59, 130, 246, 0.2)"}`
|
|
4285
|
+
},
|
|
4286
|
+
role: "status",
|
|
4287
|
+
"aria-live": "polite",
|
|
4288
|
+
children: [
|
|
4289
|
+
locale.slotHoldReserved,
|
|
4290
|
+
" ",
|
|
4291
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
4292
|
+
"(",
|
|
4293
|
+
formatHoldCountdown(slotHold.remainingSeconds),
|
|
4294
|
+
")"
|
|
4295
|
+
] })
|
|
4296
|
+
]
|
|
4297
|
+
}
|
|
4298
|
+
),
|
|
4049
4299
|
/* @__PURE__ */ jsx(
|
|
4050
4300
|
SummaryPreview,
|
|
4051
4301
|
{
|
|
@@ -4165,6 +4415,30 @@ function ResiraBookingWidget() {
|
|
|
4165
4415
|
] })
|
|
4166
4416
|
] }),
|
|
4167
4417
|
step === "payment" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4418
|
+
slotHoldsEnabled && isServiceBased && !isCheckoutMode && slotHold.holdId && slotHold.remainingSeconds != null && /* @__PURE__ */ jsxs(
|
|
4419
|
+
"div",
|
|
4420
|
+
{
|
|
4421
|
+
style: {
|
|
4422
|
+
marginBottom: 14,
|
|
4423
|
+
padding: "10px 12px",
|
|
4424
|
+
borderRadius: 8,
|
|
4425
|
+
fontSize: 14,
|
|
4426
|
+
background: slotHold.remainingSeconds <= 60 ? "rgba(220, 38, 38, 0.08)" : "rgba(59, 130, 246, 0.08)",
|
|
4427
|
+
border: `1px solid ${slotHold.remainingSeconds <= 60 ? "rgba(220, 38, 38, 0.22)" : "rgba(59, 130, 246, 0.2)"}`
|
|
4428
|
+
},
|
|
4429
|
+
role: "status",
|
|
4430
|
+
"aria-live": "polite",
|
|
4431
|
+
children: [
|
|
4432
|
+
locale.slotHoldReserved,
|
|
4433
|
+
" ",
|
|
4434
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
4435
|
+
"(",
|
|
4436
|
+
formatHoldCountdown(slotHold.remainingSeconds),
|
|
4437
|
+
")"
|
|
4438
|
+
] })
|
|
4439
|
+
]
|
|
4440
|
+
}
|
|
4441
|
+
),
|
|
4168
4442
|
paymentError && /* @__PURE__ */ jsxs("div", { className: "resira-error", children: [
|
|
4169
4443
|
/* @__PURE__ */ jsx(AlertCircleIcon, { size: 24 }),
|
|
4170
4444
|
/* @__PURE__ */ jsx("p", { className: "resira-error-message", children: paymentError })
|
|
@@ -4335,7 +4609,7 @@ function ResiraBookingWidget() {
|
|
|
4335
4609
|
onClick: () => {
|
|
4336
4610
|
void handleStartPayment();
|
|
4337
4611
|
},
|
|
4338
|
-
disabled: footerBusy,
|
|
4612
|
+
disabled: footerBusy || slotHoldsEnabled && isServiceBased && !isCheckoutMode && (!slotHold.holdId || (slotHold.remainingSeconds ?? 0) <= 0),
|
|
4339
4613
|
children: creatingPayment ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4340
4614
|
/* @__PURE__ */ jsx("div", { className: "resira-spinner", style: { width: 16, height: 16 } }),
|
|
4341
4615
|
locale.processingPayment
|
|
@@ -4840,6 +5114,6 @@ function DishShowcase({
|
|
|
4840
5114
|
] });
|
|
4841
5115
|
}
|
|
4842
5116
|
|
|
4843
|
-
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 };
|
|
5117
|
+
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, parseSlotHoldError, resolveServicePrice, resolveTheme, themeToCSS, useAvailability, useCheckoutSession, useDish, useDishes, usePaymentIntent, useProducts, useReservation, useResira, useResources, useServices, useSlotHold, validateGuestForm };
|
|
4844
5118
|
//# sourceMappingURL=index.js.map
|
|
4845
5119
|
//# sourceMappingURL=index.js.map
|