@resira/ui 0.4.2 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -652,6 +652,101 @@ function useCheckoutSession(token) {
652
652
  }, [client, token]);
653
653
  return { session, loading, error, errorCode };
654
654
  }
655
+ function formatPriceCentsUtil(cents, currency) {
656
+ return new Intl.NumberFormat("default", { style: "currency", currency }).format(cents / 100);
657
+ }
658
+ function formatDurationUtil(minutes) {
659
+ if (minutes < 60) return `${minutes} min`;
660
+ const h = Math.floor(minutes / 60);
661
+ const m = minutes % 60;
662
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
663
+ }
664
+ function enrichProduct(product) {
665
+ const currency = product.currency ?? "EUR";
666
+ const options = [];
667
+ if (product.durationPricing?.length) {
668
+ for (const dp of product.durationPricing) {
669
+ options.push({
670
+ durationMinutes: dp.durationMinutes,
671
+ durationLabel: formatDurationUtil(dp.durationMinutes),
672
+ priceCents: dp.priceCents,
673
+ priceFormatted: formatPriceCentsUtil(dp.priceCents, currency)
674
+ });
675
+ }
676
+ } else {
677
+ options.push({
678
+ durationMinutes: product.durationMinutes,
679
+ durationLabel: formatDurationUtil(product.durationMinutes),
680
+ priceCents: product.priceCents,
681
+ priceFormatted: formatPriceCentsUtil(product.priceCents, currency)
682
+ });
683
+ }
684
+ const lowestPriceCents = Math.min(...options.map((o) => o.priceCents));
685
+ return {
686
+ id: product.id,
687
+ name: product.name,
688
+ description: product.description,
689
+ imageUrl: product.imageUrl,
690
+ currency,
691
+ active: product.active,
692
+ pricingModel: product.pricingModel,
693
+ priceCents: product.priceCents,
694
+ priceFormatted: formatPriceCentsUtil(product.priceCents, currency),
695
+ lowestPriceCents,
696
+ lowestPriceFormatted: formatPriceCentsUtil(lowestPriceCents, currency),
697
+ hasMultipleOptions: options.length > 1,
698
+ durationMinutes: product.durationMinutes,
699
+ durationLabel: formatDurationUtil(product.durationMinutes),
700
+ options,
701
+ maxPartySize: product.maxPartySize,
702
+ equipmentIds: product.equipmentIds,
703
+ equipmentNames: product.equipmentNames,
704
+ serviceColor: product.serviceColor,
705
+ sortOrder: product.sortOrder,
706
+ raw: product
707
+ };
708
+ }
709
+ function useServices() {
710
+ const { client } = useResira();
711
+ const [services, setServices] = react.useState([]);
712
+ const [loading, setLoading] = react.useState(true);
713
+ const [error, setError] = react.useState(null);
714
+ const [fetchCount, setFetchCount] = react.useState(0);
715
+ react.useEffect(() => {
716
+ let cancelled = false;
717
+ setLoading(true);
718
+ setError(null);
719
+ async function load() {
720
+ try {
721
+ const data = await client.listProducts();
722
+ if (!cancelled) {
723
+ setServices((data.products ?? []).map(enrichProduct));
724
+ }
725
+ } catch (err) {
726
+ if (!cancelled) {
727
+ setError(err instanceof Error ? err.message : "Failed to load services");
728
+ }
729
+ } finally {
730
+ if (!cancelled) setLoading(false);
731
+ }
732
+ }
733
+ load();
734
+ return () => {
735
+ cancelled = true;
736
+ };
737
+ }, [client, fetchCount]);
738
+ const refetch = react.useCallback(() => setFetchCount((c) => c + 1), []);
739
+ return { services, loading, error, refetch };
740
+ }
741
+ async function fetchServices(apiKey, opts) {
742
+ const { Resira: Resira2 } = await import('@resira/sdk');
743
+ const client = new Resira2({
744
+ apiKey,
745
+ ...opts?.baseUrl ? { baseUrl: opts.baseUrl } : {}
746
+ });
747
+ const data = await client.listProducts();
748
+ return (data.products ?? []).map(enrichProduct);
749
+ }
655
750
  var defaultSize = 20;
