@resira/ui 0.4.11 → 0.4.13
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/README.md +36 -2
- package/dist/index.cjs +229 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +229 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -166,6 +166,17 @@ var DOMAIN_DEFAULTS = {
|
|
|
166
166
|
availableDurations: [30, 45, 60, 90]
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
|
+
var DEFAULT_PROMOTER_MODE = {
|
|
170
|
+
enabled: false,
|
|
171
|
+
contactMode: "phone-required",
|
|
172
|
+
disableImages: true,
|
|
173
|
+
disablePromoValidation: true,
|
|
174
|
+
hidePromoInput: false,
|
|
175
|
+
cacheTtlMs: 5 * 60 * 1e3,
|
|
176
|
+
useStaleDataOnError: true,
|
|
177
|
+
autoAdvanceAvailability: true,
|
|
178
|
+
showStepIndicator: false
|
|
179
|
+
};
|
|
169
180
|
function ResiraProvider({
|
|
170
181
|
apiKey,
|
|
171
182
|
resourceId,
|
|
@@ -223,7 +234,11 @@ function ResiraProvider({
|
|
|
223
234
|
const visibleServiceCount = config?.visibleServiceCount ?? 4;
|
|
224
235
|
const groupServicesByCategory = config?.groupServicesByCategory ?? true;
|
|
225
236
|
const renderServiceCard = config?.renderServiceCard;
|
|
226
|
-
const
|
|
237
|
+
const promoterMode = useMemo(
|
|
238
|
+
() => ({ ...DEFAULT_PROMOTER_MODE, ...config?.promoterMode }),
|
|
239
|
+
[config?.promoterMode]
|
|
240
|
+
);
|
|
241
|
+
const showStepIndicator = config?.showStepIndicator ?? (promoterMode.enabled ? promoterMode.showStepIndicator : true);
|
|
227
242
|
const deeplink = config?.deeplink;
|
|
228
243
|
const deeplinkGuest = config?.deeplinkGuest;
|
|
229
244
|
const onStepChange = config?.onStepChange;
|
|
@@ -262,12 +277,31 @@ function ResiraProvider({
|
|
|
262
277
|
onStepChange,
|
|
263
278
|
onBookingComplete,
|
|
264
279
|
onError,
|
|
265
|
-
checkoutSessionToken
|
|
280
|
+
checkoutSessionToken,
|
|
281
|
+
promoterMode
|
|
266
282
|
}),
|
|
267
|
-
[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]
|
|
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]
|
|
268
284
|
);
|
|
269
285
|
return /* @__PURE__ */ jsx(ResiraContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: "resira-root", style: cssVars, children }) });
|
|
270
286
|
}
|
|
287
|
+
var availabilityCache = /* @__PURE__ */ new Map();
|
|
288
|
+
var resourcesCache = /* @__PURE__ */ new Map();
|
|
289
|
+
var productsCache = /* @__PURE__ */ new Map();
|
|
290
|
+
function readCache(cache, key, now = Date.now()) {
|
|
291
|
+
const entry = cache.get(key);
|
|
292
|
+
if (!entry) return null;
|
|
293
|
+
if (entry.expiresAt <= now) return null;
|
|
294
|
+
return entry;
|
|
295
|
+
}
|
|
296
|
+
function readStaleCache(cache, key) {
|
|
297
|
+
return cache.get(key) ?? null;
|
|
298
|
+
}
|
|
299
|
+
function writeCache(cache, key, value, ttlMs) {
|
|
300
|
+
cache.set(key, {
|
|
301
|
+
value,
|
|
302
|
+
expiresAt: Date.now() + Math.max(1e3, ttlMs)
|
|
303
|
+
});
|
|
304
|
+
}
|
|
271
305
|
async function getDishCompat(client, dishId) {
|
|
272
306
|
const maybeClient = client;
|
|
273
307
|
if (typeof maybeClient.getDish === "function") {
|
|
@@ -289,7 +323,7 @@ async function listDishesCompat(client) {
|
|
|
289
323
|
throw new Error("Installed @resira/sdk does not support dish endpoints. Upgrade to @resira/sdk >= 0.3.0.");
|
|
290
324
|
}
|
|
291
325
|
function useAvailability(params, productId) {
|
|
292
|
-
const { client, activeResourceId } = useResira();
|
|
326
|
+
const { client, activeResourceId, promoterMode } = useResira();
|
|
293
327
|
const resourceId = activeResourceId ?? "";
|
|
294
328
|
const [data, setData] = useState(null);
|
|
295
329
|
const [loading, setLoading] = useState(false);
|
|
@@ -298,10 +332,23 @@ function useAvailability(params, productId) {
|
|
|
298
332
|
const useProduct = !!productId;
|
|
299
333
|
const enabled = useProduct ? productId.length > 0 : resourceId.length > 0;
|
|
300
334
|
const paramsKey = JSON.stringify(params ?? null);
|
|
335
|
+
const cachePrefix = client.getBaseUrl();
|
|
336
|
+
const targetKey = useProduct ? `product:${productId}` : `resource:${resourceId}`;
|
|
337
|
+
const availabilityCacheKey = `${cachePrefix}:${targetKey}:${paramsKey}`;
|
|
301
338
|
const completedKeyRef = useRef(null);
|
|
302
339
|
const refetch = useCallback(
|
|
303
340
|
async (overrideParams) => {
|
|
304
341
|
if (!enabled) return;
|
|
342
|
+
if (promoterMode.enabled && overrideParams === void 0) {
|
|
343
|
+
const cached = readCache(availabilityCache, availabilityCacheKey);
|
|
344
|
+
if (cached) {
|
|
345
|
+
setData(cached.value);
|
|
346
|
+
setLoading(false);
|
|
347
|
+
setError(null);
|
|
348
|
+
completedKeyRef.current = paramsKey;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
305
352
|
const fetchId = ++abortRef.current;
|
|
306
353
|
completedKeyRef.current = null;
|
|
307
354
|
setLoading(true);
|
|
@@ -312,9 +359,21 @@ function useAvailability(params, productId) {
|
|
|
312
359
|
if (fetchId === abortRef.current) {
|
|
313
360
|
setData(result);
|
|
314
361
|
completedKeyRef.current = paramsKey;
|
|
362
|
+
if (promoterMode.enabled) {
|
|
363
|
+
writeCache(availabilityCache, availabilityCacheKey, result, promoterMode.cacheTtlMs);
|
|
364
|
+
}
|
|
315
365
|
}
|
|
316
366
|
} catch (err) {
|
|
317
367
|
if (fetchId === abortRef.current) {
|
|
368
|
+
if (promoterMode.enabled && promoterMode.useStaleDataOnError) {
|
|
369
|
+
const stale = readStaleCache(availabilityCache, availabilityCacheKey);
|
|
370
|
+
if (stale) {
|
|
371
|
+
setData(stale.value);
|
|
372
|
+
setError(null);
|
|
373
|
+
completedKeyRef.current = paramsKey;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
318
377
|
setError(
|
|
319
378
|
err instanceof Error ? err.message : "Failed to load availability"
|
|
320
379
|
);
|
|
@@ -327,7 +386,7 @@ function useAvailability(params, productId) {
|
|
|
327
386
|
}
|
|
328
387
|
},
|
|
329
388
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
330
|
-
[client, resourceId, productId, useProduct, paramsKey, enabled]
|
|
389
|
+
[client, resourceId, productId, useProduct, paramsKey, enabled, promoterMode, availabilityCacheKey]
|
|
331
390
|
);
|
|
332
391
|
useEffect(() => {
|
|
333
392
|
if (!enabled) {
|
|
@@ -337,6 +396,16 @@ function useAvailability(params, productId) {
|
|
|
337
396
|
completedKeyRef.current = null;
|
|
338
397
|
return;
|
|
339
398
|
}
|
|
399
|
+
if (promoterMode.enabled) {
|
|
400
|
+
const cached = readCache(availabilityCache, availabilityCacheKey);
|
|
401
|
+
if (cached) {
|
|
402
|
+
setData(cached.value);
|
|
403
|
+
setLoading(false);
|
|
404
|
+
setError(null);
|
|
405
|
+
completedKeyRef.current = paramsKey;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
340
409
|
if (completedKeyRef.current === paramsKey) {
|
|
341
410
|
return;
|
|
342
411
|
}
|
|
@@ -351,9 +420,21 @@ function useAvailability(params, productId) {
|
|
|
351
420
|
if (!cancelled && fetchId === abortRef.current) {
|
|
352
421
|
setData(result);
|
|
353
422
|
completedKeyRef.current = paramsKey;
|
|
423
|
+
if (promoterMode.enabled) {
|
|
424
|
+
writeCache(availabilityCache, availabilityCacheKey, result, promoterMode.cacheTtlMs);
|
|
425
|
+
}
|
|
354
426
|
}
|
|
355
427
|
} catch (err) {
|
|
356
428
|
if (!cancelled && fetchId === abortRef.current) {
|
|
429
|
+
if (promoterMode.enabled && promoterMode.useStaleDataOnError) {
|
|
430
|
+
const stale = readStaleCache(availabilityCache, availabilityCacheKey);
|
|
431
|
+
if (stale) {
|
|
432
|
+
setData(stale.value);
|
|
433
|
+
setError(null);
|
|
434
|
+
completedKeyRef.current = paramsKey;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
357
438
|
setError(
|
|
358
439
|
err instanceof Error ? err.message : "Failed to load availability"
|
|
359
440
|
);
|
|
@@ -369,7 +450,7 @@ function useAvailability(params, productId) {
|
|
|
369
450
|
return () => {
|
|
370
451
|
cancelled = true;
|
|
371
452
|
};
|
|
372
|
-
}, [client, resourceId, productId, useProduct, paramsKey, enabled]);
|
|
453
|
+
}, [client, resourceId, productId, useProduct, paramsKey, enabled, promoterMode, availabilityCacheKey]);
|
|
373
454
|
return { data, loading, error, refetch };
|
|
374
455
|
}
|
|
375
456
|
function useReservation() {
|
|
@@ -407,22 +488,47 @@ function useReservation() {
|
|
|
407
488
|
return { reservation, submitting, error, submit, reset };
|
|
408
489
|
}
|
|
409
490
|
function useResources() {
|
|
410
|
-
const { client } = useResira();
|
|
491
|
+
const { client, promoterMode } = useResira();
|
|
411
492
|
const [resources, setResources] = useState([]);
|
|
412
493
|
const [loading, setLoading] = useState(true);
|
|
413
494
|
const [error, setError] = useState(null);
|
|
414
495
|
useEffect(() => {
|
|
415
496
|
let cancelled = false;
|
|
497
|
+
const cacheKey = `${client.getBaseUrl()}:resources`;
|
|
498
|
+
if (promoterMode.enabled) {
|
|
499
|
+
const cached = readCache(resourcesCache, cacheKey);
|
|
500
|
+
if (cached) {
|
|
501
|
+
setResources(cached.value);
|
|
502
|
+
setLoading(false);
|
|
503
|
+
setError(null);
|
|
504
|
+
return () => {
|
|
505
|
+
cancelled = true;
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
416
509
|
async function fetchResources() {
|
|
417
510
|
try {
|
|
418
511
|
setLoading(true);
|
|
419
512
|
setError(null);
|
|
420
513
|
const data = await client.listResources();
|
|
421
514
|
if (!cancelled) {
|
|
422
|
-
|
|
515
|
+
const nextResources = data.resources ?? [];
|
|
516
|
+
setResources(nextResources);
|
|
517
|
+
if (promoterMode.enabled) {
|
|
518
|
+
writeCache(resourcesCache, cacheKey, nextResources, promoterMode.cacheTtlMs);
|
|
519
|
+
}
|
|
423
520
|
}
|
|
424
521
|
} catch (err) {
|
|
425
522
|
if (!cancelled) {
|
|
523
|
+
if (promoterMode.enabled && promoterMode.useStaleDataOnError) {
|
|
524
|
+
const stale = readStaleCache(resourcesCache, cacheKey);
|
|
525
|
+
if (stale) {
|
|
526
|
+
setResources(stale.value);
|
|
527
|
+
setError(null);
|
|
528
|
+
setLoading(false);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
426
532
|
const message = err instanceof Error ? err.message : "Failed to load resources";
|
|
427
533
|
setError(message);
|
|
428
534
|
}
|
|
@@ -436,26 +542,51 @@ function useResources() {
|
|
|
436
542
|
return () => {
|
|
437
543
|
cancelled = true;
|
|
438
544
|
};
|
|
439
|
-
}, [client]);
|
|
545
|
+
}, [client, promoterMode]);
|
|
440
546
|
return { resources, loading, error };
|
|
441
547
|
}
|
|
442
548
|
function useProducts() {
|
|
443
|
-
const { client } = useResira();
|
|
549
|
+
const { client, promoterMode } = useResira();
|
|
444
550
|
const [products, setProducts] = useState([]);
|
|
445
551
|
const [loading, setLoading] = useState(true);
|
|
446
552
|
const [error, setError] = useState(null);
|
|
447
553
|
useEffect(() => {
|
|
448
554
|
let cancelled = false;
|
|
555
|
+
const cacheKey = `${client.getBaseUrl()}:products`;
|
|
556
|
+
if (promoterMode.enabled) {
|
|
557
|
+
const cached = readCache(productsCache, cacheKey);
|
|
558
|
+
if (cached) {
|
|
559
|
+
setProducts(cached.value);
|
|
560
|
+
setLoading(false);
|
|
561
|
+
setError(null);
|
|
562
|
+
return () => {
|
|
563
|
+
cancelled = true;
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
449
567
|
async function fetchProducts() {
|
|
450
568
|
try {
|
|
451
569
|
setLoading(true);
|
|
452
570
|
setError(null);
|
|
453
571
|
const data = await client.listProducts();
|
|
454
572
|
if (!cancelled) {
|
|
455
|
-
|
|
573
|
+
const nextProducts = data.products ?? [];
|
|
574
|
+
setProducts(nextProducts);
|
|
575
|
+
if (promoterMode.enabled) {
|
|
576
|
+
writeCache(productsCache, cacheKey, nextProducts, promoterMode.cacheTtlMs);
|
|
577
|
+
}
|
|
456
578
|
}
|
|
457
579
|
} catch (err) {
|
|
458
580
|
if (!cancelled) {
|
|
581
|
+
if (promoterMode.enabled && promoterMode.useStaleDataOnError) {
|
|
582
|
+
const stale = readStaleCache(productsCache, cacheKey);
|
|
583
|
+
if (stale) {
|
|
584
|
+
setProducts(stale.value);
|
|
585
|
+
setError(null);
|
|
586
|
+
setLoading(false);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
459
590
|
const message = err instanceof Error ? err.message : "Failed to load services";
|
|
460
591
|
setError(message);
|
|
461
592
|
}
|
|
@@ -469,7 +600,7 @@ function useProducts() {
|
|
|
469
600
|
return () => {
|
|
470
601
|
cancelled = true;
|
|
471
602
|
};
|
|
472
|
-
}, [client]);
|
|
603
|
+
}, [client, promoterMode]);
|
|
473
604
|
return { products, loading, error };
|
|
474
605
|
}
|
|
475
606
|
function usePaymentIntent() {
|
|
@@ -2181,7 +2312,14 @@ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
2181
2312
|
function validateGuestForm(values, labels, contactMode = "email-required") {
|
|
2182
2313
|
const errors = {};
|
|
2183
2314
|
if (!values.guestName.trim()) errors.guestName = labels.required;
|
|
2184
|
-
if (contactMode === "
|
|
2315
|
+
if (contactMode === "phone-required") {
|
|
2316
|
+
if (!values.guestPhone.trim()) {
|
|
2317
|
+
errors.guestPhone = labels.required;
|
|
2318
|
+
}
|
|
2319
|
+
if (values.guestEmail.trim() && !EMAIL_RE.test(values.guestEmail.trim())) {
|
|
2320
|
+
errors.guestEmail = labels.invalidEmail;
|
|
2321
|
+
}
|
|
2322
|
+
} else if (contactMode === "either") {
|
|
2185
2323
|
const hasPhone = values.guestPhone.trim().length > 0;
|
|
2186
2324
|
const hasEmail = values.guestEmail.trim().length > 0;
|
|
2187
2325
|
if (!hasPhone && !hasEmail) {
|
|
@@ -2201,8 +2339,9 @@ function validateGuestForm(values, labels, contactMode = "email-required") {
|
|
|
2201
2339
|
return errors;
|
|
2202
2340
|
}
|
|
2203
2341
|
function GuestForm({ values, onChange, errors = {} }) {
|
|
2204
|
-
const { locale, domain } = useResira();
|
|
2342
|
+
const { locale, domain, promoterMode } = useResira();
|
|
2205
2343
|
const isRestaurant = domain === "restaurant";
|
|
2344
|
+
const isPromoterPhoneMode = promoterMode.enabled && promoterMode.contactMode === "phone-required";
|
|
2206
2345
|
const update = useCallback(
|
|
2207
2346
|
(field, value) => {
|
|
2208
2347
|
onChange({ ...values, [field]: value });
|
|
@@ -2318,7 +2457,7 @@ function GuestForm({ values, onChange, errors = {} }) {
|
|
|
2318
2457
|
] }),
|
|
2319
2458
|
errors.guestName && /* @__PURE__ */ jsx("span", { className: "resira-field-error", children: errors.guestName })
|
|
2320
2459
|
] }),
|
|
2321
|
-
/* @__PURE__ */ jsxs("div", { className: "resira-field resira-field--half", children: [
|
|
2460
|
+
!isPromoterPhoneMode && /* @__PURE__ */ jsxs("div", { className: "resira-field resira-field--half", children: [
|
|
2322
2461
|
/* @__PURE__ */ jsxs("label", { className: "resira-field-label", children: [
|
|
2323
2462
|
locale.email,
|
|
2324
2463
|
/* @__PURE__ */ jsx("span", { children: "*" })
|
|
@@ -2341,21 +2480,43 @@ function GuestForm({ values, onChange, errors = {} }) {
|
|
|
2341
2480
|
] })
|
|
2342
2481
|
] }),
|
|
2343
2482
|
/* @__PURE__ */ jsxs("div", { className: "resira-field", children: [
|
|
2344
|
-
/* @__PURE__ */
|
|
2483
|
+
/* @__PURE__ */ jsxs("label", { className: "resira-field-label", children: [
|
|
2484
|
+
locale.phone,
|
|
2485
|
+
isPromoterPhoneMode && /* @__PURE__ */ jsx("span", { children: "*" })
|
|
2486
|
+
] }),
|
|
2345
2487
|
/* @__PURE__ */ jsxs("div", { className: "resira-field-input-wrap", children: [
|
|
2346
2488
|
/* @__PURE__ */ jsx(PhoneIcon, { size: 15, className: "resira-field-input-icon" }),
|
|
2347
2489
|
/* @__PURE__ */ jsx(
|
|
2348
2490
|
"input",
|
|
2349
2491
|
{
|
|
2350
2492
|
type: "tel",
|
|
2351
|
-
className:
|
|
2493
|
+
className: `resira-field-input resira-field-input--sm${errors.guestPhone ? " resira-field-input--error" : ""}`,
|
|
2352
2494
|
value: values.guestPhone,
|
|
2353
2495
|
onChange: (e) => update("guestPhone", e.target.value),
|
|
2354
2496
|
placeholder: locale.phone,
|
|
2355
2497
|
autoComplete: "tel"
|
|
2356
2498
|
}
|
|
2357
2499
|
)
|
|
2358
|
-
] })
|
|
2500
|
+
] }),
|
|
2501
|
+
errors.guestPhone && /* @__PURE__ */ jsx("span", { className: "resira-field-error", children: errors.guestPhone })
|
|
2502
|
+
] }),
|
|
2503
|
+
isPromoterPhoneMode && /* @__PURE__ */ jsxs("div", { className: "resira-field", children: [
|
|
2504
|
+
/* @__PURE__ */ jsx("label", { className: "resira-field-label", children: locale.email }),
|
|
2505
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-field-input-wrap", children: [
|
|
2506
|
+
/* @__PURE__ */ jsx(MailIcon, { size: 15, className: "resira-field-input-icon" }),
|
|
2507
|
+
/* @__PURE__ */ jsx(
|
|
2508
|
+
"input",
|
|
2509
|
+
{
|
|
2510
|
+
type: "email",
|
|
2511
|
+
className: `resira-field-input resira-field-input--sm${errors.guestEmail ? " resira-field-input--error" : ""}`,
|
|
2512
|
+
value: values.guestEmail,
|
|
2513
|
+
onChange: (e) => update("guestEmail", e.target.value),
|
|
2514
|
+
placeholder: locale.email,
|
|
2515
|
+
autoComplete: "email"
|
|
2516
|
+
}
|
|
2517
|
+
)
|
|
2518
|
+
] }),
|
|
2519
|
+
errors.guestEmail && /* @__PURE__ */ jsx("span", { className: "resira-field-error", children: errors.guestEmail })
|
|
2359
2520
|
] }),
|
|
2360
2521
|
/* @__PURE__ */ jsxs("div", { className: "resira-field", children: [
|
|
2361
2522
|
/* @__PURE__ */ jsx("label", { className: "resira-field-label", children: locale.notes }),
|
|
@@ -2383,7 +2544,9 @@ function WaiverConsent({
|
|
|
2383
2544
|
discountCode,
|
|
2384
2545
|
onDiscountCodeChange,
|
|
2385
2546
|
onPromoValidated,
|
|
2386
|
-
error
|
|
2547
|
+
error,
|
|
2548
|
+
disablePromoValidation = false,
|
|
2549
|
+
hidePromoInput = false
|
|
2387
2550
|
}) {
|
|
2388
2551
|
const { client, locale, showTerms, showWaiver, termsText, waiverText, refundPolicy } = useResira();
|
|
2389
2552
|
const [promoValidating, setPromoValidating] = useState(false);
|
|
@@ -2391,6 +2554,12 @@ function WaiverConsent({
|
|
|
2391
2554
|
const handleApplyPromo = useCallback(async () => {
|
|
2392
2555
|
const code = discountCode.trim();
|
|
2393
2556
|
if (!code) return;
|
|
2557
|
+
if (disablePromoValidation) {
|
|
2558
|
+
const deferredResult = { valid: true };
|
|
2559
|
+
setPromoResult(deferredResult);
|
|
2560
|
+
onPromoValidated?.(deferredResult);
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2394
2563
|
setPromoValidating(true);
|
|
2395
2564
|
setPromoResult(null);
|
|
2396
2565
|
try {
|
|
@@ -2404,7 +2573,7 @@ function WaiverConsent({
|
|
|
2404
2573
|
} finally {
|
|
2405
2574
|
setPromoValidating(false);
|
|
2406
2575
|
}
|
|
2407
|
-
}, [client, discountCode, locale.discountInvalid, onPromoValidated]);
|
|
2576
|
+
}, [client, discountCode, locale.discountInvalid, onPromoValidated, disablePromoValidation]);
|
|
2408
2577
|
const handleCodeChange = useCallback(
|
|
2409
2578
|
(code) => {
|
|
2410
2579
|
onDiscountCodeChange(code);
|
|
@@ -2448,7 +2617,7 @@ function WaiverConsent({
|
|
|
2448
2617
|
] }, i)) })
|
|
2449
2618
|
] }),
|
|
2450
2619
|
error && /* @__PURE__ */ jsx("p", { className: "resira-waiver-error", children: error }),
|
|
2451
|
-
/* @__PURE__ */ jsxs("div", { className: "resira-waiver-promo", children: [
|
|
2620
|
+
!hidePromoInput && /* @__PURE__ */ jsxs("div", { className: "resira-waiver-promo", children: [
|
|
2452
2621
|
/* @__PURE__ */ jsxs("div", { className: "resira-waiver-promo-row", children: [
|
|
2453
2622
|
/* @__PURE__ */ jsx(TagIcon, { size: 14 }),
|
|
2454
2623
|
/* @__PURE__ */ jsx(
|
|
@@ -2479,11 +2648,7 @@ function WaiverConsent({
|
|
|
2479
2648
|
className: `resira-waiver-discount-result ${promoResult.valid ? "resira-waiver-discount-result--valid" : "resira-waiver-discount-result--invalid"}`,
|
|
2480
2649
|
children: promoResult.valid ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2481
2650
|
/* @__PURE__ */ jsx(CheckCircleIcon, { size: 13 }),
|
|
2482
|
-
/* @__PURE__ */
|
|
2483
|
-
locale.discountApplied,
|
|
2484
|
-
" ",
|
|
2485
|
-
promoResult.discountType === "percent" ? `(${promoResult.discountValue}% off)` : promoResult.discountType === "fixed" && promoResult.discountValue ? `(${(promoResult.discountValue / 100).toFixed(2)} ${promoResult.currency ?? ""} off)` : ""
|
|
2486
|
-
] })
|
|
2651
|
+
/* @__PURE__ */ jsx("span", { children: disablePromoValidation ? "Code saved. Final validation happens at payment." : `${locale.discountApplied} ${promoResult.discountType === "percent" ? `(${promoResult.discountValue}% off)` : promoResult.discountType === "fixed" && promoResult.discountValue ? `(${(promoResult.discountValue / 100).toFixed(2)} ${promoResult.currency ?? ""} off)` : ""}` })
|
|
2487
2652
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2488
2653
|
/* @__PURE__ */ jsx(AlertCircleIcon, { size: 13 }),
|
|
2489
2654
|
/* @__PURE__ */ jsx("span", { children: promoResult.error ?? locale.discountInvalid })
|
|
@@ -2974,12 +3139,14 @@ function ResiraBookingWidget() {
|
|
|
2974
3139
|
onStepChange,
|
|
2975
3140
|
onBookingComplete,
|
|
2976
3141
|
onError,
|
|
2977
|
-
checkoutSessionToken
|
|
3142
|
+
checkoutSessionToken,
|
|
3143
|
+
promoterMode
|
|
2978
3144
|
} = useResira();
|
|
2979
3145
|
const isDateBased = domain === "rental";
|
|
2980
3146
|
const isTimeBased = domain === "restaurant" || domain === "watersport" || domain === "service";
|
|
2981
3147
|
const isServiceBased = domain === "watersport" || domain === "service";
|
|
2982
3148
|
const hasPayment = !!stripePublishableKey;
|
|
3149
|
+
const promoterEnabled = promoterMode.enabled;
|
|
2983
3150
|
const isCheckoutMode = !!checkoutSessionToken;
|
|
2984
3151
|
const {
|
|
2985
3152
|
session: checkoutSession,
|
|
@@ -3012,6 +3179,14 @@ function ResiraBookingWidget() {
|
|
|
3012
3179
|
error: productsError
|
|
3013
3180
|
} = useProducts();
|
|
3014
3181
|
const [selectedProduct, setSelectedProduct] = useState(null);
|
|
3182
|
+
const displayResources = useMemo(
|
|
3183
|
+
() => promoterEnabled && promoterMode.disableImages ? resources.map((resource) => ({ ...resource, imageUrl: void 0, images: void 0 })) : resources,
|
|
3184
|
+
[resources, promoterEnabled, promoterMode.disableImages]
|
|
3185
|
+
);
|
|
3186
|
+
const displayProducts = useMemo(
|
|
3187
|
+
() => promoterEnabled && promoterMode.disableImages ? products.map((product) => ({ ...product, imageUrl: void 0, images: void 0 })) : products,
|
|
3188
|
+
[products, promoterEnabled, promoterMode.disableImages]
|
|
3189
|
+
);
|
|
3015
3190
|
const [selection, setSelection] = useState({
|
|
3016
3191
|
partySize: domainConfig.defaultPartySize ?? 2,
|
|
3017
3192
|
duration: domainConfig.defaultDuration
|
|
@@ -3201,8 +3376,14 @@ function ResiraBookingWidget() {
|
|
|
3201
3376
|
const handleDateSelect = useCallback(
|
|
3202
3377
|
(start, end) => {
|
|
3203
3378
|
setSelection((prev) => ({ ...prev, startDate: start, endDate: end }));
|
|
3379
|
+
if (promoterEnabled && promoterMode.autoAdvanceAvailability && step === "availability" && end) {
|
|
3380
|
+
const nextIdx = stepIndex(step, STEPS) + 1;
|
|
3381
|
+
if (nextIdx < STEPS.length) {
|
|
3382
|
+
setStep(STEPS[nextIdx]);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3204
3385
|
},
|
|
3205
|
-
[]
|
|
3386
|
+
[promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS]
|
|
3206
3387
|
);
|
|
3207
3388
|
const handleSlotSelect = useCallback(
|
|
3208
3389
|
(start, end) => {
|
|
@@ -3213,8 +3394,14 @@ function ResiraBookingWidget() {
|
|
|
3213
3394
|
startTime: start,
|
|
3214
3395
|
endTime: end
|
|
3215
3396
|
}));
|
|
3397
|
+
if (promoterEnabled && promoterMode.autoAdvanceAvailability && step === "availability") {
|
|
3398
|
+
const nextIdx = stepIndex(step, STEPS) + 1;
|
|
3399
|
+
if (nextIdx < STEPS.length) {
|
|
3400
|
+
setStep(STEPS[nextIdx]);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3216
3403
|
},
|
|
3217
|
-
[slotDate]
|
|
3404
|
+
[slotDate, promoterEnabled, promoterMode.autoAdvanceAvailability, step, STEPS]
|
|
3218
3405
|
);
|
|
3219
3406
|
const handlePartySizeChange = useCallback((size) => {
|
|
3220
3407
|
setSelection((prev) => ({ ...prev, partySize: size }));
|
|
@@ -3308,7 +3495,7 @@ function ResiraBookingWidget() {
|
|
|
3308
3495
|
}
|
|
3309
3496
|
}, [STEPS, currentIndex, step, resetPaymentIntent]);
|
|
3310
3497
|
const handleDetailsSubmit = useCallback(async () => {
|
|
3311
|
-
const contactMode = domain === "restaurant" ? "either" : "email-required";
|
|
3498
|
+
const contactMode = promoterEnabled ? promoterMode.contactMode : domain === "restaurant" ? "either" : "email-required";
|
|
3312
3499
|
const errors = validateGuestForm(guest, {
|
|
3313
3500
|
required: locale.required,
|
|
3314
3501
|
invalidEmail: locale.invalidEmail,
|
|
@@ -3342,7 +3529,7 @@ function ResiraBookingWidget() {
|
|
|
3342
3529
|
return;
|
|
3343
3530
|
}
|
|
3344
3531
|
setStep(STEPS[currentIndex + 1]);
|
|
3345
|
-
}, [guest, locale, STEPS, currentIndex, domain, hasPayment, activeResourceId, selection, submit]);
|
|
3532
|
+
}, [guest, locale, STEPS, currentIndex, domain, hasPayment, activeResourceId, selection, submit, termsAccepted, waiverAccepted, promoterEnabled, promoterMode.contactMode]);
|
|
3346
3533
|
const handlePaymentSuccess = useCallback(
|
|
3347
3534
|
async (paymentIntentId) => {
|
|
3348
3535
|
if (paymentIntent?.reservationId) {
|
|
@@ -3364,7 +3551,7 @@ function ResiraBookingWidget() {
|
|
|
3364
3551
|
onError?.("payment_error", msg);
|
|
3365
3552
|
}, [onError]);
|
|
3366
3553
|
const handleCheckoutSubmit = useCallback(async () => {
|
|
3367
|
-
const contactMode = "email-required";
|
|
3554
|
+
const contactMode = promoterEnabled ? promoterMode.contactMode : "email-required";
|
|
3368
3555
|
const errors = validateGuestForm(guest, {
|
|
3369
3556
|
required: locale.required,
|
|
3370
3557
|
invalidEmail: locale.invalidEmail,
|
|
@@ -3391,7 +3578,7 @@ function ResiraBookingWidget() {
|
|
|
3391
3578
|
} else {
|
|
3392
3579
|
setStep("payment");
|
|
3393
3580
|
}
|
|
3394
|
-
}, [guest, locale, checkoutSession, termsAccepted, createPayment, paymentPayload, confirmPayment]);
|
|
3581
|
+
}, [guest, locale, checkoutSession, termsAccepted, createPayment, paymentPayload, confirmPayment, promoterEnabled, promoterMode.contactMode]);
|
|
3395
3582
|
const handleSubmitNoPayment = useCallback(async () => {
|
|
3396
3583
|
if (showTerms && !termsAccepted) {
|
|
3397
3584
|
setTermsError(locale.termsRequired);
|
|
@@ -3517,8 +3704,8 @@ function ResiraBookingWidget() {
|
|
|
3517
3704
|
step === "resource" && isServiceBased && /* @__PURE__ */ jsx(
|
|
3518
3705
|
ProductSelector,
|
|
3519
3706
|
{
|
|
3520
|
-
products:
|
|
3521
|
-
resources,
|
|
3707
|
+
products: displayProducts.filter((p) => p.active),
|
|
3708
|
+
resources: displayResources,
|
|
3522
3709
|
selectedId: selectedProduct?.id,
|
|
3523
3710
|
onSelect: handleProductSelect,
|
|
3524
3711
|
loading: productsLoading,
|
|
@@ -3528,7 +3715,7 @@ function ResiraBookingWidget() {
|
|
|
3528
3715
|
step === "resource" && !isServiceBased && /* @__PURE__ */ jsx(
|
|
3529
3716
|
ResourcePicker,
|
|
3530
3717
|
{
|
|
3531
|
-
resources,
|
|
3718
|
+
resources: displayResources,
|
|
3532
3719
|
selectedIds: selectedResourceIds,
|
|
3533
3720
|
onSelect: handleResourceSelect,
|
|
3534
3721
|
allowMultiSelect,
|
|
@@ -3663,7 +3850,9 @@ function ResiraBookingWidget() {
|
|
|
3663
3850
|
discountCode,
|
|
3664
3851
|
onDiscountCodeChange: setDiscountCode,
|
|
3665
3852
|
onPromoValidated: handlePromoValidated,
|
|
3666
|
-
error: termsError ?? void 0
|
|
3853
|
+
error: termsError ?? void 0,
|
|
3854
|
+
disablePromoValidation: promoterEnabled && promoterMode.disablePromoValidation,
|
|
3855
|
+
hidePromoInput: promoterEnabled && promoterMode.hidePromoInput
|
|
3667
3856
|
}
|
|
3668
3857
|
) }),
|
|
3669
3858
|
paymentError && hasPayment && /* @__PURE__ */ jsxs("div", { className: "resira-error", style: { padding: "12px 0" }, children: [
|
|
@@ -3749,7 +3938,9 @@ function ResiraBookingWidget() {
|
|
|
3749
3938
|
},
|
|
3750
3939
|
onPromoValidated: () => {
|
|
3751
3940
|
},
|
|
3752
|
-
error: termsError ?? void 0
|
|
3941
|
+
error: termsError ?? void 0,
|
|
3942
|
+
disablePromoValidation: promoterEnabled && promoterMode.disablePromoValidation,
|
|
3943
|
+
hidePromoInput: promoterEnabled && promoterMode.hidePromoInput
|
|
3753
3944
|
}
|
|
3754
3945
|
) }),
|
|
3755
3946
|
paymentError && /* @__PURE__ */ jsxs("div", { className: "resira-error", style: { padding: "12px 0" }, children: [
|