@asksable/site-connector 0.3.1 → 0.4.0

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.
@@ -1,22 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { loadStripe } from '@stripe/stripe-js';
5
- import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js';
5
+ import { Elements, PaymentElement, useElements, useStripe, } from '@stripe/react-stripe-js';
6
6
  import { Skeleton } from 'boneyard-js/react';
7
7
  // Side-effect: registers all named bones with the boneyard runtime.
8
8
  // Regenerate by running `npx boneyard-js build` against a host site
9
9
  // that mounts <BookingWidgetPanel /> in its loaded state.
10
10
  import './bones/registry.js';
11
- import { useSableSiteClient, useSableSiteConfig, useTranslation } from './provider.js';
12
- import { DEFAULT_LOCALE, localeToIntl, pickLocaleField } from './translations.js';
11
+ import { BookingWidgetPlaceholder } from './booking-widget-placeholder.js';
12
+ import { useSableSiteAnalytics, useSableSiteClient, useSableSiteConfig, useTranslation, } from './provider.js';
13
+ import { DEFAULT_LOCALE, localeToIntl, pickLocaleField, } from './translations.js';
13
14
  const stripePromises = new Map();
14
15
  function getStripePromise(publishableKey, connectAccountId) {
15
16
  const key = `${publishableKey}:${connectAccountId}`;
16
17
  const cached = stripePromises.get(key);
17
18
  if (cached)
18
19
  return cached;
19
- const promise = loadStripe(publishableKey, { stripeAccount: connectAccountId });
20
+ const promise = loadStripe(publishableKey, {
21
+ stripeAccount: connectAccountId,
22
+ });
20
23
  stripePromises.set(key, promise);
21
24
  return promise;
22
25
  }
@@ -51,6 +54,7 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
51
54
  const client = useSableSiteClient();
52
55
  const { siteSlug } = useSableSiteConfig();
53
56
  const { t, locale } = useTranslation();
57
+ const analytics = useSableSiteAnalytics();
54
58
  const intlLocale = localeToIntl(locale);
55
59
  const [setup, setSetup] = useState(null);
56
60
  const [isSetupLoading, setIsSetupLoading] = useState(true);
@@ -103,7 +107,11 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
103
107
  // Dev-only: force viewState to 'details' when previewing a payment
104
108
  // variant, since the payment panel renders inside the details form.
105
109
  useEffect(() => {
106
- if (__devForceState === 'payment-full' || __devForceState === 'payment-deposit') {
110
+ analytics.capture('sable_booking_widget_viewed', { mode });
111
+ }, [analytics, mode]);
112
+ useEffect(() => {
113
+ if (__devForceState === 'payment-full' ||
114
+ __devForceState === 'payment-deposit') {
107
115
  setViewState('details');
108
116
  }
109
117
  }, [__devForceState]);
@@ -176,7 +184,9 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
176
184
  })
177
185
  .catch((nextError) => {
178
186
  if (!cancelled) {
179
- setError(nextError instanceof Error ? nextError.message : 'Unable to load booking setup.');
187
+ setError(nextError instanceof Error
188
+ ? nextError.message
189
+ : 'Unable to load booking setup.');
180
190
  }
181
191
  })
182
192
  .finally(() => {
@@ -188,9 +198,12 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
188
198
  cancelled = true;
189
199
  };
190
200
  }, [client, siteSlug]);
191
- const selectedService = useMemo(() => setup?.services.find((service) => service._id === selectedServiceId) ?? null, [selectedServiceId, setup]);
201
+ const selectedService = useMemo(() => setup?.services.find((service) => service._id === selectedServiceId) ??
202
+ null, [selectedServiceId, setup]);
192
203
  const selectedServiceRequiresSlot = selectedService?.publicBookingMode !== 'async';
193
- const selectedServicePath = selectedServiceRequiresSlot ? 'scheduled' : 'async';
204
+ const selectedServicePath = selectedServiceRequiresSlot
205
+ ? 'scheduled'
206
+ : 'async';
194
207
  const selectedServiceHasIntakeForm = hasIntakeFormSections(selectedService);
195
208
  const isIntakeComplete = useMemo(() => !selectedService?.intakeForm ||
196
209
  areRequiredIntakeFieldsComplete(selectedService.intakeForm, intakeResponses, selectedServicePath), [intakeResponses, selectedService?.intakeForm, selectedServicePath]);
@@ -216,7 +229,7 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
216
229
  const uncategorizedKey = '__uncategorized__';
217
230
  for (const service of visibleServices) {
218
231
  const category = service.categoryId != null
219
- ? categoriesById.get(service.categoryId) ?? null
232
+ ? (categoriesById.get(service.categoryId) ?? null)
220
233
  : null;
221
234
  const key = category?._id ?? uncategorizedKey;
222
235
  const existing = groups.get(key);
@@ -285,7 +298,8 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
285
298
  // wiped on the first render before the staff list arrives.
286
299
  if (availableStaff.length === 0)
287
300
  return;
288
- if (selectedStaffId && !availableStaff.some((staffMember) => staffMember._id === selectedStaffId)) {
301
+ if (selectedStaffId &&
302
+ !availableStaff.some((staffMember) => staffMember._id === selectedStaffId)) {
289
303
  setSelectedStaffId(null);
290
304
  }
291
305
  }, [availableStaff, selectedStaffId]);
@@ -350,7 +364,9 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
350
364
  if (!cancelled) {
351
365
  setAvailabilityByDate(new Map());
352
366
  setSelectedDate(null);
353
- setError(nextError instanceof Error ? nextError.message : t('calendarLoadError'));
367
+ setError(nextError instanceof Error
368
+ ? nextError.message
369
+ : t('calendarLoadError'));
354
370
  }
355
371
  })
356
372
  .finally(() => {
@@ -361,7 +377,15 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
361
377
  return () => {
362
378
  cancelled = true;
363
379
  };
364
- }, [calendarMonth, client, selectedDate, selectedService?.publicBookingMode, selectedServiceId, selectedStaffId, siteSlug]);
380
+ }, [
381
+ calendarMonth,
382
+ client,
383
+ selectedDate,
384
+ selectedService?.publicBookingMode,
385
+ selectedServiceId,
386
+ selectedStaffId,
387
+ siteSlug,
388
+ ]);
365
389
  useEffect(() => {
366
390
  if (!selectedServiceId || selectedService?.publicBookingMode === 'async') {
367
391
  return;
@@ -374,7 +398,14 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
374
398
  calendarMonth: nextMonth,
375
399
  cacheRef: availabilityCacheRef,
376
400
  });
377
- }, [calendarMonth, client, selectedService?.publicBookingMode, selectedServiceId, selectedStaffId, siteSlug]);
401
+ }, [
402
+ calendarMonth,
403
+ client,
404
+ selectedService?.publicBookingMode,
405
+ selectedServiceId,
406
+ selectedStaffId,
407
+ siteSlug,
408
+ ]);
378
409
  useEffect(() => {
379
410
  setSelectedSlot(null);
380
411
  setPendingSlotKey(null);
@@ -393,7 +424,12 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
393
424
  else {
394
425
  setViewState('slots');
395
426
  }
396
- }, [selectedDate, selectedService?.publicBookingMode, selectedServiceId, selectedStaffId]);
427
+ }, [
428
+ selectedDate,
429
+ selectedService?.publicBookingMode,
430
+ selectedServiceId,
431
+ selectedStaffId,
432
+ ]);
397
433
  useEffect(() => {
398
434
  setIntakeResponses({});
399
435
  setQuote(null);
@@ -423,7 +459,9 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
423
459
  })
424
460
  .catch((nextError) => {
425
461
  if (!cancelled) {
426
- setError(nextError instanceof Error ? nextError.message : 'Unable to calculate price.');
462
+ setError(nextError instanceof Error
463
+ ? nextError.message
464
+ : 'Unable to calculate price.');
427
465
  }
428
466
  })
