@riosst100/pwa-marketplace 2.1.4 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/componentOverrideMapping.js +6 -0
  3. package/src/components/FavoriteSeller/AddToListButton/addToListButton.js +54 -54
  4. package/src/components/FavoriteSeller/AddToListButton/addToListButton.module.css +17 -17
  5. package/src/components/FavoriteSeller/AddToListButton/index.js +1 -1
  6. package/src/components/FavoriteSeller/AddToListButton/useCommonToasts.js +33 -33
  7. package/src/components/FilterTop/CustomFilters/customFilters.js +130 -132
  8. package/src/components/FilterTop/filterTop.js +1 -8
  9. package/src/components/LinkToOtherStores/index.js +61 -0
  10. package/src/components/NonSportCardsSets/nonSportCardsSets.js +0 -2
  11. package/src/components/RFQ/index.js +6 -3
  12. package/src/components/SellerDetail/sellerDetail.js +18 -1
  13. package/src/components/SellerInformation/sellerInformation.js +5 -3
  14. package/src/components/SellerSocialMedia/index.js +96 -0
  15. package/src/overwrites/pagebuilder/lib/ContentTypes/Products/products.js +13 -0
  16. package/src/overwrites/peregrine/lib/talons/MagentoRoute/magentoRoute.gql.js +27 -0
  17. package/src/overwrites/peregrine/lib/talons/MagentoRoute/useMagentoRoute.js +193 -0
  18. package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +40 -9
  19. package/src/overwrites/peregrine/lib/talons/ProductImageCarousel/useProductImageCarousel.js +77 -0
  20. package/src/overwrites/peregrine/lib/talons/ProductOptions/useOption.js +59 -0
  21. package/src/overwrites/peregrine/lib/talons/ProductOptions/useTile.js +47 -0
  22. package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryFragments.gql.js +13 -0
  23. package/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js +23 -0
  24. package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +0 -6
  25. package/src/overwrites/venia-ui/lib/components/AccountInformationPage/accountInformationPage.js +0 -1
  26. package/src/overwrites/venia-ui/lib/components/AccountInformationPage/editForm.js +0 -1
  27. package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +0 -3
  28. package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderSummary/orderSummary.js +0 -1
  29. package/src/overwrites/venia-ui/lib/components/Gallery/item.js +17 -3
  30. package/src/overwrites/venia-ui/lib/components/Price/price.js +113 -0
  31. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/auctionDetail.js +1 -1
  32. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +77 -104
  33. package/src/overwrites/venia-ui/lib/components/ProductImageCarousel/carousel.js +3 -1
  34. package/src/overwrites/venia-ui/lib/components/ProductOptions/option.js +112 -0
  35. package/src/overwrites/venia-ui/lib/components/ProductOptions/option.module.css +30 -0
  36. package/src/overwrites/venia-ui/lib/components/ProductOptions/options.js +49 -0
  37. package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.js +118 -0
  38. package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.module.css +68 -0
  39. package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.js +78 -0
  40. package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.module.css +6 -0
  41. package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.shimmer.js +32 -0
  42. package/src/talons/CustomFilters/useCustomFilters.js +0 -2
  43. package/src/talons/FavoriteSeller/AddToListButton/addToListButton.gql.js +30 -30
  44. package/src/talons/FavoriteSeller/AddToListButton/useAddToFavoriteListButton.js +0 -1
  45. package/src/talons/LegoSets/useLegoSets.js +0 -5
  46. package/src/talons/TrainsSets/useTrainsSets.js +0 -3
@@ -76,6 +76,23 @@ const SellerDetail = props => {
76
76
 
77
77
  }
78
78
 
