@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.
- package/package.json +1 -1
- package/src/componentOverrideMapping.js +6 -0
- package/src/components/FavoriteSeller/AddToListButton/addToListButton.js +54 -54
- package/src/components/FavoriteSeller/AddToListButton/addToListButton.module.css +17 -17
- package/src/components/FavoriteSeller/AddToListButton/index.js +1 -1
- package/src/components/FavoriteSeller/AddToListButton/useCommonToasts.js +33 -33
- package/src/components/FilterTop/CustomFilters/customFilters.js +130 -132
- package/src/components/FilterTop/filterTop.js +1 -8
- package/src/components/LinkToOtherStores/index.js +61 -0
- package/src/components/NonSportCardsSets/nonSportCardsSets.js +0 -2
- package/src/components/RFQ/index.js +6 -3
- package/src/components/SellerDetail/sellerDetail.js +18 -1
- package/src/components/SellerInformation/sellerInformation.js +5 -3
- package/src/components/SellerSocialMedia/index.js +96 -0
- package/src/overwrites/pagebuilder/lib/ContentTypes/Products/products.js +13 -0
- package/src/overwrites/peregrine/lib/talons/MagentoRoute/magentoRoute.gql.js +27 -0
- package/src/overwrites/peregrine/lib/talons/MagentoRoute/useMagentoRoute.js +193 -0
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +40 -9
- package/src/overwrites/peregrine/lib/talons/ProductImageCarousel/useProductImageCarousel.js +77 -0
- package/src/overwrites/peregrine/lib/talons/ProductOptions/useOption.js +59 -0
- package/src/overwrites/peregrine/lib/talons/ProductOptions/useTile.js +47 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryFragments.gql.js +13 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js +23 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +0 -6
- package/src/overwrites/venia-ui/lib/components/AccountInformationPage/accountInformationPage.js +0 -1
- package/src/overwrites/venia-ui/lib/components/AccountInformationPage/editForm.js +0 -1
- package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +0 -3
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderSummary/orderSummary.js +0 -1
- package/src/overwrites/venia-ui/lib/components/Gallery/item.js +17 -3
- package/src/overwrites/venia-ui/lib/components/Price/price.js +113 -0
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/auctionDetail.js +1 -1
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +77 -104
- package/src/overwrites/venia-ui/lib/components/ProductImageCarousel/carousel.js +3 -1
- package/src/overwrites/venia-ui/lib/components/ProductOptions/option.js +112 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/option.module.css +30 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/options.js +49 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.js +118 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.module.css +68 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.js +78 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.module.css +6 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.shimmer.js +32 -0
- package/src/talons/CustomFilters/useCustomFilters.js +0 -2
- package/src/talons/FavoriteSeller/AddToListButton/addToListButton.gql.js +30 -30
- package/src/talons/FavoriteSeller/AddToListButton/useAddToFavoriteListButton.js +0 -1
- package/src/talons/LegoSets/useLegoSets.js +0 -5
- 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
|
|
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 (
|
|
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
|
-
|
|
129
|
-
?
|
|
142
|
+
valueSelected = item
|
|
143
|
+
? item.product.media_gallery_entries
|
|
130
144
|
: media_gallery_entries;
|
|
131
145
|
}
|
|
132
146
|
|
|
133
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
+
};
|