429
467
  .finally(() => {
@@ -435,7 +473,14 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
435
473
  cancelled = true;
436
474
  window.clearTimeout(handle);
437
475
  };
438
- }, [client, intakeResponses, isIntakeComplete, selectedService?.pricingMode, selectedServiceId, siteSlug]);
476
+ }, [
477
+ client,
478
+ intakeResponses,
479
+ isIntakeComplete,
480
+ selectedService?.pricingMode,
481
+ selectedServiceId,
482
+ siteSlug,
483
+ ]);
439
484
  useEffect(() => {
440
485
  return () => {
441
486
  if (!holdId) {
@@ -575,9 +620,15 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
575
620
  }
576
621
  return set;
577
622
  }, [availabilityByDate, selectedDate]);
578
- const selectedDateSlots = selectedDate ? filteredAvailabilityByDate.get(selectedDate) ?? [] : [];
579
- const selectedHeldStaff = availableStaff.find((staffMember) => staffMember._id === heldStaffId) ?? selectedStaff ?? null;
580
- const holdSecondsRemaining = holdExpiresAt !== null ? Math.max(0, Math.floor((holdExpiresAt - holdNow) / 1000)) : null;
623
+ const selectedDateSlots = selectedDate
624
+ ? (filteredAvailabilityByDate.get(selectedDate) ?? [])
625
+ : [];
626
+ const selectedHeldStaff = availableStaff.find((staffMember) => staffMember._id === heldStaffId) ??
627
+ selectedStaff ??
628
+ null;
629
+ const holdSecondsRemaining = holdExpiresAt !== null
630
+ ? Math.max(0, Math.floor((holdExpiresAt - holdNow) / 1000))
631
+ : null;
581
632
  const monthOptions = useMemo(() => getMonthOptions(calendarMonth, 4, intlLocale), [calendarMonth, intlLocale]);
582
633
  const calendarDays = useMemo(() => buildCalendarDays(calendarMonth), [calendarMonth]);
583
634
  // Mobile 4-step gates. canAdvanceStepN = "the user can move PAST
@@ -622,11 +673,15 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
622
673
  const slotKey = `${slot.time}-${slot.endTime}`;
623
674
  const isPending = pendingSlotKey === slotKey;
624
675
  const isActive = isPending ||
625
- (selectedSlot?.time === slot.time && selectedSlot?.endTime === slot.endTime);
626
- return (_jsx("button", { type: "button", className: `bw-slot${isActive ? ' is-active' : ''}${isPending ? ' is-pending' : ''}`, style: { '--bw-slot-i': index }, onClick: () => void handleSlotSelect(slot), disabled: isSubmitting, children: isPending ? t('slotSecuring') : formatTimeLabel(slot.time, intlLocale) }, slotKey));
676
+ (selectedSlot?.time === slot.time &&
677
+ selectedSlot?.endTime === slot.endTime);
678
+ return (_jsx("button", { type: "button", className: `bw-slot${isActive ? ' is-active' : ''}${isPending ? ' is-pending' : ''}`, style: { '--bw-slot-i': index }, onClick: () => void handleSlotSelect(slot), disabled: isSubmitting, children: isPending
679
+ ? t('slotSecuring')
680
+ : formatTimeLabel(slot.time, intlLocale) }, slotKey));
627
681
  }) })) : (_jsx("p", { className: "bw-no-slots", children: t('slotsEmptyForDate') }))) : null }, slotsAreaKey));
628
682
  const canSubmit = Boolean(selectedService &&
629
- (!selectedServiceRequiresSlot || (selectedDate && selectedSlot && holdId)) &&
683
+ (!selectedServiceRequiresSlot ||
684
+ (selectedDate && selectedSlot && holdId)) &&
630
685
  isIntakeComplete &&
631
686
  (selectedService.pricingMode !== 'calculated' || quote) &&
632
687
  customerName.trim() &&
@@ -667,7 +722,7 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
667
722
  return null;
668
723
  const visibleBlockers = submitBlockers.slice(0, 5);
669
724
  const hiddenCount = submitBlockers.length - visibleBlockers.length;
670
- return (_jsxs("div", { className: "bw-submit-help", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "bw-submit-help-title", children: isQuoteLoading ? t('helpCalculating') : t('helpComplete') }), !isQuoteLoading && visibleBlockers.length > 0 ? (_jsxs("ul", { children: [visibleBlockers.map((blocker) => (_jsx("li", { children: blocker }, blocker))), hiddenCount > 0 ? _jsx("li", { children: t('helpMoreFields', { count: hiddenCount }) }) : null] })) : null] }));
725
+ return (_jsxs("div", { className: "bw-submit-help", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "bw-submit-help-title", children: isQuoteLoading ? t('helpCalculating') : t('helpComplete') }), !isQuoteLoading && visibleBlockers.length > 0 ? (_jsxs("ul", { children: [visibleBlockers.map((blocker) => (_jsx("li", { children: blocker }, blocker))), hiddenCount > 0 ? (_jsx("li", { children: t('helpMoreFields', { count: hiddenCount }) })) : null] })) : null] }));
671
726
  }
672
727
  function renderContactFields(idSuffix) {
673
728
  const nameId = `bw-name${idSuffix}`;
@@ -688,6 +743,12 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
688
743
  return (_jsxs(_Fragment, { children: [contactFields, intakeFields] }));
689
744
  }
690
745
  function handleServiceSelect(service) {
746
+ analytics.capture('sable_booking_service_selected', {
747
+ service_id: service._id,
748
+ service_name: pickLocaleField(service, 'name', locale) ?? service.name,
749
+ booking_mode: service.publicBookingMode ?? 'scheduled',
750
+ requires_payment: service.requiresPayment === true,
751
+ });
691
752
  if (holdId) {
692
753
  void client
693
754
  .releasePublicBookingHold({ siteSlug, holdId, sessionToken })
@@ -809,7 +870,8 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
809
870
  async function handleConfirmBooking() {
810
871
  if (!selectedServiceId ||
811
872
  !selectedService ||
812
- (selectedServiceRequiresSlot && (!selectedDate || !selectedSlot || !holdId))) {
873
+ (selectedServiceRequiresSlot &&
874
+ (!selectedDate || !selectedSlot || !holdId))) {
813
875
  return;
814
876
  }
815
877
  setIsSubmitting(true);
@@ -831,6 +893,11 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
831
893
  try {
832
894
  await onRescheduleSubmit({ newStartTime, newEndTime });
833
895
  setSuccess(t('successRescheduleConfirmed'));
896
+ analytics.capture('sable_booking_rescheduled', {
897
+ service_id: selectedServiceId,
898
+ service_name: pickLocaleField(selectedService, 'name', locale) ??
899
+ selectedService.name,
900
+ });
834
901
  setMobileStep(4);
835
902
  }
836
903
  catch (nextError) {
@@ -848,11 +915,16 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
848
915
  siteSlug,
849
916
  serviceId: selectedServiceId,
850
917
  staffMemberId: selectedServiceRequiresSlot
851
- ? heldStaffId ?? selectedStaffId ?? selectedSlot?.availableStaffIds[0]
918
+ ? (heldStaffId ??
919
+ selectedStaffId ??
920
+ selectedSlot?.availableStaffIds[0])
852
921
  : undefined,
853
922
  startTime: newStartTime,
854
923
  endTime: newEndTime,
855
- timezone: selectedHeldStaff?.timezone ?? availableStaff[0]?.timezone ?? setup?.workspaceTimezone ?? 'UTC',
924
+ timezone: selectedHeldStaff?.timezone ??
925
+ availableStaff[0]?.timezone ??
926
+ setup?.workspaceTimezone ??
927
+ 'UTC',
856
928
  deliveryMethod: selectedService.deliveryMethods[0] ?? 'in-person',
857
929
  customerName: customerName.trim(),
858
930
  customerEmail: customerEmail.trim(),
@@ -861,15 +933,34 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
861
933
  intakeResponses,
862
934
  quotedTotalCents: quote?.totalCents,
863
935
  bookingHoldId: selectedServiceRequiresSlot ? holdId : undefined,
864
- bookingSessionToken: selectedServiceRequiresSlot ? sessionToken : undefined,
936
+ bookingSessionToken: selectedServiceRequiresSlot
937
+ ? sessionToken
938
+ : undefined,
865
939
  });
866
940
  if (created.payment) {
941
+ analytics.capture('sable_booking_payment_started', {
942
+ appointment_id: created.appointmentId,
943
+ service_id: selectedServiceId,
944
+ service_name: pickLocaleField(selectedService, 'name', locale) ??
945
+ selectedService.name,
946
+ amount_cents: created.payment.amountCents,
947
+ currency: created.payment.currency,
948
+ });
867
949
  setPayment(created.payment);
868
950
  setPaymentAppointmentId(created.appointmentId);
869
951
  setError(null);
870
952
  return;
871
953
  }
872
954
  setSuccess(t('successBookingConfirmed'));
955
+ analytics.capture('sable_booking_created', {
956
+ appointment_id: created.appointmentId,
957
+ service_id: selectedServiceId,
958
+ service_name: pickLocaleField(selectedService, 'name', locale) ??
959
+ selectedService.name,
960
+ booking_mode: selectedService.publicBookingMode ?? 'scheduled',
961
+ requires_payment: selectedService.requiresPayment === true,
962
+ quoted_total_cents: quote?.totalCents ?? null,
963
+ });
873
964
  setSelectedSlot(null);
874
965
  setPendingSlotKey(null);
875
966
  setHoldId(null);
@@ -886,7 +977,9 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
886
977
  setMobileStep(4);
887
978
  }
888
979
  catch (nextError) {
889
- setError(nextError instanceof Error ? nextError.message : t('errorConfirmFailed'));
980
+ setError(nextError instanceof Error
981
+ ? nextError.message
982
+ : t('errorConfirmFailed'));
890
983
  }
891
984
  finally {
892
985
  setIsSubmitting(false);
@@ -894,9 +987,10 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
894
987
  }
895
988
  if (isSetupLoading) {
896
989
  // Boneyard renders the bones JSON captured for "booking-widget"
897
- // (see ./bones/booking-widget.bones.json). Children are an empty
898
- // sentinel section since the bones own the layout during load.
899
- return (_jsx(Skeleton, { name: "booking-widget", loading: true, children: _jsx("section", { className: "bw" }) }));
990
+ // (see ./bones/booking-widget.bones.json). The CSS placeholder is
991
+ // still rendered underneath so SSR and no-bones fallbacks reserve
992
+ // the full widget height instead of letting the footer jump upward.
993
+ return (_jsx(Skeleton, { name: "booking-widget", loading: true, fallback: _jsx(BookingWidgetPlaceholder, {}), children: _jsx(BookingWidgetPlaceholder, {}) }));
900
994
  }
901
995
  if (!setup || setup.services.length === 0) {
902
996
  return (_jsx("section", { className: "bw", children: _jsx("div", { className: "bw-empty", children: t('errorNoServices') }) }));
@@ -907,22 +1001,30 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
907
1001
  }, children: t('cancelledCta') })] }) }));
