@resira/ui 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.
- package/README.md +5 -3
- package/dist/index.cjs +264 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -6
- package/dist/index.d.ts +61 -6
- package/dist/index.js +264 -103
- package/dist/index.js.map +1 -1
- package/dist/styles.css +232 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -222,6 +222,12 @@ function ResiraProvider({
|
|
|
222
222
|
const visibleServiceCount = config?.visibleServiceCount ?? 4;
|
|
223
223
|
const groupServicesByCategory = config?.groupServicesByCategory ?? true;
|
|
224
224
|
const renderServiceCard = config?.renderServiceCard;
|
|
225
|
+
const showStepIndicator = config?.showStepIndicator ?? true;
|
|
226
|
+
const deeplink = config?.deeplink;
|
|
227
|
+
const deeplinkGuest = config?.deeplinkGuest;
|
|
228
|
+
const onStepChange = config?.onStepChange;
|
|
229
|
+
const onBookingComplete = config?.onBookingComplete;
|
|
230
|
+
const onError = config?.onError;
|
|
225
231
|
const cssVars = useMemo(() => themeToCSS(theme), [theme]);
|
|
226
232
|
const value = useMemo(
|
|
227
233
|
() => ({
|
|
@@ -248,9 +254,15 @@ function ResiraProvider({
|
|
|
248
254
|
serviceLayout,
|
|
249
255
|
visibleServiceCount,
|
|
250
256
|
groupServicesByCategory,
|
|
251
|
-
renderServiceCard
|
|
257
|
+
renderServiceCard,
|
|
258
|
+
showStepIndicator,
|
|
259
|
+
deeplink,
|
|
260
|
+
deeplinkGuest,
|
|
261
|
+
onStepChange,
|
|
262
|
+
onBookingComplete,
|
|
263
|
+
onError
|
|
252
264
|
}),
|
|
253
|
-
[client, resourceId, activeResourceId, setActiveResourceId, catalogMode, allowMultiSelect, domain, theme, locale, domainConfig, stripePublishableKey, termsText, waiverText, showWaiver, showTerms, showRemainingSpots, depositPercent, refundPolicy, onClose, classNames, serviceLayout, visibleServiceCount, groupServicesByCategory, renderServiceCard]
|
|
265
|
+
[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]
|
|
254
266
|
);
|
|
255
267
|
return /* @__PURE__ */ jsx(ResiraContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: "resira-root", style: cssVars, children }) });
|
|
256
268
|
}
|
|
@@ -545,12 +557,17 @@ function useDish(dishId) {
|
|
|
545
557
|
}, [client, dishId]);
|
|
546
558
|
return { dish, loading, error };
|
|
547
559
|
}
|
|
548
|
-
function useDishes() {
|
|
560
|
+
function useDishes(enabled = true) {
|
|
549
561
|
const { client } = useResira();
|
|
550
562
|
const [dishes, setDishes] = useState([]);
|
|
551
|
-
const [loading, setLoading] = useState(
|
|
563
|
+
const [loading, setLoading] = useState(enabled);
|
|
552
564
|
const [error, setError] = useState(null);
|
|
553
565
|
useEffect(() => {
|
|
566
|
+
if (!enabled) {
|
|
567
|
+
setLoading(false);
|
|
568
|
+
setError(null);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
554
571
|
let cancelled = false;
|
|
555
572
|
async function fetchDishes() {
|
|
556
573
|
try {
|
|
@@ -574,7 +591,7 @@ function useDishes() {
|
|
|
574
591
|
return () => {
|
|
575
592
|
cancelled = true;
|
|
576
593
|
};
|
|
577
|
-
}, [client]);
|
|
594
|
+
}, [client, enabled]);
|
|
578
595
|
return { dishes, loading, error };
|
|
579
596
|
}
|
|
580
597
|
var defaultSize = 20;
|
|
@@ -1540,13 +1557,13 @@ function ResourcePicker({
|
|
|
1540
1557
|
error = null
|
|
1541
1558
|
}) {
|
|
1542
1559
|
if (loading) {
|
|
1543
|
-
return /* @__PURE__ */ jsxs("div", { className: "resira-loading", children: [
|
|
1544
|
-
/* @__PURE__ */ jsx("div", { className: "resira-spinner" }),
|
|
1560
|
+
return /* @__PURE__ */ jsxs("div", { className: "resira-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
1561
|
+
/* @__PURE__ */ jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
|
|
1545
1562
|
/* @__PURE__ */ jsx("span", { className: "resira-loading-text", children: "Loading resources\u2026" })
|
|
1546
1563
|
] });
|
|
1547
1564
|
}
|
|
1548
1565
|
if (error) {
|
|
1549
|
-
return /* @__PURE__ */ jsx("div", { className: "resira-error", children: /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: error }) });
|
|
1566
|
+
return /* @__PURE__ */ jsx("div", { className: "resira-error", role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: error }) });
|
|
1550
1567
|
}
|
|
1551
1568
|
if (resources.length === 0) {
|
|
1552
1569
|
return /* @__PURE__ */ jsx("div", { className: "resira-empty", children: /* @__PURE__ */ jsx("p", { children: "No resources available at the moment." }) });
|
|
@@ -1625,39 +1642,90 @@ function formatCategoryLabel(resourceType) {
|
|
|
1625
1642
|
return resourceType.trim().split(/[_\-\s]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join(" ");
|
|
1626
1643
|
}
|
|
1627
1644
|
function groupProductsByCategory(products, resources) {
|
|
1628
|
-
const
|
|
1629
|
-
resources.map((resource) => [resource.id, resource
|
|
1645
|
+
const resourceById = new Map(
|
|
1646
|
+
resources.map((resource) => [resource.id, resource])
|
|
1630
1647
|
);
|
|
1631
1648
|
const groups = /* @__PURE__ */ new Map();
|
|
1632
1649
|
products.forEach((product) => {
|
|
1633
|
-
|
|
1650
|
+
let categoryResource;
|
|
1651
|
+
const categoryType = product.equipmentIds.map((equipmentId) => {
|
|
1652
|
+
const res = resourceById.get(equipmentId);
|
|
1653
|
+
if (res?.resourceType?.trim() && !categoryResource) {
|
|
1654
|
+
categoryResource = res;
|
|
1655
|
+
}
|
|
1656
|
+
return res?.resourceType?.trim();
|
|
1657
|
+
}).find((resourceType) => Boolean(resourceType));
|
|
1634
1658
|
const groupId = categoryType?.toLowerCase() ?? UNCATEGORIZED_CATEGORY_KEY;
|
|
1635
1659
|
const label = categoryType ? formatCategoryLabel(categoryType) : UNCATEGORIZED_CATEGORY_LABEL;
|
|
1636
1660
|
if (!groups.has(groupId)) {
|
|
1637
1661
|
groups.set(groupId, {
|
|
1638
1662
|
id: groupId,
|
|
1639
1663
|
label,
|
|
1664
|
+
imageUrl: categoryResource?.imageUrl ?? void 0,
|
|
1640
1665
|
products: []
|
|
1641
1666
|
});
|
|
1642
1667
|
}
|
|
1643
|
-
groups.get(groupId)
|
|
1668
|
+
const group = groups.get(groupId);
|
|
1669
|
+
group.products.push(product);
|
|
1670
|
+
if (!group.imageUrl && categoryResource?.imageUrl) {
|
|
1671
|
+
group.imageUrl = categoryResource.imageUrl;
|
|
1672
|
+
}
|
|
1644
1673
|
});
|
|
1645
1674
|
return Array.from(groups.values());
|
|
1646
1675
|
}
|
|
1647
|
-
function
|
|
1676
|
+
function BackArrow() {
|
|
1677
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1678
|
+
/* @__PURE__ */ jsx("path", { d: "M19 12H5" }),
|
|
1679
|
+
/* @__PURE__ */ jsx("path", { d: "M12 19l-7-7 7-7" })
|
|
1680
|
+
] });
|
|
1681
|
+
}
|
|
1682
|
+
function CategoryTile({
|
|
1683
|
+
group,
|
|
1684
|
+
onClick
|
|
1685
|
+
}) {
|
|
1686
|
+
return /* @__PURE__ */ jsxs(
|
|
1687
|
+
"button",
|
|
1688
|
+
{
|
|
1689
|
+
type: "button",
|
|
1690
|
+
className: "resira-category-tile",
|
|
1691
|
+
onClick,
|
|
1692
|
+
children: [
|
|
1693
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-category-tile-image", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx(
|
|
1695
|
+
"img",
|
|
1696
|
+
{
|
|
1697
|
+
src: group.imageUrl || PLACEHOLDER_IMG2,
|
|
1698
|
+
alt: group.label,
|
|
1699
|
+
loading: "lazy"
|
|
1700
|
+
}
|
|
1701
|
+
),
|
|
1702
|
+
/* @__PURE__ */ jsx("div", { className: "resira-category-tile-overlay" })
|
|
1703
|
+
] }),
|
|
1704
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-category-tile-content", children: [
|
|
1705
|
+
/* @__PURE__ */ jsx("h3", { className: "resira-category-tile-name", children: group.label }),
|
|
1706
|
+
/* @__PURE__ */ jsxs("span", { className: "resira-category-tile-count", children: [
|
|
1707
|
+
group.products.length,
|
|
1708
|
+
" ",
|
|
1709
|
+
group.products.length === 1 ? "service" : "services"
|
|
1710
|
+
] })
|
|
1711
|
+
] })
|
|
1712
|
+
]
|
|
1713
|
+
}
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
function ServiceOverlayCard({
|
|
1648
1717
|
product,
|
|
1649
1718
|
isSelected,
|
|
1650
|
-
layout,
|
|
1651
1719
|
locale,
|
|
1652
1720
|
cardClassName
|
|
1653
1721
|
}) {
|
|
1654
1722
|
const currency = product.currency ?? "EUR";
|
|
1655
1723
|
const priceLabel = product.pricingModel === "per_rider" ? "per rider" : product.pricingModel === "per_person" ? locale.perPerson : locale.perSession;
|
|
1656
|
-
let className =
|
|
1657
|
-
if (isSelected) className += " resira-service-card--selected";
|
|
1724
|
+
let className = "resira-service-overlay-card";
|
|
1725
|
+
if (isSelected) className += " resira-service-overlay-card--selected";
|
|
1658
1726
|
if (cardClassName) className += ` ${cardClassName}`;
|
|
1659
1727
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1660
|
-
/* @__PURE__ */ jsx("div", { className: "resira-service-card-
|
|
1728
|
+
/* @__PURE__ */ jsx("div", { className: "resira-service-overlay-card-bg", children: /* @__PURE__ */ jsx(
|
|
1661
1729
|
"img",
|
|
1662
1730
|
{
|
|
1663
1731
|
src: product.imageUrl ?? PLACEHOLDER_IMG2,
|
|
@@ -1665,30 +1733,27 @@ function DefaultServiceCard({
|
|
|
1665
1733
|
loading: "lazy"
|
|
1666
1734
|
}
|
|
1667
1735
|
) }),
|
|
1668
|
-
/* @__PURE__ */
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1736
|
+
/* @__PURE__ */ jsx("div", { className: "resira-service-overlay-card-gradient" }),
|
|
1737
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-service-overlay-card-content", children: [
|
|
1738
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-service-overlay-card-top", children: [
|
|
1739
|
+
/* @__PURE__ */ jsx("h3", { className: "resira-service-overlay-card-name", children: product.name }),
|
|
1740
|
+
product.description && /* @__PURE__ */ jsx("p", { className: "resira-service-overlay-card-desc", children: product.description })
|
|
1672
1741
|
] }),
|
|
1673
|
-
/* @__PURE__ */ jsxs("div", { className: "resira-service-card-bottom", children: [
|
|
1674
|
-
/* @__PURE__ */ jsxs("span", { className: "resira-service-card-price", children: [
|
|
1742
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-service-overlay-card-bottom", children: [
|
|
1743
|
+
/* @__PURE__ */ jsxs("span", { className: "resira-service-overlay-card-price", children: [
|
|
1675
1744
|
formatPrice2(product.priceCents, currency),
|
|
1676
|
-
/* @__PURE__ */ jsxs("span", { className: "resira-service-card-price-unit", children: [
|
|
1745
|
+
/* @__PURE__ */ jsxs("span", { className: "resira-service-overlay-card-price-unit", children: [
|
|
1677
1746
|
"/",
|
|
1678
1747
|
priceLabel
|
|
1679
1748
|
] })
|
|
1680
1749
|
] }),
|
|
1681
|
-
/* @__PURE__ */ jsxs("div", { className: "resira-service-card-pills", children: [
|
|
1682
|
-
product.durationMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "resira-service-card-pill", children: [
|
|
1683
|
-
/* @__PURE__ */ jsx(ClockIcon, { size:
|
|
1750
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-service-overlay-card-pills", children: [
|
|
1751
|
+
product.durationMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "resira-service-overlay-card-pill", children: [
|
|
1752
|
+
/* @__PURE__ */ jsx(ClockIcon, { size: 11 }),
|
|
1684
1753
|
formatDuration2(product.durationMinutes)
|
|
1685
1754
|
] }),
|
|
1686
|
-
product.
|
|
1687
|
-
/* @__PURE__ */ jsx(UsersIcon, { size:
|
|
1688
|
-
product.maxPartySize ? `1\u2013${product.maxPartySize}` : locale.perPerson
|
|
1689
|
-
] }),
|
|
1690
|
-
product.maxPartySize && product.pricingModel !== "per_person" && /* @__PURE__ */ jsxs("span", { className: "resira-service-card-pill", children: [
|
|
1691
|
-
/* @__PURE__ */ jsx(UsersIcon, { size: 12 }),
|
|
1755
|
+
product.maxPartySize && /* @__PURE__ */ jsxs("span", { className: "resira-service-overlay-card-pill", children: [
|
|
1756
|
+
/* @__PURE__ */ jsx(UsersIcon, { size: 11 }),
|
|
1692
1757
|
"max ",
|
|
1693
1758
|
product.maxPartySize
|
|
1694
1759
|
] })
|
|
@@ -1716,7 +1781,8 @@ function ProductSelector({
|
|
|
1716
1781
|
error = null,
|
|
1717
1782
|
layout: layoutProp,
|
|
1718
1783
|
visibleCount: visibleCountProp,
|
|
1719
|
-
groupByCategory: groupByCategoryProp
|
|
1784
|
+
groupByCategory: groupByCategoryProp,
|
|
1785
|
+
initialCategory
|
|
1720
1786
|
}) {
|
|
1721
1787
|
const {
|
|
1722
1788
|
locale,
|
|
@@ -1730,81 +1796,107 @@ function ProductSelector({
|
|
|
1730
1796
|
const visibleCount = visibleCountProp ?? providerCount;
|
|
1731
1797
|
const groupByCategory = groupByCategoryProp ?? providerGroupByCategory;
|
|
1732
1798
|
const containerRef = useRef(null);
|
|
1799
|
+
const [activeCategory, setActiveCategory] = useState(initialCategory ?? null);
|
|
1733
1800
|
const productGroups = useMemo(
|
|
1734
|
-
() => groupByCategory ? groupProductsByCategory(products, resources) : [{ id: "all", label: "", products }],
|
|
1801
|
+
() => groupByCategory ? groupProductsByCategory(products, resources) : [{ id: "all", label: "", imageUrl: void 0, products }],
|
|
1735
1802
|
[groupByCategory, products, resources]
|
|
1736
1803
|
);
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1804
|
+
const skipCategoryView = productGroups.length <= 1;
|
|
1805
|
+
useEffect(() => {
|
|
1806
|
+
if (initialCategory && productGroups.some((g) => g.id === initialCategory)) {
|
|
1807
|
+
setActiveCategory(initialCategory);
|
|
1808
|
+
}
|
|
1809
|
+
}, [initialCategory, productGroups]);
|
|
1810
|
+
useEffect(() => {
|
|
1811
|
+
if (activeCategory && !productGroups.some((g) => g.id === activeCategory)) {
|
|
1812
|
+
setActiveCategory(null);
|
|
1740
1813
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1814
|
+
}, [activeCategory, productGroups]);
|
|
1815
|
+
const activeCategoryGroup = useMemo(
|
|
1816
|
+
() => productGroups.find((g) => g.id === activeCategory) ?? null,
|
|
1817
|
+
[productGroups, activeCategory]
|
|
1818
|
+
);
|
|
1819
|
+
const displayProducts = useMemo(() => {
|
|
1820
|
+
if (skipCategoryView) return products;
|
|
1821
|
+
return activeCategoryGroup?.products ?? [];
|
|
1822
|
+
}, [skipCategoryView, products, activeCategoryGroup]);
|
|
1823
|
+
const containerStyle = useMemo(() => {
|
|
1824
|
+
if (layout === "horizontal") return {};
|
|
1825
|
+
const cardHeight = 180;
|
|
1826
|
+
const gap = 12;
|
|
1827
|
+
const maxHeight = visibleCount * cardHeight + (visibleCount - 1) * gap;
|
|
1746
1828
|
return { maxHeight, overflowY: "auto" };
|
|
1747
|
-
}, [
|
|
1829
|
+
}, [layout, visibleCount]);
|
|
1748
1830
|
if (loading) {
|
|
1749
|
-
return /* @__PURE__ */ jsxs("div", { className: "resira-loading", children: [
|
|
1750
|
-
/* @__PURE__ */ jsx("div", { className: "resira-spinner" }),
|
|
1831
|
+
return /* @__PURE__ */ jsxs("div", { className: "resira-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
1832
|
+
/* @__PURE__ */ jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
|
|
1751
1833
|
/* @__PURE__ */ jsx("span", { className: "resira-loading-text", children: locale.loading })
|
|
1752
1834
|
] });
|
|
1753
1835
|
}
|
|
1754
1836
|
if (error) {
|
|
1755
|
-
return /* @__PURE__ */ jsx("div", { className: "resira-error", children: /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: error }) });
|
|
1837
|
+
return /* @__PURE__ */ jsx("div", { className: "resira-error", role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsx("p", { className: "resira-error-message", children: error }) });
|
|
1756
1838
|
}
|
|
1757
1839
|
if (products.length === 0) {
|
|
1758
1840
|
return /* @__PURE__ */ jsx("div", { className: "resira-empty", children: /* @__PURE__ */ jsx("p", { children: "No services available at the moment." }) });
|
|
1759
1841
|
}
|
|
1842
|
+
if (groupByCategory && !skipCategoryView && !activeCategory) {
|
|
1843
|
+
return /* @__PURE__ */ jsx("div", { className: "resira-service-picker", children: /* @__PURE__ */ jsx("div", { className: "resira-category-grid", children: productGroups.map((group) => /* @__PURE__ */ jsx(
|
|
1844
|
+
CategoryTile,
|
|
1845
|
+
{
|
|
1846
|
+
group,
|
|
1847
|
+
onClick: () => setActiveCategory(group.id)
|
|
1848
|
+
},
|
|
1849
|
+
group.id
|
|
1850
|
+
)) }) });
|
|
1851
|
+
}
|
|
1760
1852
|
const listClassName = [
|
|
1761
|
-
"resira-service-list",
|
|
1762
|
-
`resira-service-list--${layout}`,
|
|
1853
|
+
"resira-service-overlay-list",
|
|
1763
1854
|
classNames.serviceList
|
|
1764
1855
|
].filter(Boolean).join(" ");
|
|
1765
|
-
const groupedListClassName = [
|
|
1766
|
-
listClassName,
|
|
1767
|
-
groupByCategory ? "resira-service-list--grouped" : ""
|
|
1768
|
-
].filter(Boolean).join(" ");
|
|
1769
1856
|
return /* @__PURE__ */ jsxs("div", { className: "resira-service-picker", children: [
|
|
1857
|
+
groupByCategory && !skipCategoryView && activeCategory && /* @__PURE__ */ jsxs(
|
|
1858
|
+
"button",
|
|
1859
|
+
{
|
|
1860
|
+
type: "button",
|
|
1861
|
+
className: "resira-category-back",
|
|
1862
|
+
onClick: () => setActiveCategory(null),
|
|
1863
|
+
children: [
|
|
1864
|
+
/* @__PURE__ */ jsx(BackArrow, {}),
|
|
1865
|
+
/* @__PURE__ */ jsx("span", { children: activeCategoryGroup?.label ?? "All categories" })
|
|
1866
|
+
]
|
|
1867
|
+
}
|
|
1868
|
+
),
|
|
1770
1869
|
/* @__PURE__ */ jsx(
|
|
1771
1870
|
"div",
|
|
1772
1871
|
{
|
|
1773
1872
|
ref: containerRef,
|
|
1774
|
-
className:
|
|
1873
|
+
className: listClassName,
|
|
1775
1874
|
style: containerStyle,
|
|
1776
|
-
children:
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
"
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
}
|
|
1799
|
-
)
|
|
1800
|
-
},
|
|
1801
|
-
product.id
|
|
1802
|
-
);
|
|
1803
|
-
}) })
|
|
1804
|
-
] }, group.id))
|
|
1875
|
+
children: displayProducts.map((product) => {
|
|
1876
|
+
const isSelected = selectedId === product.id;
|
|
1877
|
+
return /* @__PURE__ */ jsx(
|
|
1878
|
+
"button",
|
|
1879
|
+
{
|
|
1880
|
+
type: "button",
|
|
1881
|
+
className: "resira-service-card-btn",
|
|
1882
|
+
onClick: () => onSelect(product),
|
|
1883
|
+
"aria-pressed": isSelected,
|
|
1884
|
+
children: renderServiceCard ? renderServiceCard(product, isSelected) : /* @__PURE__ */ jsx(
|
|
1885
|
+
ServiceOverlayCard,
|
|
1886
|
+
{
|
|
1887
|
+
product,
|
|
1888
|
+
isSelected,
|
|
1889
|
+
locale,
|
|
1890
|
+
cardClassName: isSelected ? classNames.serviceCardSelected : classNames.serviceCard
|
|
1891
|
+
}
|
|
1892
|
+
)
|
|
1893
|
+
},
|
|
1894
|
+
product.id
|
|
1895
|
+
);
|
|
1896
|
+
})
|
|
1805
1897
|
}
|
|
1806
1898
|
),
|
|
1807
|
-
|
|
1899
|
+
displayProducts.length > visibleCount && /* @__PURE__ */ jsxs("div", { className: "resira-service-scroll-hint", children: [
|
|
1808
1900
|
/* @__PURE__ */ jsx("span", { children: "Scroll for more" }),
|
|
1809
1901
|
/* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4" }) })
|
|
1810
1902
|
] })
|
|
@@ -2328,8 +2420,8 @@ function PaymentForm({
|
|
|
2328
2420
|
] })
|
|
2329
2421
|
] }) }),
|
|
2330
2422
|
/* @__PURE__ */ jsxs("div", { className: "resira-payment-element-container", children: [
|
|
2331
|
-
!ready && !error && /* @__PURE__ */ jsxs("div", { className: "resira-loading", style: { padding: "24px 0" }, children: [
|
|
2332
|
-
/* @__PURE__ */ jsx("div", { className: "resira-spinner" }),
|
|
2423
|
+
!ready && !error && /* @__PURE__ */ jsxs("div", { className: "resira-loading", style: { padding: "24px 0" }, role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
2424
|
+
/* @__PURE__ */ jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
|
|
2333
2425
|
/* @__PURE__ */ jsx("span", { className: "resira-loading-text", children: locale.loading })
|
|
2334
2426
|
] }),
|
|
2335
2427
|
/* @__PURE__ */ jsx(
|
|
@@ -2341,7 +2433,7 @@ function PaymentForm({
|
|
|
2341
2433
|
}
|
|
2342
2434
|
)
|
|
2343
2435
|
] }),
|
|
2344
|
-
error && /* @__PURE__ */ jsxs("div", { className: "resira-payment-error", children: [
|
|
2436
|
+
error && /* @__PURE__ */ jsxs("div", { className: "resira-payment-error", role: "alert", "aria-live": "assertive", children: [
|
|
2345
2437
|
/* @__PURE__ */ jsx(AlertCircleIcon, { size: 16 }),
|
|
2346
2438
|
/* @__PURE__ */ jsx("span", { children: error })
|
|
2347
2439
|
] }),
|
|
@@ -2599,7 +2691,13 @@ function ResiraBookingWidget() {
|
|
|
2599
2691
|
showWaiver,
|
|
2600
2692
|
showRemainingSpots,
|
|
2601
2693
|
depositPercent,
|
|
2602
|
-
onClose
|
|
2694
|
+
onClose,
|
|
2695
|
+
showStepIndicator,
|
|
2696
|
+
deeplink,
|
|
2697
|
+
deeplinkGuest,
|
|
2698
|
+
onStepChange,
|
|
2699
|
+
onBookingComplete,
|
|
2700
|
+
onError
|
|
2603
2701
|
} = useResira();
|
|
2604
2702
|
const isDateBased = domain === "rental";
|
|
2605
2703
|
const isTimeBased = domain === "restaurant" || domain === "watersport" || domain === "service";
|
|
@@ -2957,8 +3055,9 @@ function ResiraBookingWidget() {
|
|
|
2957
3055
|
setStep("confirmation");
|
|
2958
3056
|
}
|
|
2959
3057
|
}, [createPayment, paymentPayload, confirmPayment]);
|
|
2960
|
-
const handlePaymentError = useCallback((
|
|
2961
|
-
|
|
3058
|
+
const handlePaymentError = useCallback((msg) => {
|
|
3059
|
+
onError?.("payment_error", msg);
|
|
3060
|
+
}, [onError]);
|
|
2962
3061
|
const handleSubmitNoPayment = useCallback(async () => {
|
|
2963
3062
|
if (showTerms && !termsAccepted) {
|
|
2964
3063
|
setTermsError(locale.termsRequired);
|
|
@@ -2988,9 +3087,63 @@ function ResiraBookingWidget() {
|
|
|
2988
3087
|
}, [guest, selection, locale, submit, termsAccepted, waiverAccepted, showTerms, showWaiver, activeResourceId, selectedProduct]);
|
|
2989
3088
|
const footerBusy = submitting || creatingPayment || confirmingPayment || paymentFormSubmitting;
|
|
2990
3089
|
const paymentActionDisabled = !paymentIntent || !paymentFormReady || paymentFormSubmitting || confirmingPayment;
|
|
3090
|
+
const [deeplinkApplied, setDeeplinkApplied] = useState(false);
|
|
3091
|
+
useEffect(() => {
|
|
3092
|
+
if (deeplinkApplied || !deeplink) return;
|
|
3093
|
+
if (deeplink.productId && products.length > 0 && !selectedProduct) {
|
|
3094
|
+
const target = products.find((p) => p.id === deeplink.productId);
|
|
3095
|
+
if (target) {
|
|
3096
|
+
handleProductSelect(target);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
if (deeplink.partySize || deeplink.duration || deeplink.date) {
|
|
3100
|
+
setSelection((prev) => ({
|
|
3101
|
+
...prev,
|
|
3102
|
+
...deeplink.partySize ? { partySize: deeplink.partySize } : {},
|
|
3103
|
+
...deeplink.duration ? { duration: deeplink.duration } : {},
|
|
3104
|
+
...deeplink.date ? { startDate: deeplink.date } : {}
|
|
3105
|
+
}));
|
|
3106
|
+
if (deeplink.date) setSlotDate(deeplink.date);
|
|
3107
|
+
}
|
|
3108
|
+
if (deeplinkGuest) {
|
|
3109
|
+
setGuest((prev) => ({
|
|
3110
|
+
guestName: deeplinkGuest.name ?? prev.guestName,
|
|
3111
|
+
guestEmail: deeplinkGuest.email ?? prev.guestEmail,
|
|
3112
|
+
guestPhone: deeplinkGuest.phone ?? prev.guestPhone,
|
|
3113
|
+
notes: deeplinkGuest.notes ?? prev.notes
|
|
3114
|
+
}));
|
|
3115
|
+
}
|
|
3116
|
+
if (deeplink.productId && step === "resource" && isServiceBased) {
|
|
3117
|
+
const target = products.find((p) => p.id === deeplink.productId);
|
|
3118
|
+
if (target) {
|
|
3119
|
+
setStep(STEPS[stepIndex("resource", STEPS) + 1]);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
setDeeplinkApplied(true);
|
|
3123
|
+
}, [deeplink, deeplinkGuest, products, selectedProduct, step, isServiceBased, STEPS, handleProductSelect]);
|
|
3124
|
+
useEffect(() => {
|
|
3125
|
+
onStepChange?.(step);
|
|
3126
|
+
}, [step, onStepChange]);
|
|
3127
|
+
useEffect(() => {
|
|
3128
|
+
if (step === "confirmation") {
|
|
3129
|
+
const bookingId = reservation?.id ?? paymentIntent?.reservationId;
|
|
3130
|
+
onBookingComplete?.({
|
|
3131
|
+
reservationId: bookingId,
|
|
3132
|
+
product: selectedProduct ?? void 0,
|
|
3133
|
+
selection,
|
|
3134
|
+
guest
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
}, [step]);
|
|
3138
|
+
useEffect(() => {
|
|
3139
|
+
if (submitError) onError?.("submit_error", submitError);
|
|
3140
|
+
}, [submitError, onError]);
|
|
3141
|
+
useEffect(() => {
|
|
3142
|
+
if (paymentError) onError?.("payment_error", paymentError);
|
|
3143
|
+
}, [paymentError, onError]);
|
|
2991
3144
|
return /* @__PURE__ */ jsxs("div", { className: "resira-widget", children: [
|
|
2992
3145
|
step !== "confirmation" && /* @__PURE__ */ jsxs("div", { className: "resira-widget-topbar", children: [
|
|
2993
|
-
/* @__PURE__ */ jsx("nav", { className: "resira-steps", "aria-label": "Booking steps", children: STEPS.filter((s) => s !== "confirmation").map((s, i) => {
|
|
3146
|
+
showStepIndicator && /* @__PURE__ */ jsx("nav", { className: "resira-steps", "aria-label": "Booking steps", children: STEPS.filter((s) => s !== "confirmation").map((s, i) => {
|
|
2994
3147
|
const isCompleted = stepIndex(step, STEPS) > i;
|
|
2995
3148
|
const isActive = s === step;
|
|
2996
3149
|
return /* @__PURE__ */ jsxs(
|
|
@@ -3012,7 +3165,7 @@ function ResiraBookingWidget() {
|
|
|
3012
3165
|
stepSubtitle && /* @__PURE__ */ jsx("p", { className: "resira-widget-subtitle", children: stepSubtitle })
|
|
3013
3166
|
] })
|
|
3014
3167
|
] }),
|
|
3015
|
-
/* @__PURE__ */ jsxs("div", { className: "resira-widget-body", children: [
|
|
3168
|
+
/* @__PURE__ */ jsxs("div", { className: "resira-widget-body", "aria-busy": loading || calendarLoading || resourcesLoading || productsLoading || footerBusy, children: [
|
|
3016
3169
|
step === "resource" && isServiceBased && /* @__PURE__ */ jsx(
|
|
3017
3170
|
ProductSelector,
|
|
3018
3171
|
{
|
|
@@ -3035,10 +3188,10 @@ function ResiraBookingWidget() {
|
|
|
3035
3188
|
error: resourcesError
|
|
3036
3189
|
}
|
|
3037
3190
|
),
|
|
3038
|
-
step === "availability" && /* @__PURE__ */ jsx(Fragment, { children: (loading || calendarLoading) && !availability && !calendarData ? /* @__PURE__ */ jsxs("div", { className: "resira-loading", children: [
|
|
3039
|
-
/* @__PURE__ */ jsx("div", { className: "resira-spinner" }),
|
|
3191
|
+
step === "availability" && /* @__PURE__ */ jsx(Fragment, { children: (loading || calendarLoading) && !availability && !calendarData ? /* @__PURE__ */ jsxs("div", { className: "resira-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
3192
|
+
/* @__PURE__ */ jsx("div", { className: "resira-spinner", "aria-hidden": "true" }),
|
|
3040
3193
|
/* @__PURE__ */ jsx("span", { className: "resira-loading-text", children: locale.loading })
|
|
3041
|
-
] }) : error ? /* @__PURE__ */ jsxs("div", { className: "resira-error", children: [
|
|
3194
|
+
] }) : error ? /* @__PURE__ */ jsxs("div", { className: "resira-error", role: "alert", "aria-live": "assertive", children: [
|
|
3042
3195
|
/* @__PURE__ */ jsx(AlertCircleIcon, { size: 24 }),
|
|
3043
3196
|
/* @__PURE__ */ jsx("p", { className: "resira-error-message", children: error }),
|
|
3044
3197
|
/* @__PURE__ */ jsx(
|
|
@@ -3509,12 +3662,17 @@ function supportsArQuickLook() {
|
|
|
3509
3662
|
const a = document.createElement("a");
|
|
3510
3663
|
return a.relList?.supports?.("ar") ?? false;
|
|
3511
3664
|
}
|
|
3665
|
+
function supportsModelViewer() {
|
|
3666
|
+
if (typeof window === "undefined") return false;
|
|
3667
|
+
return typeof customElements !== "undefined" && customElements.get("model-viewer") !== void 0;
|
|
3668
|
+
}
|
|
3512
3669
|
function ModelViewer3D({
|
|
3513
3670
|
dish,
|
|
3514
3671
|
onClose
|
|
3515
3672
|
}) {
|
|
3516
3673
|
const model = dish.model;
|
|
3517
3674
|
const arSupported = supportsArQuickLook();
|
|
3675
|
+
const modelViewerSupported = supportsModelViewer();
|
|
3518
3676
|
const arLinkRef = useRef(null);
|
|
3519
3677
|
const handleArClick = useCallback(() => {
|
|
3520
3678
|
arLinkRef.current?.click();
|
|
@@ -3536,7 +3694,7 @@ function ModelViewer3D({
|
|
|
3536
3694
|
),
|
|
3537
3695
|
/* @__PURE__ */ jsx("h3", { className: "resira-dish-viewer-title", children: dish.name })
|
|
3538
3696
|
] }),
|
|
3539
|
-
/* @__PURE__ */ jsx("div", { className: "resira-dish-viewer-canvas", children: model?.glbUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3697
|
+
/* @__PURE__ */ jsx("div", { className: "resira-dish-viewer-canvas", children: model?.glbUrl && modelViewerSupported ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3540
3698
|
/* @__PURE__ */ jsx(
|
|
3541
3699
|
"model-viewer",
|
|
3542
3700
|
{
|
|
@@ -3578,7 +3736,7 @@ function ModelViewer3D({
|
|
|
3578
3736
|
className: "resira-dish-viewer-fallback-img"
|
|
3579
3737
|
}
|
|
3580
3738
|
),
|
|
3581
|
-
/* @__PURE__ */ jsx("p", { className: "resira-dish-viewer-no3d", children: "No 3D model available" })
|
|
3739
|
+
/* @__PURE__ */ jsx("p", { className: "resira-dish-viewer-no3d", children: model?.glbUrl ? "3D preview is unavailable here. Showing image instead." : "No 3D model available" })
|
|
3582
3740
|
] }) : /* @__PURE__ */ jsxs("div", { className: "resira-dish-viewer-fallback", children: [
|
|
3583
3741
|
/* @__PURE__ */ jsx(CubeIcon, { size: 48 }),
|
|
3584
3742
|
/* @__PURE__ */ jsx("p", { className: "resira-dish-viewer-no3d", children: "No preview available" })
|
|
@@ -3592,7 +3750,7 @@ function ModelViewer3D({
|
|
|
3592
3750
|
dish.allergens && dish.allergens.length > 0 && /* @__PURE__ */ jsx("div", { className: "resira-dish-viewer-allergens", children: dish.allergens.map((a) => /* @__PURE__ */ jsx("span", { className: "resira-dish-viewer-allergen", children: a }, a)) })
|
|
3593
3751
|
] }),
|
|
3594
3752
|
/* @__PURE__ */ jsxs("div", { className: "resira-dish-viewer-actions", children: [
|
|
3595
|
-
model?.glbUrl && /* @__PURE__ */ jsxs(
|
|
3753
|
+
model?.glbUrl && modelViewerSupported && /* @__PURE__ */ jsxs(
|
|
3596
3754
|
"button",
|
|
3597
3755
|
{
|
|
3598
3756
|
type: "button",
|
|
@@ -3671,7 +3829,8 @@ function DishShowcase({
|
|
|
3671
3829
|
const { dish: singleDish, loading: singleLoading, error: singleError } = useDish(
|
|
3672
3830
|
open && dishId ? dishId : void 0
|
|
3673
3831
|
);
|
|
3674
|
-
const
|
|
3832
|
+
const shouldLoadAllDishes = open && (!dishId || showAll);
|
|
3833
|
+
const { dishes: allDishes, loading: allLoading, error: allError } = useDishes(shouldLoadAllDishes);
|
|
3675
3834
|
const isSingleMode = !!dishId;
|
|
3676
3835
|
const loading = isSingleMode ? singleLoading : allLoading;
|
|
3677
3836
|
const error = isSingleMode ? singleError : allError;
|
|
@@ -3758,6 +3917,8 @@ function DishShowcase({
|
|
|
3758
3917
|
className: className || "resira-dish-showcase-btn",
|
|
3759
3918
|
style,
|
|
3760
3919
|
onClick: handleOpen,
|
|
3920
|
+
"aria-haspopup": "dialog",
|
|
3921
|
+
"aria-expanded": open,
|
|
3761
3922
|
children
|
|
3762
3923
|
}
|
|
3763
3924
|
),
|
|
@@ -3775,7 +3936,7 @@ function DishShowcase({
|
|
|
3775
3936
|
onClick: (e) => e.stopPropagation(),
|
|
3776
3937
|
role: "dialog",
|
|
3777
3938
|
"aria-modal": "true",
|
|
3778
|
-
"aria-label": "Dish showcase",
|
|
3939
|
+
"aria-label": selectedDish ? `${selectedDish.name} dish preview` : "Dish showcase",
|
|
3779
3940
|
children: [
|
|
3780
3941
|
/* @__PURE__ */ jsx(
|
|
3781
3942
|
"button",
|
|
@@ -3787,11 +3948,11 @@ function DishShowcase({
|
|
|
3787
3948
|
children: /* @__PURE__ */ jsx(XIcon, { size: 18 })
|
|
3788
3949
|
}
|
|
3789
3950
|
),
|
|
3790
|
-
loading && /* @__PURE__ */ jsxs("div", { className: "resira-dish-loading", children: [
|
|
3791
|
-
/* @__PURE__ */ jsx("div", { className: "resira-dish-spinner" }),
|
|
3951
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "resira-dish-loading", role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
3952
|
+
/* @__PURE__ */ jsx("div", { className: "resira-dish-spinner", "aria-hidden": "true" }),
|
|
3792
3953
|
/* @__PURE__ */ jsx("span", { children: "Loading dishes\u2026" })
|
|
3793
3954
|
] }),
|
|
3794
|
-
error && !loading && /* @__PURE__ */ jsx("div", { className: "resira-dish-error", children: /* @__PURE__ */ jsx("p", { children: error }) }),
|
|
3955
|
+
error && !loading && /* @__PURE__ */ jsx("div", { className: "resira-dish-error", role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsx("p", { children: error }) }),
|
|
3795
3956
|
!loading && !error && /* @__PURE__ */ jsx(Fragment, { children: selectedDish ? /* @__PURE__ */ jsx(
|
|
3796
3957
|
ModelViewer3D,
|
|
3797
3958
|
{
|