@magento/venia-pwa-live-search 1.0.0-alpha6

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.
Files changed (202) hide show
  1. package/package.json +31 -0
  2. package/postcss.config.js +3 -0
  3. package/src/api/fragments.js +193 -0
  4. package/src/api/graphql.js +26 -0
  5. package/src/api/mutations.js +94 -0
  6. package/src/api/queries.js +225 -0
  7. package/src/api/search.js +222 -0
  8. package/src/components/AddToCartButton/AddToCartButton.jsx +32 -0
  9. package/src/components/AddToCartButton/AddToCartButton.stories.mdx +14 -0
  10. package/src/components/AddToCartButton/index.js +10 -0
  11. package/src/components/Alert/Alert.jsx +155 -0
  12. package/src/components/Alert/index.js +11 -0
  13. package/src/components/Breadcrumbs/Breadcrumbs.jsx +34 -0
  14. package/src/components/Breadcrumbs/MockPages.js +14 -0
  15. package/src/components/Breadcrumbs/index.js +11 -0
  16. package/src/components/ButtonShimmer/ButtonShimmer.css +32 -0
  17. package/src/components/ButtonShimmer/ButtonShimmer.jsx +23 -0
  18. package/src/components/ButtonShimmer/index.js +11 -0
  19. package/src/components/CategoryFilters/CategoryFilters.jsx +59 -0
  20. package/src/components/CategoryFilters/index.js +10 -0
  21. package/src/components/Facets/Facets.jsx +50 -0
  22. package/src/components/Facets/Range/RangeFacet.js +20 -0
  23. package/src/components/Facets/Scalar/ScalarFacet.js +29 -0
  24. package/src/components/Facets/SelectedFilters.js +80 -0
  25. package/src/components/Facets/format.js +52 -0
  26. package/src/components/Facets/index.js +14 -0
  27. package/src/components/Facets/mocks.js +119 -0
  28. package/src/components/FacetsShimmer/FacetsShimmer.css +49 -0
  29. package/src/components/FacetsShimmer/FacetsShimmer.jsx +25 -0
  30. package/src/components/FacetsShimmer/index.js +11 -0
  31. package/src/components/FilterButton/FilterButton.jsx +40 -0
  32. package/src/components/FilterButton/index.js +11 -0
  33. package/src/components/ImageCarousel/Image.jsx +34 -0
  34. package/src/components/ImageCarousel/ImageCarousel.jsx +103 -0
  35. package/src/components/ImageCarousel/index.js +11 -0
  36. package/src/components/InputButtonGroup/InputButtonGroup.jsx +120 -0
  37. package/src/components/InputButtonGroup/index.js +11 -0
  38. package/src/components/LabelledInput/LabelledInput.jsx +51 -0
  39. package/src/components/LabelledInput/index.js +11 -0
  40. package/src/components/Loading/Loading.jsx +32 -0
  41. package/src/components/Loading/index.js +11 -0
  42. package/src/components/NoResults/NoResults.jsx +55 -0
  43. package/src/components/NoResults/index.js +11 -0
  44. package/src/components/Pagination/Pagination.jsx +105 -0
  45. package/src/components/Pagination/index.js +10 -0
  46. package/src/components/PerPagePicker/PerPagePicker.jsx +114 -0
  47. package/src/components/PerPagePicker/index.js +11 -0
  48. package/src/components/Pill/Pill.jsx +34 -0
  49. package/src/components/Pill/index.js +11 -0
  50. package/src/components/Pill/mock.js +23 -0
  51. package/src/components/ProductCardShimmer/ProductCardShimmer.css +72 -0
  52. package/src/components/ProductCardShimmer/ProductCardShimmer.jsx +28 -0
  53. package/src/components/ProductCardShimmer/index.js +11 -0
  54. package/src/components/ProductItem/MockData.js +508 -0
  55. package/src/components/ProductItem/ProductItem.css +84 -0
  56. package/src/components/ProductItem/ProductItem.jsx +347 -0
  57. package/src/components/ProductItem/ProductPrice.jsx +181 -0
  58. package/src/components/ProductItem/index.js +11 -0
  59. package/src/components/ProductList/MockData.js +190 -0
  60. package/src/components/ProductList/ProductList.jsx +127 -0
  61. package/src/components/ProductList/index.js +11 -0
  62. package/src/components/ProductList/product-list.css +18 -0
  63. package/src/components/SearchBar/SearchBar.jsx +33 -0
  64. package/src/components/SearchBar/index.js +11 -0
  65. package/src/components/Shimmer/Shimmer.css +82 -0
  66. package/src/components/Shimmer/Shimmer.jsx +66 -0
  67. package/src/components/Shimmer/index.js +11 -0
  68. package/src/components/Slider/Slider.css +61 -0
  69. package/src/components/Slider/Slider.jsx +103 -0
  70. package/src/components/Slider/index.jsx +11 -0
  71. package/src/components/SliderDoubleControl/SliderDoubleControl.css +83 -0
  72. package/src/components/SliderDoubleControl/SliderDoubleControl.jsx +220 -0
  73. package/src/components/SliderDoubleControl/index.js +11 -0
  74. package/src/components/SortDropdown/SortDropdown.jsx +126 -0
  75. package/src/components/SortDropdown/index.js +11 -0
  76. package/src/components/SwatchButton/SwatchButton.jsx +72 -0
  77. package/src/components/SwatchButton/index.js +11 -0
  78. package/src/components/SwatchButtonGroup/SwatchButtonGroup.jsx +86 -0
  79. package/src/components/SwatchButtonGroup/index.js +11 -0
  80. package/src/components/ViewSwitcher/ViewSwitcher.jsx +46 -0
  81. package/src/components/ViewSwitcher/index.js +11 -0
  82. package/src/components/WishlistButton/WishlistButton.jsx +67 -0
  83. package/src/components/WishlistButton/index.js +11 -0
  84. package/src/containers/App.jsx +145 -0
  85. package/src/containers/LiveSearchPLPLoader.jsx +24 -0
  86. package/src/containers/LiveSearchPopoverLoader.jsx +190 -0
  87. package/src/containers/LiveSearchSRLPLoader.jsx +24 -0
  88. package/src/containers/ProductListingPage.jsx +66 -0
  89. package/src/containers/ProductsContainer.jsx +145 -0
  90. package/src/containers/ProductsHeader.jsx +123 -0
  91. package/src/context/attributeMetadata.js +63 -0
  92. package/src/context/cart.js +97 -0
  93. package/src/context/displayChange.js +90 -0
  94. package/src/context/events.js +160 -0
  95. package/src/context/index.js +19 -0
  96. package/src/context/products.jsx +336 -0
  97. package/src/context/resultsModifierContext.js +35 -0
  98. package/src/context/search.jsx +127 -0
  99. package/src/context/store.jsx +93 -0
  100. package/src/context/translation.jsx +125 -0
  101. package/src/context/widgetConfig.jsx +120 -0
  102. package/src/context/wishlist.jsx +97 -0
  103. package/src/hooks/eventing/useEventListener.js +13 -0
  104. package/src/hooks/eventing/useLocation.js +21 -0
  105. package/src/hooks/eventing/useMagentoExtensionContext.js +28 -0
  106. package/src/hooks/eventing/usePageView.js +36 -0
  107. package/src/hooks/eventing/useShopperContext.js +33 -0
  108. package/src/hooks/eventing/useStorefrontInstanceContext.js +46 -0
  109. package/src/hooks/eventing/useViewedOffsets.js +74 -0
  110. package/src/hooks/useAccessibleDropdown.js +148 -0
  111. package/src/hooks/useLiveSearchPLPConfig.js +112 -0
  112. package/src/hooks/useLiveSearchPopoverConfig.js +83 -0
  113. package/src/hooks/useLiveSearchSRLPConfig.js +97 -0
  114. package/src/hooks/usePagination.js +83 -0
  115. package/src/hooks/useRangeFacet.js +62 -0
  116. package/src/hooks/useScalarFacet.js +61 -0
  117. package/src/hooks/useSliderFacet.js +43 -0
  118. package/src/i18n/Sorani.js +60 -0
  119. package/src/i18n/ar_AE.js +60 -0
  120. package/src/i18n/bg_BG.js +60 -0
  121. package/src/i18n/bn_IN.js +60 -0
  122. package/src/i18n/ca_ES.js +60 -0
  123. package/src/i18n/cs_CZ.js +60 -0
  124. package/src/i18n/da_DK.js +60 -0
  125. package/src/i18n/de_DE.js +60 -0
  126. package/src/i18n/el_GR.js +60 -0
  127. package/src/i18n/en_GA.js +60 -0
  128. package/src/i18n/en_GB.js +60 -0
  129. package/src/i18n/en_US.js +70 -0
  130. package/src/i18n/es_ES.js +60 -0
  131. package/src/i18n/et_EE.js +60 -0
  132. package/src/i18n/eu_ES.js +60 -0
  133. package/src/i18n/fa_IR.js +60 -0
  134. package/src/i18n/fi_FI.js +60 -0
  135. package/src/i18n/fr_FR.js +60 -0
  136. package/src/i18n/gl_ES.js +60 -0
  137. package/src/i18n/hi_IN.js +60 -0
  138. package/src/i18n/hu_HU.js +60 -0
  139. package/src/i18n/hy_AM.js +60 -0
  140. package/src/i18n/id_ID.js +60 -0
  141. package/src/i18n/index.js +89 -0
  142. package/src/i18n/it_IT.js +60 -0
  143. package/src/i18n/ja_JP.js +60 -0
  144. package/src/i18n/ko_KR.js +60 -0
  145. package/src/i18n/lt_LT.js +60 -0
  146. package/src/i18n/lv_LV.js +60 -0
  147. package/src/i18n/nb_NO.js +60 -0
  148. package/src/i18n/nl_NL.js +60 -0
  149. package/src/i18n/pt_BR.js +60 -0
  150. package/src/i18n/pt_PT.js +60 -0
  151. package/src/i18n/ro_RO.js +60 -0
  152. package/src/i18n/ru_RU.js +60 -0
  153. package/src/i18n/sv_SE.js +60 -0
  154. package/src/i18n/th_TH.js +60 -0
  155. package/src/i18n/tr_TR.js +60 -0
  156. package/src/i18n/zh_Hans_CN.js +60 -0
  157. package/src/i18n/zh_Hant_TW.js +60 -0
  158. package/src/icons/NoImage.svg +1 -0
  159. package/src/icons/adjustments.svg +3 -0
  160. package/src/icons/cart.svg +3 -0
  161. package/src/icons/checkmark.svg +3 -0
  162. package/src/icons/chevron.svg +3 -0
  163. package/src/icons/emptyHeart.svg +3 -0
  164. package/src/icons/error.svg +3 -0
  165. package/src/icons/filledHeart.svg +3 -0
  166. package/src/icons/filter.svg +29 -0
  167. package/src/icons/gridView.svg +11 -0
  168. package/src/icons/info.svg +3 -0
  169. package/src/icons/listView.svg +5 -0
  170. package/src/icons/loading.svg +6 -0
  171. package/src/icons/plus.svg +4 -0
  172. package/src/icons/sort.svg +18 -0
  173. package/src/icons/warning.svg +3 -0
  174. package/src/icons/x.svg +3 -0
  175. package/src/index.jsx +65 -0
  176. package/src/queries/customerGroupCode.gql.js +10 -0
  177. package/src/queries/eventing/getMagentoExtensionContext.gql.js +13 -0
  178. package/src/queries/eventing/getPageType.gql.js +14 -0
  179. package/src/queries/eventing/getStorefrontContext.gql.js +27 -0
  180. package/src/queries/index.js +3 -0
  181. package/src/queries/liveSearchPlpConfigs.gql.js +30 -0
  182. package/src/queries/liveSearchPopoverConfigs.gql.js +28 -0
  183. package/src/styles/autocomplete.module.css +56 -0
  184. package/src/styles/index.css +1638 -0
  185. package/src/styles/searchBar.module.css +119 -0
  186. package/src/styles/tokens.css +99 -0
  187. package/src/targets/intercept.js +21 -0
  188. package/src/utils/constants.js +26 -0
  189. package/src/utils/decodeHtmlString.js +13 -0
  190. package/src/utils/dom.js +14 -0
  191. package/src/utils/eventing/getCookie.js +9 -0
  192. package/src/utils/eventing/usePageTypeFromUrl.js +26 -0
  193. package/src/utils/getProductImage.js +94 -0
  194. package/src/utils/getProductPrice.js +83 -0
  195. package/src/utils/getUserViewHistory.js +27 -0
  196. package/src/utils/handleUrlFilters.js +164 -0
  197. package/src/utils/htmlStringDecode.js +13 -0
  198. package/src/utils/modifyResults.js +164 -0
  199. package/src/utils/sort.js +95 -0
  200. package/src/utils/useIntersectionObserver.js +27 -0
  201. package/src/utils/validateStoreDetails.js +39 -0
  202. package/src/wrappers/wrapUseApp.js +28 -0
