@riosst100/pwa-marketplace 1.4.2 → 1.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riosst100/pwa-marketplace",
3
3
  "author": "riosst100@gmail.com",
4
- "version": "1.4.2",
4
+ "version": "1.4.4",
5
5
  "main": "src/index.js",
6
6
  "pwa-studio": {
7
7
  "targets": {
@@ -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',
@@ -3,7 +3,7 @@
3
3
  composes: flex-wrap from global;
4
4
  composes: mt-3 from global;
5
5
  composes: gap-[15px] from global;
6
- margin-bottom: 30px;
6
+ margin-bottom: 10px;
7
7
  }
8
8
 
9
9
  .item {
@@ -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
+ };
@@ -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 />}>
@@ -4,7 +4,7 @@
4
4
  composes: flex from global;
5
5
  composes: items-center from global;
6
6
  composes: gap-[6px] from global;
7
- composes: my-[30px] from global;
7
+ composes: my-[10px] from global;
8
8
  padding-top: 20px;
9
9
  }
10
10
 
@@ -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 "react-router-dom";
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
- attribute_metadata: {
149
- uid: 'attribute_sku',
150
- used_in_components: ['PRODUCT_DETAILS_PAGE'],
151
- ui_input: {
152
- ui_input_type: 'TEXT'
153
- },
154
- label: formatMessage({
155
- id: 'global.sku',
156
- defaultMessage: 'SKU'
157
- })
158
- },
159
- entered_attribute_value: {
160
- value: productDetails.sku
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
- Terms and Conditions
299
- </p>
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: 'Details',
348
+ title: 'Description',
317
349
  content: <ProductDescription />
318
350
  },
319
351
  {
320
352
  id: 'product-more-info',
321
- title: 'More Info',
353
+ title: 'Details',
322
354
  content: <ProductMoreInfo />
323
355
  },
324
356
  {
325
357
  id: 'product-tnc',
326
- title: 'Terms & Conditions',
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
- Shiping From <span className='font-semibold'>Singapore</span>
451
- </div>
493
+ Ship From <span className='font-semibold'>{sellerDetails.country}</span>
494
+ </div>}
452
495
  <div className='text-xs'>
453
- Shiping To <span className='font-semibold'>Yishun</span>
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
- Good Smile
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
- Jurong West
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
- <button class="flex items-center justify-center gap-[5px] py-1 px-5 relative bg-white rounded-[30px] border border-solid border-[#6243fa]">
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
- </button>
578
+ </Link>
536
579
  </div>
537
580
  </div>
538
581
  </div>