656
751
  function CalendarIcon({ size = defaultSize, className }) {
657
752
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1779,6 +1874,10 @@ function ServiceOverlayCard({
1779
1874
  }) {
1780
1875
  const currency = product.currency ?? "EUR";
1781
1876
  const priceLabel = product.pricingModel === "per_rider" ? "per rider" : product.pricingModel === "per_person" ? locale.perPerson : locale.perSession;
1877
+ const hasMultipleOptions = (product.durationPricing?.length ?? 0) > 1 || (product.riderTierPricing?.length ?? 0) > 1;
1878
+ const lowestPrice = hasMultipleOptions && product.durationPricing?.length ? Math.min(...product.durationPricing.map((dp) => dp.priceCents)) : product.priceCents;
1879
+ const pricePrefix = hasMultipleOptions ? "from " : "";
1880
+ const optionCount = product.durationPricing?.length ?? (product.riderTierPricing?.length ?? 0);
1782
1881
  let className = "resira-service-overlay-card";
1783
1882
  if (isSelected) className += " resira-service-overlay-card--selected";
1784
1883
  if (cardClassName) className += ` ${cardClassName}`;
@@ -1799,17 +1898,22 @@ function ServiceOverlayCard({
1799
1898
  ] }),
1800
1899
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-bottom", children: [
1801
1900
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-price", children: [
1802
- formatPrice2(product.priceCents, currency),
1901
+ pricePrefix,
1902
+ formatPrice2(lowestPrice, currency),
1803
1903
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-price-unit", children: [
1804
1904
  "/",
1805
1905
  priceLabel
1806
1906
  ] })
1807
1907
  ] }),
1808
1908
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-pills", children: [
1809
- product.durationMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
1909
+ hasMultipleOptions && optionCount > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
1910
+ /* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size: 11 }),
1911
+ optionCount,
1912
+ " options"
1913
+ ] }) : product.durationMinutes > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
1810
1914
  /* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size: 11 }),
1811
1915
  formatDuration2(product.durationMinutes)
1812
- ] }),
1916
+ ] }) : null,
1813
1917
  product.maxPartySize && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
1814
1918
  /* @__PURE__ */ jsxRuntime.jsx(UsersIcon, { size: 11 }),
1815
1919
  "max ",
@@ -3026,8 +3130,14 @@ function ResiraBookingWidget() {
3026
3130
  partySize: clampedPartySize
3027
3131
  };
3028
3132
  });
3133
+ if (step === "resource" && isServiceBased) {
3134
+ const nextIdx = stepIndex("resource", STEPS) + 1;
3135
+ if (nextIdx < STEPS.length) {
3136
+ setStep(STEPS[nextIdx]);
3137
+ }
3138
+ }
3029
3139
  },
3030
- [setActiveResourceId, domainConfig.maxPartySize]
3140
+ [setActiveResourceId, domainConfig.maxPartySize, step, isServiceBased, STEPS]
3031
3141
  );
3032
3142
  const handleResourceSelect = react.useCallback(
3033
3143
  (resourceId) => {
@@ -4245,6 +4355,7 @@ exports.UsersIcon = UsersIcon;
4245
4355
  exports.ViewfinderIcon = ViewfinderIcon;
4246
4356
  exports.WaiverConsent = WaiverConsent;
4247
4357
  exports.XIcon = XIcon;
4358
+ exports.fetchServices = fetchServices;
4248
4359
  exports.resolveTheme = resolveTheme;
4249
4360
  exports.themeToCSS = themeToCSS;
4250
4361
  exports.useAvailability = useAvailability;
@@ -4256,6 +4367,7 @@ exports.useProducts = useProducts;
4256
4367
  exports.useReservation = useReservation;
4257
4368
  exports.useResira = useResira;
4258
4369
  exports.useResources = useResources;
4370
+ exports.useServices = useServices;
4259
4371
  exports.validateGuestForm = validateGuestForm;
4260
4372
  //# sourceMappingURL=index.cjs.map
4261
4373
  //# sourceMappingURL=index.cjs.map