@bigz-app/booking-widget 1.4.2 → 1.4.3

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.esm.js CHANGED
@@ -14132,7 +14132,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14132
14132
  padding: "2px 8px",
14133
14133
  borderRadius: "var(--bw-border-radius)",
14134
14134
  fontFamily: "var(--bw-font-family)",
14135
- }, children: eventType.category.name }) }), jsx("div", { className: "event-type-img", style: { position: "relative", width: "100%", height: "300px" }, children: jsx(ImageCarousel, { images: eventType.images, eventName: eventType.name }) }), jsxs("div", { className: "event-type-content", style: {
14135
+ }, children: eventType.category.name }) }), eventType.images && eventType.images.length > 0 && (jsx("div", { className: "event-type-img", style: { position: "relative", width: "100%", height: "300px" }, children: jsx(ImageCarousel, { images: eventType.images, eventName: eventType.name }) })), jsxs("div", { className: "event-type-content", style: {
14136
14136
  padding: "12px 18px",
14137
14137
  display: "flex",
14138
14138
  flexDirection: "column",
@@ -14275,8 +14275,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14275
14275
  alignItems: "center",
14276
14276
  marginTop: "10px",
14277
14277
  gap: "12px",
14278
- }, children: [(eventType.description ||
14279
- (eventType.highlights && eventType.highlights.length > 0)) && (jsx("button", { onClick: (e) => {
14278
+ }, children: [eventType.description && (jsx("button", { onClick: (e) => {
14280
14279
  e.stopPropagation();
14281
14280
  handleShowDetails(eventType);
14282
14281
  }, style: {
@@ -14840,55 +14839,44 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
14840
14839
  gap: "8px",
14841
14840
  }, children: [jsx("span", { style: { fontSize: "16px" }, children: "\uD83D\uDD04" }), t("common.reload")] })] }) }));
14842
14841
  }
14842
+ const today = new Date();
14843
14843
  return (jsxs("div", { style: {
14844
14844
  maxWidth: "500px",
14845
14845
  margin: "0 auto",
14846
14846
  padding: "12px 16px",
14847
14847
  fontFamily: "var(--bw-font-family)",
14848
- }, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "4px", marginBottom: "10px" }, children: events.map((event) => {
14848
+ }, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events.map((event) => {
14849
14849
  const availableSpots = event.maxParticipants - event.participantCount;
14850
14850
  const isFullyBooked = availableSpots === 0;
14851
14851
  const startDate = new Date(event.startTime);
14852
- const today = new Date();
14853
14852
  const isPastEvent = today.toISOString() >= startDate.toISOString();
14854
- const isFullAvailability = availableSpots / event.maxParticipants >= 0.8;
14855
- const isHighAvailability = availableSpots / event.maxParticipants >= 0.5;
14856
- const isMediumAvailability = availableSpots / event.maxParticipants >= 0.3;
14857
- const isLowAvailability = availableSpots / event.maxParticipants >= 0.15;
14858
- const getAvailabilityColor = () => {
14859
- if (isFullAvailability)
14860
- return "var(--bw-success-color)";
14861
- if (isHighAvailability)
14862
- return "var(--bw-warning-color)";
14863
- if (isMediumAvailability)
14864
- return "var(--bw-availability-medium-color, #f97316)";
14865
- if (isLowAvailability)
14866
- return "var(--bw-availability-low-color, #8b5cf6)";
14867
- return "var(--bw-highlight-color)";
14868
- };
14869
14853
  const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14854
+ const availabilityRatio = availableSpots / event.maxParticipants;
14855
+ const allocationBadge = (() => {
14856
+ if (availabilityRatio >= 0.6)
14857
+ return null;
14858
+ if (availabilityRatio === 0)
14859
+ return { text: t("events.soldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
14860
+ if (availabilityRatio <= 0.3)
14861
+ return { text: t("instances.almostSoldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
14862
+ return { text: t("instances.popularDate"), backgroundColor: "#b45309", textColor: "#fbbf24" };
14863
+ })();
14864
+ const sameDay = formatWeekday(event.startTime, timezone, locale) === formatWeekday(event.endTime, timezone, locale);
14870
14865
  return (jsxs("div", { style: {
14871
14866
  position: "relative",
14872
- borderRadius: "var(--bw-border-radius-small)",
14873
- padding: "0 4px",
14867
+ border: "1px solid var(--bw-border-color)",
14868
+ backgroundColor: "var(--bw-surface-color)",
14869
+ borderRadius: "var(--bw-border-radius)",
14870
+ padding: "16px 10px",
14874
14871
  transition: "all 0.2s ease",
14875
- border: "1px solid transparent",
14876
14872
  fontFamily: "var(--bw-font-family)",
14877
14873
  opacity: isDisabled ? 0.3 : 1,
14878
- filter: isDisabled ? "grayscale(100%)" : "none",
14874
+ filter: isDisabled ? "grayscale(40%)" : "none",
14879
14875
  cursor: isDisabled ? "not-allowed" : "pointer",
14880
14876
  }, onClick: () => {
14881
14877
  if (!isDisabled) {
14882
14878
  handleEventSelect(event.id);
14883
14879
  }
14884
- }, onMouseEnter: (hoverEvent) => {
14885
- if (!isDisabled) {
14886
- hoverEvent.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
14887
- hoverEvent.currentTarget.style.borderColor = getAvailabilityColor();
14888
- }
14889
- }, onMouseLeave: (hoverEvent) => {
14890
- hoverEvent.currentTarget.style.backgroundColor = "transparent";
14891
- hoverEvent.currentTarget.style.borderColor = "transparent";
14892
14880
  }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
14893
14881
  position: "absolute",
14894
14882
  inset: 0,
@@ -14897,37 +14885,71 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
14897
14885
  justifyContent: "center",
14898
14886
  backgroundColor: "rgba(15, 23, 42, 0.8)",
14899
14887
  borderRadius: "var(--bw-border-radius)",
14900
- }, children: jsx("div", { style: {
14901
- width: "32px",
14902
- height: "32px",
14903
- color: "var(--bw-highlight-color)",
14904
- fontSize: "32px",
14905
- animation: "spin 1s linear infinite",
14906
- }, children: "\u27F3" }) })), jsxs("div", { style: {
14907
- minHeight: "32px",
14908
- display: "grid",
14909
- gridTemplateColumns: "1fr auto 20px",
14910
- alignItems: "center",
14911
- gap: "8px",
14888
+ }, children: jsx("div", { style: { width: "32px", height: "32px", color: "var(--bw-highlight-color)", opacity: 0.8, fontSize: "32px" }, children: "\u27F3" }) })), jsx("div", { style: {
14889
+ display: "flex",
14890
+ marginLeft: "auto",
14891
+ marginTop: "-20px",
14892
+ marginBottom: "4px",
14893
+ borderRadius: "var(--bw-border-radius-small)",
14894
+ fontFamily: "var(--bw-font-family)",
14895
+ zIndex: 50,
14896
+ whiteSpace: "nowrap",
14897
+ width: "fit-content",
14898
+ fontSize: "11px",
14899
+ fontWeight: 700,
14900
+ padding: "2px 8px",
14901
+ backgroundColor: allocationBadge?.backgroundColor || "transparent",
14902
+ color: allocationBadge?.textColor || "transparent",
14903
+ }, children: allocationBadge?.text || " - " }), jsxs("div", { style: {
14904
+ display: "flex",
14905
+ justifyContent: "space-between",
14912
14906
  width: "100%",
14913
- fontSize: "13px",
14914
- color: "var(--bw-text-muted)",
14915
- }, children: [jsxs("span", { style: {
14916
- whiteSpace: "nowrap",
14917
- display: "flex",
14918
- alignItems: "center",
14919
- gap: "3px",
14920
- overflow: "hidden",
14921
- textOverflow: "ellipsis",
14922
- textTransform: "capitalize",
14923
- }, children: [formatWeekday(event.startTime, timezone, locale), " ", formatDate(event.startTime, timezone, locale)] }), jsx("span", { style: {
14924
- display: "flex",
14925
- alignItems: "center",
14926
- gap: "4px",
14927
- whiteSpace: "nowrap",
14928
- fontWeight: 700,
14929
- color: "var(--bw-text-color)",
14930
- }, children: event.price !== null ? formatCurrency(event.price) : t("nextEvents.priceOnRequest") }), event.notes ? jsx(InfoBadge, { text: event.notes }) : jsx("span", {})] })] }, event.id));
14907
+ alignItems: "flex-start",
14908
+ gap: "12px",
14909
+ marginBottom: "4px",
14910
+ }, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
14911
+ fontSize: "16px",
14912
+ transition: "all 0.2s ease",
14913
+ borderRadius: "var(--bw-border-radius-small)",
14914
+ borderTop: "4px solid var(--bw-border-color)",
14915
+ border: "1px solid var(--bw-border-color)",
14916
+ width: "40px",
14917
+ height: "40px",
14918
+ display: "flex",
14919
+ alignItems: "center",
14920
+ justifyContent: "center",
14921
+ fontWeight: 700,
14922
+ color: "var(--bw-text-color)",
14923
+ backgroundColor: "var(--bw-background-color)",
14924
+ }, children: startDate.getDate() }), jsxs("div", { style: {
14925
+ fontSize: "16px",
14926
+ color: "var(--bw-text-color)",
14927
+ display: "flex",
14928
+ flexDirection: "column",
14929
+ alignItems: "flex-start",
14930
+ justifyContent: "flex-start",
14931
+ lineHeight: 1.25,
14932
+ }, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), !sameDay && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsx("div", { children: sameDay ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxs("span", { style: {
14933
+ fontSize: "12px",
14934
+ fontWeight: 400,
14935
+ color: "var(--bw-text-muted)",
14936
+ marginLeft: "6px",
14937
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
14938
+ whiteSpace: "nowrap",
14939
+ padding: "2px 6px",
14940
+ borderRadius: "var(--bw-border-radius-small)",
14941
+ }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsxs("div", { style: { textAlign: "right", display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "4px" }, children: [jsx("span", { style: {
14942
+ display: "inline-flex",
14943
+ alignItems: "center",
14944
+ fontSize: "16px",
14945
+ fontWeight: 600,
14946
+ padding: "2px 8px",
14947
+ borderRadius: "var(--bw-border-radius-small)",
14948
+ fontFamily: "var(--bw-font-family)",
14949
+ backgroundColor: "var(--bw-background-color)",
14950
+ color: "var(--bw-text-color)",
14951
+ border: "1px solid var(--bw-border-color)",
14952
+ }, children: event.price !== null ? formatCurrency(event.price) : t("nextEvents.priceOnRequest") }), event.notes && jsx(InfoBadge, { text: event.notes })] })] })] }, event.id));
14931
14953
  }) }), showAllButton && (jsx("div", { style: { textAlign: "center" }, children: jsx("button", { type: "button", disabled: isLoadingShowAll, style: {
14932
14954
  display: "inline-flex",
14933
14955
  alignItems: "center",
@@ -15748,6 +15770,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15748
15770
  const [isSuccess, setIsSuccess] = useState(false);
15749
15771
  const [successPaymentId, setSuccessPaymentId] = useState(null);
15750
15772
  const [systemConfig, setSystemConfig] = useState(null);
15773
+ // When true, loadEventInstances skips the single-instance auto-select (used by handleShowAllEvents)
15774
+ const skipInstanceAutoSelectRef = useRef(false);
15751
15775
  // PERFORMANCE OPTIMIZATION: Lazy component loading
15752
15776
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
15753
15777
  const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
@@ -16243,7 +16267,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16243
16267
  }
16244
16268
  setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16245
16269
  }
16246
- if (data.eventInstances.length === 1) {
16270
+ if (!skipInstanceAutoSelectRef.current && data.eventInstances.length === 1) {
16247
16271
  setSelectedEventInstance(data.eventInstances[0]);
16248
16272
  setCurrentStep("booking");
16249
16273
  await loadEventDetails(data.eventInstances[0].id);
@@ -16638,12 +16662,30 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16638
16662
  const handleShowAllEvents = async () => {
16639
16663
  setIsLoadingShowAll(true);
16640
16664
  try {
16641
- if (isSingleEventTypeMode && selectedEventType) {
16665
+ if (isSingleEventTypeMode) {
16666
+ // Lock in the correct render branch before any async work so no
16667
+ // intermediate state can accidentally show the cards view.
16642
16668
  setShouldRenderInstanceSelection(true);
16643
- setIsLoadingEventInstances(true);
16644
- await loadEventInstances(selectedEventType.id);
16645
16669
  setCurrentStep("eventInstances");
16646
16670
  setShowingPreview(false);
16671
+ setIsLoadingEventInstances(true);
16672
+ // Suppress the single-instance auto-select inside loadEventInstances
16673
+ // so it doesn't override currentStep to "booking" mid-flight.
16674
+ skipInstanceAutoSelectRef.current = true;
16675
+ try {
16676
+ if (selectedEventType) {
16677
+ await loadEventInstances(selectedEventType.id);
16678
+ }
16679
+ else {
16680
+ // next-events mode skips event type loading on init.
16681
+ // loadEventTypes sets selectedEventType (for the sidebar title)
16682
+ // and internally calls loadEventInstances (auto-select suppressed).
16683
+ await loadEventTypes();
16684
+ }
16685
+ }
16686
+ finally {
16687
+ skipInstanceAutoSelectRef.current = false;
16688
+ }
16647
16689
  setIsLoadingEventInstances(false);
16648
16690
  }
16649
16691
  else {
@@ -16806,12 +16848,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16806
16848
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16807
16849
  }
16808
16850
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
16851
+ const isButtonBusy = (isDirectInstanceMode && isLoadingEventDetails) ||
16852
+ (!isDirectInstanceMode && (isLoadingEventInstances || (shouldRenderInstanceSelection && !sidebarOpen)));
16809
16853
  return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
16810
16854
  display: "flex",
16811
16855
  justifyContent: "center",
16812
16856
  alignItems: "center",
16813
16857
  minHeight: "120px",
16814
- }, children: [jsx("button", { type: "button", style: {
16858
+ }, children: [jsxs("button", { type: "button", disabled: isButtonBusy, style: {
16859
+ display: "inline-flex",
16860
+ alignItems: "center",
16861
+ gap: "10px",
16815
16862
  backgroundColor: "var(--bw-highlight-color)",
16816
16863
  color: "white",
16817
16864
  padding: "16px 32px",
@@ -16821,7 +16868,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16821
16868
  fontWeight: 600,
16822
16869
  fontFamily: "var(--bw-font-family)",
16823
16870
  boxShadow: "var(--bw-shadow-md)",
16824
- cursor: "pointer",
16871
+ cursor: isButtonBusy ? "default" : "pointer",
16872
+ opacity: isButtonBusy ? 0.85 : 1,
16873
+ transition: "opacity 0.2s ease",
16825
16874
  }, onClick: () => {
16826
16875
  if (isDirectInstanceMode) {
16827
16876
  setCurrentStep("booking");
@@ -16832,8 +16881,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16832
16881
  setSidebarOpen(true);
16833
16882
  setShouldRenderInstanceSelection(true);
16834
16883
  }
16835
- }, children: config.buttonText ||
16836
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates")) }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16884
+ }, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "white" }), config.buttonText ||
16885
+ (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16837
16886
  setIsSuccess(false);
16838
16887
  setCurrentStep("eventTypes");
16839
16888
  setSidebarOpen(false);