@resira/ui 0.3.2 → 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.
- package/dist/index.cjs +227 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -5
- package/dist/index.d.ts +60 -5
- package/dist/index.js +227 -79
- package/dist/index.js.map +1 -1
- package/dist/styles.css +232 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -224,6 +224,12 @@ function ResiraProvider({
|
|
|
224
224
|
const visibleServiceCount = config?.visibleServiceCount ?? 4;
|
|
225
225
|
const groupServicesByCategory = config?.groupServicesByCategory ?? true;
|
|
226
226
|
const renderServiceCard = config?.renderServiceCard;
|
|
227
|
+
const showStepIndicator = config?.showStepIndicator ?? true;
|
|
228
|
+
const deeplink = config?.deeplink;
|
|
229
|
+
const deeplinkGuest = config?.deeplinkGuest;
|
|
230
|
+
const onStepChange = config?.onStepChange;
|
|
231
|
+
const onBookingComplete = config?.onBookingComplete;
|
|
232
|
+
const onError = config?.onError;
|
|
227
233
|
const cssVars = react.useMemo(() => themeToCSS(theme), [theme]);
|
|
228
234
|
const value = react.useMemo(
|
|
229
235
|
() => ({
|
|
@@ -250,9 +256,15 @@ function ResiraProvider({
|
|
|
250
256
|
serviceLayout,
|
|
251
257
|
visibleServiceCount,
|
|
252
258
|
groupServicesByCategory,
|
|
253
|
-
renderServiceCard
|
|
259
|
+
renderServiceCard,
|
|
260
|
+
showStepIndicator,
|
|
261
|
+
deeplink,
|
|
262
|
+
deeplinkGuest,
|
|
263
|
+
onStepChange,
|
|
264
|
+
onBookingComplete,
|
|
265
|
+
onError
|
|
254
266
|
}),
|
|
255
|
-
[client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard]
|
|
267
|
+
[client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard, showStepIndicator, deeplink, deeplinkGuest, onStepChange, onBookingComplete, onError]
|
|
256
268
|
);
|
|
257
269
|
return /* @__PURE__ */ jsxRuntime.jsx(ResiraContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-root", style: cssVars, children }) });
|
|
258
270
|
}
|
|
@@ -1632,39 +1644,90 @@ function formatCategoryLabel(resourceType) {
|
|
|
1632
1644
|
return resourceType.trim().split(/[_\-\s]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join(" ");
|
|
1633
1645
|
}
|
|
1634
1646
|
function groupProductsByCategory(products, resources) {
|
|
1635
|
-
const
|
|
1636
|
-
resources.map((resource) => [resource.id, resource
|
|
1647
|
+
const resourceById = new Map(
|
|
1648
|
+
resources.map((resource) => [resource.id, resource])
|
|
1637
1649
|
);
|
|
1638
1650
|
const groups = /* @__PURE__ */ new Map();
|
|
1639
1651
|
products.forEach((product) => {
|
|
1640
|
-
|
|
1652
|
+
let categoryResource;
|
|
1653
|
+
const categoryType = product.equipmentIds.map((equipmentId) => {
|
|
1654
|
+
const res = resourceById.get(equipmentId);
|
|
1655
|
+
if (res?.resourceType?.trim() && !categoryResource) {
|
|
1656
|
+
categoryResource = res;
|
|
1657
|
+
}
|
|
1658
|
+
return res?.resourceType?.trim();
|
|
1659
|
+
}).find((resourceType) => Boolean(resourceType));
|
|
1641
1660
|
const groupId = categoryType?.toLowerCase() ?? UNCATEGORIZED_CATEGORY_KEY;
|
|
1642
1661
|
const label = categoryType ? formatCategoryLabel(categoryType) : UNCATEGORIZED_CATEGORY_LABEL;
|
|
1643
1662
|
if (!groups.has(groupId)) {
|
|
1644
1663
|
groups.set(groupId, {
|
|
1645
1664
|
id: groupId,
|
|
1646
1665
|
label,
|
|
1666
|
+
imageUrl: categoryResource?.imageUrl ?? void 0,
|
|
1647
1667
|
products: []
|
|
1648
1668
|
});
|
|
1649
1669
|
}
|
|
1650
|
-
groups.get(groupId)
|
|
1670
|
+
const group = groups.get(groupId);
|
|
1671
|
+
group.products.push(product);
|
|
1672
|
+
if (!group.imageUrl && categoryResource?.imageUrl) {
|
|
1673
|
+
group.imageUrl = categoryResource.imageUrl;
|
|
1674
|
+
}
|
|
1651
1675
|
});
|
|
1652
1676
|
return Array.from(groups.values());
|
|
1653
1677
|
}
|
|
1654
|
-
function
|
|
1678
|
+
function BackArrow() {
|
|
1679
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1680
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5" }),
|
|
1681
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 19l-7-7 7-7" })
|
|
1682
|
+
] });
|
|
1683
|
+
}
|
|
1684
|
+
function CategoryTile({
|
|
1685
|
+
group,
|
|
1686
|
+
onClick
|
|
1687
|
+
}) {
|
|
1688
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1689
|
+
"button",
|
|
1690
|
+
{
|
|
1691
|
+
type: "button",
|
|
1692
|
+
className: "resira-category-tile",
|
|
1693
|
+
onClick,
|
|
1694
|
+
children: [
|
|
1695
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-category-tile-image", children: [
|
|
1696
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1697
|
+
"img",
|
|
1698
|
+
{
|
|
1699
|
+
src: group.imageUrl || PLACEHOLDER_IMG2,
|
|
1700
|
+
alt: group.label,
|
|
1701
|
+
loading: "lazy"
|
|
1702
|
+
}
|
|
1703
|
+
),
|
|
1704
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-category-tile-overlay" })
|
|
1705
|
+
] }),
|
|
1706
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-category-tile-content", children: [
|
|
1707
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "resira-category-tile-name", children: group.label }),
|
|
1708
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-category-tile-count", children: [
|
|
1709
|
+
group.products.length,
|
|
1710
|
+
" ",
|
|
1711
|
+
group.products.length === 1 ? "service" : "services"
|
|
1712
|
+
] })
|
|
1713
|
+
] })
|
|
1714
|
+
]
|
|
1715
|
+
}
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
function ServiceOverlayCard({
|
|
1655
1719
|
product,
|
|
1656
1720
|
isSelected,
|
|
1657
|
-
layout,
|
|
1658
1721
|
locale,
|
|
1659
1722
|
cardClassName
|
|
1660
1723
|
}) {
|
|
1661
1724
|
const currency = product.currency ?? "EUR";
|
|
1662
1725
|
const priceLabel = product.pricingModel === "per_rider" ? "per rider" : product.pricingModel === "per_person" ? locale.perPerson : locale.perSession;
|
|
1663
|
-
let className =
|
|
1664
|
-
if (isSelected) className += " resira-service-card--selected";
|
|
1726
|
+
let className = "resira-service-overlay-card";
|
|
1727
|
+
if (isSelected) className += " resira-service-overlay-card--selected";
|
|
1665
1728
|
if (cardClassName) className += ` ${cardClassName}`;
|
|
1666
1729
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
|
|
1667
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-service-card-
|
|
1730
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-service-overlay-card-bg", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1668
1731
|
"img",
|
|
1669
1732
|
{
|
|
1670
1733
|
src: product.imageUrl ?? PLACEHOLDER_IMG2,
|
|
@@ -1672,30 +1735,27 @@ function DefaultServiceCard({
|
|
|
1672
1735
|
loading: "lazy"
|
|
1673
1736
|
}
|
|
1674
1737
|
) }),
|
|
1675
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1738
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-service-overlay-card-gradient" }),
|
|
1739
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-content", children: [
|
|
1740
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-top", children: [
|
|
1741
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "resira-service-overlay-card-name", children: product.name }),
|
|
1742
|
+
product.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "resira-service-overlay-card-desc", children: product.description })
|
|
1679
1743
|
] }),
|
|
1680
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-card-bottom", children: [
|
|
1681
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-card-price", children: [
|
|
1744
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-bottom", children: [
|
|
1745
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-price", children: [
|
|
1682
1746
|
formatPrice2(product.priceCents, currency),
|
|
1683
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-card-price-unit", children: [
|
|
1747
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-price-unit", children: [
|
|
1684
1748
|
"/",
|
|
1685
1749
|
priceLabel
|
|
1686
1750
|
] })
|
|
1687
1751
|
] }),
|
|
1688
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-card-pills", children: [
|
|
1689
|
-
product.durationMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-card-pill", children: [
|
|
1690
|
-
/* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size:
|
|
1752
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-overlay-card-pills", children: [
|
|
1753
|
+
product.durationMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx(ClockIcon, { size: 11 }),
|
|
1691
1755
|
formatDuration2(product.durationMinutes)
|
|
1692
1756
|
] }),
|
|
1693
|
-
product.
|
|
1694
|
-
/* @__PURE__ */ jsxRuntime.jsx(UsersIcon, { size:
|
|
1695
|
-
product.maxPartySize ? `1\u2013${product.maxPartySize}` : locale.perPerson
|
|
1696
|
-
] }),
|
|
1697
|
-
product.maxPartySize && product.pricingModel !== "per_person" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-card-pill", children: [
|
|
1698
|
-
/* @__PURE__ */ jsxRuntime.jsx(UsersIcon, { size: 12 }),
|
|
1757
|
+
product.maxPartySize && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "resira-service-overlay-card-pill", children: [
|
|
1758
|
+
/* @__PURE__ */ jsxRuntime.jsx(UsersIcon, { size: 11 }),
|
|
1699
1759
|
"max ",
|
|
1700
1760
|
product.maxPartySize
|
|
1701
1761
|
] })
|
|
@@ -1723,7 +1783,8 @@ function ProductSelector({
|
|
|
1723
1783
|
error = null,
|
|
1724
1784
|
layout: layoutProp,
|
|
1725
1785
|
visibleCount: visibleCountProp,
|
|
1726
|
-
groupByCategory: groupByCategoryProp
|
|
1786
|
+
groupByCategory: groupByCategoryProp,
|
|
1787
|
+
initialCategory
|
|
1727
1788
|
}) {
|
|
1728
1789
|
const {
|
|
1729
1790
|
locale,
|
|
@@ -1737,21 +1798,37 @@ function ProductSelector({
|
|
|
1737
1798
|
const visibleCount = visibleCountProp ?? providerCount;
|
|
1738
1799
|
const groupByCategory = groupByCategoryProp ?? providerGroupByCategory;
|
|
1739
1800
|
const containerRef = react.useRef(null);
|
|
1801
|
+
const [activeCategory, setActiveCategory] = react.useState(initialCategory ?? null);
|
|
1740
1802
|
const productGroups = react.useMemo(
|
|
1741
|
-
() => groupByCategory ? groupProductsByCategory(products, resources) : [{ id: "all", label: "", products }],
|
|
1803
|
+
() => groupByCategory ? groupProductsByCategory(products, resources) : [{ id: "all", label: "", imageUrl: void 0, products }],
|
|
1742
1804
|
[groupByCategory, products, resources]
|
|
1743
1805
|
);
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1806
|
+
const skipCategoryView = productGroups.length <= 1;
|
|
1807
|
+
react.useEffect(() => {
|
|
1808
|
+
if (initialCategory && productGroups.some((g) => g.id === initialCategory)) {
|
|
1809
|
+
setActiveCategory(initialCategory);
|
|
1810
|
+
}
|
|
1811
|
+
}, [initialCategory, productGroups]);
|
|
1812
|
+
react.useEffect(() => {
|
|
1813
|
+
if (activeCategory && !productGroups.some((g) => g.id === activeCategory)) {
|
|
1814
|
+
setActiveCategory(null);
|
|
1747
1815
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1816
|
+
}, [activeCategory, productGroups]);
|
|
1817
|
+
const activeCategoryGroup = react.useMemo(
|
|
1818
|
+
() => productGroups.find((g) => g.id === activeCategory) ?? null,
|
|
1819
|
+
[productGroups, activeCategory]
|
|
1820
|
+
);
|
|
1821
|
+
const displayProducts = react.useMemo(() => {
|
|
1822
|
+
if (skipCategoryView) return products;
|
|
1823
|
+
return activeCategoryGroup?.products ?? [];
|
|
1824
|
+
}, [skipCategoryView, products, activeCategoryGroup]);
|
|
1825
|
+
const containerStyle = react.useMemo(() => {
|
|
1826
|
+
if (layout === "horizontal") return {};
|
|
1827
|
+
const cardHeight = 180;
|
|
1828
|
+
const gap = 12;
|
|
1829
|
+
const maxHeight = visibleCount * cardHeight + (visibleCount - 1) * gap;
|
|
1753
1830
|
return { maxHeight, overflowY: "auto" };
|
|
1754
|
-
}, [
|
|
1831
|
+
}, [layout, visibleCount]);
|
|
1755
1832
|
if (loading) {
|
|
1756
1833
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
1757
1834
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
|
|
@@ -1764,54 +1841,64 @@ function ProductSelector({
|
|
|
1764
1841
|
if (products.length === 0) {
|
|
1765
1842
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-empty", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No services available at the moment." }) });
|
|
1766
1843
|
}
|
|
1844
|
+
if (groupByCategory && !skipCategoryView && !activeCategory) {
|
|
1845
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-service-picker", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "resira-category-grid", children: productGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1846
|
+
CategoryTile,
|
|
1847
|
+
{
|
|
1848
|
+
group,
|
|
1849
|
+
onClick: () => setActiveCategory(group.id)
|
|
1850
|
+
},
|
|
1851
|
+
group.id
|
|
1852
|
+
)) }) });
|
|
1853
|
+
}
|
|
1767
1854
|
const listClassName = [
|
|
1768
|
-
"resira-service-list",
|
|
1769
|
-
`resira-service-list--${layout}`,
|
|
1855
|
+
"resira-service-overlay-list",
|
|
1770
1856
|
classNames.serviceList
|
|
1771
1857
|
].filter(Boolean).join(" ");
|
|
1772
|
-
const groupedListClassName = [
|
|
1773
|
-
listClassName,
|
|
1774
|
-
groupByCategory ? "resira-service-list--grouped" : ""
|
|
1775
|
-
].filter(Boolean).join(" ");
|
|
1776
1858
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-picker", children: [
|
|
1859
|
+
groupByCategory && !skipCategoryView && activeCategory && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1860
|
+
"button",
|
|
1861
|
+
{
|
|
1862
|
+
type: "button",
|
|
1863
|
+
className: "resira-category-back",
|
|
1864
|
+
onClick: () => setActiveCategory(null),
|
|
1865
|
+
children: [
|
|
1866
|
+
/* @__PURE__ */ jsxRuntime.jsx(BackArrow, {}),
|
|
1867
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: activeCategoryGroup?.label ?? "All categories" })
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
),
|
|
1777
1871
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1778
1872
|
"div",
|
|
1779
1873
|
{
|
|
1780
1874
|
ref: containerRef,
|
|
1781
|
-
className:
|
|
1875
|
+
className: listClassName,
|
|
1782
1876
|
style: containerStyle,
|
|
1783
|
-
children:
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
"
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
)
|
|
1807
|
-
},
|
|
1808
|
-
product.id
|
|
1809
|
-
);
|
|
1810
|
-
}) })
|
|
1811
|
-
] }, group.id))
|
|
1877
|
+
children: displayProducts.map((product) => {
|
|
1878
|
+
const isSelected = selectedId === product.id;
|
|
1879
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1880
|
+
"button",
|
|
1881
|
+
{
|
|
1882
|
+
type: "button",
|
|
1883
|
+
className: "resira-service-card-btn",
|
|
1884
|
+
onClick: () => onSelect(product),
|
|
1885
|
+
"aria-pressed": isSelected,
|
|
1886
|
+
children: renderServiceCard ? renderServiceCard(product, isSelected) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1887
|
+
ServiceOverlayCard,
|
|
1888
|
+
{
|
|
1889
|
+
product,
|
|
1890
|
+
isSelected,
|
|
1891
|
+
locale,
|
|
1892
|
+
cardClassName: isSelected ? classNames.serviceCardSelected : classNames.serviceCard
|
|
1893
|
+
}
|
|
1894
|
+
)
|
|
1895
|
+
},
|
|
1896
|
+
product.id
|
|
1897
|
+
);
|
|
1898
|
+
})
|
|
1812
1899
|
}
|
|
1813
1900
|
),
|
|
1814
|
-
|
|
1901
|
+
displayProducts.length > visibleCount && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-service-scroll-hint", children: [
|
|
1815
1902
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Scroll for more" }),
|
|
1816
1903
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6l4 4 4-4" }) })
|
|
1817
1904
|
] })
|
|
@@ -2606,7 +2693,13 @@ function ResiraBookingWidget() {
|
|
|
2606
2693
|
showWaiver,
|
|
2607
2694
|
showRemainingSpots,
|
|
2608
2695
|
depositPercent,
|
|
2609
|
-
onClose
|
|
2696
|
+
onClose,
|
|
2697
|
+
showStepIndicator,
|
|
2698
|
+
deeplink,
|
|
2699
|
+
deeplinkGuest,
|
|
2700
|
+
onStepChange,
|
|
2701
|
+
onBookingComplete,
|
|
2702
|
+
onError
|
|
2610
2703
|
} = useResira();
|
|
2611
2704
|
const isDateBased = domain === "rental";
|
|
2612
2705
|
const isTimeBased = domain === "restaurant" || domain === "watersport" || domain === "service";
|
|
@@ -2964,8 +3057,9 @@ function ResiraBookingWidget() {
|
|
|
2964
3057
|
setStep("confirmation");
|
|
2965
3058
|
}
|
|
2966
3059
|
}, [createPayment, paymentPayload, confirmPayment]);
|
|
2967
|
-
const handlePaymentError = react.useCallback((
|
|
2968
|
-
|
|
3060
|
+
const handlePaymentError = react.useCallback((msg) => {
|
|
3061
|
+
onError?.("payment_error", msg);
|
|
3062
|
+
}, [onError]);
|
|
2969
3063
|
const handleSubmitNoPayment = react.useCallback(async () => {
|
|
2970
3064
|
if (showTerms && !termsAccepted) {
|
|
2971
3065
|
setTermsError(locale.termsRequired);
|
|
@@ -2995,9 +3089,63 @@ function ResiraBookingWidget() {
|
|
|
2995
3089
|
}, [guest, selection, locale, submit, termsAccepted, waiverAccepted, showTerms, showWaiver, activeResourceId, selectedProduct]);
|
|
2996
3090
|
const footerBusy = submitting || creatingPayment || confirmingPayment || paymentFormSubmitting;
|
|
2997
3091
|
const paymentActionDisabled = !paymentIntent || !paymentFormReady || paymentFormSubmitting || confirmingPayment;
|
|
3092
|
+
const [deeplinkApplied, setDeeplinkApplied] = react.useState(false);
|
|
3093
|
+
react.useEffect(() => {
|
|
3094
|
+
if (deeplinkApplied || !deeplink) return;
|
|
3095
|
+
if (deeplink.productId && products.length > 0 && !selectedProduct) {
|
|
3096
|
+
const target = products.find((p) => p.id === deeplink.productId);
|
|
3097
|
+
if (target) {
|
|
3098
|
+
handleProductSelect(target);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (deeplink.partySize || deeplink.duration || deeplink.date) {
|
|
3102
|
+
setSelection((prev) => ({
|
|
3103
|
+
...prev,
|
|
3104
|
+
...deeplink.partySize ? { partySize: deeplink.partySize } : {},
|
|
3105
|
+
...deeplink.duration ? { duration: deeplink.duration } : {},
|
|
3106
|
+
...deeplink.date ? { startDate: deeplink.date } : {}
|
|
3107
|
+
}));
|
|
3108
|
+
if (deeplink.date) setSlotDate(deeplink.date);
|
|
3109
|
+
}
|
|
3110
|
+
if (deeplinkGuest) {
|
|
3111
|
+
setGuest((prev) => ({
|
|
3112
|
+
guestName: deeplinkGuest.name ?? prev.guestName,
|
|
3113
|
+
guestEmail: deeplinkGuest.email ?? prev.guestEmail,
|
|
3114
|
+
guestPhone: deeplinkGuest.phone ?? prev.guestPhone,
|
|
3115
|
+
notes: deeplinkGuest.notes ?? prev.notes
|
|
3116
|
+
}));
|
|
3117
|
+
}
|
|
3118
|
+
if (deeplink.productId && step === "resource" && isServiceBased) {
|
|
3119
|
+
const target = products.find((p) => p.id === deeplink.productId);
|
|
3120
|
+
if (target) {
|
|
3121
|
+
setStep(STEPS[stepIndex("resource", STEPS) + 1]);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
setDeeplinkApplied(true);
|
|
3125
|
+
}, [deeplink, deeplinkGuest, products, selectedProduct, step, isServiceBased, STEPS, handleProductSelect]);
|
|
3126
|
+
react.useEffect(() => {
|
|
3127
|
+
onStepChange?.(step);
|
|
3128
|
+
}, [step, onStepChange]);
|
|
3129
|
+
react.useEffect(() => {
|
|
3130
|
+
if (step === "confirmation") {
|
|
3131
|
+
const bookingId = reservation?.id ?? paymentIntent?.reservationId;
|
|
3132
|
+
onBookingComplete?.({
|
|
3133
|
+
reservationId: bookingId,
|
|
3134
|
+
product: selectedProduct ?? void 0,
|
|
3135
|
+
selection,
|
|
3136
|
+
guest
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
}, [step]);
|
|
3140
|
+
react.useEffect(() => {
|
|
3141
|
+
if (submitError) onError?.("submit_error", submitError);
|
|
3142
|
+
}, [submitError, onError]);
|
|
3143
|
+
react.useEffect(() => {
|
|
3144
|
+
if (paymentError) onError?.("payment_error", paymentError);
|
|
3145
|
+
}, [paymentError, onError]);
|
|
2998
3146
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-widget", children: [
|
|
2999
3147
|
step !== "confirmation" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "resira-widget-topbar", children: [
|
|
3000
|
-
/* @__PURE__ */ jsxRuntime.jsx("nav", { className: "resira-steps", "aria-label": "Booking steps", children: STEPS.filter((s) => s !== "confirmation").map((s, i) => {
|
|
3148
|
+
showStepIndicator && /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "resira-steps", "aria-label": "Booking steps", children: STEPS.filter((s) => s !== "confirmation").map((s, i) => {
|
|
3001
3149
|
const isCompleted = stepIndex(step, STEPS) > i;
|
|
3002
3150
|
const isActive = s === step;
|
|
3003
3151
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|