@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/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 showStepIndicator = config?.showStepIndicator ?? true;
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
- setResources(data.resources ?? []);
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
- setProducts(data.products ?? []);
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 === "either") {
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__ */ jsx("label", { className: "resira-field-label", children: locale.phone }),
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: "resira-field-input resira-field-input--sm",
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__ */ jsxs("span", { children: [
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: products.filter((p) => p.active),
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: [