@riosst100/pwa-marketplace 1.4.1 → 1.4.3
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 +2 -0
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +639 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/product.gql.js +31 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js +191 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +2 -2
- package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.module.css +1 -1
- package/src/overwrites/venia-ui/lib/components/MegaMenu/megaMenuItem.js +2 -2
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +86 -43
package/package.json
CHANGED
|
@@ -14,6 +14,8 @@ module.exports = componentOverrideMapping = {
|
|
|
14
14
|
[`@magento/peregrine/lib/talons/SignIn/useSignIn.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/SignIn/useSignIn.js',
|
|
15
15
|
[`@magento/peregrine/lib/talons/AccountMenu/useAccountMenuItems.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/AccountMenu/useAccountMenuItems.js',
|
|
16
16
|
[`@magento/peregrine/lib/talons/MegaMenu/megaMenu.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/MegaMenu/megaMenu.gql.js',
|
|
17
|
+
[`@magento/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js',
|
|
18
|
+
[`@magento/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js',
|
|
17
19
|
[`@magento/peregrine/lib/util/deriveErrorMessage.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/util/deriveErrorMessage.js',
|
|
18
20
|
[`@magento/venia-ui/lib/components/MegaMenu/megaMenu.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/MegaMenu/megaMenu.js',
|
|
19
21
|
[`@magento/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js',
|
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { useCallback, useState, useMemo } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
import { useMutation, useQuery } from '@apollo/client';
|
|
4
|
+
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
5
|
+
import { useUserContext } from '@magento/peregrine/lib/context/user';
|
|
6
|
+
|
|
7
|
+
import { appendOptionsToPayload } from '@magento/peregrine/lib/util/appendOptionsToPayload';
|
|
8
|
+
import { findMatchingVariant } from '@magento/peregrine/lib/util/findMatchingProductVariant';
|
|
9
|
+
import { isProductConfigurable } from '@magento/peregrine/lib/util/isProductConfigurable';
|
|
10
|
+
import { isSupportedProductType as isSupported } from '@magento/peregrine/lib/util/isSupportedProductType';
|
|
11
|
+
import { deriveErrorMessage } from '@magento/peregrine/lib/util/deriveErrorMessage';
|
|
12
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
13
|
+
import defaultOperations from '@magento/peregrine/lib/talons/ProductFullDetail/productFullDetail.gql';
|
|
14
|
+
import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
|
|
15
|
+
import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants';
|
|
16
|
+
|
|
17
|
+
const INITIAL_OPTION_CODES = new Map();
|
|
18
|
+
const INITIAL_OPTION_SELECTIONS = new Map();
|
|
19
|
+
const OUT_OF_STOCK_CODE = 'OUT_OF_STOCK';
|
|
20
|
+
const IN_STOCK_CODE = 'IN_STOCK';
|
|
21
|
+
|
|
22
|
+
const deriveOptionCodesFromProduct = product => {
|
|
23
|
+
// If this is a simple product it has no option codes.
|
|
24
|
+
if (!isProductConfigurable(product)) {
|
|
25
|
+
return INITIAL_OPTION_CODES;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Initialize optionCodes based on the options of the product.
|
|
29
|
+
const initialOptionCodes = new Map();
|
|
30
|
+
for (const {
|
|
31
|
+
attribute_id,
|
|
32
|
+
attribute_code
|
|
33
|
+
} of product.configurable_options) {
|
|
34
|
+
initialOptionCodes.set(attribute_id, attribute_code);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return initialOptionCodes;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Similar to deriving the initial codes for each option.
|
|
41
|
+
const deriveOptionSelectionsFromProduct = product => {
|
|
42
|
+
if (!isProductConfigurable(product)) {
|
|
43
|
+
return INITIAL_OPTION_SELECTIONS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const initialOptionSelections = new Map();
|
|
47
|
+
for (const { attribute_id } of product.configurable_options) {
|
|
48
|
+
initialOptionSelections.set(attribute_id, undefined);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return initialOptionSelections;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getIsMissingOptions = (product, optionSelections) => {
|
|
55
|
+
// Non-configurable products can't be missing options.
|
|
56
|
+
if (!isProductConfigurable(product)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Configurable products are missing options if we have fewer
|
|
61
|
+
// option selections than the product has options.
|
|
62
|
+
const { configurable_options } = product;
|
|
63
|
+
const numProductOptions = configurable_options.length;
|
|
64
|
+
const numProductSelections = Array.from(optionSelections.values()).filter(
|
|
65
|
+
value => !!value
|
|
66
|
+
).length;
|
|
67
|
+
|
|
68
|
+
return numProductSelections < numProductOptions;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const getIsOutOfStock = (product, optionCodes, optionSelections) => {
|
|
72
|
+
const { stock_status, variants } = product;
|
|
73
|
+
const isConfigurable = isProductConfigurable(product);
|
|
74
|
+
const optionsSelected =
|
|
75
|
+
Array.from(optionSelections.values()).filter(value => !!value).length >
|
|
76
|
+
0;
|
|
77
|
+
|
|
78
|
+
if (isConfigurable && optionsSelected) {
|
|
79
|
+
const item = findMatchingVariant({
|
|
80
|
+
optionCodes,
|
|
81
|
+
optionSelections,
|
|
82
|
+
variants
|
|
83
|
+
});
|
|
84
|
+
const stockStatus = item?.product?.stock_status;
|
|
85
|
+
|
|
86
|
+
return stockStatus === OUT_OF_STOCK_CODE || !stockStatus;
|
|
87
|
+
}
|
|
88
|
+
return stock_status === OUT_OF_STOCK_CODE;
|
|
89
|
+
};
|
|
90
|
+
const getIsAllOutOfStock = product => {
|
|
91
|
+
const { stock_status, variants } = product;
|
|
92
|
+
const isConfigurable = isProductConfigurable(product);
|
|
93
|
+
|
|
94
|
+
if (isConfigurable) {
|
|
95
|
+
const inStockItem = variants.find(item => {
|
|
96
|
+
return item.product.stock_status === IN_STOCK_CODE;
|
|
97
|
+
});
|
|
98
|
+
return !inStockItem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return stock_status === OUT_OF_STOCK_CODE;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getMediaGalleryEntries = (product, optionCodes, optionSelections) => {
|
|
105
|
+
let value = [];
|
|
106
|
+
|
|
107
|
+
const { media_gallery_entries, variants } = product;
|
|
108
|
+
const isConfigurable = isProductConfigurable(product);
|
|
109
|
+
|
|
110
|
+
// Selections are initialized to "code => undefined". Once we select a value, like color, the selections change. This filters out unselected options.
|
|
111
|
+
const optionsSelected =
|
|
112
|
+
Array.from(optionSelections.values()).filter(value => !!value).length >
|
|
113
|
+
0;
|
|
114
|
+
|
|
115
|
+
if (!isConfigurable || !optionsSelected) {
|
|
116
|
+
value = media_gallery_entries;
|
|
117
|
+
} else {
|
|
118
|
+
// If any of the possible variants matches the selection add that
|
|
119
|
+
// variant's image to the media gallery. NOTE: This _can_, and does,
|
|
120
|
+
// include variants such as size. If Magento is configured to display
|
|
121
|
+
// an image for a size attribute, it will render that image.
|
|
122
|
+
const item = findMatchingVariant({
|
|
123
|
+
optionCodes,
|
|
124
|
+
optionSelections,
|
|
125
|
+
variants
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
value = item
|
|
129
|
+
? [...item.product.media_gallery_entries, ...media_gallery_entries]
|
|
130
|
+
: media_gallery_entries;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return value;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// We only want to display breadcrumbs for one category on a PDP even if a
|
|
137
|
+
// product has multiple related categories. This function filters and selects
|
|
138
|
+
// one category id for that purpose.
|
|
139
|
+
const getBreadcrumbCategoryId = categories => {
|
|
140
|
+
// Exit if there are no categories for this product.
|
|
141
|
+
if (!categories || !categories.length) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const breadcrumbSet = new Set();
|
|
145
|
+
categories.forEach(({ breadcrumbs }) => {
|
|
146
|
+
// breadcrumbs can be `null`...
|
|
147
|
+
(breadcrumbs || []).forEach(({ category_id }) =>
|
|
148
|
+
breadcrumbSet.add(category_id)
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Until we can get the single canonical breadcrumb path to a product we
|
|
153
|
+
// will just return the first category id of the potential leaf categories.
|
|
154
|
+
const leafCategory = categories.find(
|
|
155
|
+
category => !breadcrumbSet.has(category.uid)
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// If we couldn't find a leaf category then just use the first category
|
|
159
|
+
// in the list for this product.
|
|
160
|
+
return leafCategory.uid || categories[0].uid;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const getConfigPrice = (product, optionCodes, optionSelections) => {
|
|
164
|
+
let value;
|
|
165
|
+
|
|
166
|
+
const { variants } = product;
|
|
167
|
+
const isConfigurable = isProductConfigurable(product);
|
|
168
|
+
|
|
169
|
+
const optionsSelected =
|
|
170
|
+
Array.from(optionSelections.values()).filter(value => !!value).length >
|
|
171
|
+
0;
|
|
172
|
+
|
|
173
|
+
if (!isConfigurable || !optionsSelected) {
|
|
174
|
+
value = product.price_range?.maximum_price;
|
|
175
|
+
} else {
|
|
176
|
+
const item = findMatchingVariant({
|
|
177
|
+
optionCodes,
|
|
178
|
+
optionSelections,
|
|
179
|
+
variants
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
value = item
|
|
183
|
+
? item.product.price_range?.maximum_price
|
|
184
|
+
: product.price_range?.maximum_price;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return value;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const attributeLabelCompare = (attribute1, attribute2) => {
|
|
191
|
+
const label1 = attribute1['attribute_metadata']['label'].toLowerCase();
|
|
192
|
+
const label2 = attribute2['attribute_metadata']['label'].toLowerCase();
|
|
193
|
+
if (label1 < label2) return -1;
|
|
194
|
+
else if (label1 > label2) return 1;
|
|
195
|
+
else return 0;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const getCustomAttributes = (product, optionCodes, optionSelections) => {
|
|
199
|
+
const { custom_attributes, variants } = product;
|
|
200
|
+
const isConfigurable = isProductConfigurable(product);
|
|
201
|
+
const optionsSelected =
|
|
202
|
+
Array.from(optionSelections.values()).filter(value => !!value).length >
|
|
203
|
+
0;
|
|
204
|
+
|
|
205
|
+
if (isConfigurable && optionsSelected) {
|
|
206
|
+
const item = findMatchingVariant({
|
|
207
|
+
optionCodes,
|
|
208
|
+
optionSelections,
|
|
209
|
+
variants
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return item && item.product
|
|
213
|
+
? [...item.product.custom_attributes].sort(attributeLabelCompare)
|
|
214
|
+
: [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return custom_attributes
|
|
218
|
+
? [...custom_attributes].sort(attributeLabelCompare)
|
|
219
|
+
: [];
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {GraphQLDocument} props.addConfigurableProductToCartMutation - configurable product mutation
|
|
224
|
+
* @param {GraphQLDocument} props.addSimpleProductToCartMutation - configurable product mutation
|
|
225
|
+
* @param {Object.<string, GraphQLDocument>} props.operations - collection of operation overrides merged into defaults
|
|
226
|
+
* @param {Object} props.product - the product, see RootComponents/Product
|
|
227
|
+
*
|
|
228
|
+
* @returns {{
|
|
229
|
+
* breadcrumbCategoryId: string|undefined,
|
|
230
|
+
* errorMessage: string|undefined,
|
|
231
|
+
* handleAddToCart: func,
|
|
232
|
+
* handleSelectionChange: func,
|
|
233
|
+
* handleSetQuantity: func,
|
|
234
|
+
* isAddToCartDisabled: boolean,
|
|
235
|
+
* isSupportedProductType: boolean,
|
|
236
|
+
* mediaGalleryEntries: array,
|
|
237
|
+
* productDetails: object,
|
|
238
|
+
* quantity: number
|
|
239
|
+
* }}
|
|
240
|
+
*/
|
|
241
|
+
export const useProductFullDetail = props => {
|
|
242
|
+
const {
|
|
243
|
+
addConfigurableProductToCartMutation,
|
|
244
|
+
addSimpleProductToCartMutation,
|
|
245
|
+
product
|
|
246
|
+
} = props;
|
|
247
|
+
|
|
248
|
+
const [, { dispatch }] = useEventingContext();
|
|
249
|
+
|
|
250
|
+
const hasDeprecatedOperationProp = !!(
|
|
251
|
+
addConfigurableProductToCartMutation || addSimpleProductToCartMutation
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const operations = mergeOperations(defaultOperations, props.operations);
|
|
255
|
+
|
|
256
|
+
const productType = product.__typename;
|
|
257
|
+
|
|
258
|
+
const isSupportedProductType = isSupported(productType);
|
|
259
|
+
|
|
260
|
+
const [{ cartId }] = useCartContext();
|
|
261
|
+
const [{ isSignedIn }] = useUserContext();
|
|
262
|
+
const { formatMessage } = useIntl();
|
|
263
|
+
|
|
264
|
+
const { data: storeConfigData } = useQuery(
|
|
265
|
+
operations.getWishlistConfigQuery,
|
|
266
|
+
{
|
|
267
|
+
fetchPolicy: 'cache-and-network'
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const [
|
|
272
|
+
addConfigurableProductToCart,
|
|
273
|
+
{
|
|
274
|
+
error: errorAddingConfigurableProduct,
|
|
275
|
+
loading: isAddConfigurableLoading
|
|
276
|
+
}
|
|
277
|
+
] = useMutation(
|
|
278
|
+
addConfigurableProductToCartMutation ||
|
|
279
|
+
operations.addConfigurableProductToCartMutation
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const [
|
|
283
|
+
addSimpleProductToCart,
|
|
284
|
+
{ error: errorAddingSimpleProduct, loading: isAddSimpleLoading }
|
|
285
|
+
] = useMutation(
|
|
286
|
+
addSimpleProductToCartMutation ||
|
|
287
|
+
operations.addSimpleProductToCartMutation
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const [
|
|
291
|
+
addProductToCart,
|
|
292
|
+
{
|
|
293
|
+
data: addToCartResponseData,
|
|
294
|
+
error: errorAddingProductToCart,
|
|
295
|
+
loading: isAddProductLoading
|
|
296
|
+
}
|
|
297
|
+
] = useMutation(operations.addProductToCartMutation);
|
|
298
|
+
|
|
299
|
+
const breadcrumbCategoryId = useMemo(
|
|
300
|
+
() => getBreadcrumbCategoryId(product.categories),
|
|
301
|
+
[product.categories]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const derivedOptionSelections = useMemo(
|
|
305
|
+
() => deriveOptionSelectionsFromProduct(product),
|
|
306
|
+
[product]
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const [optionSelections, setOptionSelections] = useState(
|
|
310
|
+
derivedOptionSelections
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const [singleOptionSelection, setSingleOptionSelection] = useState();
|
|
314
|
+
|
|
315
|
+
const derivedOptionCodes = useMemo(
|
|
316
|
+
() => deriveOptionCodesFromProduct(product),
|
|
317
|
+
[product]
|
|
318
|
+
);
|
|
319
|
+
const [optionCodes] = useState(derivedOptionCodes);
|
|
320
|
+
|
|
321
|
+
const isMissingOptions = useMemo(
|
|
322
|
+
() => getIsMissingOptions(product, optionSelections),
|
|
323
|
+
[product, optionSelections]
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const isOutOfStock = useMemo(
|
|
327
|
+
() => getIsOutOfStock(product, optionCodes, optionSelections),
|
|
328
|
+
[product, optionCodes, optionSelections]
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// Check if display out of stock products option is selected in the Admin Dashboard
|
|
332
|
+
const isOutOfStockProductDisplayed = useMemo(() => {
|
|
333
|
+
let totalVariants = 1;
|
|
334
|
+
const isConfigurable = isProductConfigurable(product);
|
|
335
|
+
if (product.configurable_options && isConfigurable) {
|
|
336
|
+
for (const option of product.configurable_options) {
|
|
337
|
+
const length = option.values.length;
|
|
338
|
+
totalVariants = totalVariants * length;
|
|
339
|
+
}
|
|
340
|
+
return product.variants.length === totalVariants;
|
|
341
|
+
}
|
|
342
|
+
}, [product]);
|
|
343
|
+
|
|
344
|
+
const isEverythingOutOfStock = useMemo(() => getIsAllOutOfStock(product), [
|
|
345
|
+
product
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
const outOfStockVariants = useMemo(
|
|
349
|
+
() =>
|
|
350
|
+
getOutOfStockVariants(
|
|
351
|
+
product,
|
|
352
|
+
optionCodes,
|
|
353
|
+
singleOptionSelection,
|
|
354
|
+
optionSelections,
|
|
355
|
+
isOutOfStockProductDisplayed
|
|
356
|
+
),
|
|
357
|
+
[
|
|
358
|
+
product,
|
|
359
|
+
optionCodes,
|
|
360
|
+
singleOptionSelection,
|
|
361
|
+
optionSelections,
|
|
362
|
+
isOutOfStockProductDisplayed
|
|
363
|
+
]
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const mediaGalleryEntries = useMemo(
|
|
367
|
+
() => getMediaGalleryEntries(product, optionCodes, optionSelections),
|
|
368
|
+
[product, optionCodes, optionSelections]
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const customAttributes = useMemo(
|
|
372
|
+
() => getCustomAttributes(product, optionCodes, optionSelections),
|
|
373
|
+
[product, optionCodes, optionSelections]
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const productPrice = useMemo(
|
|
377
|
+
() => getConfigPrice(product, optionCodes, optionSelections),
|
|
378
|
+
[product, optionCodes, optionSelections]
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
// The map of ids to values (and their uids)
|
|
382
|
+
// For example:
|
|
383
|
+
// { "179" => [{ uid: "abc", value_index: 1 }, { uid: "def", value_index: 2 }]}
|
|
384
|
+
const attributeIdToValuesMap = useMemo(() => {
|
|
385
|
+
const map = new Map();
|
|
386
|
+
// For simple items, this will be an empty map.
|
|
387
|
+
const options = product.configurable_options || [];
|
|
388
|
+
for (const { attribute_id, values } of options) {
|
|
389
|
+
map.set(attribute_id, values);
|
|
390
|
+
}
|
|
391
|
+
return map;
|
|
392
|
+
}, [product.configurable_options]);
|
|
393
|
+
|
|
394
|
+
// An array of selected option uids. Useful for passing to mutations.
|
|
395
|
+
// For example:
|
|
396
|
+
// ["abc", "def"]
|
|
397
|
+
const selectedOptionsArray = useMemo(() => {
|
|
398
|
+
const selectedOptions = [];
|
|
399
|
+
|
|
400
|
+
optionSelections.forEach((value, key) => {
|
|
401
|
+
const values = attributeIdToValuesMap.get(key);
|
|
402
|
+
|
|
403
|
+
const selectedValue = values?.find(
|
|
404
|
+
item => item.value_index === value
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (selectedValue) {
|
|
408
|
+
selectedOptions.push(selectedValue.uid);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return selectedOptions;
|
|
412
|
+
}, [attributeIdToValuesMap, optionSelections]);
|
|
413
|
+
|
|
414
|
+
const handleAddToCart = useCallback(
|
|
415
|
+
async formValues => {
|
|
416
|
+
const { quantity } = formValues;
|
|
417
|
+
|
|
418
|
+
/*
|
|
419
|
+
@deprecated in favor of general addProductsToCart mutation. Will support until the next MAJOR.
|
|
420
|
+
*/
|
|
421
|
+
if (hasDeprecatedOperationProp) {
|
|
422
|
+
const payload = {
|
|
423
|
+
item: product,
|
|
424
|
+
productType,
|
|
425
|
+
quantity
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
if (isProductConfigurable(product)) {
|
|
429
|
+
appendOptionsToPayload(
|
|
430
|
+
payload,
|
|
431
|
+
optionSelections,
|
|
432
|
+
optionCodes
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (isSupportedProductType) {
|
|
437
|
+
const variables = {
|
|
438
|
+
cartId,
|
|
439
|
+
parentSku: payload.parentSku,
|
|
440
|
+
product: payload.item,
|
|
441
|
+
quantity: payload.quantity,
|
|
442
|
+
sku: payload.item.sku
|
|
443
|
+
};
|
|
444
|
+
// Use the proper mutation for the type.
|
|
445
|
+
if (productType === 'SimpleProduct') {
|
|
446
|
+
try {
|
|
447
|
+
await addSimpleProductToCart({
|
|
448
|
+
variables
|
|
449
|
+
});
|
|
450
|
+
} catch {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
} else if (productType === 'ConfigurableProduct') {
|
|
454
|
+
try {
|
|
455
|
+
await addConfigurableProductToCart({
|
|
456
|
+
variables
|
|
457
|
+
});
|
|
458
|
+
} catch {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
console.error(
|
|
464
|
+
'Unsupported product type. Cannot add to cart.'
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
const variables = {
|
|
469
|
+
cartId,
|
|
470
|
+
product: {
|
|
471
|
+
sku: product.sku,
|
|
472
|
+
quantity
|
|
473
|
+
},
|
|
474
|
+
entered_options: [
|
|
475
|
+
{
|
|
476
|
+
uid: product.uid,
|
|
477
|
+
value: product.name
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
if (selectedOptionsArray.length) {
|
|
483
|
+
variables.product.selected_options = selectedOptionsArray;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
await addProductToCart({ variables });
|
|
488
|
+
|
|
489
|
+
const selectedOptionsLabels =
|
|
490
|
+
selectedOptionsArray?.map((uid, i) => ({
|
|
491
|
+
attribute: product.configurable_options[i].label,
|
|
492
|
+
value:
|
|
493
|
+
product.configurable_options[i].values.findLast(
|
|
494
|
+
x => x.uid === uid
|
|
495
|
+
)?.label || null
|
|
496
|
+
})) || null;
|
|
497
|
+
|
|
498
|
+
dispatch({
|
|
499
|
+
type: 'CART_ADD_ITEM',
|
|
500
|
+
payload: {
|
|
501
|
+
cartId,
|
|
502
|
+
sku: product.sku,
|
|
503
|
+
name: product.name,
|
|
504
|
+
pricing: product.price,
|
|
505
|
+
priceTotal: productPrice.final_price.value,
|
|
506
|
+
currencyCode: productPrice.final_price.currency,
|
|
507
|
+
discountAmount: productPrice.discount.amount_off,
|
|
508
|
+
selectedOptions: selectedOptionsLabels,
|
|
509
|
+
quantity
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
} catch {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
[
|
|
518
|
+
addConfigurableProductToCart,
|
|
519
|
+
addProductToCart,
|
|
520
|
+
addSimpleProductToCart,
|
|
521
|
+
cartId,
|
|
522
|
+
dispatch,
|
|
523
|
+
hasDeprecatedOperationProp,
|
|
524
|
+
isSupportedProductType,
|
|
525
|
+
optionCodes,
|
|
526
|
+
optionSelections,
|
|
527
|
+
product,
|
|
528
|
+
productPrice,
|
|
529
|
+
productType,
|
|
530
|
+
selectedOptionsArray
|
|
531
|
+
]
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
const handleSelectionChange = useCallback(
|
|
535
|
+
(optionId, selection) => {
|
|
536
|
+
// We must create a new Map here so that React knows that the value
|
|
537
|
+
// of optionSelections has changed.
|
|
538
|
+
const nextOptionSelections = new Map([...optionSelections]);
|
|
539
|
+
nextOptionSelections.set(optionId, selection);
|
|
540
|
+
setOptionSelections(nextOptionSelections);
|
|
541
|
+
// Create a new Map to keep track of single selections with key as String
|
|
542
|
+
const nextSingleOptionSelection = new Map();
|
|
543
|
+
nextSingleOptionSelection.set(optionId, selection);
|
|
544
|
+
setSingleOptionSelection(nextSingleOptionSelection);
|
|
545
|
+
},
|
|
546
|
+
[optionSelections]
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Normalization object for product details we need for rendering.
|
|
550
|
+
const productDetails = {
|
|
551
|
+
description: product.description,
|
|
552
|
+
shortDescription: product.short_description,
|
|
553
|
+
name: product.name,
|
|
554
|
+
price: productPrice?.final_price,
|
|
555
|
+
sku: product.sku,
|
|
556
|
+
term_and_conditions: product.term_and_conditions,
|
|
557
|
+
shipping_policy: product.shipping_policy,
|
|
558
|
+
return_policy: product.return_policy
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const sellerDetails = {
|
|
562
|
+
name: product.seller?.name,
|
|
563
|
+
url_key: product.seller?.url_key,
|
|
564
|
+
city: product.seller?.city,
|
|
565
|
+
country: product.seller?.country
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const derivedErrorMessage = useMemo(
|
|
569
|
+
() =>
|
|
570
|
+
deriveErrorMessage([
|
|
571
|
+
errorAddingSimpleProduct,
|
|
572
|
+
errorAddingConfigurableProduct,
|
|
573
|
+
errorAddingProductToCart,
|
|
574
|
+
...(addToCartResponseData?.addProductsToCart?.user_errors || [])
|
|
575
|
+
]),
|
|
576
|
+
[
|
|
577
|
+
errorAddingConfigurableProduct,
|
|
578
|
+
errorAddingProductToCart,
|
|
579
|
+
errorAddingSimpleProduct,
|
|
580
|
+
addToCartResponseData
|
|
581
|
+
]
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const wishlistItemOptions = useMemo(() => {
|
|
585
|
+
const options = {
|
|
586
|
+
quantity: 1,
|
|
587
|
+
sku: product.sku
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
if (productType === 'ConfigurableProduct') {
|
|
591
|
+
options.selected_options = selectedOptionsArray;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return options;
|
|
595
|
+
}, [product, productType, selectedOptionsArray]);
|
|
596
|
+
|
|
597
|
+
const wishlistButtonProps = {
|
|
598
|
+
buttonText: isSelected =>
|
|
599
|
+
isSelected
|
|
600
|
+
? formatMessage({
|
|
601
|
+
id: 'wishlistButton.addedText',
|
|
602
|
+
defaultMessage: 'Added to Favorites'
|
|
603
|
+
})
|
|
604
|
+
: formatMessage({
|
|
605
|
+
id: 'wishlistButton.addText',
|
|
606
|
+
defaultMessage: 'Add to Favorites'
|
|
607
|
+
}),
|
|
608
|
+
item: wishlistItemOptions,
|
|
609
|
+
storeConfig: storeConfigData ? storeConfigData.storeConfig : {}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
breadcrumbCategoryId,
|
|
614
|
+
errorMessage: derivedErrorMessage,
|
|
615
|
+
handleAddToCart,
|
|
616
|
+
handleSelectionChange,
|
|
617
|
+
isOutOfStock,
|
|
618
|
+
isEverythingOutOfStock,
|
|
619
|
+
outOfStockVariants,
|
|
620
|
+
isAddToCartDisabled:
|
|
621
|
+
isOutOfStock ||
|
|
622
|
+
isEverythingOutOfStock ||
|
|
623
|
+
isMissingOptions ||
|
|
624
|
+
isAddConfigurableLoading ||
|
|
625
|
+
isAddSimpleLoading ||
|
|
626
|
+
isAddProductLoading,
|
|
627
|
+
isSupportedProductType,
|
|
628
|
+
mediaGalleryEntries,
|
|
629
|
+
shouldShowWishlistButton:
|
|
630
|
+
isSignedIn &&
|
|
631
|
+
storeConfigData &&
|
|
632
|
+
!!storeConfigData.storeConfig.magento_wishlist_general_is_enabled,
|
|
633
|
+
productDetails,
|
|
634
|
+
customAttributes,
|
|
635
|
+
wishlistButtonProps,
|
|
636
|
+
wishlistItemOptions,
|
|
637
|
+
sellerDetails
|
|
638
|
+
};
|
|
639
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
import { ProductDetailsFragment } from './productDetailFragment.gql';
|
|
4
|
+
|
|
5
|
+
export const GET_STORE_CONFIG_DATA = gql`
|
|
6
|
+
query getStoreConfigData {
|
|
7
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
8
|
+
storeConfig {
|
|
9
|
+
store_code
|
|
10
|
+
product_url_suffix
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const GET_PRODUCT_DETAIL_QUERY = gql`
|
|
16
|
+
query getProductDetailForProductPage($urlKey: String!) {
|
|
17
|
+
products(filter: { url_key: { eq: $urlKey } }) {
|
|
18
|
+
items {
|
|
19
|
+
id
|
|
20
|
+
uid
|
|
21
|
+
...ProductDetailsFragment
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
${ProductDetailsFragment}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
getStoreConfigData: GET_STORE_CONFIG_DATA,
|
|
30
|
+
getProductDetailQuery: GET_PRODUCT_DETAIL_QUERY
|
|
31
|
+
};
|
package/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const ProductDetailsFragment = gql`
|
|
4
|
+
fragment ProductDetailsFragment on ProductInterface {
|
|
5
|
+
__typename
|
|
6
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
7
|
+
categories {
|
|
8
|
+
uid
|
|
9
|
+
breadcrumbs {
|
|
10
|
+
category_uid
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
seller {
|
|
14
|
+
name
|
|
15
|
+
url_key
|
|
16
|
+
city
|
|
17
|
+
country
|
|
18
|
+
}
|
|
19
|
+
description {
|
|
20
|
+
html
|
|
21
|
+
}
|
|
22
|
+
short_description {
|
|
23
|
+
html
|
|
24
|
+
}
|
|
25
|
+
id
|
|
26
|
+
uid
|
|
27
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
28
|
+
media_gallery_entries {
|
|
29
|
+
uid
|
|
30
|
+
label
|
|
31
|
+
position
|
|
32
|
+
disabled
|
|
33
|
+
file
|
|
34
|
+
}
|
|
35
|
+
meta_description
|
|
36
|
+
name
|
|
37
|
+
price {
|
|
38
|
+
regularPrice {
|
|
39
|
+
amount {
|
|
40
|
+
currency
|
|
41
|
+
value
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
price_range {
|
|
46
|
+
maximum_price {
|
|
47
|
+
final_price {
|
|
48
|
+
currency
|
|
49
|
+
value
|
|
50
|
+
}
|
|
51
|
+
discount {
|
|
52
|
+
amount_off
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
sku
|
|
57
|
+
term_and_conditions
|
|
58
|
+
shipping_policy
|
|
59
|
+
return_policy
|
|
60
|
+
small_image {
|
|
61
|
+
url
|
|
62
|
+
}
|
|
63
|
+
stock_status
|
|
64
|
+
url_key
|
|
65
|
+
custom_attributes {
|
|
66
|
+
selected_attribute_options {
|
|
67
|
+
attribute_option {
|
|
68
|
+
uid
|
|
69
|
+
label
|
|
70
|
+
is_default
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
entered_attribute_value {
|
|
74
|
+
value
|
|
75
|
+
}
|
|
76
|
+
attribute_metadata {
|
|
77
|
+
uid
|
|
78
|
+
code
|
|
79
|
+
label
|
|
80
|
+
attribute_labels {
|
|
81
|
+
store_code
|
|
82
|
+
label
|
|
83
|
+
}
|
|
84
|
+
data_type
|
|
85
|
+
is_system
|
|
86
|
+
entity_type
|
|
87
|
+
ui_input {
|
|
88
|
+
ui_input_type
|
|
89
|
+
is_html_allowed
|
|
90
|
+
}
|
|
91
|
+
... on ProductAttributeMetadata {
|
|
92
|
+
used_in_components
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
... on ConfigurableProduct {
|
|
97
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
98
|
+
configurable_options {
|
|
99
|
+
attribute_code
|
|
100
|
+
attribute_id
|
|
101
|
+
uid
|
|
102
|
+
label
|
|
103
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
104
|
+
values {
|
|
105
|
+
uid
|
|
106
|
+
default_label
|
|
107
|
+
label
|
|
108
|
+
store_label
|
|
109
|
+
use_default_value
|
|
110
|
+
value_index
|
|
111
|
+
swatch_data {
|
|
112
|
+
... on ImageSwatchData {
|
|
113
|
+
thumbnail
|
|
114
|
+
}
|
|
115
|
+
value
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
variants {
|
|
120
|
+
attributes {
|
|
121
|
+
code
|
|
122
|
+
value_index
|
|
123
|
+
}
|
|
124
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
125
|
+
product {
|
|
126
|
+
uid
|
|
127
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
128
|
+
media_gallery_entries {
|
|
129
|
+
uid
|
|
130
|
+
disabled
|
|
131
|
+
file
|
|
132
|
+
label
|
|
133
|
+
position
|
|
134
|
+
}
|
|
135
|
+
sku
|
|
136
|
+
stock_status
|
|
137
|
+
price {
|
|
138
|
+
regularPrice {
|
|
139
|
+
amount {
|
|
140
|
+
currency
|
|
141
|
+
value
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
price_range {
|
|
146
|
+
maximum_price {
|
|
147
|
+
final_price {
|
|
148
|
+
currency
|
|
149
|
+
value
|
|
150
|
+
}
|
|
151
|
+
discount {
|
|
152
|
+
amount_off
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
custom_attributes {
|
|
157
|
+
selected_attribute_options {
|
|
158
|
+
attribute_option {
|
|
159
|
+
uid
|
|
160
|
+
label
|
|
161
|
+
is_default
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
entered_attribute_value {
|
|
165
|
+
value
|
|
166
|
+
}
|
|
167
|
+
attribute_metadata {
|
|
168
|
+
uid
|
|
169
|
+
code
|
|
170
|
+
label
|
|
171
|
+
attribute_labels {
|
|
172
|
+
store_code
|
|
173
|
+
label
|
|
174
|
+
}
|
|
175
|
+
data_type
|
|
176
|
+
is_system
|
|
177
|
+
entity_type
|
|
178
|
+
ui_input {
|
|
179
|
+
ui_input_type
|
|
180
|
+
is_html_allowed
|
|
181
|
+
}
|
|
182
|
+
... on ProductAttributeMetadata {
|
|
183
|
+
used_in_components
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
@@ -185,9 +185,9 @@ const CategoryContent = props => {
|
|
|
185
185
|
</div>
|
|
186
186
|
<SubCategory children={children} />
|
|
187
187
|
<AttributesBlock category={category} attributesBlock={attributesBlock} />
|
|
188
|
-
<section className='category_brand-slider my-5'>
|
|
188
|
+
{/* <section className='category_brand-slider my-5'>
|
|
189
189
|
<BrandSlider />
|
|
190
|
-
</section>
|
|
190
|
+
</section> */}
|
|
191
191
|
<div className={classes.contentWrapper}>
|
|
192
192
|
<div ref={sidebarRef} className={classes.sidebar}>
|
|
193
193
|
<Suspense fallback={<FilterSidebarShimmer />}>
|
|
@@ -24,7 +24,8 @@ import Tabs from '@riosst100/pwa-marketplace/src/components/commons/Tabs';
|
|
|
24
24
|
import ProductReviews from './components/productReview';
|
|
25
25
|
import RichText from '@magento/venia-ui/lib/components/RichText';
|
|
26
26
|
import { Star1, Verify, Sms, Message, Shop } from 'iconsax-react';
|
|
27
|
-
import { Link } from
|
|
27
|
+
import { Link } from 'react-router-dom';
|
|
28
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
28
29
|
import Divider from '@riosst100/pwa-marketplace/src/components/Divider';
|
|
29
30
|
|
|
30
31
|
const WishlistButton = React.lazy(() => import('@magento/venia-ui/lib/components/Wishlist/AddToListButton'));
|
|
@@ -62,7 +63,8 @@ const ProductFullDetail = props => {
|
|
|
62
63
|
mediaGalleryEntries,
|
|
63
64
|
productDetails,
|
|
64
65
|
customAttributes,
|
|
65
|
-
wishlistButtonProps
|
|
66
|
+
wishlistButtonProps,
|
|
67
|
+
sellerDetails
|
|
66
68
|
} = talonProps;
|
|
67
69
|
|
|
68
70
|
const { formatMessage } = useIntl();
|
|
@@ -144,22 +146,22 @@ const ProductFullDetail = props => {
|
|
|
144
146
|
const customAttributesDetails = useMemo(() => {
|
|
145
147
|
const list = [];
|
|
146
148
|
const pagebuilder = [];
|
|
147
|
-
const skuAttribute = {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
149
|
+
// const skuAttribute = {
|
|
150
|
+
// attribute_metadata: {
|
|
151
|
+
// uid: 'attribute_sku',
|
|
152
|
+
// used_in_components: ['PRODUCT_DETAILS_PAGE'],
|
|
153
|
+
// ui_input: {
|
|
154
|
+
// ui_input_type: 'TEXT'
|
|
155
|
+
// },
|
|
156
|
+
// label: formatMessage({
|
|
157
|
+
// id: 'global.sku',
|
|
158
|
+
// defaultMessage: 'SKU'
|
|
159
|
+
// })
|
|
160
|
+
// },
|
|
161
|
+
// entered_attribute_value: {
|
|
162
|
+
// value: productDetails.sku
|
|
163
|
+
// }
|
|
164
|
+
// };
|
|
163
165
|
if (Array.isArray(customAttributes)) {
|
|
164
166
|
customAttributes.forEach(customAttribute => {
|
|
165
167
|
if (
|
|
@@ -172,7 +174,7 @@ const ProductFullDetail = props => {
|
|
|
172
174
|
}
|
|
173
175
|
});
|
|
174
176
|
}
|
|
175
|
-
list.unshift(skuAttribute);
|
|
177
|
+
// list.unshift(skuAttribute);
|
|
176
178
|
return {
|
|
177
179
|
list: list,
|
|
178
180
|
pagebuilder: pagebuilder
|
|
@@ -275,15 +277,6 @@ const ProductFullDetail = props => {
|
|
|
275
277
|
const ProductMoreInfo = () => (
|
|
276
278
|
<>
|
|
277
279
|
<div className={cn(contentContainerClass)}>
|
|
278
|
-
<span
|
|
279
|
-
data-cy="ProductFullDetail-detailsTitle"
|
|
280
|
-
className={classes.detailsTitle}
|
|
281
|
-
>
|
|
282
|
-
<FormattedMessage
|
|
283
|
-
id={'productFullDetail.details'}
|
|
284
|
-
defaultMessage={'Details'}
|
|
285
|
-
/>
|
|
286
|
-
</span>
|
|
287
280
|
<CustomAttributes
|
|
288
281
|
customAttributes={customAttributesDetails.list}
|
|
289
282
|
/>
|
|
@@ -292,11 +285,50 @@ const ProductFullDetail = props => {
|
|
|
292
285
|
</>
|
|
293
286
|
);
|
|
294
287
|
|
|
288
|
+
const customAttributesList = useMemo(
|
|
289
|
+
() =>
|
|
290
|
+
customAttributesDetails.list.reduce((customAttributesData, currentAttribute) => {
|
|
291
|
+
const attrCode = currentAttribute.attribute_metadata?.code || null;
|
|
292
|
+
const value = currentAttribute.entered_attribute_value?.value || "";
|
|
293
|
+
if (attrCode) {
|
|
294
|
+
customAttributesData.push({
|
|
295
|
+
'code': attrCode,
|
|
296
|
+
'value': value
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return customAttributesData;
|
|
301
|
+
}, []),
|
|
302
|
+
[customAttributesDetails.list]
|
|
303
|
+
);
|
|
304
|
+
|
|
295
305
|
const ProductTNC = () => (
|
|
296
306
|
<div className={cn(contentContainerClass)}>
|
|
297
|
-
<p>
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
<p>{customAttributesList.map((data, index) => {
|
|
308
|
+
if (data.code == "term_and_conditions") {
|
|
309
|
+
return data.value;
|
|
310
|
+
}
|
|
311
|
+
})}</p>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
const ShippingPolicy = () => (
|
|
316
|
+
<div className={cn(contentContainerClass)}>
|
|
317
|
+
<p>{customAttributesList.map((data, index) => {
|
|
318
|
+
if (data.code == "shipping_policy") {
|
|
319
|
+
return data.value;
|
|
320
|
+
}
|
|
321
|
+
})}</p>
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
const ReturnPolicy = () => (
|
|
326
|
+
<div className={cn(contentContainerClass)}>
|
|
327
|
+
<p>{customAttributesList.map((data, index) => {
|
|
328
|
+
if (data.code == "return_policy") {
|
|
329
|
+
return data.value;
|
|
330
|
+
}
|
|
331
|
+
})}</p>
|
|
300
332
|
</div>
|
|
301
333
|
)
|
|
302
334
|
|
|
@@ -313,19 +345,29 @@ const ProductFullDetail = props => {
|
|
|
313
345
|
[
|
|
314
346
|
{
|
|
315
347
|
id: 'product-detail',
|
|
316
|
-
title: '
|
|
348
|
+
title: 'Description',
|
|
317
349
|
content: <ProductDescription />
|
|
318
350
|
},
|
|
319
351
|
{
|
|
320
352
|
id: 'product-more-info',
|
|
321
|
-
title: '
|
|
353
|
+
title: 'Details',
|
|
322
354
|
content: <ProductMoreInfo />
|
|
323
355
|
},
|
|
324
356
|
{
|
|
325
357
|
id: 'product-tnc',
|
|
326
|
-
title: '
|
|
358
|
+
title: 'Term & Conditions',
|
|
327
359
|
content: <ProductTNC />
|
|
328
360
|
},
|
|
361
|
+
{
|
|
362
|
+
id: 'product-shipping-policy',
|
|
363
|
+
title: 'Shipping Policy',
|
|
364
|
+
content: <ShippingPolicy />
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: 'product-return-policy',
|
|
368
|
+
title: 'Return Policy',
|
|
369
|
+
content: <ReturnPolicy />
|
|
370
|
+
},
|
|
329
371
|
{
|
|
330
372
|
id: 'product-faq',
|
|
331
373
|
title: 'FAQ',
|
|
@@ -394,11 +436,11 @@ const ProductFullDetail = props => {
|
|
|
394
436
|
>
|
|
395
437
|
{shortDescription}
|
|
396
438
|
</div>
|
|
397
|
-
<div className='product_brand-container mb-[30px] leading-[18px]'>
|
|
439
|
+
{/* <div className='product_brand-container mb-[30px] leading-[18px]'>
|
|
398
440
|
<div className='text-sm'>
|
|
399
441
|
Brand : <span className='text-blue-700'><Link to="">Bandai Namco</Link></span>
|
|
400
442
|
</div>
|
|
401
|
-
</div>
|
|
443
|
+
</div> */}
|
|
402
444
|
<Divider />
|
|
403
445
|
<div
|
|
404
446
|
className={cn(
|
|
@@ -446,11 +488,12 @@ const ProductFullDetail = props => {
|
|
|
446
488
|
/>
|
|
447
489
|
|
|
448
490
|
<div className='product_shipping-information mb-[30px] leading-[18px] mt-[25px]'>
|
|
491
|
+
{sellerDetails &&
|
|
449
492
|
<div className='text-xs'>
|
|
450
|
-
|
|
451
|
-
</div>
|
|
493
|
+
Ship From <span className='font-semibold'>{sellerDetails.country}</span>
|
|
494
|
+
</div>}
|
|
452
495
|
<div className='text-xs'>
|
|
453
|
-
|
|
496
|
+
Ship To <span className='font-semibold'>Yishun</span>
|
|
454
497
|
</div>
|
|
455
498
|
<div className='text-xs'>
|
|
456
499
|
Shiping Method <span className='font-semibold'>Store Pick Up | Meet Up</span>
|
|
@@ -498,14 +541,14 @@ const ProductFullDetail = props => {
|
|
|
498
541
|
<div className='flex flex-col xs_items-center md_items-start gap-[6px] relative'>
|
|
499
542
|
<div className="gap-x-[10px] gap-y-1 flex xs_flex-col md_flex-row xs_items-center md_items-start relative">
|
|
500
543
|
<div className="text-sm">
|
|
501
|
-
|
|
544
|
+
{sellerDetails ? sellerDetails.name : ''}
|
|
502
545
|
</div>
|
|
503
546
|
<div className="flex items-center relative">
|
|
504
547
|
<Verify variant='Bold' color='#4E31DB' size={20} />
|
|
505
548
|
</div>
|
|
506
549
|
</div>
|
|
507
550
|
<div class="relative w-fit font-normal text-[#999999] text-[12px] tracking-[0] leading-[14px] whitespace-nowrap">
|
|
508
|
-
|
|
551
|
+
{sellerDetails ? sellerDetails.city + ', ' + sellerDetails.country : ''}
|
|
509
552
|
</div>
|
|
510
553
|
</div>
|
|
511
554
|
<div className='flex flex-wrap items-start gap-4 relative'>
|
|
@@ -525,14 +568,14 @@ const ProductFullDetail = props => {
|
|
|
525
568
|
</div>
|
|
526
569
|
</div>
|
|
527
570
|
</button>
|
|
528
|
-
<
|
|
571
|
+
<Link to={`/seller/${sellerDetails.url_key}`} class="flex items-center justify-center gap-[5px] py-1 px-5 relative bg-white rounded-[30px] border border-solid border-[#6243fa]">
|
|
529
572
|
<div class="flex items-center justify-center gap-[10px] relative">
|
|
530
573
|
<Shop color="#6243FA" size={14} variant="Outline" className='stroke-[#6243FA]' />
|
|
531
574
|
<div class="relative xs_hidden lg_flex w-fit font-medium text-[#6243fa] text-[14px] tracking-[0] leading-[20px] whitespace-nowrap">
|
|
532
575
|
Visit Store
|
|
533
576
|
</div>
|
|
534
577
|
</div>
|
|
535
|
-
</
|
|
578
|
+
</Link>
|
|
536
579
|
</div>
|
|
537
580
|
</div>
|
|
538
581
|
</div>
|