79
+ const getSellerAddressDisplay = (seller) => {
80
+ let city = seller?.city;
81
+ let country = seller?.country;
82
+
83
+ let result = '';
84
+ if (city && country) {
85
+ result = `${city}, ${country}`;
86
+ }
87
+ if (city && !country) {
88
+ result = `${city}`;
89
+ }
90
+ if (!city && country) {
91
+ result = `${country}`;
92
+ }
93
+ return result;
94
+ }
95
+
79
96
  return (
80
97
  <div className=' py-8'>
81
98
  <Slider seller={seller} rootClassname='mb-[30px]' />
@@ -106,7 +123,7 @@ const SellerDetail = props => {
106
123
  </div>
107
124
  </div>
108
125
  <div class="relative w-fit font-normal text-[#999999] text-[12px] tracking-[0] leading-[14px] whitespace-nowrap">
109
- {seller ? seller.city + ', ' + seller.country : ''}
126
+ {seller ? getSellerAddressDisplay(seller) : ''}
110
127
  </div>
111
128
  </div>
112
129
  <div className='flex flex-wrap items-start gap-4 relative'>
@@ -3,6 +3,7 @@ import SellerLocation from '../SellerLocation';
3
3
  import { Location, ShopAdd } from 'iconsax-react';
4
4
  import OperatingHours from '@riosst100/pwa-marketplace/src/components/OperatingHours';
5
5
  import SellerAddressCard from '@riosst100/pwa-marketplace/src/components/Seller/sellerAddressCard';
6
+ import SellerSocialMedia from '@riosst100/pwa-marketplace/src/components/SellerSocialMedia';
6
7
 
7
8
  const SellerInformation = ({ seller }) => {
8
9
  return (
@@ -59,10 +60,11 @@ const SellerInformation = ({ seller }) => {
59
60
  {seller ? seller.term_and_conditions : ''}
60
61
  </div>
61
62
  </div>
62
- </div >
63
- </div >
63
+ </div>
64
+ </div>
65
+ <SellerSocialMedia seller={seller} />
64
66
  {seller ? <SellerLocation storeLocators={seller.store_locators} /> : ''}
65
- </div >
67
+ </div>
66
68
  </>
67
69
  )
68
70
  }
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+
3
+ const SellerSocialMedia = (props) => {
4
+
5
+ const { seller } = props;
6
+
7
+ const { twitter_id, facebook_id, youtube_id, instagram_id, linkedin_id } = seller;
8
+
9
+ const socialMediaLinks = [
10
+ {
11
+ code: 'X',
12
+ link: twitter_id
13
+ },
14
+ {
15
+ code: 'Facebook',
16
+ link: facebook_id
17
+ },
18
+ {
19
+ code: 'Youtube',
20
+ link: youtube_id
21
+ },
22
+ {
23
+ code: 'Instagram',
24
+ link: instagram_id
25
+ },
26
+ {
27
+ code: 'LinkedIn',
28
+ link: linkedin_id
29
+ }
30
+ ];
31
+
32
+ const getLogo = (code) => {
33
+ let img = '';
34
+ if (code == "X") {
35
+ img = 'https://upload.wikimedia.org/wikipedia/commons/5/53/X_logo_2023_original.svg';
36
+ }
37
+ if (code == "Facebook") {
38
+ img = 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Facebook_f_logo_%282019%29.svg/1280px-Facebook_f_logo_%282019%29.svg.png';
39
+ }
40
+ if (code == "Youtube") {
41
+ img = 'https://cdn3.iconfinder.com/data/icons/social-network-30/512/social-06-512.png';
42
+ }
43
+ if (code == "Instagram") {
44
+ img = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Instagram_logo_2022.svg/768px-Instagram_logo_2022.svg.png';
45
+ }
46
+ if (code == "LinkedIn") {
47
+ img = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/LinkedIn_icon.svg/1024px-LinkedIn_icon.svg.png';
48
+ }
49
+
50
+ return img;
51
+ }
52
+
53
+ let sellerSocialMediaResult = [];
54
+
55
+ if (socialMediaLinks && socialMediaLinks.length) {
56
+ socialMediaLinks.forEach((socialMedia) => {
57
+ if (socialMedia.code && socialMedia.link) {
58
+ const logo = getLogo(socialMedia.code);
59
+ sellerSocialMediaResult.push(
60
+ <a href={socialMedia.link} className='platform_logo-wrapper rounded-md px-5 py-2.5 border border-gray-100 flex items-center' target="_blank">
61
+ {logo ? <img
62
+ alt={socialMedia.code}
63
+ // width="auto"
64
+ // height={30}
65
+ src={logo}
66
+ style={{
67
+ "height":"20px"
68
+ }}
69
+ /> : socialMedia.code}
70
+ </a>
71
+ )
72
+ }
73
+ })
74
+ }
75
+
76
+ return sellerSocialMediaResult && sellerSocialMediaResult.length ? <>
77
+ <div class="flex items-start relative self-stretch w-full flex-[0_0_auto]">
78
+ <div class="flex flex-col items-start gap-[15px] relative flex-1 grow">
79
+ <div class="relative w-fit mt-[-1.00px] [font-family:'Frederik-DemiBold',Helvetica] font-bold text-[14px] tracking-[0] leading-[normal] whitespace-nowrap">Social Media</div>
80
+ <div class="inline-flex items-center justify-center gap-[10px] relative flex-[0_0_auto]">
81
+ <div class="flex flex-col items-start gap-[10px]" style={
82
+ {
83
+ "display": "flex",
84
+ "flex-direction": "row",
85
+ "align-items": "center"
86
+ }
87
+ }>
88
+ {sellerSocialMediaResult}
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </> : '';
94
+ }
95
+
96
+ export default SellerSocialMedia;
@@ -335,6 +335,19 @@ export const GET_PRODUCTS_BY_URL_KEY = gql`
335
335
  amount_off
336
336
  }
337
337
  }
338
+ minimum_price {
339
+ final_price {
340
+ currency
341
+ value
342
+ }
343
+ regular_price {
344
+ currency
345
+ value
346
+ }
347
+ discount {
348
+ amount_off
349
+ }
350
+ }
338
351
  }
339
352
  sku
340
353
  small_image {
@@ -0,0 +1,27 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const RESOLVE_URL = gql`
4
+ query ResolveURL($url: String!, $product_preview_token: String) {
5
+ route(url: $url, product_preview_token: $product_preview_token) {
6
+ relative_url
7
+ redirect_code
8
+ type
9
+ ... on CmsPage {
10
+ identifier
11
+ }
12
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
13
+ ... on ProductInterface {
14
+ uid
15
+ __typename
16
+ }
17
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
18
+ ... on CategoryInterface {
19
+ uid
20
+ }
21
+ }
22
+ }
23
+ `;
24
+
25
+ export default {
26
+ resolveUrlQuery: RESOLVE_URL
27
+ };
@@ -0,0 +1,193 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { useHistory, useLocation } from 'react-router-dom';
3
+ import { useLazyQuery } from '@apollo/client';
4
+ import { useRootComponents } from '@magento/peregrine/lib/context/rootComponents';
5
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
6
+ import { getComponentData } from '@magento/peregrine/lib/util/magentoRouteData';
7
+ import { useAppContext } from '@magento/peregrine/lib/context/app';
8
+
9
+ import { getRootComponent, isRedirect } from '@magento/peregrine/lib/talons/MagentoRoute/helpers';
10
+ import DEFAULT_OPERATIONS from './magentoRoute.gql';
11
+
12
+ import { useParams } from 'react-router-dom';
13
+
14
+ const getInlinedPageData = () => {
15
+ return globalThis.INLINED_PAGE_TYPE && globalThis.INLINED_PAGE_TYPE.type
16
+ ? globalThis.INLINED_PAGE_TYPE
17
+ : null;
18
+ };
19
+
20
+ const resetInlinedPageData = () => {
21
+ globalThis.INLINED_PAGE_TYPE = false;
22
+ };
23
+
24
+ export const useMagentoRoute = (props = {}) => {
25
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
26
+ const { resolveUrlQuery } = operations;
27
+ const { replace } = useHistory();
28
+ const { pathname, search } = useLocation();
29
+ // const { preview } = useParams();
30
+ const [componentMap, setComponentMap] = useRootComponents();
31
+
32
+ const initialized = useRef(false);
33
+ const fetchedPathname = useRef(null);
34
+
35
+ const [appState, appApi] = useAppContext();
36
+ const { actions: appActions } = appApi;
37
+ const { nextRootComponent } = appState;
38
+ const { setNextRootComponent, setPageLoading } = appActions;
39
+
40
+ const setComponent = useCallback(
41
+ (key, value) => {
42
+ setComponentMap(prevMap => new Map(prevMap).set(key, value));
43
+ },
44
+ [setComponentMap]
45
+ );
46
+
47
+ const component = componentMap.get(pathname);
48
+
49
+ const [runQuery, queryResult] = useLazyQuery(resolveUrlQuery);
50
+ // destructure the query result
51
+ const { data, error, loading } = queryResult;
52
+ const { route } = data || {};
53
+
54
+ // redirect to external url
55
+ useEffect(() => {
56
+ if (route) {
57
+ const external_URL = route.relative_url;
58
+ if (external_URL && external_URL.startsWith('http')) {
59
+ window.location.replace(external_URL);
60
+ }
61
+ }
62
+ }, [route]);
63
+
64
+ useEffect(() => {
65
+ if (initialized.current || !getInlinedPageData()) {
66
+ const params = new URLSearchParams(search);
67
+ const previewToken = params.get('preview');
68
+
69
+ runQuery({
70
+ fetchPolicy: 'cache-and-network',
71
+ nextFetchPolicy: 'cache-first',
72
+ variables: { url: pathname, product_preview_token: previewToken }
73
+ });
74
+ fetchedPathname.current = pathname;
75
+ }
76
+ }, [initialized, pathname, search]); // eslint-disable-line react-hooks/exhaustive-deps
77
+
78
+ useEffect(() => {
79
+ if (component) {
80
+ return;
81
+ }
82
+
83
+ (async () => {
84
+ const { type, ...routeData } = route || {};
85
+ const { id, identifier, uid } = routeData || {};
86
+ const isEmpty = !id && !identifier && !uid;
87
+
88
+ if (!type || isEmpty) {
89
+ return;
90
+ }
91
+
92
+ try {
93
+ const rootComponent = await getRootComponent(type);
94
+ setComponent(pathname, {
95
+ component: rootComponent,
96
+ ...getComponentData(routeData),
97
+ type
98
+ });
99
+ } catch (error) {
100
+ if (process.env.NODE_ENV !== 'production') {
101
+ console.error(error);
102
+ }
103
+
104
+ setComponent(pathname, error);
105
+ }
106
+ })();
107
+ }, [route]); // eslint-disable-line react-hooks/exhaustive-deps
108
+
109
+ const { id, identifier, uid, redirect_code, relative_url, type } =
110
+ route || {};
111
+
112
+ // evaluate both results and determine the response type
113
+ const empty = !route || !type || (!id && !identifier && !uid);
114
+ const redirect = isRedirect(redirect_code);
115
+ const fetchError = component instanceof Error && component;
116
+ const routeError = fetchError || error;
117
+ const isInitialized = initialized.current || !getInlinedPageData();
118
+
119
+ let showPageLoader = false;
120
+ let routeData;
121
+
122
+ if (component && !fetchError) {
123
+ // FOUND
124
+ routeData = component;
125
+ } else if (routeError) {
126
+ // ERROR
127
+ routeData = { hasError: true, routeError };
128
+ } else if (empty && fetchedPathname.current === pathname && !loading) {
129
+ // NOT FOUND
130
+ routeData = { isNotFound: true };
131
+ } else if (nextRootComponent) {
132
+ // LOADING with full page shimmer
133
+ showPageLoader = true;
134
+ routeData = { isLoading: true, shimmer: nextRootComponent };
135
+ } else if (redirect) {
136
+ // REDIRECT
137
+ routeData = {
138
+ isRedirect: true,
139
+ relativeUrl: relative_url.startsWith('/')
140
+ ? relative_url
141
+ : '/' + relative_url
142
+ };
143
+ } else {
144
+ // LOADING
145
+ const isInitialLoad = !isInitialized;
146
+ routeData = { isLoading: true, initial: isInitialLoad };
147
+ }
148
+
149
+ useEffect(() => {
150
+ (async () => {
151
+ const inlinedData = getInlinedPageData();
152
+ if (inlinedData) {
153
+ try {
154
+ const componentType = inlinedData.type;
155
+ const rootComponent = await getRootComponent(componentType);
156
+ setComponent(pathname, {
157
+ component: rootComponent,
158
+ type: componentType,
159
+ ...getComponentData(inlinedData)
160
+ });
161
+ } catch (error) {
162
+ setComponent(pathname, error);
163
+ }
164
+ }
165
+ initialized.current = true;
166
+ })();
167
+
168
+ return () => {
169
+ // Unmount
170
+ resetInlinedPageData();
171
+ };
172
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
173
+
174
+ // perform a redirect if necesssary
175
+ useEffect(() => {
176
+ if (routeData && routeData.isRedirect) {
177
+ replace(routeData.relativeUrl);
178
+ }
179
+ }, [pathname, replace, routeData]);
180
+
181
+ useEffect(() => {
182
+ if (component) {
183
+ // Reset loading shimmer whenever component resolves
184
+ setNextRootComponent(null);
185
+ }
186
+ }, [component, pathname, setNextRootComponent]);
187
+
188
+ useEffect(() => {
189
+ setPageLoading(showPageLoader);
190
+ }, [showPageLoader, setPageLoading]);
191
+
192
+ return routeData;
193
+ };
@@ -107,14 +107,28 @@ const getMediaGalleryEntries = (product, optionCodes, optionSelections) => {
107
107
  const { media_gallery_entries, variants } = product;
108
108
  const isConfigurable = isProductConfigurable(product);
109
109
 
110
+ value = isConfigurable && variants && variants.length > 0
111
+ ? [
112
+ ...variants.flatMap(variant => variant.product.media_gallery_entries || []),
113
+ ...media_gallery_entries
114
+ ]
115
+ : media_gallery_entries;
116
+
117
+ return value;
118
+ };
119
+
120
+ const getSelectedMedia = (product, optionCodes, optionSelections) => {
121
+ let valueSelected = [];
122
+
123
+ const { media_gallery_entries, variants } = product;
124
+ const isConfigurable = isProductConfigurable(product);
125
+
110
126
  // Selections are initialized to "code => undefined". Once we select a value, like color, the selections change. This filters out unselected options.
111
127
  const optionsSelected =
112
128
  Array.from(optionSelections.values()).filter(value => !!value).length >
113
129
  0;
114
130
 
115
- if (!isConfigurable || !optionsSelected) {
116
- value = media_gallery_entries;
117
- } else {
131
+ if (isConfigurable && optionsSelected) {
118
132
  // If any of the possible variants matches the selection add that
119
133
  // variant's image to the media gallery. NOTE: This _can_, and does,
120
134
  // include variants such as size. If Magento is configured to display
@@ -125,12 +139,12 @@ const getMediaGalleryEntries = (product, optionCodes, optionSelections) => {
125
139
  variants
126
140
  });
127
141
 
128
- value = item
129
- ? [...item.product.media_gallery_entries, ...media_gallery_entries]
142
+ valueSelected = item
143
+ ? item.product.media_gallery_entries
130
144
  : media_gallery_entries;
131
145
  }
132
146
 
133
- return value;
147
+ return valueSelected;
134
148
  };
135
149
 
136
150
  // We only want to display breadcrumbs for one category on a PDP even if a
@@ -170,7 +184,7 @@ const getConfigPrice = (product, optionCodes, optionSelections) => {
170
184
  Array.from(optionSelections.values()).filter(value => !!value).length >
171
185
  0;
172
186
 
173
- if (!isConfigurable || !optionsSelected) {
187
+ if (!isConfigurable) {
174
188
  value = product.price_range?.maximum_price;
175
189
  } else {
176
190
  const item = findMatchingVariant({
@@ -179,11 +193,21 @@ const getConfigPrice = (product, optionCodes, optionSelections) => {
179
193
  variants
180
194
  });
181
195
 
182
- value = item
196
+ const priceRange = {
197
+ final_price: {
198
+ value: product.price_range?.minimum_price.final_price.value + ' - ' + product.price_range?.maximum_price.final_price.value,
199
+ currency: product.price_range?.minimum_price.final_price.currency
200
+ }
201
+ };
202
+
203
+ value = optionsSelected && item
183
204
  ? item.product.price_range?.maximum_price
184
- : product.price_range?.maximum_price;
205
+ : priceRange;
185
206
  }
186
207
 
208
+ console.log('value----=====')
209
+ console.log(value)
210
+
187
211
  return value;
188
212
  };
189
213
 
@@ -368,6 +392,11 @@ export const useProductFullDetail = props => {
368
392
  [product, optionCodes, optionSelections]
369
393
  );
370
394
 
395
+ const selectedMedia = useMemo(
396
+ () => getSelectedMedia(product, optionCodes, optionSelections),
397
+ [product, optionCodes, optionSelections]
398
+ );
399
+
371
400
  const customAttributes = useMemo(
372
401
  () => getCustomAttributes(product, optionCodes, optionSelections),
373
402
  [product, optionCodes, optionSelections]
@@ -555,6 +584,7 @@ export const useProductFullDetail = props => {
555
584
  price_range: product?.price_range,
556
585
  sku: product.sku,
557
586
  term_and_conditions: product.term_and_conditions,
587
+ link_to_other_stores: product.link_to_other_stores,
558
588
  shipping_policy: product.shipping_policy,
559
589
  return_policy: product.return_policy,
560
590
  preorder: product.preorder,
@@ -629,6 +659,7 @@ export const useProductFullDetail = props => {
629
659
  isAddProductLoading,
630
660
  isSupportedProductType,
631
661
  mediaGalleryEntries,
662
+ selectedMedia,
632
663
  shouldShowWishlistButton:
633
664
  isSignedIn &&
634
665
  storeConfigData &&
@@ -0,0 +1,77 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { useCarousel } from '@magento/peregrine';
3
+ import { generateUrlFromContainerWidth } from '@magento/peregrine/lib/util/imageUtils';
4
+ import {
5
+ MESSAGE_TYPES,
6
+ VALID_SERVICE_WORKER_ENVIRONMENT,
7
+ sendMessageToSW
8
+ } from '@magento/peregrine/lib/util/swUtils';
9
+
10
+ export const useProductImageCarousel = props => {
11
+ const { images, hoveredMedia, selectedMedia, type, imageWidth } = props;
12
+ const [carouselState, carouselApi] = useCarousel(images);
13
+ const { activeItemIndex, sortedImages } = carouselState;
14
+ const { handlePrevious, handleNext, setActiveItemIndex } = carouselApi;
15
+
16
+ const handleThumbnailClick = useCallback(
17
+ index => {
18
+ setActiveItemIndex(index);
19
+ },
20
+ [setActiveItemIndex]
21
+ );
22
+
23
+ // Whenever the incoming images changes reset the active item to the first.
24
+ useEffect(() => {
25
+ if (hoveredMedia || selectedMedia && selectedMedia.length) {
26
+
27
+ // Extract the uid from the first element of selectedMedia
28
+ const selectedMediaUid = hoveredMedia ? hoveredMedia[0].uid : selectedMedia[0].uid; // Get the uid from selectedMedia
29
+
30
+ // Find the index of the image with the matching uid
31
+ const selectedMediaIndex = sortedImages.findIndex(image => {
32
+ return image.uid === selectedMediaUid;
33
+ });
34
+
35
+ if (selectedMediaIndex) {
36
+ setActiveItemIndex(selectedMediaIndex);
37
+ } else {
38
+ setActiveItemIndex(0);
39
+ }
40
+ } else {
41
+ setActiveItemIndex(0);
42
+ }
43
+ }, [sortedImages, selectedMedia, hoveredMedia, setActiveItemIndex]);
44
+
45
+ useEffect(() => {
46
+ if (VALID_SERVICE_WORKER_ENVIRONMENT) {
47
+ const urls = images.map(
48
+ ({ file }) =>
49
+ new URL(
50
+ generateUrlFromContainerWidth(file, imageWidth, type),
51
+ location.origin
52
+ ).href
53
+ );
54
+ sendMessageToSW(MESSAGE_TYPES.PREFETCH_IMAGES, {
55
+ urls
56
+ }).catch(err => {
57
+ console.error(
58
+ 'Unable to send PREFETCH_IMAGES message to SW',
59
+ err
60
+ );
61
+ });
62
+ }
63
+ }, [images, imageWidth, type]);
64
+
65
+ const currentImage = sortedImages[activeItemIndex] || {};
66
+ const altText = currentImage.label || 'image-product';
67
+
68
+ return {
69
+ currentImage,
70
+ activeItemIndex,
71
+ altText,
72
+ handleNext,
73
+ handlePrevious,
74
+ handleThumbnailClick,
75
+ sortedImages
76
+ };
77
+ };
@@ -0,0 +1,59 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+
3
+ /**
4
+ * Talon for Option.
5
+ *
6
+ * @param {number} props.attribute_id the id of the option
7
+ * @param {function} props.onSelectionChange callback handler for when the option is clicked
8
+ * @param {string} props.selectedValue the label of the selected option
9
+ * @param {array} props.values an array containing possible values
10
+ */
11
+ export const useOption = props => {
12
+ const { attribute_id, attribute_code, onSelectionChange, selectedValue, values, variants } = props;
13
+ const [selection, setSelection] = useState(null);
14
+ const initialSelection = useMemo(() => {
15
+ let initialSelection = {};
16
+ const searchValue = selection || selectedValue;
17
+ if (searchValue) {
18
+ initialSelection =
19
+ values.find(value => value.default_label === searchValue) || {};
20
+ }
21
+ return initialSelection;
22
+ }, [selectedValue, selection, values]);
23
+
24
+ const filteredVariants = useMemo(() => {
25
+ let filteredVariants = {};
26
+ if (attribute_code) {
27
+ filteredVariants = variants.filter(variant => {
28
+ return variant.attributes.some(attribute => attribute.code === attribute_code);
29
+ });
30
+ }
31
+ return filteredVariants;
32
+ }, [attribute_code, variants]);
33
+
34
+ const valuesMap = useMemo(() => {
35
+ return new Map(
36
+ values.map(value => [value.value_index, value.store_label])
37
+ );
38
+ }, [values]);
39
+
40
+ const selectedValueDescription =
41
+ selection || initialSelection.default_label || 'None';
42
+
43
+ const handleSelectionChange = useCallback(
44
+ selection => {
45
+ setSelection(valuesMap.get(selection));
46
+
47
+ if (onSelectionChange) {
48
+ onSelectionChange(attribute_id, selection);
49
+ }
50
+ },
51
+ [attribute_id, onSelectionChange, valuesMap]
52
+ );
53
+ return {
54
+ handleSelectionChange,
55
+ initialSelection,
56
+ selectedValueDescription,
57
+ filteredVariants
58
+ };
59
+ };
@@ -0,0 +1,47 @@
1
+ import { useCallback, useMemo } from 'react';
2
+
3
+ export const useTile = props => {
4
+ const { onClick, value_index, itemVariant, setHoveredMedia } = props;
5
+
6
+ const handleClick = useCallback(() => {
7
+ onClick(value_index);
8
+ }, [value_index, onClick]);
9
+
10
+ const handleMouseEnter = useCallback(() => {
11
+ var variantMedia = itemVariant
12
+ ? itemVariant[0].product.media_gallery_entries
13
+ : null;
14
+
15
+ setHoveredMedia(variantMedia);
16
+ }, [setHoveredMedia, itemVariant, value_index]);
17
+
18
+ const handleMouseLeave = useCallback(() => {
19
+ setHoveredMedia(null);
20
+ }, [setHoveredMedia]);
21
+
22
+ const variantImg = useMemo(() => {
23
+ let variantImg = '';
24
+ if (itemVariant) {
25
+ if (itemVariant.length > 0) {
26
+ const firstVariant = itemVariant[0];
27
+
28
+ // Accessing media_gallery_entries
29
+ const mediaGalleryEntries = firstVariant.product.media_gallery_entries;
30
+
31
+ // Extracting file paths
32
+ const mediaFiles = mediaGalleryEntries.map(entry => entry.file);
33
+ variantImg = mediaFiles[0];
34
+ }
35
+ }
36
+ return variantImg;
37
+ }, [itemVariant]);
38
+
39
+ // const variantImg = "https://down-sg.img.susercontent.com/file/sg-11134207-7rbk0-lkkqr1dahoah2c";
40
+
41
+ return {
42
+ handleClick,
43
+ handleMouseEnter,
44
+ handleMouseLeave,
45
+ variantImg
46
+ };
47
+ };