908
1002
  }
909
1003
  if (success || forcedState === 'success') {
910
- return (_jsx("section", { className: "bw", children: _jsxs("div", { className: "bw-done", children: [_jsx("div", { className: "bw-done-icon", children: _jsx(CheckIcon, {}) }), _jsx("h3", { className: "bw-done-title", children: isReschedule ? t('successTitleReschedule') : t('successTitleCreate') }), _jsx("p", { className: "bw-done-text", children: isReschedule ? t('successBodyReschedule') : t('successBodyCreate') }), isReschedule ? null : (_jsx("button", { type: "button", className: "bw-btn-primary", onClick: () => {
1004
+ return (_jsx("section", { className: "bw", children: _jsxs("div", { className: "bw-done", children: [_jsx("div", { className: "bw-done-icon", children: _jsx(CheckIcon, {}) }), _jsx("h3", { className: "bw-done-title", children: isReschedule
1005
+ ? t('successTitleReschedule')
1006
+ : t('successTitleCreate') }), _jsx("p", { className: "bw-done-text", children: isReschedule ? t('successBodyReschedule') : t('successBodyCreate') }), isReschedule ? null : (_jsx("button", { type: "button", className: "bw-btn-primary", onClick: () => {
911
1007
  window.location.assign(successRedirectHref);
912
1008
  }, children: t('successCtaBackHome') }))] }) }));
913
1009
  }
914
- return (_jsx(Skeleton, { name: "booking-widget", loading: false, children: _jsxs("section", { className: "bw", "data-view-state": viewState, ref: widgetRootRef, children: [mobileHeader ? _jsx("div", { className: "bw-mobile-header", children: mobileHeader }) : null, _jsxs("div", { className: "bw-header", children: [_jsxs("div", { className: "bw-header-row", children: [_jsx("h2", { className: "bw-title bw-title--desktop", children: title ||
1010
+ return (_jsx(Skeleton, { name: "booking-widget", loading: false, children: _jsxs("section", { className: "bw", "data-view-state": viewState, ref: widgetRootRef, children: [mobileHeader ? (_jsx("div", { className: "bw-mobile-header", children: mobileHeader })) : null, _jsxs("div", { className: "bw-header", children: [_jsxs("div", { className: "bw-header-row", children: [_jsx("h2", { className: "bw-title bw-title--desktop", children: title ||
915
1011
  (isReschedule
916
1012
  ? rescheduleContext?.customerName
917
- ? t('rescheduleTitleNamed', { name: rescheduleContext.customerName })
1013
+ ? t('rescheduleTitleNamed', {
1014
+ name: rescheduleContext.customerName,
1015
+ })
918
1016
  : t('rescheduleTitle')
919
1017
  : t('pageTitle')) }), _jsx("h2", { className: "bw-title bw-title--mobile", children: isReschedule
920
1018
  ? rescheduleContext?.customerName
921
- ? t('rescheduleTitleNamed', { name: rescheduleContext.customerName })
1019
+ ? t('rescheduleTitleNamed', {
1020
+ name: rescheduleContext.customerName,
1021
+ })
922
1022
  : t('rescheduleTitle')
923
1023
  : mobileStepTitle(mobileStep, t) }), mobileStep > 1 ? (_jsx("div", { className: "bw-progress", "aria-hidden": "true", children: (selectedServiceRequiresSlot
924
1024
  ? MOBILE_PROGRESS_STEPS_SCHEDULED
925
- : MOBILE_PROGRESS_STEPS_ASYNC).map((step) => (_jsx("span", { className: `bw-dot${mobileStep >= step ? ' is-filled' : ''}` }, step))) })) : null] }), description ? _jsx("p", { className: "bw-description", children: description }) : null] }), _jsxs("div", { className: "bw-content", children: [_jsx("div", { className: "bw-body", "data-mobile-step": mobileStep, children: _jsxs("div", { className: "bw-body-inner", children: [_jsxs("div", { className: "bw-col bw-col--left", children: [_jsxs("div", { className: "bw-step-1", children: [_jsx("div", { className: "bw-service-picker", children: selectedService && !isEditingService ? (_jsxs("div", { className: "bw-svc-selected", children: [_jsxs("div", { className: "bw-svc-selected-header", children: [_jsx("label", { className: "bw-label", children: t('selectedService') }), _jsx("button", { type: "button", className: "bw-svc-change", onClick: () => setIsEditingService(true), children: t('btnChange') })] }), _jsxs("div", { className: "bw-svc-row bw-svc-row--readonly", children: [selectedService.image ? (_jsxs("span", { className: "bw-svc-image is-checked", children: [_jsx("img", { src: selectedService.image.url, alt: selectedService.image.alt ?? pickLocaleField(selectedService, 'name', locale) ?? selectedService.name, loading: "lazy" }), _jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })] })) : (_jsx("span", { className: "bw-check is-checked", children: _jsx(CheckIcon, {}) })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name }), pickLocaleField(selectedService, 'description', locale) ?? selectedService.description ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(selectedService, 'description', locale) ?? selectedService.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(selectedService.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(selectedService, undefined, intlLocale) })] })] })] })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { className: "bw-label", children: t('selectService') }), _jsx("div", { className: "bw-svc-scroll is-open", children: _jsx("div", { className: "bw-svc-scroll-wrap", children: _jsx("div", { className: "bw-svc-scroll-inner", children: serviceGroups.map((group) => (_jsxs("div", { className: "bw-svc-group", children: [group.categoryName ? (_jsx("div", { className: "bw-svc-category", children: group.categoryName })) : null, group.services.map((service) => {
1025
+ : MOBILE_PROGRESS_STEPS_ASYNC).map((step) => (_jsx("span", { className: `bw-dot${mobileStep >= step ? ' is-filled' : ''}` }, step))) })) : null] }), description ? _jsx("p", { className: "bw-description", children: description }) : null] }), _jsxs("div", { className: "bw-content", children: [_jsx("div", { className: "bw-body", "data-mobile-step": mobileStep, children: _jsxs("div", { className: "bw-body-inner", children: [_jsxs("div", { className: "bw-col bw-col--left", children: [_jsxs("div", { className: "bw-step-1", children: [_jsx("div", { className: "bw-service-picker", children: selectedService && !isEditingService ? (_jsxs("div", { className: "bw-svc-selected", children: [_jsxs("div", { className: "bw-svc-selected-header", children: [_jsx("label", { className: "bw-label", children: t('selectedService') }), _jsx("button", { type: "button", className: "bw-svc-change", onClick: () => setIsEditingService(true), children: t('btnChange') })] }), _jsxs("div", { className: "bw-svc-row bw-svc-row--readonly", children: [selectedService.image ? (_jsxs("span", { className: "bw-svc-image is-checked", children: [_jsx("img", { src: selectedService.image.url, alt: selectedService.image.alt ??
1026
+ pickLocaleField(selectedService, 'name', locale) ??
1027
+ selectedService.name, loading: "lazy" }), _jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })] })) : (_jsx("span", { className: "bw-check is-checked", children: _jsx(CheckIcon, {}) })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name }), (pickLocaleField(selectedService, 'description', locale) ?? selectedService.description) ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(selectedService, 'description', locale) ?? selectedService.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(selectedService.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(selectedService, undefined, intlLocale) })] })] })] })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { className: "bw-label", children: t('selectService') }), _jsx("div", { className: "bw-svc-scroll is-open", children: _jsx("div", { className: "bw-svc-scroll-wrap", children: _jsx("div", { className: "bw-svc-scroll-inner", children: serviceGroups.map((group) => (_jsxs("div", { className: "bw-svc-group", children: [group.categoryName ? (_jsx("div", { className: "bw-svc-category", children: group.categoryName })) : null, group.services.map((service) => {
926
1028
  const isActive = selectedServiceId === service._id;
927
1029
  return (_jsxs("button", { type: "button", className: "bw-svc-row", onMouseEnter: () => {
928
1030
  void prefetchAvailability({
@@ -942,8 +1044,12 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
942
1044
  });
943
1045
  }, onClick: () => {
944
1046
  handleServiceSelect(service);
945
- }, children: [service.image ? (_jsxs("span", { className: `bw-svc-image${isActive ? ' is-checked' : ''}`, children: [_jsx("img", { src: service.image.url, alt: service.image.alt ?? pickLocaleField(service, 'name', locale) ?? service.name, loading: "lazy" }), isActive ? (_jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })) : null] })) : (_jsx("span", { className: `bw-check${isActive ? ' is-checked' : ''}`, children: isActive ? _jsx(CheckIcon, {}) : null })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(service, 'name', locale) ?? service.name }), pickLocaleField(service, 'description', locale) ?? service.description ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(service, 'description', locale) ?? service.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(service.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(service, undefined, intlLocale) })] })] })] }, service._id));
946
- })] }, group.key))) }) }) })] })) }), selectedService && selectedServiceRequiresSlot && !isEditingService ? (_jsxs("div", { className: "bw-provider-section", children: [_jsx("div", { className: "bw-section-divider" }), _jsx("label", { className: "bw-label", children: t('selectProvider') }), providerRow] })) : null] }), _jsxs("div", { className: "bw-step-2", children: [_jsx("p", { className: "bw-step-2-heading", children: t('chooseYourService') }), _jsx("div", { className: "bw-step-2-divider" }), serviceGroups.map((group) => (_jsxs("div", { className: "bw-svc-group", children: [group.categoryName ? (_jsx("div", { className: "bw-svc-category", children: group.categoryName })) : null, group.services.map((service) => {
1047
+ }, children: [service.image ? (_jsxs("span", { className: `bw-svc-image${isActive ? ' is-checked' : ''}`, children: [_jsx("img", { src: service.image.url, alt: service.image.alt ??
1048
+ pickLocaleField(service, 'name', locale) ??
1049
+ service.name, loading: "lazy" }), isActive ? (_jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })) : null] })) : (_jsx("span", { className: `bw-check${isActive ? ' is-checked' : ''}`, children: isActive ? _jsx(CheckIcon, {}) : null })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(service, 'name', locale) ?? service.name }), (pickLocaleField(service, 'description', locale) ?? service.description) ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(service, 'description', locale) ?? service.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(service.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(service, undefined, intlLocale) })] })] })] }, service._id));
1050
+ })] }, group.key))) }) }) })] })) }), selectedService &&
1051
+ selectedServiceRequiresSlot &&
1052
+ !isEditingService ? (_jsxs("div", { className: "bw-provider-section", children: [_jsx("div", { className: "bw-section-divider" }), _jsx("label", { className: "bw-label", children: t('selectProvider') }), providerRow] })) : null] }), _jsxs("div", { className: "bw-step-2", children: [_jsx("p", { className: "bw-step-2-heading", children: t('chooseYourService') }), _jsx("div", { className: "bw-step-2-divider" }), serviceGroups.map((group) => (_jsxs("div", { className: "bw-svc-group", children: [group.categoryName ? (_jsx("div", { className: "bw-svc-category", children: group.categoryName })) : null, group.services.map((service) => {
947
1053
  const isActive = selectedServiceId === service._id;
948
1054
  return (_jsxs("div", { children: [_jsxs("button", { type: "button", className: "bw-svc-row bw-svc-row--full", onMouseEnter: () => {
949
1055
  void prefetchAvailability({
@@ -963,12 +1069,16 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
963
1069
  });
964
1070
  }, onClick: () => {
965
1071
  handleServiceSelect(service);
966
- }, children: [service.image ? (_jsxs("span", { className: `bw-svc-image bw-svc-image--lg${isActive ? ' is-checked' : ''}`, children: [_jsx("img", { src: service.image.url, alt: service.image.alt ?? pickLocaleField(service, 'name', locale) ?? service.name, loading: "lazy" }), isActive ? (_jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })) : null] })) : (_jsx("span", { className: `bw-check bw-check--lg${isActive ? ' is-checked' : ''}`, children: isActive ? _jsx(CheckIcon, {}) : null })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(service, 'name', locale) ?? service.name }), pickLocaleField(service, 'description', locale) ?? service.description ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(service, 'description', locale) ?? service.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(service.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(service, undefined, intlLocale) })] })] })] }), _jsx("div", { className: "bw-row-divider" })] }, service._id));
1072
+ }, children: [service.image ? (_jsxs("span", { className: `bw-svc-image bw-svc-image--lg${isActive ? ' is-checked' : ''}`, children: [_jsx("img", { src: service.image.url, alt: service.image.alt ??
1073
+ pickLocaleField(service, 'name', locale) ??
1074
+ service.name, loading: "lazy" }), isActive ? (_jsx("span", { className: "bw-svc-image-badge", children: _jsx(CheckIcon, {}) })) : null] })) : (_jsx("span", { className: `bw-check bw-check--lg${isActive ? ' is-checked' : ''}`, children: isActive ? _jsx(CheckIcon, {}) : null })), _jsxs("span", { className: "bw-svc-info", children: [_jsx("span", { className: "bw-svc-name", children: pickLocaleField(service, 'name', locale) ??
1075
+ service.name }), (pickLocaleField(service, 'description', locale) ?? service.description) ? (_jsx("span", { className: "bw-svc-desc", children: pickLocaleField(service, 'description', locale) ?? service.description })) : null, _jsxs("span", { className: "bw-svc-meta-row", children: [_jsx("span", { className: "bw-svc-meta", children: formatDuration(service.durationMinutes) }), _jsx("span", { className: "bw-svc-meta-dot", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "bw-svc-price", children: formatServicePrice(service, undefined, intlLocale) })] })] })] }), _jsx("div", { className: "bw-row-divider" })] }, service._id));
967
1076
  })] }, group.key)))] })] }), _jsxs("div", { className: "bw-col bw-col--center", children: [_jsxs("div", { className: "bw-cal-card", ref: calCardRef, children: [_jsxs("div", { className: "bw-cal-header", children: [_jsxs("div", { className: "bw-month-dropdown", children: [_jsxs("button", { type: "button", className: "bw-month-btn", onClick: () => setMonthOpen((current) => !current), children: [_jsx("span", { children: formatMonthLabel(calendarMonth, intlLocale) }), _jsx(ChevronDownIcon, {})] }), monthOpen ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "bw-month-overlay", "aria-label": t('closeMonthPicker'), onClick: () => setMonthOpen(false) }), _jsx("div", { className: "bw-month-list", children: monthOptions.map((option) => (_jsx("button", { type: "button", className: `bw-month-option${option.value === optionCurrentValue(calendarMonth) ? ' is-active' : ''}`, onClick: () => {
968
1077
  setCalendarMonth(option.date);
969
1078
  setMonthOpen(false);
970
1079
  }, children: option.label }, option.value))) })] })) : null] }), _jsxs("div", { className: "bw-cal-navs", children: [_jsx("button", { type: "button", className: "bw-cal-nav", onMouseEnter: () => {
971
- if (!sameMonth(calendarMonth, startOfMonth(new Date())) && selectedServiceId) {
1080
+ if (!sameMonth(calendarMonth, startOfMonth(new Date())) &&
1081
+ selectedServiceId) {
972
1082
  void prefetchAvailability({
973
1083
  client,
974
1084
  siteSlug,
@@ -978,7 +1088,8 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
978
1088
  });
979
1089
  }
980
1090
  }, onClick: () => setCalendarMonth((current) => addMonths(current, -1)), disabled: sameMonth(calendarMonth, startOfMonth(new Date())), "aria-label": t('prevMonth'), children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "bw-cal-nav", onMouseEnter: () => {
981
- if (!sameMonth(calendarMonth, addMonths(startOfMonth(new Date()), 3)) && selectedServiceId) {
1091
+ if (!sameMonth(calendarMonth, addMonths(startOfMonth(new Date()), 3)) &&
1092
+ selectedServiceId) {
982
1093
  void prefetchAvailability({
983
1094
  client,
984
1095
  siteSlug,
@@ -1008,22 +1119,53 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
1008
1119
  isSelected ? 'is-selected' : '',
1009
1120
  isAvailable && !isPast ? 'is-available' : '',
1010
1121
  isPast ? 'is-disabled' : '',
1011
- !isAvailable && isCurrentMonth && !isPast ? 'is-blocked' : '',
1012
- dateKey === formatDateKey(startOfDay(new Date())) ? 'is-today' : '',
1122
+ !isAvailable && isCurrentMonth && !isPast
1123
+ ? 'is-blocked'
1124
+ : '',
1125
+ dateKey === formatDateKey(startOfDay(new Date()))
1126
+ ? 'is-today'
1127
+ : '',
1013
1128
  ]
1014
1129
  .filter(Boolean)
1015
1130
  .join(' ');
1016
1131
  return (_jsx("button", { type: "button", className: className, disabled: !isCurrentMonth || isPast || !isAvailable, onClick: () => {
1017
1132
  setSelectedDate(dateKey);
1018
1133
  setSelectedSlot(null);
1019
- setMobileStep((current) => (current < 2 ? 2 : current));
1134
+ setMobileStep((current) => current < 2 ? 2 : current);
1020
1135
  }, children: day.getDate() }, dateKey));
1021
- }) }), !selectedService ? (_jsx("p", { className: "bw-cal-prompt", children: t('calendarSelectServicePrompt') })) : null, _jsxs("div", { className: "bw-timezone", children: [_jsx(GlobeIcon, {}), _jsx("span", { children: selectedHeldStaff?.timezone ?? selectedStaff?.timezone ?? setup?.workspaceTimezone ?? setup?.staff?.[0]?.timezone ?? 'UTC' })] })] }), selectedService && !isEditingService ? (_jsxs("div", { className: "bw-mobile-card bw-provider-section--mobile", children: [_jsx("label", { className: "bw-label", children: t('selectProvider') }), providerRow] })) : null, selectedService && selectedDate ? (_jsxs("div", { className: "bw-mobile-card bw-slots-mobile", children: [_jsx("label", { className: "bw-label", children: t('selectTimePrompt') }), slotsArea] })) : null] }), _jsx("div", { className: "bw-col bw-col--review", "aria-hidden": mobileStep !== 4, children: selectedService ? (_jsxs("div", { className: "bw-summary bw-summary--review", children: [_jsx("span", { className: "bw-summary-title", children: isReschedule ? t('summaryTitleReschedule') : t('summaryTitleCreate') }), holdSecondsRemaining !== null ? (_jsx("div", { className: "bw-summary-rows", children: _jsxs("div", { className: "bw-summary-row bw-summary-row--hold", "aria-live": "polite", children: [_jsx("span", { children: t('summaryReservationHold') }), _jsx("span", { className: "bw-summary-val bw-summary-val--hold", children: formatHoldCountdown(holdSecondsRemaining) })] }) })) : null, _jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: t('reviewBookingDetails') }), _jsxs("div", { className: "bw-summary-rows", children: [isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerTime') }), _jsx("span", { className: "bw-summary-val", style: { ...summaryValStyle, textDecoration: 'line-through', opacity: 0.55 }, children: formatFormerSlot(rescheduleContext.formerStartTime, rescheduleContext.formerEndTime, intlLocale) })] })) : null, serviceChanged && formerService ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerService') }), _jsx("span", { className: "bw-summary-val", style: { ...summaryValStyle, textDecoration: 'line-through', opacity: 0.55 }, children: formerService.name })] })) : null, staffChanged ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerStaff') }), _jsx("span", { className: "bw-summary-val", style: { ...summaryValStyle, textDecoration: 'line-through', opacity: 0.55 }, children: formerStaffName ?? t('providerAny') })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryService') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryProvider') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: selectedHeldStaff?.name ?? selectedStaff?.name ?? t('providerAny') })] }), selectedDate ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDate') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatReadableDate(selectedDate, intlLocale) })] })) : null, selectedSlot ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryTime') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatTimeLabel(selectedSlot.time, intlLocale) })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDuration') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatDuration(selectedService.durationMinutes) })] })] })] }), _jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: t('reviewYourDetails') }), _jsxs("div", { className: "bw-summary-rows", children: [_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactFullName') }), _jsx("span", { className: "bw-summary-val", children: customerName.trim() || t('reviewNotProvided') })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactEmail') }), _jsx("span", { className: "bw-summary-val", children: customerEmail.trim() || t('reviewNotProvided') })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactPhone') }), _jsx("span", { className: "bw-summary-val", children: customerPhone.trim() || t('reviewNotProvided') })] })] })] }), (() => {
1136
+ }) }), !selectedService ? (_jsx("p", { className: "bw-cal-prompt", children: t('calendarSelectServicePrompt') })) : null, _jsxs("div", { className: "bw-timezone", children: [_jsx(GlobeIcon, {}), _jsx("span", { children: selectedHeldStaff?.timezone ??
1137
+ selectedStaff?.timezone ??
1138
+ setup?.workspaceTimezone ??
1139
+ setup?.staff?.[0]?.timezone ??
1140
+ 'UTC' })] })] }), selectedService && !isEditingService ? (_jsxs("div", { className: "bw-mobile-card bw-provider-section--mobile", children: [_jsx("label", { className: "bw-label", children: t('selectProvider') }), providerRow] })) : null, selectedService && selectedDate ? (_jsxs("div", { className: "bw-mobile-card bw-slots-mobile", children: [_jsx("label", { className: "bw-label", children: t('selectTimePrompt') }), slotsArea] })) : null] }), _jsx("div", { className: "bw-col bw-col--review", "aria-hidden": mobileStep !== 4, children: selectedService ? (_jsxs("div", { className: "bw-summary bw-summary--review", children: [_jsx("span", { className: "bw-summary-title", children: isReschedule
1141
+ ? t('summaryTitleReschedule')
1142
+ : t('summaryTitleCreate') }), holdSecondsRemaining !== null ? (_jsx("div", { className: "bw-summary-rows", children: _jsxs("div", { className: "bw-summary-row bw-summary-row--hold", "aria-live": "polite", children: [_jsx("span", { children: t('summaryReservationHold') }), _jsx("span", { className: "bw-summary-val bw-summary-val--hold", children: formatHoldCountdown(holdSecondsRemaining) })] }) })) : null, _jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: t('reviewBookingDetails') }), _jsxs("div", { className: "bw-summary-rows", children: [isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerTime') }), _jsx("span", { className: "bw-summary-val", style: {
1143
+ ...summaryValStyle,
1144
+ textDecoration: 'line-through',
1145
+ opacity: 0.55,
1146
+ }, children: formatFormerSlot(rescheduleContext.formerStartTime, rescheduleContext.formerEndTime, intlLocale) })] })) : null, serviceChanged && formerService ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerService') }), _jsx("span", { className: "bw-summary-val", style: {
1147
+ ...summaryValStyle,
1148
+ textDecoration: 'line-through',
1149
+ opacity: 0.55,
1150
+ }, children: formerService.name })] })) : null, staffChanged ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerStaff') }), _jsx("span", { className: "bw-summary-val", style: {
1151
+ ...summaryValStyle,
1152
+ textDecoration: 'line-through',
1153
+ opacity: 0.55,
1154
+ }, children: formerStaffName ?? t('providerAny') })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryService') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: pickLocaleField(selectedService, 'name', locale) ??
1155
+ selectedService.name })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryProvider') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: selectedHeldStaff?.name ??
1156
+ selectedStaff?.name ??
1157
+ t('providerAny') })] }), selectedDate ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDate') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatReadableDate(selectedDate, intlLocale) })] })) : null, selectedSlot ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryTime') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatTimeLabel(selectedSlot.time, intlLocale) })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDuration') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatDuration(selectedService.durationMinutes) })] })] })] }), _jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: t('reviewYourDetails') }), _jsxs("div", { className: "bw-summary-rows", children: [_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactFullName') }), _jsx("span", { className: "bw-summary-val", children: customerName.trim() || t('reviewNotProvided') })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactEmail') }), _jsx("span", { className: "bw-summary-val", children: customerEmail.trim() || t('reviewNotProvided') })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('contactPhone') }), _jsx("span", { className: "bw-summary-val", children: customerPhone.trim() || t('reviewNotProvided') })] })] })] }), (() => {
1022
1158
  const intakeRows = buildIntakeReviewRows(selectedService.intakeForm, intakeResponses, selectedServicePath, locale, t);
