@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.
- package/package.json +31 -0
- package/postcss.config.js +3 -0
- package/src/api/fragments.js +193 -0
- package/src/api/graphql.js +26 -0
- package/src/api/mutations.js +94 -0
- package/src/api/queries.js +225 -0
- package/src/api/search.js +222 -0
- package/src/components/AddToCartButton/AddToCartButton.jsx +32 -0
- package/src/components/AddToCartButton/AddToCartButton.stories.mdx +14 -0
- package/src/components/AddToCartButton/index.js +10 -0
- package/src/components/Alert/Alert.jsx +155 -0
- package/src/components/Alert/index.js +11 -0
- package/src/components/Breadcrumbs/Breadcrumbs.jsx +34 -0
- package/src/components/Breadcrumbs/MockPages.js +14 -0
- package/src/components/Breadcrumbs/index.js +11 -0
- package/src/components/ButtonShimmer/ButtonShimmer.css +32 -0
- package/src/components/ButtonShimmer/ButtonShimmer.jsx +23 -0
- package/src/components/ButtonShimmer/index.js +11 -0
- package/src/components/CategoryFilters/CategoryFilters.jsx +59 -0
- package/src/components/CategoryFilters/index.js +10 -0
- package/src/components/Facets/Facets.jsx +50 -0
- package/src/components/Facets/Range/RangeFacet.js +20 -0
- package/src/components/Facets/Scalar/ScalarFacet.js +29 -0
- package/src/components/Facets/SelectedFilters.js +80 -0
- package/src/components/Facets/format.js +52 -0
- package/src/components/Facets/index.js +14 -0
- package/src/components/Facets/mocks.js +119 -0
- package/src/components/FacetsShimmer/FacetsShimmer.css +49 -0
- package/src/components/FacetsShimmer/FacetsShimmer.jsx +25 -0
- package/src/components/FacetsShimmer/index.js +11 -0
- package/src/components/FilterButton/FilterButton.jsx +40 -0
- package/src/components/FilterButton/index.js +11 -0
- package/src/components/ImageCarousel/Image.jsx +34 -0
- package/src/components/ImageCarousel/ImageCarousel.jsx +103 -0
- package/src/components/ImageCarousel/index.js +11 -0
- package/src/components/InputButtonGroup/InputButtonGroup.jsx +120 -0
- package/src/components/InputButtonGroup/index.js +11 -0
- package/src/components/LabelledInput/LabelledInput.jsx +51 -0
- package/src/components/LabelledInput/index.js +11 -0
- package/src/components/Loading/Loading.jsx +32 -0
- package/src/components/Loading/index.js +11 -0
- package/src/components/NoResults/NoResults.jsx +55 -0
- package/src/components/NoResults/index.js +11 -0
- package/src/components/Pagination/Pagination.jsx +105 -0
- package/src/components/Pagination/index.js +10 -0
- package/src/components/PerPagePicker/PerPagePicker.jsx +114 -0
- package/src/components/PerPagePicker/index.js +11 -0
- package/src/components/Pill/Pill.jsx +34 -0
- package/src/components/Pill/index.js +11 -0
- package/src/components/Pill/mock.js +23 -0
- package/src/components/ProductCardShimmer/ProductCardShimmer.css +72 -0
- package/src/components/ProductCardShimmer/ProductCardShimmer.jsx +28 -0
- package/src/components/ProductCardShimmer/index.js +11 -0
- package/src/components/ProductItem/MockData.js +508 -0
- package/src/components/ProductItem/ProductItem.css +84 -0
- package/src/components/ProductItem/ProductItem.jsx +347 -0
- package/src/components/ProductItem/ProductPrice.jsx +181 -0
- package/src/components/ProductItem/index.js +11 -0
- package/src/components/ProductList/MockData.js +190 -0
- package/src/components/ProductList/ProductList.jsx +127 -0
- package/src/components/ProductList/index.js +11 -0
- package/src/components/ProductList/product-list.css +18 -0
- package/src/components/SearchBar/SearchBar.jsx +33 -0
- package/src/components/SearchBar/index.js +11 -0
- package/src/components/Shimmer/Shimmer.css +82 -0
- package/src/components/Shimmer/Shimmer.jsx +66 -0
- package/src/components/Shimmer/index.js +11 -0
- package/src/components/Slider/Slider.css +61 -0
- package/src/components/Slider/Slider.jsx +103 -0
- package/src/components/Slider/index.jsx +11 -0
- package/src/components/SliderDoubleControl/SliderDoubleControl.css +83 -0
- package/src/components/SliderDoubleControl/SliderDoubleControl.jsx +220 -0
- package/src/components/SliderDoubleControl/index.js +11 -0
- package/src/components/SortDropdown/SortDropdown.jsx +126 -0
- package/src/components/SortDropdown/index.js +11 -0
- package/src/components/SwatchButton/SwatchButton.jsx +72 -0
- package/src/components/SwatchButton/index.js +11 -0
- package/src/components/SwatchButtonGroup/SwatchButtonGroup.jsx +86 -0
- package/src/components/SwatchButtonGroup/index.js +11 -0
- package/src/components/ViewSwitcher/ViewSwitcher.jsx +46 -0
- package/src/components/ViewSwitcher/index.js +11 -0
- package/src/components/WishlistButton/WishlistButton.jsx +67 -0
- package/src/components/WishlistButton/index.js +11 -0
- package/src/containers/App.jsx +145 -0
- package/src/containers/LiveSearchPLPLoader.jsx +24 -0
- package/src/containers/LiveSearchPopoverLoader.jsx +190 -0
- package/src/containers/LiveSearchSRLPLoader.jsx +24 -0
- package/src/containers/ProductListingPage.jsx +66 -0
- package/src/containers/ProductsContainer.jsx +145 -0
- package/src/containers/ProductsHeader.jsx +123 -0
- package/src/context/attributeMetadata.js +63 -0
- package/src/context/cart.js +97 -0
- package/src/context/displayChange.js +90 -0
- package/src/context/events.js +160 -0
- package/src/context/index.js +19 -0
- package/src/context/products.jsx +336 -0
- package/src/context/resultsModifierContext.js +35 -0
- package/src/context/search.jsx +127 -0
- package/src/context/store.jsx +93 -0
- package/src/context/translation.jsx +125 -0
- package/src/context/widgetConfig.jsx +120 -0
- package/src/context/wishlist.jsx +97 -0
- package/src/hooks/eventing/useEventListener.js +13 -0
- package/src/hooks/eventing/useLocation.js +21 -0
- package/src/hooks/eventing/useMagentoExtensionContext.js +28 -0
- package/src/hooks/eventing/usePageView.js +36 -0
- package/src/hooks/eventing/useShopperContext.js +33 -0
- package/src/hooks/eventing/useStorefrontInstanceContext.js +46 -0
- package/src/hooks/eventing/useViewedOffsets.js +74 -0
- package/src/hooks/useAccessibleDropdown.js +148 -0
- package/src/hooks/useLiveSearchPLPConfig.js +112 -0
- package/src/hooks/useLiveSearchPopoverConfig.js +83 -0
- package/src/hooks/useLiveSearchSRLPConfig.js +97 -0
- package/src/hooks/usePagination.js +83 -0
- package/src/hooks/useRangeFacet.js +62 -0
- package/src/hooks/useScalarFacet.js +61 -0
- package/src/hooks/useSliderFacet.js +43 -0
- package/src/i18n/Sorani.js +60 -0
- package/src/i18n/ar_AE.js +60 -0
- package/src/i18n/bg_BG.js +60 -0
- package/src/i18n/bn_IN.js +60 -0
- package/src/i18n/ca_ES.js +60 -0
- package/src/i18n/cs_CZ.js +60 -0
- package/src/i18n/da_DK.js +60 -0
- package/src/i18n/de_DE.js +60 -0
- package/src/i18n/el_GR.js +60 -0
- package/src/i18n/en_GA.js +60 -0
- package/src/i18n/en_GB.js +60 -0
- package/src/i18n/en_US.js +70 -0
- package/src/i18n/es_ES.js +60 -0
- package/src/i18n/et_EE.js +60 -0
- package/src/i18n/eu_ES.js +60 -0
- package/src/i18n/fa_IR.js +60 -0
- package/src/i18n/fi_FI.js +60 -0
- package/src/i18n/fr_FR.js +60 -0
- package/src/i18n/gl_ES.js +60 -0
- package/src/i18n/hi_IN.js +60 -0
- package/src/i18n/hu_HU.js +60 -0
- package/src/i18n/hy_AM.js +60 -0
- package/src/i18n/id_ID.js +60 -0
- package/src/i18n/index.js +89 -0
- package/src/i18n/it_IT.js +60 -0
- package/src/i18n/ja_JP.js +60 -0
- package/src/i18n/ko_KR.js +60 -0
- package/src/i18n/lt_LT.js +60 -0
- package/src/i18n/lv_LV.js +60 -0
- package/src/i18n/nb_NO.js +60 -0
- package/src/i18n/nl_NL.js +60 -0
- package/src/i18n/pt_BR.js +60 -0
- package/src/i18n/pt_PT.js +60 -0
- package/src/i18n/ro_RO.js +60 -0
- package/src/i18n/ru_RU.js +60 -0
- package/src/i18n/sv_SE.js +60 -0
- package/src/i18n/th_TH.js +60 -0
- package/src/i18n/tr_TR.js +60 -0
- package/src/i18n/zh_Hans_CN.js +60 -0
- package/src/i18n/zh_Hant_TW.js +60 -0
- package/src/icons/NoImage.svg +1 -0
- package/src/icons/adjustments.svg +3 -0
- package/src/icons/cart.svg +3 -0
- package/src/icons/checkmark.svg +3 -0
- package/src/icons/chevron.svg +3 -0
- package/src/icons/emptyHeart.svg +3 -0
- package/src/icons/error.svg +3 -0
- package/src/icons/filledHeart.svg +3 -0
- package/src/icons/filter.svg +29 -0
- package/src/icons/gridView.svg +11 -0
- package/src/icons/info.svg +3 -0
- package/src/icons/listView.svg +5 -0
- package/src/icons/loading.svg +6 -0
- package/src/icons/plus.svg +4 -0
- package/src/icons/sort.svg +18 -0
- package/src/icons/warning.svg +3 -0
- package/src/icons/x.svg +3 -0
- package/src/index.jsx +65 -0
- package/src/queries/customerGroupCode.gql.js +10 -0
- package/src/queries/eventing/getMagentoExtensionContext.gql.js +13 -0
- package/src/queries/eventing/getPageType.gql.js +14 -0
- package/src/queries/eventing/getStorefrontContext.gql.js +27 -0
- package/src/queries/index.js +3 -0
- package/src/queries/liveSearchPlpConfigs.gql.js +30 -0
- package/src/queries/liveSearchPopoverConfigs.gql.js +28 -0
- package/src/styles/autocomplete.module.css +56 -0
- package/src/styles/index.css +1638 -0
- package/src/styles/searchBar.module.css +119 -0
- package/src/styles/tokens.css +99 -0
- package/src/targets/intercept.js +21 -0
- package/src/utils/constants.js +26 -0
- package/src/utils/decodeHtmlString.js +13 -0
- package/src/utils/dom.js +14 -0
- package/src/utils/eventing/getCookie.js +9 -0
- package/src/utils/eventing/usePageTypeFromUrl.js +26 -0
- package/src/utils/getProductImage.js +94 -0
- package/src/utils/getProductPrice.js +83 -0
- package/src/utils/getUserViewHistory.js +27 -0
- package/src/utils/handleUrlFilters.js +164 -0
- package/src/utils/htmlStringDecode.js +13 -0
- package/src/utils/modifyResults.js +164 -0
- package/src/utils/sort.js +95 -0
- package/src/utils/useIntersectionObserver.js +27 -0
- package/src/utils/validateStoreDetails.js +39 -0
- 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
|
+
};
|