@riosst100/pwa-marketplace 2.8.8 → 2.9.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/i18n/en_US.json +1 -1
- package/i18n/id_ID.json +1 -1
- package/package.json +1 -1
- package/src/components/FilterTop/FilterBlockList/filterBlockList.js +2 -0
- package/src/components/FilterTop/FilterBlockList/filterTopItemGroup.js +20 -2
- package/src/components/FilterTop/filterTop.js +4 -1
- package/src/components/FilterTop/filterTopBlock.js +2 -0
- package/src/components/PaymentMethod/PaypalExpress/paypalExpress.js +3 -2
- package/src/components/PaymentMethod/Xendit/xendit.js +53 -0
- package/src/components/ProductReviewItem/index.js +1 -0
- package/src/components/ProductReviewItem/productReviewItem.js +67 -0
- package/src/components/SellerReview/sellerReview.js +21 -25
- package/src/components/VerifyEmailPage/index.js +1 -0
- package/src/components/VerifyEmailPage/verifyEmail.js +79 -0
- package/src/components/VerifyEmailPage/verifyEmail.module.css +71 -0
- package/src/intercept.js +8 -0
- package/src/overwrites/peregrine/lib/talons/CheckoutPage/checkoutPage.extended.gql.js +88 -0
- package/src/overwrites/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js +124 -13
- package/src/overwrites/peregrine/lib/talons/CheckoutPage/xendit.gql.js +15 -0
- package/src/overwrites/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js +12 -10
- package/src/overwrites/venia-ui/lib/components/Checkbox/checkbox.module.css +2 -0
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/BillingAddress/billingAddress.module.css +9 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/item.js +1 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/item.module.css +7 -2
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.module.css +3 -2
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/showAllButton.js +1 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/showAllButton.module.css +4 -3
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js +68 -44
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.module.css +102 -5
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.js +4 -8
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.module.css +6 -0
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethodCollection.js +6 -20
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js +92 -4
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css +13 -3
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/summary.module.css +3 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.js +3 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.module.css +51 -2
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.js +11 -28
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.module.css +16 -4
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/modalFormReview.js +2 -2
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productReview.js +156 -38
- package/src/talons/FilterTop/filterTop.gql.js +1 -0
- package/src/talons/FilterTop/useFilterTop.js +2 -1
- package/src/talons/PaymentMethod/PaypalExpress/usePaypalExpress.js +5 -1
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* add new payment component mappings for the checkout page.
|
|
2
|
+
* Delegate to Venia's canonical payment method collection so build-target
|
|
3
|
+
* augmentation (checkoutPagePaymentTypes) from third-party integrations works.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A payment definition object that describes a payment in your storefront.
|
|
12
|
-
*
|
|
13
|
-
* @typedef {Object} PaymentDefinition
|
|
14
|
-
* @property {string} paymentCode is use to map your payment
|
|
15
|
-
* @property {string} importPath Resolvable path to the component the
|
|
16
|
-
* Route component will render
|
|
17
|
-
*
|
|
18
|
-
* @example <caption>A custom payment method</caption>
|
|
19
|
-
* const myCustomPayment = {
|
|
20
|
-
* paymentCode: 'cc',
|
|
21
|
-
* importPath: '@partner/module/path_to_your_component'
|
|
22
|
-
* }
|
|
5
|
+
* Note: Keeping a local empty object here prevents integrations like
|
|
6
|
+
* Stripe/MultiSafepay from registering their UI components, resulting in
|
|
7
|
+
* "There was an error loading payments" on checkout.
|
|
23
8
|
*/
|
|
9
|
+
export { default } from '@magento/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethodCollection';
|
package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
import { shape, string, bool, func } from 'prop-types';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
|
+
import { useMutation, useQuery } from '@apollo/client';
|
|
4
5
|
|
|
5
6
|
import { usePaymentMethods } from '@magento/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods';
|
|
7
|
+
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
6
8
|
|
|
7
9
|
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
8
10
|
import RadioGroup from '@magento/venia-ui/lib/components/RadioGroup';
|
|
9
11
|
import Radio from '@magento/venia-ui/lib/components/RadioGroup/radio';
|
|
10
12
|
import defaultClasses from './paymentMethods.module.css';
|
|
11
13
|
import payments from './paymentMethodCollection';
|
|
14
|
+
import {
|
|
15
|
+
GET_SHIPPING_ADDRESS,
|
|
16
|
+
SET_BILLING_ADDRESS
|
|
17
|
+
} from '@riosst100/pwa-marketplace/src/talons/Xendit/xendit';
|
|
12
18
|
|
|
13
19
|
const PaymentMethods = props => {
|
|
14
20
|
const {
|
|
@@ -33,11 +39,85 @@ const PaymentMethods = props => {
|
|
|
33
39
|
isLoading
|
|
34
40
|
} = talonProps;
|
|
35
41
|
|
|
42
|
+
const [{ cartId }] = useCartContext();
|
|
43
|
+
|
|
44
|
+
const { data: shippingData } = useQuery(GET_SHIPPING_ADDRESS, {
|
|
45
|
+
skip: !cartId,
|
|
46
|
+
variables: { cartId }
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const [setBillingAddress] = useMutation(SET_BILLING_ADDRESS);
|
|
50
|
+
|
|
51
|
+
const getRegionValue = region => {
|
|
52
|
+
if (!region) return '';
|
|
53
|
+
return region.region_id || region.label || region.code || '';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleChangeAndSetBilling = useCallback(
|
|
57
|
+
async event => {
|
|
58
|
+
// First, run canonical selection logic (updates selected payment on cart)
|
|
59
|
+
handlePaymentMethodSelection(event);
|
|
60
|
+
|
|
61
|
+
// Then, proactively set billing address using the selected shipping address
|
|
62
|
+
try {
|
|
63
|
+
const shippingAddresses =
|
|
64
|
+
shippingData?.cart?.shippingAddresses || [];
|
|
65
|
+
const addr = shippingAddresses[0];
|
|
66
|
+
if (!addr || !cartId) return;
|
|
67
|
+
|
|
68
|
+
const street1 = Array.isArray(addr.street) ? addr.street[0] || '' : '';
|
|
69
|
+
const street2 = Array.isArray(addr.street) ? addr.street[1] || '' : '';
|
|
70
|
+
await setBillingAddress({
|
|
71
|
+
variables: {
|
|
72
|
+
cartId,
|
|
73
|
+
firstName: addr.firstName || '',
|
|
74
|
+
lastName: addr.lastName || '',
|
|
75
|
+
street1,
|
|
76
|
+
street2,
|
|
77
|
+
city: addr.city || '',
|
|
78
|
+
region: getRegionValue(addr.region),
|
|
79
|
+
postcode: addr.postcode || '',
|
|
80
|
+
country: addr.country?.code || '',
|
|
81
|
+
phoneNumber: addr.phoneNumber || ''
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// Non-blocking; payment selection should still work even if billing sync fails
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.warn('Gagal menyetel billing address saat memilih metode pembayaran:', e);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
[cartId, handlePaymentMethodSelection, setBillingAddress, shippingData]
|
|
91
|
+
);
|
|
92
|
+
|
|
36
93
|
if (isLoading) {
|
|
37
94
|
return null;
|
|
38
95
|
}
|
|
39
96
|
|
|
40
|
-
|
|
97
|
+
// Warn if Magento exposes payment methods that don't have a mapped UI component.
|
|
98
|
+
const knownCodes = Object.keys(payments);
|
|
99
|
+
const unknownMethods = availablePaymentMethods.filter(
|
|
100
|
+
({ code }) => !knownCodes.includes(code)
|
|
101
|
+
);
|
|
102
|
+
if (unknownMethods.length) {
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.warn(
|
|
105
|
+
'Checkout: Ditemukan metode pembayaran yang belum dipetakan:',
|
|
106
|
+
unknownMethods.map(m => m.code),
|
|
107
|
+
'\nPastikan metode tersebut terdaftar melalui target build Venia "checkoutPagePaymentTypes" atau ditambahkan ke paymentMethodCollection.'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Title fallback map to ensure labels are always visible.
|
|
112
|
+
const TITLE_FALLBACK = {
|
|
113
|
+
braintree: 'Credit/Debit Card',
|
|
114
|
+
paypal_express: 'PayPal Express Checkout',
|
|
115
|
+
paypal_express_bml: 'PayPal Credit',
|
|
116
|
+
checkmo: 'Check / Money Order',
|
|
117
|
+
xendit: 'Xendit',
|
|
118
|
+
cc: 'Credit Card',
|
|
119
|
+
free: 'Free'
|
|
120
|
+
};
|
|
41
121
|
|
|
42
122
|
const radios = availablePaymentMethods
|
|
43
123
|
.map(({ code, title }) => {
|
|
@@ -48,6 +128,7 @@ const PaymentMethods = props => {
|
|
|
48
128
|
|
|
49
129
|
const id = `paymentMethod--${code}`;
|
|
50
130
|
const isSelected = currentSelectedPaymentMethod === code;
|
|
131
|
+
const displayTitle = title || TITLE_FALLBACK[code] || code;
|
|
51
132
|
const PaymentMethodComponent = payments[code];
|
|
52
133
|
const renderedComponent = isSelected ? (
|
|
53
134
|
<PaymentMethodComponent
|
|
@@ -55,6 +136,7 @@ const PaymentMethods = props => {
|
|
|
55
136
|
onPaymentError={onPaymentError}
|
|
56
137
|
resetShouldSubmit={resetShouldSubmit}
|
|
57
138
|
shouldSubmit={shouldSubmit}
|
|
139
|
+
currentSelectedPaymentMethod={currentSelectedPaymentMethod}
|
|
58
140
|
/>
|
|
59
141
|
) : null;
|
|
60
142
|
|
|
@@ -62,13 +144,13 @@ const PaymentMethods = props => {
|
|
|
62
144
|
<div key={code} className={classes.payment_method}>
|
|
63
145
|
<Radio
|
|
64
146
|
id={id}
|
|
65
|
-
label={
|
|
147
|
+
label={displayTitle}
|
|
66
148
|
value={code}
|
|
67
149
|
classes={{
|
|
68
150
|
label: classes.radio_label
|
|
69
151
|
}}
|
|
70
152
|
checked={isSelected}
|
|
71
|
-
onChange={
|
|
153
|
+
onChange={handleChangeAndSetBilling}
|
|
72
154
|
/>
|
|
73
155
|
{renderedComponent}
|
|
74
156
|
</div>
|
|
@@ -95,6 +177,12 @@ const PaymentMethods = props => {
|
|
|
95
177
|
|
|
96
178
|
return (
|
|
97
179
|
<div className={classes.root}>
|
|
180
|
+
<h5 className={classes.cardTitle}>
|
|
181
|
+
{formatMessage({
|
|
182
|
+
id: 'checkoutPage.paymentInformation',
|
|
183
|
+
defaultMessage: 'Payment Information'
|
|
184
|
+
})}
|
|
185
|
+
</h5>
|
|
98
186
|
<RadioGroup
|
|
99
187
|
classes={{ root: classes.radio_group }}
|
|
100
188
|
field="selectedPaymentMethod"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
.root {
|
|
2
2
|
composes: grid from global;
|
|
3
|
-
composes: p-md from global;
|
|
3
|
+
/* composes: p-md from global; */
|
|
4
4
|
composes: pb-s from global;
|
|
5
|
+
padding: 1rem;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
.radio_group {
|
|
@@ -10,10 +11,11 @@
|
|
|
10
11
|
|
|
11
12
|
.payment_method {
|
|
12
13
|
composes: border-b from global;
|
|
13
|
-
composes: border-solid from global;
|
|
14
|
-
composes: border-subtle from global;
|
|
14
|
+
/* composes: border-solid from global; */
|
|
15
|
+
/* composes: border-subtle from global; */
|
|
15
16
|
composes: pb-xs from global;
|
|
16
17
|
composes: pt-xs from global;
|
|
18
|
+
border-color: lightgray;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/* TODO @TW: cannot compose */
|
|
@@ -32,3 +34,11 @@
|
|
|
32
34
|
composes: gap-2xs from global;
|
|
33
35
|
composes: text-error from global;
|
|
34
36
|
}
|
|
37
|
+
.cardTitle{
|
|
38
|
+
font-weight: 500;
|
|
39
|
+
font-size: 16px;
|
|
40
|
+
}
|
|
41
|
+
.address_check{
|
|
42
|
+
margin-left: 5px;
|
|
43
|
+
margin-top: 5px;
|
|
44
|
+
}
|
package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/summary.module.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.root {
|
|
2
2
|
composes: gap-xs from global;
|
|
3
3
|
composes: grid from global;
|
|
4
|
-
composes: p-
|
|
4
|
+
composes: p-xs from global;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.heading_container {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.heading {
|
|
14
|
+
/* composes: text-base from global; */
|
|
15
|
+
font-size: 16px;
|
|
14
16
|
composes: font-medium from global;
|
|
15
17
|
}
|
|
16
18
|
|
package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.js
CHANGED
|
@@ -26,7 +26,9 @@ const ShippingRadios = props => {
|
|
|
26
26
|
const radioGroupClasses = {
|
|
27
27
|
message: classes.radioMessage,
|
|
28
28
|
radioLabel: classes.radioLabel,
|
|
29
|
-
root: classes.radioRoot
|
|
29
|
+
root: classes.radioRoot,
|
|
30
|
+
// Ensure we can tweak the label container (the clickable row)
|
|
31
|
+
radioContainer: classes.radioContainer
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
const shippingRadios = shippingMethods.map(method => {
|
package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.module.css
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
composes: grid from global;
|
|
9
9
|
composes: grid-cols-[100%] from global;
|
|
10
10
|
composes: justify-start from global;
|
|
11
|
-
|
|
12
|
-
composes: sm_grid-cols-
|
|
11
|
+
/* On small screens and up, make two columns: name grows, price auto */
|
|
12
|
+
composes: sm_grid-cols-[1fr,auto] from global;
|
|
13
|
+
composes: items-center from global;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
.radioMessage {
|
|
@@ -20,3 +21,51 @@
|
|
|
20
21
|
composes: leading-normal from global;
|
|
21
22
|
composes: text-error from global;
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
/* Fix alignment and visual of radio controls inside the modal */
|
|
26
|
+
.radioContainer {
|
|
27
|
+
/* Ensure consistent layout: control + content */
|
|
28
|
+
composes: justify-items-start from global;
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-columns: min-content 1fr;
|
|
31
|
+
grid-template-areas: 'input label';
|
|
32
|
+
justify-content: flex-start !important;
|
|
33
|
+
align-items: flex-start;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Normalize the control size and overlaying layers so input and icon overlap */
|
|
37
|
+
.radioContainer :global([class*="radio-input-"]) {
|
|
38
|
+
width: 1.5rem; /* 24px default Venia size for harmony with icon */
|
|
39
|
+
height: 1.5rem;
|
|
40
|
+
box-shadow: none !important; /* remove active/focus blobs that appear offset */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.radioContainer :global([class*="radio-icon-"]) {
|
|
44
|
+
position: relative !important;
|
|
45
|
+
width: 1.5rem !important;
|
|
46
|
+
height: 1.5rem !important;
|
|
47
|
+
display: inline-block;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Prevent filled background bleed that can look like an offset dot */
|
|
51
|
+
.radioContainer :global([class*="radio-input-"]:checked) {
|
|
52
|
+
background-color: transparent !important; /* dot handled via icon ::after */
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Draw an inner dot centered in the ring when checked */
|
|
56
|
+
.radioContainer :global([class*="radio-icon-"]) {
|
|
57
|
+
position: relative;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.radioContainer :global([class*="radio-input-"]:checked) + :global([class*="radio-icon-"])::after {
|
|
61
|
+
content: '';
|
|
62
|
+
position: absolute;
|
|
63
|
+
left: 30%;
|
|
64
|
+
top: 30%;
|
|
65
|
+
width: 0.6rem;
|
|
66
|
+
height: 0.6rem;
|
|
67
|
+
background: #f76b1c;
|
|
68
|
+
border-radius: 9999px;
|
|
69
|
+
transform: translate(-50%, -50%);
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
}
|
|
@@ -244,36 +244,19 @@ const CheckoutPage = props => {
|
|
|
244
244
|
</div>
|
|
245
245
|
) : null;
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
<Button
|
|
250
|
-
onClick={handleReviewOrder}
|
|
251
|
-
onKeyDown={handleReviewOrderEnterKeyPress}
|
|
252
|
-
priority="high"
|
|
253
|
-
className={classes.review_order_button}
|
|
254
|
-
data-cy="CheckoutPage-reviewOrderButton"
|
|
255
|
-
disabled={
|
|
256
|
-
reviewOrderButtonClicked ||
|
|
257
|
-
isUpdating ||
|
|
258
|
-
!isPaymentAvailable
|
|
259
|
-
}
|
|
260
|
-
>
|
|
261
|
-
<FormattedMessage
|
|
262
|
-
id={'checkoutPage.reviewOrder'}
|
|
263
|
-
defaultMessage={'Review Order'}
|
|
264
|
-
/>
|
|
265
|
-
</Button>
|
|
266
|
-
) : null;
|
|
247
|
+
// Remove Review Order button; flow uses only Place Order
|
|
248
|
+
const reviewOrderButton = null;
|
|
267
249
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
250
|
+
// Show ItemsReview from the beginning, not only at the REVIEW step
|
|
251
|
+
const itemsReview = (
|
|
252
|
+
<div className={classes.items_review_container}>
|
|
253
|
+
<ItemsReview />
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
274
256
|
|
|
257
|
+
// Allow placing order starting from the Payment step (no separate review action)
|
|
275
258
|
const placeOrderButton =
|
|
276
|
-
checkoutStep
|
|
259
|
+
checkoutStep >= CHECKOUT_STEP.PAYMENT ? (
|
|
277
260
|
<Button
|
|
278
261
|
onClick={handlePlaceOrder}
|
|
279
262
|
onKeyDown={handlePlaceOrderEnterKeyPress}
|
|
@@ -312,6 +295,7 @@ const CheckoutPage = props => {
|
|
|
312
295
|
}
|
|
313
296
|
>
|
|
314
297
|
<OrderSummary isUpdating={isUpdating} />
|
|
298
|
+
{itemsReview}
|
|
315
299
|
</div>
|
|
316
300
|
);
|
|
317
301
|
|
|
@@ -394,7 +378,6 @@ const CheckoutPage = props => {
|
|
|
394
378
|
</div>
|
|
395
379
|
{priceAdjustmentsSection}
|
|
396
380
|
{reviewOrderButton}
|
|
397
|
-
{itemsReview}
|
|
398
381
|
{orderSummary}
|
|
399
382
|
{placeOrderButton}
|
|
400
383
|
<GoogleReCaptcha {...recaptchaWidgetProps} />
|
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
|
|
125
125
|
.items_review_container {
|
|
126
126
|
grid-column: 1 / span 1;
|
|
127
|
+
margin-top: 2rem;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
.summaryContainer {
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
composes: lg_h-minContent from global;
|
|
133
134
|
composes: lg_sticky from global;
|
|
134
135
|
/* TODO @TW: review. Magic number. Slightly bigger than sticky header. */
|
|
135
|
-
composes: lg_top-[
|
|
136
|
+
composes: lg_top-[2.5rem] from global;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
@media (min-width: 960px) {
|
|
@@ -173,14 +174,25 @@
|
|
|
173
174
|
|
|
174
175
|
.review_order_button {
|
|
175
176
|
composes: root_highPriority from '@magento/venia-ui/lib/components/Button/button.module.css';
|
|
176
|
-
|
|
177
|
+
box-shadow: none;
|
|
177
178
|
grid-column: 1 / span 1;
|
|
178
179
|
composes: m-auto from global;
|
|
180
|
+
text-transform: capitalize;
|
|
181
|
+
}
|
|
182
|
+
.review_order_button:hover,
|
|
183
|
+
.review_order_button:focus,
|
|
184
|
+
.review_order_button:active {
|
|
185
|
+
box-shadow: none;
|
|
179
186
|
}
|
|
180
|
-
|
|
181
187
|
.place_order_button {
|
|
182
188
|
composes: root_highPriority from '@magento/venia-ui/lib/components/Button/button.module.css';
|
|
183
|
-
|
|
189
|
+
box-shadow: none;
|
|
184
190
|
grid-column: 1 / span 1;
|
|
185
191
|
composes: m-auto from global;
|
|
192
|
+
text-transform: capitalize;
|
|
193
|
+
}
|
|
194
|
+
.place_order_button:hover,
|
|
195
|
+
.place_order_button:focus,
|
|
196
|
+
.place_order_button:active {
|
|
197
|
+
box-shadow: none;
|
|
186
198
|
}
|
package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/modalFormReview.js
CHANGED
|
@@ -27,7 +27,7 @@ const modalFormReview = (props) => {
|
|
|
27
27
|
className="modal_form_review !p-[30px] md_min-w-[650px]"
|
|
28
28
|
>
|
|
29
29
|
<div className='form_review-container'>
|
|
30
|
-
<div className='header_title-modal flex justify-between mb-
|
|
30
|
+
<div className='header_title-modal flex justify-between mb-5'>
|
|
31
31
|
<div className='text-lg text-black font-medium'>
|
|
32
32
|
Write Review
|
|
33
33
|
</div>
|
|
@@ -38,7 +38,7 @@ const modalFormReview = (props) => {
|
|
|
38
38
|
|
|
39
39
|
<Form
|
|
40
40
|
data-cy="form_review"
|
|
41
|
-
className="flex flex-col gap-y-
|
|
41
|
+
className="flex flex-col gap-y-3"
|
|
42
42
|
initialValues={{}}
|
|
43
43
|
onSubmit={() => { }}
|
|
44
44
|
onChange={() => { }}
|
package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productReview.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import Review from '@riosst100/pwa-marketplace/src/components/
|
|
2
|
+
import Review from '@riosst100/pwa-marketplace/src/components/ProductReviewItem';
|
|
3
3
|
import { Star1 } from 'iconsax-react';
|
|
4
4
|
import Button from '../../Button';
|
|
5
5
|
import ModalFormReview from './modalFormReview';
|
|
@@ -7,53 +7,171 @@ import ModalFormReview from './modalFormReview';
|
|
|
7
7
|
const productReview = (props) => {
|
|
8
8
|
|
|
9
9
|
const { className } = props;
|
|
10
|
-
|
|
11
10
|
const [open, setOpen] = useState(false);
|
|
11
|
+
const [filter, setFilter] = useState('All');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// Dummy reviews data
|
|
15
|
+
const dummyReviews = {
|
|
16
|
+
__typename: 'ProductRates',
|
|
17
|
+
total_count: 6,
|
|
18
|
+
items: [
|
|
19
|
+
{
|
|
20
|
+
__typename: 'ProductRate',
|
|
21
|
+
id: 1,
|
|
22
|
+
name: 'John Doe',
|
|
23
|
+
date: '18 January 2024',
|
|
24
|
+
rating: 5,
|
|
25
|
+
comment: 'Got item at a great price. Arrived way quicker than expected, extremely well packaged and exactly as described. Highly recommend the seller.'
|
|
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
|
+
{
|
|
44
|
+
__typename: 'ProductRate',
|
|
45
|
+
id: 4,
|
|
46
|
+
name: 'Michael Johnson',
|
|
47
|
+
date: '10 February 2024',
|
|
48
|
+
rating: 3,
|
|
49
|
+
comment: 'Average experience, item as described but nothing special.'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
__typename: 'ProductRate',
|
|
53
|
+
id: 5,
|
|
54
|
+
name: 'Emily Davis',
|
|
55
|
+
date: '15 February 2024',
|
|
56
|
+
rating: 5,
|
|
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.'
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
page_info: {
|
|
69
|
+
__typename: 'SearchResultPageInfo',
|
|
70
|
+
total_pages: 1,
|
|
71
|
+
page_size: 10,
|
|
72
|
+
current_page: 1,
|
|
73
|
+
total_count: 6
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
const totalReviews = dummyReviews.items.length;
|
|
79
|
+
const averageRating = totalReviews > 0
|
|
80
|
+
? (dummyReviews.items.reduce((sum, item) => sum + item.rating, 0) / totalReviews).toFixed(1)
|
|
81
|
+
: 0;
|
|
82
|
+
|
|
83
|
+
const starCounts = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 };
|
|
84
|
+
dummyReviews.items.forEach(item => {
|
|
85
|
+
if (starCounts[item.rating] !== undefined) {
|
|
86
|
+
starCounts[item.rating]++;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const getPercent = (count) => totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0;
|
|
91
|
+
|
|
92
|
+
const filteredReviews = filter === 'All'
|
|
93
|
+
? dummyReviews.items
|
|
94
|
+
: dummyReviews.items.filter(item => item.rating === parseInt(filter));
|
|
12
95
|
|
|
13
96
|
return (
|
|
14
97
|
<>
|
|
15
98
|
<ModalFormReview open={open} setOpen={setOpen} />
|
|
16
99
|
<div className={className}>
|
|
17
|
-
<div className=
|
|
18
|
-
<div className="
|
|
19
|
-
<div className="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<Star1 color='#F7C317' size={14} className='fill-[#F7C317]' />
|
|
33
|
-
</div>
|
|
34
|
-
<div className="w-3.5 h-3.5 relative">
|
|
35
|
-
<Star1 color='#D9D9D9' size={14} className='fill-[#D9D9D9]' />
|
|
100
|
+
<div className="w-full flex items-start xs_flex-col lg_flex-row gap-[30px]">
|
|
101
|
+
<div className="w-full xs_max-w-full lg_max-w-[365px] border border-[#E6E9EA] rounded-md p-6">
|
|
102
|
+
<div className="flex justify-between items-start mb-1">
|
|
103
|
+
<div className="flex flex-col">
|
|
104
|
+
<div className="flex items-center gap-1 mb-1">
|
|
105
|
+
{[...Array(5)].map((_, i) => (
|
|
106
|
+
<div key={i} className="w-4 h-4">
|
|
107
|
+
<Star1
|
|
108
|
+
color={i < Math.round(averageRating) ? '#F7C317' : '#D9D9D9'}
|
|
109
|
+
size={16}
|
|
110
|
+
className={i < Math.round(averageRating) ? 'fill-[#F7C317]' : ''}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
))}
|
|
114
|
+
<span className="ml-1 font-medium text-sm">{averageRating} out of 5</span>
|
|
36
115
|
</div>
|
|
37
116
|
</div>
|
|
38
|
-
|
|
117
|
+
</div>
|
|
118
|
+
<div className="mt-4">
|
|
119
|
+
{[5, 4, 3, 2, 1].map(star => {
|
|
120
|
+
const count = starCounts[star] || 0;
|
|
121
|
+
const percentage = totalReviews ? Math.round((count / totalReviews) * 100) : 0;
|
|
122
|
+
return (
|
|
123
|
+
<div key={star} className="flex items-center mb-2">
|
|
124
|
+
<div className="flex items-center mr-2">
|
|
125
|
+
<Star1 color="#F7C317" size={14} className="fill-[#F7C317]" />
|
|
126
|
+
<span className="text-sm ml-1">{star}</span>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="relative h-[8px] flex-1 bg-[#E4EBF5] rounded-sm mr-2">
|
|
129
|
+
<div
|
|
130
|
+
className="absolute h-[8px] bg-[#FF7A00] rounded-sm"
|
|
131
|
+
style={{width: `${percentage}%`}}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
<span className="text-sm text-right">{percentage}%</span>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div className="flex-1">
|
|
141
|
+
<div className="flex items-center justify-between mb-6">
|
|
142
|
+
<div className="flex items-center">
|
|
143
|
+
<span className="font-medium mr-3">Filter By</span>
|
|
144
|
+
<select
|
|
145
|
+
className="border border-[#E6E9EA] rounded px-3 py-2 text-sm"
|
|
146
|
+
value={filter}
|
|
147
|
+
onChange={e => setFilter(e.target.value)}
|
|
148
|
+
>
|
|
149
|
+
<option value="All">All Stars</option>
|
|
150
|
+
<option value="5">5 Stars</option>
|
|
151
|
+
<option value="4">4 Stars</option>
|
|
152
|
+
<option value="3">3 Stars</option>
|
|
153
|
+
<option value="2">2 Stars</option>
|
|
154
|
+
<option value="1">1 Stars</option>
|
|
155
|
+
</select>
|
|
156
|
+
</div>
|
|
157
|
+
<Button
|
|
158
|
+
priority='low'
|
|
159
|
+
classes={{
|
|
160
|
+
content: 'normal-case font-normal text-base'
|
|
161
|
+
}}
|
|
162
|
+
onClick={() => setOpen(true)}
|
|
163
|
+
>
|
|
164
|
+
Write a review
|
|
165
|
+
</Button>
|
|
166
|
+
</div>
|
|
167
|
+
{/* Reviews List */}
|
|
168
|
+
<div className='space-y-4 mb-6'>
|
|
169
|
+
<Review reviews={{
|
|
170
|
+
...dummyReviews,
|
|
171
|
+
items: filteredReviews
|
|
172
|
+
}} />
|
|
39
173
|
</div>
|
|
40
174
|
</div>
|
|
41
|
-
<Button
|
|
42
|
-
priority='low'
|
|
43
|
-
classes={{
|
|
44
|
-
content: 'normal-case font-normal text-base'
|
|
45
|
-
}}
|
|
46
|
-
onClick={() => setOpen(true)}
|
|
47
|
-
>
|
|
48
|
-
Write a review
|
|
49
|
-
</Button>
|
|
50
|
-
</div>
|
|
51
|
-
<div className='review-list flex flex-col gap-y-4'>
|
|
52
|
-
<Review />
|
|
53
|
-
<Review />
|
|
54
|
-
<Review />
|
|
55
|
-
<Review />
|
|
56
|
-
<Review />
|
|
57
175
|
</div>
|
|
58
176
|
</div>
|
|
59
177
|
</>
|