@riosst100/pwa-marketplace 2.9.7 → 3.0.0
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 -1
- package/src/components/FilterContent/filterContent.js +4 -0
- package/src/components/RMAPage/RMADetail.js +1 -1
- package/src/components/ShopBy/shopBy.js +4 -1
- package/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js +54 -0
- package/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/usePriceSummary.js +2 -4
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/productReview.gql.js +89 -0
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +72 -3
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +5 -1
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +2 -1
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +4 -3
- package/src/overwrites/venia-ui/lib/components/CartPage/PriceSummary/priceSummary.js +97 -23
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterList.js +0 -2
- package/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js +29 -0
- package/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.module.css +1 -1
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/modalFormReview.js +102 -95
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productReview.js +111 -70
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +19 -3
package/package.json
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
module.exports = componentOverrideMapping = {
|
|
2
|
+
[`@magento/venia-ui/lib/components/CartPage/PriceSummary/priceSummary.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/CartPage/PriceSummary/priceSummary.js',
|
|
3
|
+
[`@magento/peregrine/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js',
|
|
2
4
|
[`@magento/venia-ui/lib/components/Adapter/adapter.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/Adapter/adapter.js',
|
|
3
5
|
[`@magento/venia-ui/lib/components/ToastContainer/toast.module.css`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/ToastContainer/toast.module.css',
|
|
4
6
|
[`@magento/venia-ui/lib/components/ToastContainer/toastContainer.module.css`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/ToastContainer/toastContainer.module.css',
|
|
@@ -87,7 +89,6 @@ module.exports = componentOverrideMapping = {
|
|
|
87
89
|
[`@magento/peregrine/lib/talons/ProductOptions/useTile.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/ProductOptions/useTile.js',
|
|
88
90
|
[`@magento/peregrine/lib/talons/ProductImageCarousel/useProductImageCarousel.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/ProductImageCarousel/useProductImageCarousel.js',
|
|
89
91
|
[`@magento/peregrine/lib/talons/OrderHistoryPage/orderHistoryPage.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/OrderHistoryPage/orderHistoryPage.gql.js',
|
|
90
|
-
// Added overrides to fix TypeError in useOrderRow and to ensure required GQL queries are present
|
|
91
92
|
[`@magento/peregrine/lib/talons/OrderHistoryPage/useOrderRow.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/OrderHistoryPage/useOrderRow.js',
|
|
92
93
|
[`@magento/peregrine/lib/talons/OrderHistoryPage/orderRow.gql.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/OrderHistoryPage/orderRow.gql.js'
|
|
93
94
|
};
|
|
@@ -31,6 +31,10 @@ const FilterContent = props => {
|
|
|
31
31
|
const [searchQuery, setSearchQuery] = useState('');
|
|
32
32
|
|
|
33
33
|
const { search } = useLocation();
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
window.scrollTo(0, 0);
|
|
37
|
+
}, []);
|
|
34
38
|
|
|
35
39
|
const sortProps = useCustomSort({ sortFromSearch: false, defaultSort: {
|
|
36
40
|
sortText: 'All (A-Z)',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment, Suspense, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { Fragment, useEffect, Suspense, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { FormattedMessage } from 'react-intl';
|
|
3
3
|
import { array, number, shape, string } from 'prop-types';
|
|
4
4
|
|
|
@@ -84,6 +84,9 @@ const ShopBy = props => {
|
|
|
84
84
|
// attributesBlock,
|
|
85
85
|
// category,
|
|
86
86
|
// } = talonProps;
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
window.scrollTo(0, 0);
|
|
89
|
+
}, []);
|
|
87
90
|
|
|
88
91
|
const [active, setActive] = useState('all')
|
|
89
92
|
const [activeTab, setActiveTab] = useState('all');
|
package/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
import { DiscountSummaryFragment } from '@magento/peregrine/lib/talons/CartPage/PriceSummary/discountSummary.gql';
|
|
4
|
+
import { GiftCardSummaryFragment } from '@magento/peregrine/lib/talons/CartPage/PriceSummary/queries/giftCardSummary';
|
|
5
|
+
import { GiftOptionsSummaryFragment } from '@magento/peregrine/lib/talons/CartPage/PriceSummary/queries/giftOptionsSummary';
|
|
6
|
+
import { ShippingSummaryFragment } from '@magento/peregrine/lib/talons/CartPage/PriceSummary/shippingSummary.gql';
|
|
7
|
+
import { TaxSummaryFragment } from '@magento/peregrine/lib/talons/CartPage/PriceSummary/taxSummary.gql';
|
|
8
|
+
|
|
9
|
+
export const GrandTotalFragment = gql`
|
|
10
|
+
fragment GrandTotalFragment on CartPrices {
|
|
11
|
+
grand_total {
|
|
12
|
+
currency
|
|
13
|
+
value
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const PriceSummaryFragment = gql`
|
|
19
|
+
fragment PriceSummaryFragment on Cart {
|
|
20
|
+
id
|
|
21
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
22
|
+
items {
|
|
23
|
+
uid
|
|
24
|
+
quantity
|
|
25
|
+
}
|
|
26
|
+
...ShippingSummaryFragment
|
|
27
|
+
prices {
|
|
28
|
+
...TaxSummaryFragment
|
|
29
|
+
...DiscountSummaryFragment
|
|
30
|
+
...GrandTotalFragment
|
|
31
|
+
subtotal_excluding_tax {
|
|
32
|
+
currency
|
|
33
|
+
value
|
|
34
|
+
}
|
|
35
|
+
subtotal_including_tax {
|
|
36
|
+
currency
|
|
37
|
+
value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
payment_fees {
|
|
41
|
+
title
|
|
42
|
+
value
|
|
43
|
+
currency
|
|
44
|
+
}
|
|
45
|
+
...GiftCardSummaryFragment
|
|
46
|
+
...GiftOptionsSummaryFragment
|
|
47
|
+
}
|
|
48
|
+
${DiscountSummaryFragment}
|
|
49
|
+
${GiftCardSummaryFragment}
|
|
50
|
+
${GiftOptionsSummaryFragment}
|
|
51
|
+
${GrandTotalFragment}
|
|
52
|
+
${ShippingSummaryFragment}
|
|
53
|
+
${TaxSummaryFragment}
|
|
54
|
+
`;
|
|
@@ -28,7 +28,8 @@ const flattenData = data => {
|
|
|
28
28
|
giftCards: data.cart.applied_gift_cards,
|
|
29
29
|
giftOptions: data.cart.prices.gift_options,
|
|
30
30
|
taxes: data.cart.prices.applied_taxes,
|
|
31
|
-
shipping: data.cart.shipping_addresses
|
|
31
|
+
shipping: data.cart.shipping_addresses,
|
|
32
|
+
payment_fees: data.cart.payment_fees
|
|
32
33
|
};
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -99,9 +100,6 @@ export const usePriceSummary = (props = {}) => {
|
|
|
99
100
|
|
|
100
101
|
await createSellerCart({ initCheckoutSplitCart, sellerUrl });
|
|
101
102
|
|
|
102
|
-
console.log('initCheckoutSplitCartData',initCheckoutSplitCartData)
|
|
103
|
-
console.log('fetchCartId',fetchCartId)
|
|
104
|
-
|
|
105
103
|
await removeCart();
|
|
106
104
|
await apolloClient.clearCacheData(apolloClient, 'cart');
|
|
107
105
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
const GET_PRODUCT_REVIEW_RATINGS_METADATA = gql`
|
|
4
|
+
query getProductReviewRatingsMetadata {
|
|
5
|
+
productReviewRatingsMetadata {
|
|
6
|
+
items {
|
|
7
|
+
id
|
|
8
|
+
name
|
|
9
|
+
values {
|
|
10
|
+
value
|
|
11
|
+
value_id
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const CREATE_PRODUCT_REVIEW = gql`
|
|
19
|
+
mutation CreateProductReview($input: CreateProductReviewInput!) {
|
|
20
|
+
createProductReview(input: $input) {
|
|
21
|
+
review {
|
|
22
|
+
average_rating
|
|
23
|
+
created_at
|
|
24
|
+
nickname
|
|
25
|
+
product {
|
|
26
|
+
uuid: uid
|
|
27
|
+
uid
|
|
28
|
+
name
|
|
29
|
+
sku
|
|
30
|
+
}
|
|
31
|
+
ratings_breakdown {
|
|
32
|
+
name
|
|
33
|
+
value
|
|
34
|
+
}
|
|
35
|
+
summary
|
|
36
|
+
text
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const GET_PRODUCT_REVIEWS = gql`
|
|
43
|
+
query getProductReviews($url_key: String) {
|
|
44
|
+
products(
|
|
45
|
+
filter: { url_key: { eq: $url_key } }
|
|
46
|
+
pageSize: 20
|
|
47
|
+
currentPage: 1
|
|
48
|
+
) {
|
|
49
|
+
items {
|
|
50
|
+
uid
|
|
51
|
+
rating_summary
|
|
52
|
+
review_count
|
|
53
|
+
reviews(pageSize: 20, currentPage: 1){
|
|
54
|
+
items {
|
|
55
|
+
average_rating
|
|
56
|
+
created_at
|
|
57
|
+
nickname
|
|
58
|
+
summary
|
|
59
|
+
text
|
|
60
|
+
product {
|
|
61
|
+
uid
|
|
62
|
+
name
|
|
63
|
+
sku
|
|
64
|
+
}
|
|
65
|
+
ratings_breakdown {
|
|
66
|
+
name
|
|
67
|
+
value
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
page_info {
|
|
71
|
+
total_pages
|
|
72
|
+
current_page
|
|
73
|
+
page_size
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
page_info {
|
|
78
|
+
total_pages
|
|
79
|
+
current_page
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
export default {
|
|
86
|
+
getProductReviews: GET_PRODUCT_REVIEWS,
|
|
87
|
+
createProductReview: CREATE_PRODUCT_REVIEW,
|
|
88
|
+
getProductReviewRatingsMetadata: GET_PRODUCT_REVIEW_RATINGS_METADATA
|
|
89
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useState, useMemo } from 'react';
|
|
2
|
+
import { useToasts } from '@magento/peregrine/lib';
|
|
2
3
|
import { useIntl } from 'react-intl';
|
|
3
4
|
import { useMutation, useQuery } from '@apollo/client';
|
|
4
5
|
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
@@ -11,6 +12,7 @@ import { isSupportedProductType as isSupported } from '@magento/peregrine/lib/ut
|
|
|
11
12
|
import { deriveErrorMessage } from '@magento/peregrine/lib/util/deriveErrorMessage';
|
|
12
13
|
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
13
14
|
import defaultOperations from '@magento/peregrine/lib/talons/ProductFullDetail/productFullDetail.gql';
|
|
15
|
+
import productReviewOperations from './productReview.gql';
|
|
14
16
|
import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
|
|
15
17
|
import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants';
|
|
16
18
|
|
|
@@ -334,12 +336,60 @@ export const useProductFullDetail = props => {
|
|
|
334
336
|
isPreview
|
|
335
337
|
} = props;
|
|
336
338
|
|
|
339
|
+
const [, { addToast }] = useToasts();
|
|
340
|
+
|
|
341
|
+
const { data: productReviewData, loading: loadingProductReview, error: errorProductReview, refetch: refetchProductReviews } = useQuery(
|
|
342
|
+
productReviewOperations.getProductReviews,
|
|
343
|
+
{
|
|
344
|
+
variables: { url_key: product?.url_key },
|
|
345
|
+
skip: !product?.url_key,
|
|
346
|
+
fetchPolicy: 'network-only'
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Query for ratings metadata
|
|
351
|
+
const { data: ratingsMetadataData, loading: loadingRatingsMetadata } = useQuery(
|
|
352
|
+
productReviewOperations.getProductReviewRatingsMetadata,
|
|
353
|
+
{ fetchPolicy: 'network-only' }
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Mutation for creating review (allow partial data with errors)
|
|
357
|
+
const [createProductReview, { loading: loadingCreateReview, error: errorCreateReview }] = useMutation(
|
|
358
|
+
productReviewOperations.createProductReview,
|
|
359
|
+
{ errorPolicy: 'all' }
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// Handler for submitting review with robust toast handling
|
|
363
|
+
const handleSubmitReview = async (formValues) => {
|
|
364
|
+
try {
|
|
365
|
+
const result = await createProductReview({
|
|
366
|
+
variables: { input: formValues },
|
|
367
|
+
errorPolicy: 'all'
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const gqlErrors = result?.errors || [];
|
|
371
|
+
const review = result?.data?.createProductReview?.review;
|
|
372
|
+
|
|
373
|
+
if (gqlErrors.length > 0 || !review) {
|
|
374
|
+
const message = gqlErrors[0]?.message || 'Failed to submit review!';
|
|
375
|
+
addToast({ type: 'error', message });
|
|
376
|
+
return { success: false, error: gqlErrors };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await refetchProductReviews();
|
|
380
|
+
addToast({ type: 'success', message: 'Review submitted successfully!' });
|
|
381
|
+
return { success: true };
|
|
382
|
+
} catch (e) {
|
|
383
|
+
addToast({ type: 'error', message: e?.message || 'Failed to submit review!' });
|
|
384
|
+
return { success: false, error: e };
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
337
388
|
const [, { dispatch }] = useEventingContext();
|
|
338
389
|
|
|
339
390
|
const hasDeprecatedOperationProp = !!(
|
|
340
391
|
addConfigurableProductToCartMutation || addSimpleProductToCartMutation
|
|
341
392
|
);
|
|
342
|
-
|
|
343
393
|
const operations = mergeOperations(defaultOperations, props.operations);
|
|
344
394
|
|
|
345
395
|
const productType = product.__typename;
|
|
@@ -347,7 +397,7 @@ export const useProductFullDetail = props => {
|
|
|
347
397
|
const isSupportedProductType = isSupported(productType);
|
|
348
398
|
|
|
349
399
|
const [{ cartId }] = useCartContext();
|
|
350
|
-
const [{ isSignedIn }] = useUserContext();
|
|
400
|
+
const [{ isSignedIn, currentUser }] = useUserContext();
|
|
351
401
|
const { formatMessage } = useIntl();
|
|
352
402
|
|
|
353
403
|
const { data: storeConfigData } = useQuery(
|
|
@@ -718,6 +768,16 @@ export const useProductFullDetail = props => {
|
|
|
718
768
|
storeConfig: storeConfigData ? storeConfigData.storeConfig : {}
|
|
719
769
|
};
|
|
720
770
|
|
|
771
|
+
const defaultNickname = useMemo(() => {
|
|
772
|
+
if (!currentUser) return '';
|
|
773
|
+
const first = currentUser.firstname || '';
|
|
774
|
+
const last = currentUser.lastname || '';
|
|
775
|
+
const full = `${first} ${last}`.trim();
|
|
776
|
+
if (full) return full;
|
|
777
|
+
const email = currentUser.email || '';
|
|
778
|
+
return email ? email.split('@')[0] : '';
|
|
779
|
+
}, [currentUser]);
|
|
780
|
+
|
|
721
781
|
return {
|
|
722
782
|
breadcrumbCategoryId,
|
|
723
783
|
errorMessage: derivedErrorMessage,
|
|
@@ -744,6 +804,15 @@ export const useProductFullDetail = props => {
|
|
|
744
804
|
customAttributes,
|
|
745
805
|
wishlistButtonProps,
|
|
746
806
|
wishlistItemOptions,
|
|
747
|
-
sellerDetails
|
|
807
|
+
sellerDetails,
|
|
808
|
+
productReviewData,
|
|
809
|
+
loadingProductReview,
|
|
810
|
+
errorProductReview,
|
|
811
|
+
ratingsMetadataData,
|
|
812
|
+
loadingRatingsMetadata,
|
|
813
|
+
handleSubmitReview,
|
|
814
|
+
loadingCreateReview,
|
|
815
|
+
errorCreateReview,
|
|
816
|
+
defaultNickname
|
|
748
817
|
};
|
|
749
818
|
};
|
|
@@ -3,8 +3,12 @@ import { gql } from '@apollo/client';
|
|
|
3
3
|
export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql`
|
|
4
4
|
query getProductFiltersByCategory(
|
|
5
5
|
$filters: ProductAttributeFilterInput!
|
|
6
|
+
$category_uid: String
|
|
6
7
|
) {
|
|
7
|
-
products(
|
|
8
|
+
products(
|
|
9
|
+
filter: $filters
|
|
10
|
+
category_uid: $category_uid
|
|
11
|
+
) {
|
|
8
12
|
aggregations {
|
|
9
13
|
label
|
|
10
14
|
count
|
|
@@ -142,9 +142,10 @@ const CategoryContent = props => {
|
|
|
142
142
|
|
|
143
143
|
const sidebarRef = useRef(null);
|
|
144
144
|
const classes = useStyle(defaultClasses, props.classes);
|
|
145
|
-
const shouldRenderSidebarContent = useIsInViewport({
|
|
146
|
-
|
|
147
|
-
});
|
|
145
|
+
// const shouldRenderSidebarContent = useIsInViewport({
|
|
146
|
+
// elementRef: sidebarRef
|
|
147
|
+
// });
|
|
148
|
+
const shouldRenderSidebarContent = true;
|
|
148
149
|
|
|
149
150
|
const shouldShowFilterButtons = filters && filters.length;
|
|
150
151
|
const shouldShowFilterShimmer = filters === null;
|
|
@@ -67,7 +67,8 @@ const PriceSummary = props => {
|
|
|
67
67
|
giftCards,
|
|
68
68
|
giftOptions,
|
|
69
69
|
taxes,
|
|
70
|
-
shipping
|
|
70
|
+
shipping,
|
|
71
|
+
payment_fees
|
|
71
72
|
} = flatData;
|
|
72
73
|
|
|
73
74
|
const isPriceUpdating = isUpdating || isLoading;
|
|
@@ -109,30 +110,103 @@ const PriceSummary = props => {
|
|
|
109
110
|
|
|
110
111
|
return (
|
|
111
112
|
<div className={cn(classes.root, 'pb-6 px-3')} data-cy="PriceSummary-root">
|
|
112
|
-
{
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
{isCheckout && (
|
|
114
|
+
<div>
|
|
115
|
+
<ul>
|
|
116
|
+
<li className={classes.lineItems}>
|
|
117
|
+
<span
|
|
118
|
+
data-cy="PriceSummary-lineItemLabel"
|
|
119
|
+
className={classes.lineItemLabel}
|
|
120
|
+
>
|
|
121
|
+
<FormattedMessage
|
|
122
|
+
id={'priceSummary.lineItemLabel'}
|
|
123
|
+
defaultMessage={'Subtotal'}
|
|
124
|
+
/>
|
|
125
|
+
</span>
|
|
126
|
+
<span
|
|
127
|
+
data-cy="PriceSummary-subtotalValue"
|
|
128
|
+
className={priceClass}
|
|
129
|
+
>
|
|
130
|
+
<Price
|
|
131
|
+
value={subtotal.value}
|
|
132
|
+
currencyCode={subtotal.currency}
|
|
133
|
+
/>
|
|
134
|
+
</span>
|
|
135
|
+
</li>
|
|
136
|
+
<DiscountSummary
|
|
137
|
+
classes={{
|
|
138
|
+
lineItems: classes.lineItems,
|
|
139
|
+
lineItemLabel: classes.lineItemLabel,
|
|
140
|
+
price: priceClass
|
|
141
|
+
}}
|
|
142
|
+
data={discounts}
|
|
143
|
+
/>
|
|
144
|
+
<li className={classes.lineItems}>
|
|
145
|
+
<GiftCardSummary
|
|
146
|
+
classes={{
|
|
147
|
+
lineItemLabel: classes.lineItemLabel,
|
|
148
|
+
price: priceClass
|
|
149
|
+
}}
|
|
150
|
+
data={giftCards}
|
|
122
151
|
/>
|
|
123
|
-
</
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
</li>
|
|
153
|
+
<li className={classes.lineItems}>
|
|
154
|
+
<GiftOptionsSummary
|
|
155
|
+
classes={{
|
|
156
|
+
lineItemLabel: classes.lineItemLabel,
|
|
157
|
+
price: priceClass
|
|
158
|
+
}}
|
|
159
|
+
data={giftOptions}
|
|
131
160
|
/>
|
|
132
|
-
</
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
161
|
+
</li>
|
|
162
|
+
<li className={classes.lineItems}>
|
|
163
|
+
<TaxSummary
|
|
164
|
+
classes={{
|
|
165
|
+
lineItemLabel: classes.lineItemLabel,
|
|
166
|
+
price: priceClass
|
|
167
|
+
}}
|
|
168
|
+
data={taxes}
|
|
169
|
+
isCheckout={isCheckout}
|
|
170
|
+
/>
|
|
171
|
+
</li>
|
|
172
|
+
<li className={classes.lineItems}>
|
|
173
|
+
<ShippingSummary
|
|
174
|
+
classes={{
|
|
175
|
+
lineItemLabel: classes.lineItemLabel,
|
|
176
|
+
price: priceClass
|
|
177
|
+
}}
|
|
178
|
+
data={shipping}
|
|
179
|
+
isCheckout={isCheckout}
|
|
180
|
+
/>
|
|
181
|
+
</li>
|
|
182
|
+
{payment_fees && payment_fees.length > 0 && payment_fees.map(fee => (
|
|
183
|
+
<li className={classes.lineItems} key={fee.title}>
|
|
184
|
+
<span className={classes.lineItemLabel}>{fee.title}</span>
|
|
185
|
+
<span className={priceClass}>
|
|
186
|
+
<Price value={fee.value} currencyCode={fee.currency} />
|
|
187
|
+
</span>
|
|
188
|
+
</li>
|
|
189
|
+
))}
|
|
190
|
+
<li className={classes.lineItems}>
|
|
191
|
+
<span
|
|
192
|
+
data-cy="PriceSummary-totalLabel"
|
|
193
|
+
className={classes.totalLabel}
|
|
194
|
+
>
|
|
195
|
+
{totalPriceLabel}
|
|
196
|
+
</span>
|
|
197
|
+
<span
|
|
198
|
+
data-cy="PriceSummary-totalValue"
|
|
199
|
+
className={totalPriceClass}
|
|
200
|
+
>
|
|
201
|
+
<Price
|
|
202
|
+
value={total.value}
|
|
203
|
+
currencyCode={total.currency}
|
|
204
|
+
/>
|
|
205
|
+
</span>
|
|
206
|
+
</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
136
210
|
{proceedToCheckoutButton}
|
|
137
211
|
</div>
|
|
138
212
|
);
|
|
@@ -260,8 +260,6 @@ const FilterList = props => {
|
|
|
260
260
|
const { pathname, search } = useLocation();
|
|
261
261
|
|
|
262
262
|
const showMoreItem = useMemo(() => {
|
|
263
|
-
console.log('itemCountToShow')
|
|
264
|
-
console.log(itemCountToShow)
|
|
265
263
|
if (items.length <= itemCountToShow) {
|
|
266
264
|
return null;
|
|
267
265
|
}
|
|
@@ -52,6 +52,8 @@ const FilterSidebar = props => {
|
|
|
52
52
|
// const windowScrollY =
|
|
53
53
|
// window.scrollY + filterTop - SCROLL_OFFSET;
|
|
54
54
|
// window.scrollTo(0, windowScrollY);
|
|
55
|
+
|
|
56
|
+
window.scrollTo(0, 0);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
handleApply(...args);
|
|
@@ -89,6 +91,33 @@ const FilterSidebar = props => {
|
|
|
89
91
|
allowedFiltersArr.push(val.code);
|
|
90
92
|
});
|
|
91
93
|
|
|
94
|
+
// const ordering = ['card_artist','card_product_type', 'card_type'];
|
|
95
|
+
// const sorted = new Map(
|
|
96
|
+
// [...filterItems.entries()].sort((a, b) => {
|
|
97
|
+
// const keyA = a[0];
|
|
98
|
+
// const keyB = b[0];
|
|
99
|
+
|
|
100
|
+
// const matchA = ordering.findIndex(o => keyA.endsWith(o));
|
|
101
|
+
// const matchB = ordering.findIndex(o => keyB.endsWith(o));
|
|
102
|
+
|
|
103
|
+
// const orderA = matchA === -1 ? Infinity : matchA;
|
|
104
|
+
// const orderB = matchB === -1 ? Infinity : matchB;
|
|
105
|
+
|
|
106
|
+
// // jika dua-duanya tidak match ordering → sort alphabetis
|
|
107
|
+
// if (orderA === Infinity && orderB === Infinity) {
|
|
108
|
+
// return keyA.localeCompare(keyB);
|
|
109
|
+
// }
|
|
110
|
+
|
|
111
|
+
// // urutkan sesuai ordering
|
|
112
|
+
// return orderA - orderB;
|
|
113
|
+
// })
|
|
114
|
+
// );
|
|
115
|
+
|
|
116
|
+
// // console.log([...sorted.entries()]);
|
|
117
|
+
// const filterItems = [...sorted.entries()];
|
|
118
|
+
|
|
119
|
+
// console.log('filterItems',filterItems)
|
|
120
|
+
|
|
92
121
|
const filtersList = useMemo(
|
|
93
122
|
() =>
|
|
94
123
|
Array.from(filterItems, ([group, items], iteration) => {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
composes: lg_block from global;
|
|
12
12
|
composes: border from global;
|
|
13
13
|
composes: border-gray-100 from global;
|
|
14
|
-
composes:
|
|
14
|
+
composes: hover_shadow-type-1 from global;
|
|
15
15
|
composes: rounded-[6px] from global;
|
|
16
16
|
composes: py-2.5 from global;
|
|
17
17
|
|
package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/modalFormReview.js
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import Modal from '@riosst100/pwa-marketplace/src/components/Modal';
|
|
3
3
|
import { X } from 'react-feather';
|
|
4
4
|
import Field from '@magento/venia-ui/lib/components/Field';
|
|
5
|
-
import TextInput from '@magento/venia-ui/lib/components/TextInput';
|
|
5
|
+
// import TextInput from '@magento/venia-ui/lib/components/TextInput';
|
|
6
6
|
import Button from '@magento/venia-ui/lib/components/Button';
|
|
7
7
|
import { isRequired } from '@magento/venia-ui/lib/util/formValidators';
|
|
8
|
-
import { Form } from 'informed';
|
|
9
8
|
import StarRating from './starInput';
|
|
10
9
|
|
|
11
10
|
import { primary900 } from '@riosst100/pwa-marketplace/src/theme/vars';
|
|
12
11
|
|
|
13
12
|
const modalFormReview = (props) => {
|
|
13
|
+
const { open, setOpen, ratingsMetadata = [], loadingRatingsMetadata, onSubmit, submitting, defaultNickname } = props;
|
|
14
14
|
|
|
15
|
-
const {
|
|
16
|
-
const [currentRating, setCurrentRating] = useState(0);
|
|
15
|
+
const [formState, setFormState] = useState({ nickname: defaultNickname || '' });
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (open && defaultNickname && !formState.nickname) {
|
|
19
|
+
setFormState(prev => ({ ...prev, nickname: defaultNickname }));
|
|
20
|
+
}
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
+
}, [open, defaultNickname]);
|
|
23
|
+
|
|
24
|
+
// ratings: [{ id, value_id }]
|
|
25
|
+
const [ratings, setRatings] = useState([]);
|
|
26
|
+
|
|
27
|
+
const handleRatingChange = (id, value_id) => {
|
|
28
|
+
setRatings(prev => {
|
|
29
|
+
const filtered = prev.filter(r => r.id !== id);
|
|
30
|
+
return [...filtered, { id, value_id }];
|
|
31
|
+
});
|
|
20
32
|
};
|
|
21
33
|
|
|
34
|
+
const handleChange = (field, value) => {
|
|
35
|
+
setFormState(prev => ({ ...prev, [field]: value }));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleSubmit = (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
if (onSubmit) {
|
|
41
|
+
onSubmit({ ...formState, ratings });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
22
44
|
|
|
23
45
|
return (
|
|
24
46
|
<>
|
|
@@ -35,106 +57,88 @@ const modalFormReview = (props) => {
|
|
|
35
57
|
<X size={24} color={primary900} />
|
|
36
58
|
</button>
|
|
37
59
|
</div>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
className="flex flex-col gap-y-3"
|
|
42
|
-
initialValues={{}}
|
|
43
|
-
onSubmit={() => { }}
|
|
44
|
-
onChange={() => { }}
|
|
45
|
-
>
|
|
46
|
-
<Field
|
|
47
|
-
id="nickname_field"
|
|
48
|
-
label={'Nickname'}
|
|
49
|
-
>
|
|
50
|
-
<TextInput
|
|
60
|
+
<form className="flex flex-col gap-y-3" onSubmit={handleSubmit}>
|
|
61
|
+
<Field id="nickname_field" label={'Nickname'}>
|
|
62
|
+
<input
|
|
51
63
|
id="nickname"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
name="nickname"
|
|
65
|
+
type="text"
|
|
66
|
+
required
|
|
67
|
+
readOnly
|
|
68
|
+
value={formState.nickname || ''}
|
|
69
|
+
onChange={e => handleChange('nickname', e.target.value)}
|
|
57
70
|
data-cy="nickname"
|
|
58
71
|
aria-label={'nickname'}
|
|
59
72
|
placeholder={'e.g John Doe'}
|
|
73
|
+
className="border border-gray-100 rounded px-3 py-2 bg-gray-50 cursor-not-allowed"
|
|
74
|
+
aria-readonly="true"
|
|
60
75
|
/>
|
|
61
76
|
</Field>
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
{/* Ratings breakdown from metadata */}
|
|
78
|
+
<div className="flex flex-col gap-y-3">
|
|
79
|
+
{loadingRatingsMetadata ? (
|
|
80
|
+
<div>Loading ratings...</div>
|
|
81
|
+
) : ratingsMetadata.length > 0 ? (
|
|
82
|
+
ratingsMetadata.map(rating => {
|
|
83
|
+
const selected = ratings.find(r => r.id === rating.id)?.value_id || '';
|
|
84
|
+
// Find value (1-5) for selected value_id
|
|
85
|
+
const selectedValue = rating.values.find(v => v.value_id === selected)?.value || '';
|
|
86
|
+
return (
|
|
87
|
+
<div key={rating.id} className="mb-2">
|
|
88
|
+
<label className="block mb-1 font-bold text-gray-700">{rating.name}</label>
|
|
89
|
+
<div className="flex items-center gap-1">
|
|
90
|
+
{[1,2,3,4,5].map(star => {
|
|
91
|
+
const valObj = rating.values.find(v => v.value === String(star));
|
|
92
|
+
if (!valObj) return null;
|
|
93
|
+
return (
|
|
94
|
+
<button
|
|
95
|
+
key={valObj.value_id}
|
|
96
|
+
type="button"
|
|
97
|
+
aria-label={`${star} star${star > 1 ? 's' : ''}`}
|
|
98
|
+
className={`focus:outline-none ${selectedValue == star ? 'text-yellow-400' : 'text-gray-300'}`}
|
|
99
|
+
onClick={() => handleRatingChange(rating.id, valObj.value_id)}
|
|
100
|
+
>
|
|
101
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill={selectedValue >= star ? '#F7C317' : '#D9D9D9'} viewBox="0 0 20 20" width="15" height="15">
|
|
102
|
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.286 3.967a1 1 0 00.95.69h4.175c.969 0 1.371 1.24.588 1.81l-3.38 2.455a1 1 0 00-.364 1.118l1.287 3.966c.3.922-.755 1.688-1.54 1.118l-3.38-2.454a1 1 0 00-1.175 0l-3.38 2.454c-.784.57-1.838-.196-1.54-1.118l1.287-3.966a1 1 0 00-.364-1.118L2.05 9.394c-.783-.57-.38-1.81.588-1.81h4.175a1 1 0 00.95-.69l1.286-3.967z" />
|
|
103
|
+
</svg>
|
|
104
|
+
</button>
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
{/* <span className="ml-2 text-sm text-gray-600">{selectedValue || ''}</span> */}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
})
|
|
112
|
+
) : null}
|
|
113
|
+
</div>
|
|
114
|
+
<Field id="summary_field" label={'Summary'}>
|
|
115
|
+
<input
|
|
75
116
|
id="summary"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
name="summary"
|
|
118
|
+
type="text"
|
|
119
|
+
required
|
|
120
|
+
value={formState.summary || ''}
|
|
121
|
+
onChange={e => handleChange('summary', e.target.value)}
|
|
81
122
|
data-cy="summary"
|
|
82
123
|
aria-label={'summary'}
|
|
83
124
|
placeholder={'Summary of your rating'}
|
|
125
|
+
className="border border-gray-100 rounded px-3 py-2"
|
|
84
126
|
/>
|
|
85
127
|
</Field>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
id="review_field"
|
|
89
|
-
label={'Review'}
|
|
90
|
-
>
|
|
91
|
-
<TextInput
|
|
128
|
+
<Field id="review_field" label={'Review'}>
|
|
129
|
+
<textarea
|
|
92
130
|
id="review"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
maskOnBlur={true}
|
|
131
|
+
name="review"
|
|
132
|
+
required
|
|
133
|
+
value={formState.review || ''}
|
|
134
|
+
onChange={e => handleChange('review', e.target.value)}
|
|
98
135
|
data-cy="review"
|
|
99
136
|
aria-label={'review'}
|
|
100
|
-
placeholder={'Let us know your
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<Field
|
|
105
|
-
id="like_reason_field"
|
|
106
|
-
label={'I like about'}
|
|
107
|
-
>
|
|
108
|
-
<TextInput
|
|
109
|
-
id="like_reason"
|
|
110
|
-
field="like_reason"
|
|
111
|
-
validate={isRequired}
|
|
112
|
-
validateOnBlur
|
|
113
|
-
mask={value => value && value.trim()}
|
|
114
|
-
maskOnBlur={true}
|
|
115
|
-
data-cy="like_reason"
|
|
116
|
-
aria-label={'like_reason'}
|
|
117
|
-
placeholder={'Summary of your rating'}
|
|
137
|
+
placeholder={'Let us know your thoughts'}
|
|
138
|
+
className="border border-gray-100 rounded px-3 py-2"
|
|
139
|
+
rows={3}
|
|
118
140
|
/>
|
|
119
141
|
</Field>
|
|
120
|
-
|
|
121
|
-
<Field
|
|
122
|
-
id="dont_like_reason_field"
|
|
123
|
-
label={"I dont't like about"}
|
|
124
|
-
>
|
|
125
|
-
<TextInput
|
|
126
|
-
id="dont_like_reason"
|
|
127
|
-
field="dont_like_reason"
|
|
128
|
-
validate={isRequired}
|
|
129
|
-
validateOnBlur
|
|
130
|
-
mask={value => value && value.trim()}
|
|
131
|
-
maskOnBlur={true}
|
|
132
|
-
data-cy="dont_like_reason"
|
|
133
|
-
aria-label={'dont_like_reason'}
|
|
134
|
-
placeholder={'Summary of your rating'}
|
|
135
|
-
/>
|
|
136
|
-
</Field>
|
|
137
|
-
|
|
138
142
|
<div className='actions flex justify-end gap-x-2.5 mt-4'>
|
|
139
143
|
<Button
|
|
140
144
|
priority='low'
|
|
@@ -142,6 +146,7 @@ const modalFormReview = (props) => {
|
|
|
142
146
|
content: 'capitalize text-[16px] font-medium'
|
|
143
147
|
}}
|
|
144
148
|
onClick={() => setOpen(false)}
|
|
149
|
+
type="button"
|
|
145
150
|
>
|
|
146
151
|
Cancel
|
|
147
152
|
</Button>
|
|
@@ -150,15 +155,17 @@ const modalFormReview = (props) => {
|
|
|
150
155
|
classes={{
|
|
151
156
|
content: 'capitalize text-[16px] font-medium'
|
|
152
157
|
}}
|
|
158
|
+
type="submit"
|
|
159
|
+
disabled={submitting}
|
|
153
160
|
>
|
|
154
|
-
Submit Review
|
|
161
|
+
{submitting ? 'Submitting...' : 'Submit Review'}
|
|
155
162
|
</Button>
|
|
156
163
|
</div>
|
|
157
|
-
</
|
|
164
|
+
</form>
|
|
158
165
|
</div>
|
|
159
166
|
</Modal>
|
|
160
167
|
</>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
168
|
+
);
|
|
169
|
+
};
|
|
163
170
|
|
|
164
|
-
export default modalFormReview
|
|
171
|
+
export default modalFormReview;
|
package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productReview.js
CHANGED
|
@@ -4,98 +4,139 @@ import { Star1 } from 'iconsax-react';
|
|
|
4
4
|
import Button from '../../Button';
|
|
5
5
|
import ModalFormReview from './modalFormReview';
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
const productReview = (props) => {
|
|
10
|
+
const {
|
|
11
|
+
className,
|
|
12
|
+
productReviewData,
|
|
13
|
+
loadingProductReview,
|
|
14
|
+
errorProductReview,
|
|
15
|
+
ratingsMetadataData,
|
|
16
|
+
loadingRatingsMetadata,
|
|
17
|
+
handleSubmitReview,
|
|
18
|
+
defaultNickname,
|
|
19
|
+
product
|
|
20
|
+
} = props;
|
|
8
21
|
|
|
9
|
-
const { className } = props;
|
|
10
22
|
const [open, setOpen] = useState(false);
|
|
11
23
|
const [filter, setFilter] = useState('All');
|
|
24
|
+
const [submitting, setSubmitting] = useState(false);
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
items
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
__typename: 'ProductRate',
|
|
29
|
-
id: 2,
|
|
30
|
-
name: 'Roger Taylor',
|
|
31
|
-
date: '25 January 2024',
|
|
32
|
-
rating: 2,
|
|
33
|
-
comment: 'Arrived late and packaging was damaged. Not satisfied.'
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
__typename: 'ProductRate',
|
|
37
|
-
id: 3,
|
|
38
|
-
name: 'Sarah Smith',
|
|
39
|
-
date: '02 February 2024',
|
|
40
|
-
rating: 4,
|
|
41
|
-
comment: 'Good product, but delivery could be faster.'
|
|
42
|
-
},
|
|
43
|
-
{
|
|
26
|
+
let reviewsData = null;
|
|
27
|
+
if (
|
|
28
|
+
productReviewData &&
|
|
29
|
+
productReviewData.products &&
|
|
30
|
+
productReviewData.products.items &&
|
|
31
|
+
productReviewData.products.items.length > 0
|
|
32
|
+
) {
|
|
33
|
+
const item = productReviewData.products.items[0];
|
|
34
|
+
const reviewItems = (item.reviews && item.reviews.items) || [];
|
|
35
|
+
reviewsData = {
|
|
36
|
+
__typename: 'ProductRates',
|
|
37
|
+
total_count: item.review_count || reviewItems.length,
|
|
38
|
+
items: reviewItems.map((r, idx) => ({
|
|
44
39
|
__typename: 'ProductRate',
|
|
45
|
-
id:
|
|
46
|
-
name:
|
|
47
|
-
date:
|
|
48
|
-
rating:
|
|
49
|
-
comment:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
comment: 'Excellent service and product quality! Will buy again.'
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
__typename: 'ProductRate',
|
|
61
|
-
id: 6,
|
|
62
|
-
name: 'David Lee',
|
|
63
|
-
date: '20 February 2024',
|
|
64
|
-
rating: 1,
|
|
65
|
-
comment: 'Item not as described. Very disappointed.'
|
|
40
|
+
id: idx + 1,
|
|
41
|
+
name: r.nickname,
|
|
42
|
+
date: r.created_at,
|
|
43
|
+
rating: r.average_rating ? Math.round(r.average_rating / 20) : (r.ratings_breakdown && r.ratings_breakdown[0] ? parseInt(r.ratings_breakdown[0].value) : 0),
|
|
44
|
+
comment: r.text || r.summary || '',
|
|
45
|
+
summary: r.summary || '',
|
|
46
|
+
productName: r.product?.name || '',
|
|
47
|
+
ratings_breakdown: r.ratings_breakdown || []
|
|
48
|
+
})),
|
|
49
|
+
page_info: productReviewData.products.page_info || {
|
|
50
|
+
total_pages: 1,
|
|
51
|
+
current_page: 1
|
|
66
52
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
__typename: 'SearchResultPageInfo',
|
|
70
|
-
total_pages: 1,
|
|
71
|
-
page_size: 10,
|
|
72
|
-
current_page: 1,
|
|
73
|
-
total_count: 6
|
|
74
|
-
}
|
|
75
|
-
};
|
|
53
|
+
};
|
|
54
|
+
}
|
|
76
55
|
|
|
56
|
+
if (!reviewsData || !reviewsData.items.length) {
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<ModalFormReview
|
|
60
|
+
open={open}
|
|
61
|
+
setOpen={setOpen}
|
|
62
|
+
defaultNickname={defaultNickname}
|
|
63
|
+
ratingsMetadata={ratingsMetadataData?.productReviewRatingsMetadata?.items || []}
|
|
64
|
+
loadingRatingsMetadata={loadingRatingsMetadata}
|
|
65
|
+
onSubmit={async (formValues) => {
|
|
66
|
+
setSubmitting(true);
|
|
67
|
+
const input = {
|
|
68
|
+
nickname: formValues.nickname,
|
|
69
|
+
summary: formValues.summary,
|
|
70
|
+
text: formValues.review,
|
|
71
|
+
sku: product?.sku,
|
|
72
|
+
ratings: formValues.ratings
|
|
73
|
+
};
|
|
74
|
+
const result = await handleSubmitReview(input);
|
|
75
|
+
setSubmitting(false);
|
|
76
|
+
if (result.success) setOpen(false);
|
|
77
|
+
}}
|
|
78
|
+
submitting={submitting}
|
|
79
|
+
/>
|
|
80
|
+
<div className={className}>
|
|
81
|
+
<div className="flex items-center justify-between mb-6">
|
|
82
|
+
<div />
|
|
83
|
+
<Button
|
|
84
|
+
priority='low'
|
|
85
|
+
classes={{
|
|
86
|
+
content: 'normal-case font-normal text-base'
|
|
87
|
+
}}
|
|
88
|
+
onClick={() => setOpen(true)}
|
|
89
|
+
>
|
|
90
|
+
Write a review
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="text-center py-8 text-gray-500">No reviews yet.</div>
|
|
94
|
+
</div>
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
77
98
|
|
|
78
|
-
const totalReviews =
|
|
99
|
+
const totalReviews = reviewsData.items.length;
|
|
79
100
|
const averageRating = totalReviews > 0
|
|
80
|
-
? (
|
|
101
|
+
? (reviewsData.items.reduce((sum, item) => sum + item.rating, 0) / totalReviews).toFixed(1)
|
|
81
102
|
: 0;
|
|
82
103
|
|
|
83
104
|
const starCounts = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 };
|
|
84
|
-
|
|
105
|
+
reviewsData.items.forEach(item => {
|
|
85
106
|
if (starCounts[item.rating] !== undefined) {
|
|
86
107
|
starCounts[item.rating]++;
|
|
87
108
|
}
|
|
88
109
|
});
|
|
89
110
|
|
|
90
111
|
const getPercent = (count) => totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0;
|
|
91
|
-
|
|
112
|
+
|
|
92
113
|
const filteredReviews = filter === 'All'
|
|
93
|
-
?
|
|
94
|
-
:
|
|
114
|
+
? reviewsData.items
|
|
115
|
+
: reviewsData.items.filter(item => item.rating === parseInt(filter));
|
|
95
116
|
|
|
96
117
|
return (
|
|
97
118
|
<>
|
|
98
|
-
<ModalFormReview
|
|
119
|
+
<ModalFormReview
|
|
120
|
+
open={open}
|
|
121
|
+
setOpen={setOpen}
|
|
122
|
+
defaultNickname={defaultNickname}
|
|
123
|
+
ratingsMetadata={ratingsMetadataData?.productReviewRatingsMetadata?.items || []}
|
|
124
|
+
loadingRatingsMetadata={loadingRatingsMetadata}
|
|
125
|
+
onSubmit={async (formValues) => {
|
|
126
|
+
setSubmitting(true);
|
|
127
|
+
const input = {
|
|
128
|
+
nickname: formValues.nickname,
|
|
129
|
+
summary: formValues.summary,
|
|
130
|
+
text: formValues.review,
|
|
131
|
+
sku: product?.sku,
|
|
132
|
+
ratings: formValues.ratings
|
|
133
|
+
};
|
|
134
|
+
const result = await handleSubmitReview(input);
|
|
135
|
+
setSubmitting(false);
|
|
136
|
+
if (result.success) setOpen(false);
|
|
137
|
+
}}
|
|
138
|
+
submitting={submitting}
|
|
139
|
+
/>
|
|
99
140
|
<div className={className}>
|
|
100
141
|
<div className="w-full flex items-start xs_flex-col lg_flex-row gap-[30px]">
|
|
101
142
|
<div className="w-full xs_max-w-full lg_max-w-[365px] border border-[#E6E9EA] rounded-md p-6">
|
|
@@ -167,7 +208,7 @@ const productReview = (props) => {
|
|
|
167
208
|
{/* Reviews List */}
|
|
168
209
|
<div className='space-y-4 mb-6'>
|
|
169
210
|
<Review reviews={{
|
|
170
|
-
...
|
|
211
|
+
...reviewsData,
|
|
171
212
|
items: filteredReviews
|
|
172
213
|
}} />
|
|
173
214
|
</div>
|
|
@@ -57,7 +57,6 @@ const ERROR_FIELD_TO_MESSAGE_MAPPING = {
|
|
|
57
57
|
|
|
58
58
|
const ProductDetailsCollapsible = (props) => {
|
|
59
59
|
const { data } = props;
|
|
60
|
-
|
|
61
60
|
return (
|
|
62
61
|
<>
|
|
63
62
|
{data.map((_data) => (
|
|
@@ -99,7 +98,14 @@ const ProductFullDetail = props => {
|
|
|
99
98
|
productDetails,
|
|
100
99
|
customAttributes,
|
|
101
100
|
wishlistButtonProps,
|
|
102
|
-
sellerDetails
|
|
101
|
+
sellerDetails,
|
|
102
|
+
productReviewData,
|
|
103
|
+
loadingProductReview,
|
|
104
|
+
errorProductReview,
|
|
105
|
+
ratingsMetadataData,
|
|
106
|
+
loadingRatingsMetadata,
|
|
107
|
+
handleSubmitReview,
|
|
108
|
+
defaultNickname
|
|
103
109
|
} = talonProps;
|
|
104
110
|
|
|
105
111
|
const [, { addToast }] = useToasts();
|
|
@@ -493,7 +499,17 @@ const ProductFullDetail = props => {
|
|
|
493
499
|
{
|
|
494
500
|
id: 'product-reviews',
|
|
495
501
|
title: 'Reviews',
|
|
496
|
-
content: <ProductReviews
|
|
502
|
+
content: <ProductReviews
|
|
503
|
+
className={cn(contentContainerClass, classes.contentContainerTabOverride)}
|
|
504
|
+
productReviewData={productReviewData}
|
|
505
|
+
loadingProductReview={loadingProductReview}
|
|
506
|
+
errorProductReview={errorProductReview}
|
|
507
|
+
ratingsMetadataData={ratingsMetadataData}
|
|
508
|
+
loadingRatingsMetadata={loadingRatingsMetadata}
|
|
509
|
+
handleSubmitReview={handleSubmitReview}
|
|
510
|
+
defaultNickname={defaultNickname}
|
|
511
|
+
product={product}
|
|
512
|
+
/>
|
|
497
513
|
}
|
|
498
514
|
];
|
|
499
515
|
|