@@ -0,0 +1,120 @@
1
+ /*
2
+ Copyright 2024 Adobe
3
+ All Rights Reserved.
4
+
5
+ NOTICE: Adobe permits you to use, modify, and distribute this file in
6
+ accordance with the terms of the Adobe license agreement accompanying
7
+ it.
8
+ */
9
+
10
+ import React, { createContext, useContext, useEffect, useState } from 'react';
11
+ import { useStore } from './store';
12
+
13
+ // Default widget config state
14
+ const defaultWidgetConfigState = {
15
+ badge: {
16
+ enabled: false,
17
+ label: '',
18
+ attributeCode: '',
19
+ backgroundColor: '',
20
+ },
21
+ price: {
22
+ showNoPrice: false,
23
+ showRange: true,
24
+ showRegularPrice: true,
25
+ showStrikethruPrice: true,
26
+ },
27
+ attributeSlot: {
28
+ enabled: false,
29
+ attributeCode: '',
30
+ backgroundColor: '',
31
+ },
32
+ addToWishlist: {
33
+ enabled: true,
34
+ placement: 'inLineWithName',
35
+ },
36
+ layout: {
37
+ defaultLayout: 'grid',
38
+ allowedLayouts: [],
39
+ showToggle: true,
40
+ },
41
+ addToCart: { enabled: true },
42
+ stockStatusFilterLook: 'radio',
43
+ swatches: {
44
+ enabled: false,
45
+ swatchAttributes: [],
46
+ swatchesOnPage: 10,
47
+ },
48
+ multipleImages: {
49
+ enabled: true,
50
+ limit: 10,
51
+ },
52
+ compare: {
53
+ enabled: true,
54
+ },
55
+ };
56
+
57
+ const WidgetConfigContext = createContext(defaultWidgetConfigState);
58
+
59
+ const WidgetConfigContextProvider = ({ children }) => {
60
+ const storeCtx = useStore();
61
+ const { environmentId, storeCode } = storeCtx;
62
+
63
+ const [widgetConfig, setWidgetConfig] = useState(defaultWidgetConfigState);
64
+ const [widgetConfigFetched, setWidgetConfigFetched] = useState(false);
65
+
66
+ useEffect(() => {
67
+ if (!environmentId || !storeCode) {
68
+ return;
69
+ }
70
+
71
+ if (!widgetConfigFetched) {
72
+ getWidgetConfig(environmentId, storeCode)
73
+ .then((results) => {
74
+ const newWidgetConfig = {
75
+ ...defaultWidgetConfigState,
76
+ ...results,
77
+ };
78
+ setWidgetConfig(newWidgetConfig);
79
+ setWidgetConfigFetched(true);
80
+ })
81
+ .finally(() => {
82
+ setWidgetConfigFetched(true);
83
+ });
84
+ }
85
+ }, [environmentId, storeCode, widgetConfigFetched]);
86
+
87
+ const getWidgetConfig = async (envId, storeCode) => {
88
+ const fileName = 'widgets-config.json';
89
+ const S3path = `/${envId}/${storeCode}/${fileName}`;
90
+ const widgetConfigUrl = `${WIDGET_CONFIG_URL}${S3path}`;
91
+
92
+ const response = await fetch(widgetConfigUrl, {
93
+ method: 'GET',
94
+ });
95
+
96
+ if (response.status !== 200) {
97
+ return defaultWidgetConfigState;
98
+ }
99
+
100
+ const results = await response.json();
101
+ return results;
102
+ };
103
+
104
+ const widgetConfigCtx = {
105
+ ...widgetConfig,
106
+ };
107
+
108
+ return (
109
+ <WidgetConfigContext.Provider value={widgetConfigCtx}>
110
+ {widgetConfigFetched && children}
111
+ </WidgetConfigContext.Provider>
112
+ );
113
+ };
114
+
115
+ const useWidgetConfig = () => {
116
+ const widgetConfigCtx = useContext(WidgetConfigContext);
117
+ return widgetConfigCtx;
118
+ };
119
+
120
+ export { WidgetConfigContextProvider, useWidgetConfig };
@@ -0,0 +1,97 @@
1
+ /*
2
+ Copyright 2024 Adobe
3
+ All Rights Reserved.
4
+
5
+ NOTICE: Adobe permits you to use, modify, and distribute this file in
6
+ accordance with the terms of the Adobe license agreement accompanying
7
+ it.
8
+ */
9
+
10
+ import React, { createContext, useContext, useEffect, useState } from 'react';
11
+ import { getGraphQL } from '../api/graphql';
12
+ import { ADD_TO_WISHLIST, REMOVE_FROM_WISHLIST } from '../api/mutations';
13
+ import { GET_CUSTOMER_WISHLISTS } from '../api/queries';
14
+ import { useStore } from './store';
15
+
16
+ // Default values for WishlistContext
17
+ const WishlistContext = createContext({});
18
+
19
+ const useWishlist = () => {
20
+ return useContext(WishlistContext);
21
+ };
22
+
23
+ const WishlistProvider = ({ children }) => {
24
+ const [isAuthorized, setIsAuthorized] = useState(false);
25
+ const [allWishlist, setAllWishlist] = useState([]);
26
+ const [wishlist, setWishlist] = useState();
27
+ const { storeViewCode, config } = useStore();
28
+
29
+ useEffect(() => {
30
+ getWishlists();
31
+ }, []);
32
+
33
+ const getWishlists = async () => {
34
+ const { data } =
35
+ (await getGraphQL(
36
+ GET_CUSTOMER_WISHLISTS,
37
+ {},
38
+ storeViewCode,
39
+ config?.baseUrl
40
+ )) || {};
41
+ const wishlistResponse = data?.customer;
42
+ const isAuthorized = !!wishlistResponse;
43
+
44
+ setIsAuthorized(isAuthorized);
45
+ if (isAuthorized) {
46
+ const firstWishlist = wishlistResponse.wishlists[0];
47
+ setWishlist(firstWishlist);
48
+ setAllWishlist(wishlistResponse.wishlists);
49
+ }
50
+ };
51
+
52
+ const addItemToWishlist = async (wishlistId, wishlistItem) => {
53
+ const { data } =
54
+ (await getGraphQL(
55
+ ADD_TO_WISHLIST,
56
+ {
57
+ wishlistId,
58
+ wishlistItems: [wishlistItem],
59
+ },
60
+ storeViewCode,
61
+ config?.baseUrl
62
+ )) || {};
63
+ const wishlistResponse = data?.addProductsToWishlist.wishlist;
64
+ setWishlist(wishlistResponse);
65
+ };
66
+
67
+ const removeItemFromWishlist = async (wishlistId, wishlistItemsIds) => {
68
+ const { data } =
69
+ (await getGraphQL(
70
+ REMOVE_FROM_WISHLIST,
71
+ {
72
+ wishlistId,
73
+ wishlistItemsIds: [wishlistItemsIds],
74
+ },
75
+ storeViewCode,
76
+ config?.baseUrl
77
+ )) || {};
78
+ const wishlistResponse = data?.removeProductsFromWishlist.wishlist;
79
+ setWishlist(wishlistResponse);
80
+ };
81
+
82
+ const wishlistContext = {
83
+ isAuthorized,
84
+ wishlist,
85
+ allWishlist,
86
+ addItemToWishlist,
87
+ removeItemFromWishlist,
88
+ };
89
+
90
+ return (
91
+ <WishlistContext.Provider value={wishlistContext}>
92
+ {children}
93
+ </WishlistContext.Provider>
94
+ );
95
+ };
96
+
97
+ export { useWishlist, WishlistProvider };
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+
3
+ const useEventListener = (target, type, listener) => {
4
+ useEffect(() => {
5
+ target.addEventListener(type, listener);
6
+
7
+ return () => {
8
+ target.removeEventListener(type, listener);
9
+ };
10
+ }, [listener, target, type]);
11
+ };
12
+
13
+ export default useEventListener;
@@ -0,0 +1,21 @@
1
+ import { useLocation as useLocationDriver } from 'react-router-dom';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ // This thin wrapper prevents us from having references to venia drivers literred around the code
5
+ const useLocation = () => {
6
+ const locationDriver = useLocationDriver();
7
+ const [location, setLocation] = useState(locationDriver);
8
+
9
+ useEffect(() => {
10
+ // Location consistency described here (https://reactrouter.com/web/api/Hooks/uselocation)
11
+ // is disrupted by Venia's implementation. This wrapper ensures that location
12
+ // only changes when the user navigates
13
+ if (locationDriver.pathname !== location.pathname) {
14
+ setLocation(locationDriver);
15
+ }
16
+ }, [locationDriver, location.pathname]);
17
+
18
+ return location;
19
+ };
20
+
21
+ export default useLocation;
@@ -0,0 +1,28 @@
1
+ import { useQuery } from '@apollo/client';
2
+ import { useEffect } from 'react';
3
+ import { GET_MAGENTO_EXTENSION_CONTEXT } from '../../queries/eventing/getMagentoExtensionContext.gql.js';
4
+ import mse from '@adobe/magento-storefront-events-sdk';
5
+
6
+ const useMagentoExtensionContext = () => {
7
+ const { data, error } = useQuery(GET_MAGENTO_EXTENSION_CONTEXT);
8
+ if (
9
+ (process.env.NODE_ENV === 'development' ||
10
+ process.env.NODE_ENV === 'test') &&
11
+ error
12
+ ) {
13
+ console.error('Magento Extension context query failed!', error);
14
+ }
15
+
16
+ useEffect(() => {
17
+ let magentoExtensionContext = null;
18
+ if (data && data.magentoExtensionContext) {
19
+ magentoExtensionContext = {
20
+ magentoExtensionVersion:
21
+ data.magentoExtensionContext.magento_extension_version
22
+ };
23
+ }
24
+ mse.context.setMagentoExtension(magentoExtensionContext);
25
+ }, [data]);
26
+ };
27
+
28
+ export default useMagentoExtensionContext;
@@ -0,0 +1,36 @@
1
+ import useLocation from './useLocation';
2
+ import { useEffect, useRef } from 'react';
3
+ import mse from '@adobe/magento-storefront-events-sdk';
4
+ import usePageTypeFromUrl from '../../utils/eventing/usePageTypeFromUrl';
5
+
6
+ // const usePageView = () => {
7
+ // const location = useLocation();
8
+ // const pageType = getPagetype(location);
9
+ // useEffect(() => {
10
+ // mse.context.setPage({ pageType });
11
+ // mse.publish.pageView();
12
+ // }, [location]);
13
+ // };
14
+
15
+ const usePageView = () => {
16
+ const location = useLocation();
17
+ const pathname = location?.pathname;
18
+
19
+ const pageType = usePageTypeFromUrl(pathname);
20
+
21
+ const lastPathnameRef = useRef(null);
22
+
23
+ useEffect(() => {
24
+ if (!pathname || !pageType) return;
25
+
26
+ // Publish only if pathname actually changed
27
+ if (lastPathnameRef.current === pathname) return;
28
+
29
+ lastPathnameRef.current = pathname;
30
+
31
+ mse.context.setPage({ pageType });
32
+ mse.publish.pageView();
33
+ }, [pathname, pageType]);
34
+ };
35
+
36
+ export default usePageView;
@@ -0,0 +1,33 @@
1
+ import { useEffect } from 'react';
2
+ import { useUserContext } from '@magento/peregrine/lib/context/user';
3
+ import mse from '@adobe/magento-storefront-events-sdk';
4
+ import { getDecodedCookie } from '../../utils/eventing/getCookie';
5
+
6
+ const useShopperContext = () => {
7
+ const [{ isSignedIn }] = useUserContext();
8
+
9
+ useEffect(() => {
10
+ if (isSignedIn) {
11
+ try {
12
+ const customerGroupCode = getDecodedCookie(
13
+ 'dataservices_customer_group='
14
+ );
15
+ mse.context.setContext('customerGroup', customerGroupCode);
16
+ } catch (error) {
17
+ console.error(
18
+ 'Cannot access customer group cookie. It seems the data-services module is not able to populate cookies properly.',
19
+ error
20
+ );
21
+ }
22
+ mse.context.setShopper({
23
+ shopperId: 'logged-in'
24
+ });
25
+ } else {
26
+ mse.context.setShopper({
27
+ shopperId: 'guest'
28
+ });
29
+ }
30
+ }, [isSignedIn]);
31
+ };
32
+
33
+ export default useShopperContext;
@@ -0,0 +1,46 @@
1
+ import { useQuery } from '@apollo/client';
2
+ import { GET_STOREFRONT_CONTEXT } from '../../queries/eventing/getStorefrontContext.gql';
3
+ import { useEffect } from 'react';
4
+ import mse from '@adobe/magento-storefront-events-sdk';
5
+
6
+ const useStorefrontInstanceContext = () => {
7
+ const { data, error } = useQuery(GET_STOREFRONT_CONTEXT);
8
+ if (
9
+ error &&
10
+ (process.env.NODE_ENV === 'development' ||
11
+ process.env.NODE_ENV === 'test')
12
+ ) {
13
+ console.error(
14
+ 'Magento Storefront Instance context query failed!',
15
+ error
16
+ );
17
+ }
18
+
19
+ useEffect(() => {
20
+ let storefrontInstanceContext = null;
21
+ if (data && data.storefrontInstanceContext) {
22
+ const { storefrontInstanceContext: storefront } = data;
23
+ storefrontInstanceContext = {
24
+ catalogExtensionVersion: storefront.catalog_extension_version,
25
+ environment: storefront.environment,
26
+ environmentId: storefront.environment_id,
27
+ storeCode: storefront.store_code,
28
+ storeId: storefront.store_id,
29
+ storeName: storefront.store_name,
30
+ storeUrl: storefront.store_url,
31
+ storeViewCode: storefront.store_view_code,
32
+ storeViewId: storefront.store_view_id,
33
+ storeViewName: storefront.store_view_name,
34
+ websiteCode: storefront.website_code,
35
+ websiteId: storefront.website_id,
36
+ websiteName: storefront.website_name,
37
+ baseCurrencyCode: storefront.base_currency_code,
38
+ storeViewCurrencyCode: storefront.store_view_currency_code,
39
+ storefrontTemplate: 'PWA Studio'
40
+ };
41
+ }
42
+ mse.context.setStorefrontInstance(storefrontInstanceContext);
43
+ }, [data, error]);
44
+ };
45
+
46
+ export default useStorefrontInstanceContext;
@@ -0,0 +1,74 @@
1
+ import { useCallback, useState } from 'react';
2
+ import useEventListener from './useEventListener';
3
+
4
+ const documentScrollTop = () =>
5
+ document.body.scrollTop || document.documentElement.scrollTop;
6
+
7
+ const documentScrollLeft = () =>
8
+ document.body.scrollLeft || document.documentElement.scrollLeft;
9
+
10
+ const useViewedOffsets = () => {
11
+ const [offsets, setOffsets] = useState(() => {
12
+ const xOffset = documentScrollLeft();
13
+ const yOffset = documentScrollTop();
14
+ return {
15
+ minXOffset: xOffset,
16
+ maxXOffset: xOffset + window.innerWidth,
17
+ minYOffset: yOffset,
18
+ maxYOffset: yOffset + window.innerHeight
19
+ };
20
+ });
21
+ let waitingOnAnimRequest = false;
22
+
23
+ // Update refs for resetting the scroll position after navigation
24
+ // Do we need this? Or could we just reset both to the current scroll position when the location changes?
25
+ const handleChange = () => {
26
+ if (!waitingOnAnimRequest) {
27
+ requestAnimationFrame(() => {
28
+ const windowLeft = documentScrollLeft();
29
+ const windowRight = windowLeft + window.innerWidth;
30
+ const windowTop = documentScrollTop();
31
+ const windowBottom = windowTop + window.innerHeight;
32
+ const newOffsets = { ...offsets };
33
+ if (windowRight > offsets.maxXOffset) {
34
+ newOffsets.maxXOffset = windowRight;
35
+ }
36
+ if (windowLeft < offsets.minXOffset) {
37
+ newOffsets.minXOffset = windowLeft;
38
+ }
39
+ if (windowBottom > offsets.maxYOffset) {
40
+ newOffsets.maxYOffset = windowBottom;
41
+ }
42
+ if (windowTop < offsets.minYOffset) {
43
+ newOffsets.minYOffset = windowTop;
44
+ }
45
+ setOffsets(newOffsets);
46
+ waitingOnAnimRequest = false;
47
+ });
48
+ waitingOnAnimRequest = true;
49
+ }
50
+ };
51
+
52
+ useEventListener(window, 'scroll', handleChange);
53
+ useEventListener(window, 'resize', handleChange);
54
+
55
+ const resetScrollOffsets = useCallback(() => {
56
+ const windowLeft = documentScrollLeft();
57
+ const windowRight = windowLeft + window.innerWidth;
58
+ const windowTop = documentScrollTop();
59
+ const windowBottom = windowTop + window.innerHeight;
60
+ setOffsets({
61
+ minXOffset: windowLeft,
62
+ maxXOffset: windowRight,
63
+ minYOffset: windowTop,
64
+ maxYOffset: windowBottom
65
+ });
66
+ }, []);
67
+
68
+ return {
69
+ resetScrollOffsets,
70
+ offsets
71
+ };
72
+ };
73
+
74
+ export default useViewedOffsets;
@@ -0,0 +1,148 @@
1
+ import { useEffect, useRef, useState, useCallback } from 'react';
2
+
3
+ const registerOpenDropdownHandlers = ({
4
+ options,
5
+ activeIndex,
6
+ setActiveIndex,
7
+ select
8
+ }) => {
9
+ const optionsLength = options.length;
10
+
11
+ const keyDownCallback = e => {
12
+ e.preventDefault();
13
+
14
+ switch (e.key) {
15
+ case 'Up':
16
+ case 'ArrowUp':
17
+ setActiveIndex(
18
+ activeIndex <= 0 ? optionsLength - 1 : activeIndex - 1
19
+ );
20
+ return;
21
+ case 'Down':
22
+ case 'ArrowDown':
23
+ setActiveIndex(
24
+ activeIndex + 1 === optionsLength ? 0 : activeIndex + 1
25
+ );
26
+ return;
27
+ case 'Enter':
28
+ case ' ': // Space
29
+ select(options[activeIndex].value);
30
+ return;
31
+ case 'Esc':
32
+ case 'Escape':
33
+ select(null);
34
+ return;
35
+ case 'PageUp':
36
+ case 'Home':
37
+ setActiveIndex(0);
38
+ return;
39
+ case 'PageDown':
40
+ case 'End':
41
+ setActiveIndex(options.length - 1);
42
+ return;
43
+ }
44
+ };
45
+
46
+ document.addEventListener('keydown', keyDownCallback);
47
+ return () => {
48
+ document.removeEventListener('keydown', keyDownCallback);
49
+ };
50
+ };
51
+
52
+ const registerClosedDropdownHandlers = ({ setIsDropdownOpen }) => {
53
+ const keyDownCallback = e => {
54
+ switch (e.key) {
55
+ case 'Up':
56
+ case 'ArrowUp':
57
+ case 'Down':
58
+ case 'ArrowDown':
59
+ case ' ': // Space
60
+ case 'Enter':
61
+ e.preventDefault();
62
+ setIsDropdownOpen(true);
63
+ }
64
+ };
65
+
66
+ document.addEventListener('keydown', keyDownCallback);
67
+ return () => {
68
+ document.removeEventListener('keydown', keyDownCallback);
69
+ };
70
+ };
71
+
72
+ const isSafari = () => {
73
+ const chromeInAgent = navigator.userAgent.indexOf('Chrome') > -1;
74
+ const safariInAgent = navigator.userAgent.indexOf('Safari') > -1;
75
+ return safariInAgent && !chromeInAgent;
76
+ };
77
+
78
+ export const useAccessibleDropdown = ({ options, value, onChange }) => {
79
+ const [isDropdownOpen, setIsDropdownOpenInternal] = useState(false);
80
+ const listRef = useRef(null);
81
+ const [activeIndex, setActiveIndex] = useState(0);
82
+ const [isFocus, setIsFocus] = useState(false);
83
+
84
+ const setIsDropdownOpen = useCallback(
85
+ v => {
86
+ if (v) {
87
+ const selected = options?.findIndex(o => o.value === value);
88
+ setActiveIndex(selected < 0 ? 0 : selected);
89
+
90
+ if (listRef.current && isSafari()) {
91
+ requestAnimationFrame(() => {
92
+ listRef.current?.focus();
93
+ });
94
+ }
95
+ } else if (listRef.current && isSafari()) {
96
+ requestAnimationFrame(() => {
97
+ listRef.current?.previousSibling?.focus();
98
+ });
99
+ }
100
+
101
+ setIsDropdownOpenInternal(v);
102
+ },
103
+ [options, value]
104
+ );
105
+
106
+ const select = useCallback(
107
+ val => {
108
+ if (val !== null && val !== undefined) {
109
+ onChange && onChange(val);
110
+ }
111
+ setIsDropdownOpen(false);
112
+ setIsFocus(false);
113
+ },
114
+ [onChange, setIsDropdownOpen]
115
+ );
116
+
117
+ useEffect(() => {
118
+ if (isDropdownOpen) {
119
+ return registerOpenDropdownHandlers({
120
+ activeIndex,
121
+ setActiveIndex,
122
+ options,
123
+ select
124
+ });
125
+ }
126
+
127
+ if (isFocus) {
128
+ return registerClosedDropdownHandlers({ setIsDropdownOpen });
129
+ }
130
+ }, [
131
+ isDropdownOpen,
132
+ activeIndex,
133
+ isFocus,
134
+ options,
135
+ select,
136
+ setIsDropdownOpen
137
+ ]);
138
+
139
+ return {
140
+ isDropdownOpen,
141
+ setIsDropdownOpen,
142
+ activeIndex,
143
+ setActiveIndex,
144
+ select,
145
+ setIsFocus,
146
+ listRef
147
+ };
148
+ };