1023
1159
  if (intakeRows.length === 0)
1024
1160
  return null;
1025
1161
  return (_jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: t('reviewAdditionalInfo') }), _jsx("div", { className: "bw-summary-rows", children: intakeRows.map((row) => (_jsxs("div", { className: `bw-summary-row${row.multiline ? ' bw-summary-row--stack' : ''}`, children: [_jsx("span", { children: row.label }), _jsx("span", { className: "bw-summary-val", children: row.value })] }, row.key))) })] }));
1026
- })(), customerNotes.trim() ? (_jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: isReschedule ? t('notesLabelReschedule') : t('reviewNotesSection') }), _jsx("p", { className: "bw-summary-notes", children: customerNotes.trim() })] })) : null, _jsx("div", { className: "bw-summary-rows bw-summary-rows--total", children: _jsxs("div", { className: "bw-summary-row bw-summary-total", children: [_jsx("span", { children: isQuoteLoading ? t('summaryCalculating') : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatServicePrice(selectedService, quote, intlLocale) })] }) })] })) : null }), _jsx("div", { className: "bw-col bw-col--right", children: _jsx("div", { className: "bw-right-scroll", children: _jsx("div", { className: "bw-right-inner", children: _jsxs("div", { className: "bw-right-stage", children: [_jsxs("div", { className: `bw-pane bw-pane--slots${viewState === 'slots' ? ' is-active' : ' is-hidden'}`, "aria-hidden": viewState !== 'slots', children: [_jsxs("div", { className: "bw-slots-heading bw-slots-heading--sticky", children: [_jsx("span", { children: t('slotsHeading') }), selectedDateSlots.length > 0 ? (_jsx("span", { className: "bw-slots-count", children: selectedDateSlots.length })) : null] }), _jsx("div", { className: "bw-slots-desktop", children: slotsArea })] }), _jsxs("div", { className: `bw-pane bw-pane--details${viewState === 'details' ? ' is-active' : ' is-hidden'}`, "aria-hidden": viewState !== 'details', children: [selectedServiceRequiresSlot ? (_jsxs("button", { type: "button", className: "bw-back-btn", onClick: () => void handleBackToSlots(), children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentTime') })] })) : (_jsxs("button", { type: "button", className: "bw-back-btn", onClick: handleBackToServicePicker, children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentService') })] })), _jsxs("div", { className: "bw-form", children: [serviceWarning ? _jsx(ServiceWarningCallout, { warning: serviceWarning }) : null, renderIntakeAndContact('bw-intake', ''), renderNotesField('bw-notes', t('notesLabel'), t('notesPlaceholder')), selectedService ? (_jsxs("div", { className: "bw-summary", children: [_jsx("span", { className: "bw-summary-title", children: isReschedule ? t('summaryTitleReschedule') : t('summaryTitleCreate') }), _jsxs("div", { className: "bw-summary-rows", children: [holdSecondsRemaining !== null ? (_jsxs("div", { className: "bw-summary-row bw-summary-row--hold", "aria-live": "polite", children: [_jsx("span", { children: t('summaryReservationHold') }), _jsx("span", { className: "bw-summary-val bw-summary-val--hold", children: formatHoldCountdown(holdSecondsRemaining) })] })) : null, isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerTime') }), _jsx("span", { className: "bw-summary-val", style: {
1162
+ })(), customerNotes.trim() ? (_jsxs("div", { className: "bw-summary-group", children: [_jsx("span", { className: "bw-summary-subhead", children: isReschedule
1163
+ ? t('notesLabelReschedule')
1164
+ : t('reviewNotesSection') }), _jsx("p", { className: "bw-summary-notes", children: customerNotes.trim() })] })) : null, _jsx("div", { className: "bw-summary-rows bw-summary-rows--total", children: _jsxs("div", { className: "bw-summary-row bw-summary-total", children: [_jsx("span", { children: isQuoteLoading
1165
+ ? t('summaryCalculating')
1166
+ : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatServicePrice(selectedService, quote, intlLocale) })] }) })] })) : null }), _jsx("div", { className: "bw-col bw-col--right", children: _jsx("div", { className: "bw-right-scroll", children: _jsx("div", { className: "bw-right-inner", children: _jsxs("div", { className: "bw-right-stage", children: [_jsxs("div", { className: `bw-pane bw-pane--slots${viewState === 'slots' ? ' is-active' : ' is-hidden'}`, "aria-hidden": viewState !== 'slots', children: [_jsxs("div", { className: "bw-slots-heading bw-slots-heading--sticky", children: [_jsx("span", { children: t('slotsHeading') }), selectedDateSlots.length > 0 ? (_jsx("span", { className: "bw-slots-count", children: selectedDateSlots.length })) : null] }), _jsx("div", { className: "bw-slots-desktop", children: slotsArea })] }), _jsxs("div", { className: `bw-pane bw-pane--details${viewState === 'details' ? ' is-active' : ' is-hidden'}`, "aria-hidden": viewState !== 'details', children: [selectedServiceRequiresSlot ? (_jsxs("button", { type: "button", className: "bw-back-btn", onClick: () => void handleBackToSlots(), children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentTime') })] })) : (_jsxs("button", { type: "button", className: "bw-back-btn", onClick: handleBackToServicePicker, children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentService') })] })), _jsxs("div", { className: "bw-form", children: [serviceWarning ? (_jsx(ServiceWarningCallout, { warning: serviceWarning })) : null, renderIntakeAndContact('bw-intake', ''), renderNotesField('bw-notes', t('notesLabel'), t('notesPlaceholder')), selectedService ? (_jsxs("div", { className: "bw-summary", children: [_jsx("span", { className: "bw-summary-title", children: isReschedule
1167
+ ? t('summaryTitleReschedule')
1168
+ : t('summaryTitleCreate') }), _jsxs("div", { className: "bw-summary-rows", children: [holdSecondsRemaining !== null ? (_jsxs("div", { className: "bw-summary-row bw-summary-row--hold", "aria-live": "polite", children: [_jsx("span", { children: t('summaryReservationHold') }), _jsx("span", { className: "bw-summary-val bw-summary-val--hold", children: formatHoldCountdown(holdSecondsRemaining) })] })) : null, isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryFormerTime') }), _jsx("span", { className: "bw-summary-val", style: {
1027
1169
  ...summaryValStyle,
1028
1170
  textDecoration: 'line-through',
1029
1171
  opacity: 0.55,
@@ -1035,7 +1177,11 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
1035
1177
  ...summaryValStyle,
1036
1178
  textDecoration: 'line-through',
1037
1179
  opacity: 0.55,
1038
- }, children: formerStaffName ?? t('providerAny') })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryService') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryProvider') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: selectedHeldStaff?.name ?? selectedStaff?.name ?? t('providerAny') })] }), selectedDate ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDate') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatReadableDate(selectedDate, intlLocale) })] })) : null, selectedSlot ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryTime') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatTimeLabel(selectedSlot.time, intlLocale) })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDuration') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatDuration(selectedService.durationMinutes) })] }), _jsxs("div", { className: "bw-summary-row bw-summary-total", children: [_jsx("span", { children: isQuoteLoading ? t('summaryCalculating') : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatServicePrice(selectedService, quote, intlLocale) })] })] })] })) : null, error ? _jsx("div", { className: "bw-error", children: error }) : null, renderSubmitHelp(), payment ? (_jsx(StripePaymentBlock, { payment: payment, appointmentId: paymentAppointmentId, showInlineButton: false, actionTarget: mobilePaymentActionTarget, onSuccess: () => {
1180
+ }, children: formerStaffName ?? t('providerAny') })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryService') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name })] }), _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryProvider') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: selectedHeldStaff?.name ??
1181
+ selectedStaff?.name ??
1182
+ t('providerAny') })] }), selectedDate ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDate') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatReadableDate(selectedDate, intlLocale) })] })) : null, selectedSlot ? (_jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryTime') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatTimeLabel(selectedSlot.time, intlLocale) })] })) : null, _jsxs("div", { className: "bw-summary-row", children: [_jsx("span", { children: t('summaryDuration') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatDuration(selectedService.durationMinutes) })] }), _jsxs("div", { className: "bw-summary-row bw-summary-total", children: [_jsx("span", { children: isQuoteLoading
1183
+ ? t('summaryCalculating')
1184
+ : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-summary-val", style: summaryValStyle, children: formatServicePrice(selectedService, quote, intlLocale) })] })] })] })) : null, error ? (_jsx("div", { className: "bw-error", children: error })) : null, renderSubmitHelp(), payment ? (_jsx(StripePaymentBlock, { payment: payment, appointmentId: paymentAppointmentId, showInlineButton: false, actionTarget: mobilePaymentActionTarget, onSuccess: () => {
1039
1185
  setSuccess(t('successPaymentReceived'));
1040
1186
  setPayment(null);
1041
1187
  }, onError: setError })) : (_jsxs("button", { type: "button", className: `bw-confirm-btn${!canSubmit || isQuoteLoading ? ' is-disabled' : ''}`, "aria-disabled": !canSubmit || isQuoteLoading, onClick: handleConfirmAttempt, children: [_jsx("span", { children: isSubmitting
@@ -1046,7 +1192,23 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
1046
1192
  ? t('btnConfirmReschedule')
1047
1193
  : selectedServiceRequiresPayment
1048
1194
  ? t('btnContinueToPayment')
1049
- : t('btnConfirmBooking') }), !isSubmitting ? _jsx(ArrowRightIcon, {}) : null] }))] })] })] }) }) }) })] }) }), _jsxs("div", { className: "bw-details-view", children: [_jsx("div", { className: "bw-details-summary", children: selectedService ? (_jsxs(_Fragment, { children: [selectedServiceRequiresSlot ? (_jsxs("button", { type: "button", className: "bw-back-btn bw-back-btn--details", onClick: () => void handleBackToSlots(), children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentTime') })] })) : (_jsxs("button", { type: "button", className: "bw-back-btn bw-back-btn--details", onClick: handleBackToServicePicker, children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentService') })] })), _jsx("h3", { className: "bw-details-service", children: pickLocaleField(selectedService, 'name', locale) ?? selectedService.name }), pickLocaleField(selectedService, 'description', locale) ?? selectedService.description ? (_jsx("p", { className: "bw-details-desc", children: pickLocaleField(selectedService, 'description', locale) ?? selectedService.description })) : null, _jsxs("div", { className: "bw-details-meta", children: [isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-details-meta-row bw-details-meta-row--strike", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(CalendarIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: t('summaryFormerTime') }), _jsx("span", { className: "bw-details-meta-value", children: formatFormerSlot(rescheduleContext.formerStartTime, rescheduleContext.formerEndTime, intlLocale) })] })] })) : null, selectedDate && selectedSlot ? (_jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(CalendarIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: isReschedule ? t('summaryNewTime') : t('summaryWhen') }), _jsxs("span", { className: "bw-details-meta-value", children: [formatReadableDate(selectedDate, intlLocale), _jsx("br", {}), formatTimeLabel(selectedSlot.time, intlLocale), " \u2013 ", formatTimeLabel(selectedSlot.endTime, intlLocale)] })] })] })) : null, _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(ClockIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: formatDuration(selectedService.durationMinutes) })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(UserIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: selectedHeldStaff?.name ?? selectedStaff?.name ?? t('providerAny') })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(GlobeIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: selectedHeldStaff?.timezone ?? selectedStaff?.timezone ?? setup?.workspaceTimezone ?? setup?.staff?.[0]?.timezone ?? 'UTC' })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", "aria-hidden": "true", children: "$" }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: isQuoteLoading ? t('summaryCalculating') : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-details-meta-value", children: formatServicePrice(selectedService, quote, intlLocale) })] })] }), holdSecondsRemaining !== null ? (_jsxs("div", { className: "bw-details-meta-row bw-details-meta-row--hold", "aria-live": "polite", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(ClockIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: t('summaryReservationHold') }), _jsx("span", { className: "bw-details-meta-value bw-details-hold", children: formatHoldCountdown(holdSecondsRemaining) })] })] })) : null] })] })) : null }), _jsx("div", { className: "bw-details-form", children: _jsxs("div", { className: "bw-form bw-form--details", children: [serviceWarning ? _jsx(ServiceWarningCallout, { warning: serviceWarning }) : null, renderIntakeAndContact('bw-intake-d', '-d'), renderNotesField('bw-notes-d', isReschedule ? t('notesLabelReschedule') : t('notesLabel'), isReschedule ? t('notesPlaceholderReschedule') : t('notesPlaceholder')), error ? _jsx("div", { className: "bw-error", children: error }) : null, renderSubmitHelp(), !payment && (forcedState === 'payment-full' || forcedState === 'payment-deposit') ? (_jsx(PaymentPanel, { service: selectedService, mode: forcedState === 'payment-full' ? 'full' : 'deposit' })) : null, payment ? (_jsx(StripePaymentBlock, { payment: payment, appointmentId: paymentAppointmentId, onSuccess: () => {
1195
+ : t('btnConfirmBooking') }), !isSubmitting ? _jsx(ArrowRightIcon, {}) : null] }))] })] })] }) }) }) })] }) }), _jsxs("div", { className: "bw-details-view", children: [_jsx("div", { className: "bw-details-summary", children: selectedService ? (_jsxs(_Fragment, { children: [selectedServiceRequiresSlot ? (_jsxs("button", { type: "button", className: "bw-back-btn bw-back-btn--details", onClick: () => void handleBackToSlots(), children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentTime') })] })) : (_jsxs("button", { type: "button", className: "bw-back-btn bw-back-btn--details", onClick: handleBackToServicePicker, children: [_jsx(ArrowLeftIcon, {}), _jsx("span", { children: t('backToDifferentService') })] })), _jsx("h3", { className: "bw-details-service", children: pickLocaleField(selectedService, 'name', locale) ??
1196
+ selectedService.name }), (pickLocaleField(selectedService, 'description', locale) ??
1197
+ selectedService.description) ? (_jsx("p", { className: "bw-details-desc", children: pickLocaleField(selectedService, 'description', locale) ?? selectedService.description })) : null, _jsxs("div", { className: "bw-details-meta", children: [isReschedule && rescheduleContext ? (_jsxs("div", { className: "bw-details-meta-row bw-details-meta-row--strike", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(CalendarIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: t('summaryFormerTime') }), _jsx("span", { className: "bw-details-meta-value", children: formatFormerSlot(rescheduleContext.formerStartTime, rescheduleContext.formerEndTime, intlLocale) })] })] })) : null, selectedDate && selectedSlot ? (_jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(CalendarIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: isReschedule
1198
+ ? t('summaryNewTime')
1199
+ : t('summaryWhen') }), _jsxs("span", { className: "bw-details-meta-value", children: [formatReadableDate(selectedDate, intlLocale), _jsx("br", {}), formatTimeLabel(selectedSlot.time, intlLocale), " \u2013", ' ', formatTimeLabel(selectedSlot.endTime, intlLocale)] })] })] })) : null, _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(ClockIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: formatDuration(selectedService.durationMinutes) })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(UserIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: selectedHeldStaff?.name ??
1200
+ selectedStaff?.name ??
1201
+ t('providerAny') })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(GlobeIcon, {}) }), _jsx("span", { className: "bw-details-meta-value", children: selectedHeldStaff?.timezone ??
1202
+ selectedStaff?.timezone ??
1203
+ setup?.workspaceTimezone ??
1204
+ setup?.staff?.[0]?.timezone ??
1205
+ 'UTC' })] }), _jsxs("div", { className: "bw-details-meta-row", children: [_jsx("span", { className: "bw-details-meta-icon", "aria-hidden": "true", children: "$" }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: isQuoteLoading
1206
+ ? t('summaryCalculating')
1207
+ : t('summaryEstimatedTotal') }), _jsx("span", { className: "bw-details-meta-value", children: formatServicePrice(selectedService, quote, intlLocale) })] })] }), holdSecondsRemaining !== null ? (_jsxs("div", { className: "bw-details-meta-row bw-details-meta-row--hold", "aria-live": "polite", children: [_jsx("span", { className: "bw-details-meta-icon", children: _jsx(ClockIcon, {}) }), _jsxs("div", { className: "bw-details-meta-text", children: [_jsx("span", { className: "bw-details-meta-label", children: t('summaryReservationHold') }), _jsx("span", { className: "bw-details-meta-value bw-details-hold", children: formatHoldCountdown(holdSecondsRemaining) })] })] })) : null] })] })) : null }), _jsx("div", { className: "bw-details-form", children: _jsxs("div", { className: "bw-form bw-form--details", children: [serviceWarning ? (_jsx(ServiceWarningCallout, { warning: serviceWarning })) : null, renderIntakeAndContact('bw-intake-d', '-d'), renderNotesField('bw-notes-d', isReschedule ? t('notesLabelReschedule') : t('notesLabel'), isReschedule
1208
+ ? t('notesPlaceholderReschedule')
1209
+ : t('notesPlaceholder')), error ? _jsx("div", { className: "bw-error", children: error }) : null, renderSubmitHelp(), !payment &&
1210
+ (forcedState === 'payment-full' ||
1211
+ forcedState === 'payment-deposit') ? (_jsx(PaymentPanel, { service: selectedService, mode: forcedState === 'payment-full' ? 'full' : 'deposit' })) : null, payment ? (_jsx(StripePaymentBlock, { payment: payment, appointmentId: paymentAppointmentId, onSuccess: () => {
1050
1212
  setSuccess(t('successPaymentReceived'));
1051
1213
  setPayment(null);
1052
1214
  }, onError: setError })) : (_jsxs("button", { type: "button", className: `bw-confirm-btn${!canSubmit || isQuoteLoading ? ' is-disabled' : ''}`, "aria-disabled": !canSubmit || isQuoteLoading, onClick: handleConfirmAttempt, children: [_jsx("span", { children: isSubmitting
@@ -1054,7 +1216,9 @@ export function BookingWidgetPanel({ title, description, mobileHeader, mode = 'c
1054
1216
  ? t('btnRescheduling')
1055
1217
  : t('btnBooking')
1056
1218
  : forcedState === 'payment-full'
1057
- ? t('btnPayAndConfirm', { price: formatPrice(selectedService, intlLocale) })
1219
+ ? t('btnPayAndConfirm', {
1220
+ price: formatPrice(selectedService, intlLocale),
1221
+ })
1058
1222
  : forcedState === 'payment-deposit'
1059
1223
  ? t('btnPayDepositAndConfirm')
1060
1224
  : isReschedule
@@ -1154,7 +1318,8 @@ function areRequiredIntakeFieldsComplete(form, responses, mode) {
1154
1318
  }
1155
1319
  function getSubmitBlockers({ selectedService, selectedServiceRequiresSlot, selectedDate, selectedSlot, holdId, isIntakeComplete, intakeResponses, selectedServicePath, quote, customerName, customerEmail, t, locale = DEFAULT_LOCALE, }) {
1156
1320
  const blockers = [];
1157
- if (selectedServiceRequiresSlot && (!selectedDate || !selectedSlot || !holdId)) {
1321
+ if (selectedServiceRequiresSlot &&
1322
+ (!selectedDate || !selectedSlot || !holdId)) {
1158
1323
  blockers.push(t('blockerDateTime'));
1159
1324
  }
1160
1325
  if (!customerName.trim())
@@ -1282,7 +1447,9 @@ function buildIntakeReviewRows(form, responses, servicePath, locale, t) {
1282
1447
  continue;
1283
1448
  for (const field of section.fields) {
1284
1449
  if (field.type === 'repeatable-group') {
1285
- const items = Array.isArray(responses[field.id]) ? responses[field.id] : [];
1450
+ const items = Array.isArray(responses[field.id])
1451
+ ? responses[field.id]
1452
+ : [];
1286
1453
  items.forEach((item, index) => {
1287
1454
  const record = item && typeof item === 'object' && !Array.isArray(item)
1288
1455
  ? item
@@ -1487,7 +1654,9 @@ function IntakeField({ field, value, onChange, idPrefix, }) {
1487
1654
  if (field.type === 'repeatable-group') {
1488
1655
  const items = Array.isArray(value) && value.length > 0 ? value : [{}];
1489
1656
  return (_jsxs("div", { className: "bw-field bw-field--wide bw-repeatable", children: [_jsx("label", { children: label }), items.map((item, index) => {
1490
- const record = item && typeof item === 'object' && !Array.isArray(item) ? item : {};
1657
+ const record = item && typeof item === 'object' && !Array.isArray(item)
1658
+ ? item
1659
+ : {};
1491
1660
  return (_jsxs("div", { className: "bw-repeatable-item", children: [_jsxs("div", { className: "bw-repeatable-head", children: [_jsx("span", { children: t('repeatItemLabel', { index: index + 1 }) }), items.length > 1 ? (_jsx("button", { type: "button", className: "bw-link-btn", onClick: () => onChange(items.filter((_, itemIndex) => itemIndex !== index)), children: t('repeatRemove') })) : null] }), _jsx("div", { className: "bw-form-fields", children: (field.fields ?? []).map((child) => (_jsx(IntakeField, { field: child, value: record[child.id], idPrefix: `${id}-${index}`, onChange: (nextValue) => {
1492
1661
  const nextItems = [...items];
1493
1662
  nextItems[index] = { ...record, [child.id]: nextValue };
@@ -1511,7 +1680,11 @@ function IntakeField({ field, value, onChange, idPrefix, }) {
1511
1680
  if (field.type === 'textarea') {
1512
1681
  return (_jsxs("div", { className: "bw-field bw-field--wide", children: [_jsxs("label", { htmlFor: id, children: [label, field.required ? _jsx("span", { className: "bw-required", children: " *" }) : null] }), _jsx("textarea", { id: id, rows: 3, required: field.required, value: stringValue, placeholder: field.placeholder, onChange: (event) => onChange(event.target.value) }), field.helpText ? _jsx("p", { className: "bw-help", children: field.helpText }) : null] }));
1513
1682
  }
1514
- return (_jsxs("div", { className: "bw-field", children: [_jsxs("label", { htmlFor: id, children: [label, field.required ? _jsx("span", { className: "bw-required", children: " *" }) : null] }), _jsx("input", { id: id, type: field.type === 'email' ? 'email' : field.type === 'number' ? 'number' : 'text', min: field.min, max: field.max, required: field.required, value: stringValue, placeholder: field.placeholder, onChange: (event) => {
1683
+ return (_jsxs("div", { className: "bw-field", children: [_jsxs("label", { htmlFor: id, children: [label, field.required ? _jsx("span", { className: "bw-required", children: " *" }) : null] }), _jsx("input", { id: id, type: field.type === 'email'
1684
+ ? 'email'
1685
+ : field.type === 'number'
1686
+ ? 'number'
1687
+ : 'text', min: field.min, max: field.max, required: field.required, value: stringValue, placeholder: field.placeholder, onChange: (event) => {
1515
1688
  onChange(field.type === 'number'
1516
1689
  ? event.target.value === ''
1517
1690
  ? ''
@@ -1594,10 +1767,9 @@ function readString(value) {
1594
1767
  return typeof value === 'string' && value.trim() ? value : null;
1595
1768
  }
1596
1769
  // BookingWidgetSkeleton was a hand-built first-load ghost that
1597
- // constantly drifted out of sync with the real layout. Replaced by
1598
- // boneyard-js: BookingWidgetPanel wraps its main render in
1599
- // <Skeleton name="booking-widget"> and `./bones/registry` ships
1600
- // pre-captured bones JSON regenerated via `npx boneyard-js build`.
1770
+ // constantly drifted out of sync with the real layout. Boneyard now
1771
+ // owns the detailed bones; BookingWidgetPlaceholder is the stable
1772
+ // SSR/no-bones space holder and Suspense fallback for host sites.
1601
1773
  function AvailabilitySkeleton() {
1602
1774
  // Reuses .bw-time-slots so the skeleton inherits the same layout
1603
1775
  // overrides as the real slots — vertical 1-col on desktop (via the
@@ -1743,7 +1915,8 @@ function buildCalendarDays(month) {
1743
1915
  });
1744
1916
  }
1745
1917
  function sameMonth(left, right) {
1746
- return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth();
1918
+ return (left.getFullYear() === right.getFullYear() &&
1919
+ left.getMonth() === right.getMonth());
1747
1920
  }
1748
1921
  function getMonthOptions(currentMonth, total, locale = 'en-US') {
1749
1922
  const start = startOfMonth(new Date());
@@ -1762,7 +1935,8 @@ function optionCurrentValue(date) {
1762
1935
  }
1763
1936
  function isDateInMonth(dateKey, month) {
1764
1937
  const date = new Date(`${dateKey}T00:00:00`);
1765
- return date.getMonth() === month.getMonth() && date.getFullYear() === month.getFullYear();
1938
+ return (date.getMonth() === month.getMonth() &&
1939
+ date.getFullYear() === month.getFullYear());
1766
1940
  }
1767
1941
  function findFirstAvailableDate(availabilityByDate, month) {
1768
1942
  const dates = [...availabilityByDate.entries()]