@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.
Files changed (44) hide show
  1. package/i18n/en_US.json +1 -1
  2. package/i18n/id_ID.json +1 -1
  3. package/package.json +1 -1
  4. package/src/components/FilterTop/FilterBlockList/filterBlockList.js +2 -0
  5. package/src/components/FilterTop/FilterBlockList/filterTopItemGroup.js +20 -2
  6. package/src/components/FilterTop/filterTop.js +4 -1
  7. package/src/components/FilterTop/filterTopBlock.js +2 -0
  8. package/src/components/PaymentMethod/PaypalExpress/paypalExpress.js +3 -2
  9. package/src/components/PaymentMethod/Xendit/xendit.js +53 -0
  10. package/src/components/ProductReviewItem/index.js +1 -0
  11. package/src/components/ProductReviewItem/productReviewItem.js +67 -0
  12. package/src/components/SellerReview/sellerReview.js +21 -25
  13. package/src/components/VerifyEmailPage/index.js +1 -0
  14. package/src/components/VerifyEmailPage/verifyEmail.js +79 -0
  15. package/src/components/VerifyEmailPage/verifyEmail.module.css +71 -0
  16. package/src/intercept.js +8 -0
  17. package/src/overwrites/peregrine/lib/talons/CheckoutPage/checkoutPage.extended.gql.js +88 -0
  18. package/src/overwrites/peregrine/lib/talons/CheckoutPage/useCheckoutPage.js +124 -13
  19. package/src/overwrites/peregrine/lib/talons/CheckoutPage/xendit.gql.js +15 -0
  20. package/src/overwrites/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js +12 -10
  21. package/src/overwrites/venia-ui/lib/components/Checkbox/checkbox.module.css +2 -0
  22. package/src/overwrites/venia-ui/lib/components/CheckoutPage/BillingAddress/billingAddress.module.css +9 -1
  23. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/item.js +1 -1
  24. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/item.module.css +7 -2
  25. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/itemsReview.module.css +3 -2
  26. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/showAllButton.js +1 -1
  27. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ItemsReview/showAllButton.module.css +4 -3
  28. package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.js +68 -44
  29. package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderConfirmationPage/orderConfirmationPage.module.css +102 -5
  30. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.js +4 -8
  31. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.module.css +6 -0
  32. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethodCollection.js +6 -20
  33. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js +92 -4
  34. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css +13 -3
  35. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/summary.module.css +3 -1
  36. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.js +3 -1
  37. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingMethod/shippingRadios.module.css +51 -2
  38. package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.js +11 -28
  39. package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.module.css +16 -4
  40. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/modalFormReview.js +2 -2
  41. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productReview.js +156 -38
  42. package/src/talons/FilterTop/filterTop.gql.js +1 -0
  43. package/src/talons/FilterTop/useFilterTop.js +2 -1
  44. package/src/talons/PaymentMethod/PaypalExpress/usePaypalExpress.js +5 -1
@@ -1,23 +1,9 @@
1
1
  /**
2
- * This file is augmented at build time using the @magento/venia-ui build
3
- * target "checkoutPagePaymentTypes", which allows third-party modules to
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
- * @see [Payment definition object]{@link PaymentDefinition}
7
- */
8
- export default {};
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';
@@ -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
- console.log('payments', payments)
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={title}
147
+ label={displayTitle}
66
148
  value={code}
67
149
  classes={{
68
150
  label: classes.radio_label
69
151
  }}
70
152
  checked={isSelected}
71
- onChange={handlePaymentMethodSelection}
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
+ }
@@ -1,7 +1,7 @@
1
1
  .root {
2
2
  composes: gap-xs from global;
3
3
  composes: grid from global;
4
- composes: p-md from global;
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
 
@@ -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 => {
@@ -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-autoAuto from global;
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
- const reviewOrderButton =
248
- checkoutStep === CHECKOUT_STEP.PAYMENT ? (
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
- const itemsReview =
269
- checkoutStep === CHECKOUT_STEP.REVIEW ? (
270
- <div className={classes.items_review_container}>
271
- <ItemsReview />
272
- </div>
273
- ) : null;
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 === CHECKOUT_STEP.REVIEW ? (
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-[9rem] from global;
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
  }
@@ -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-10'>
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-6"
41
+ className="flex flex-col gap-y-3"
42
42
  initialValues={{}}
43
43
  onSubmit={() => { }}
44
44
  onChange={() => { }}
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react'
2
- import Review from '@riosst100/pwa-marketplace/src/components/SellerReviewItem';
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='review-summary flex justify-between mb-[30px] pb-[30px] border-b border-gray-100'>
18
- <div className="justify-start items-end gap-[15px] inline-flex">
19
- <div className="text-center text-zinc-900 text-[40px] font-medium leading-10">4.7</div>
20
- <div className="flex-col justify-start items-start gap-[9px] inline-flex">
21
- <div className="justify-start items-start gap-1.5 inline-flex">
22
- <div className="w-3.5 h-3.5 relative">
23
- <Star1 color='#F7C317' size={14} className='fill-[#F7C317]' />
24
- </div>
25
- <div className="w-3.5 h-3.5 relative">
26
- <Star1 color='#F7C317' size={14} className='fill-[#F7C317]' />
27
- </div>
28
- <div className="w-3.5 h-3.5 relative">
29
- <Star1 color='#F7C317' size={14} className='fill-[#F7C317]' />
30
- </div>
31
- <div className="w-3.5 h-3.5 relative">
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
- <div className="text-center text-zinc-900 text-sm font-normal leading-[18px]">(26 Reviews)</div>
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
  </>
@@ -7,6 +7,7 @@ export const GET_CUSTOM_FILTERS = gql`
7
7
  filters {
8
8
  label
9
9
  count
10
+ show_all_btn
10
11
  attribute_code
11
12
  options {
12
13
  label