@riosst100/pwa-marketplace 3.3.0 → 3.3.2

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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/components/AboutUs/aboutUs.css +111 -0
  3. package/src/components/AboutUs/aboutUs.js +6 -2
  4. package/src/components/AgeVerification/ageVerification.js +121 -0
  5. package/src/components/AgeVerification/index.js +1 -2
  6. package/src/components/AgeVerificationModal/ageVerificationModal.js +83 -0
  7. package/src/components/{AgeVerification → AgeVerificationModal}/ageVerificationModal.module.css +77 -10
  8. package/src/components/AgeVerificationModal/index.js +2 -0
  9. package/src/components/Messages/messages.js +0 -10
  10. package/src/components/PrivacyPolicy/index.js +1 -0
  11. package/src/components/PrivacyPolicy/privacyPolicy.css +125 -0
  12. package/src/components/PrivacyPolicy/privacyPolicy.js +13 -0
  13. package/src/components/RFQPage/quoteDetail.js +2 -3
  14. package/src/components/ShowMore/showMore.js +8 -5
  15. package/src/components/TermsOfUse/index.js +1 -0
  16. package/src/components/TermsOfUse/termsOfUse.css +113 -0
  17. package/src/components/TermsOfUse/termsOfUse.js +13 -0
  18. package/src/intercept.js +23 -0
  19. package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategory.js +0 -4
  20. package/src/overwrites/venia-ui/lib/RootComponents/Category/category.js +2 -0
  21. package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +1 -1
  22. package/src/overwrites/venia-ui/lib/RootComponents/Product/product.js +2 -0
  23. package/src/overwrites/venia-ui/lib/components/Adapter/adapter.js +0 -23
  24. package/src/overwrites/venia-ui/lib/components/Footer/footer.js +2 -2
  25. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productFAQ.js +82 -0
  26. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +49 -42
  27. package/src/talons/AgeVerification/ageVerification.gql.js +31 -0
  28. package/src/talons/AgeVerification/useAgeVerification.js +126 -0
  29. package/src/talons/ProductFAQ/productFaq.gql.js +39 -0
  30. package/src/talons/ProductFAQ/useProductFAQ.js +48 -0
  31. package/src/talons/RFQ/useRFQ.js +0 -2
  32. package/src/components/AgeVerification/ageVerificationModal.js +0 -163
  33. package/src/components/AgeVerification/sellerCoupon.js +0 -119
  34. package/src/components/AgeVerification/sellerCouponCheckout.js +0 -164
  35. /package/src/components/{AgeVerification → AgeVerificationModal}/ageVerificationModal.shimmer.js +0 -0
@@ -0,0 +1,126 @@
1
+ import { useQuery, useLazyQuery } from '@apollo/client';
2
+ import { useMemo, useEffect } from 'react';
3
+
4
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
5
+ import DEFAULT_OPERATIONS from './ageVerification.gql';
6
+
7
+ export const useAgeVerification = props => {
8
+ const { pageType, product } = props;
9
+
10
+ const operations = mergeOperations(DEFAULT_OPERATIONS, null);
11
+ const { getAgeVerificationConfigQuery, getMenuTopFiltersAttributesConfigDataQuery } = operations;
12
+
13
+ const { location } = globalThis;
14
+
15
+ const { data: storeConfigData } = useQuery(getMenuTopFiltersAttributesConfigDataQuery, {
16
+ fetchPolicy: 'cache-and-network',
17
+ nextFetchPolicy: 'cache-first'
18
+ });
19
+
20
+ const activeMenuTopFilterAttribute = useMemo(() => {
21
+ if (storeConfigData) {
22
+
23
+ const menuTopFiltersAttributes = storeConfigData.storeConfig.marketplace_menu_topfilters_attributes;
24
+ if (!menuTopFiltersAttributes) {
25
+ return null;
26
+ }
27
+
28
+ const menuTopFiltersAttributeCodes = Object.values(JSON.parse(menuTopFiltersAttributes)).map(
29
+ item => item.attribute_code
30
+ );
31
+
32
+ if (!menuTopFiltersAttributeCodes) {
33
+ return null;
34
+ }
35
+
36
+ let result = null;
37
+
38
+ if (pageType == "CATEGORY") {
39
+ const params = new URLSearchParams(location.search);
40
+ if (!params.size) {
41
+ return null;
42
+ }
43
+
44
+ params.forEach((value, param) => {
45
+ const attrCode = param.replace("[filter]", "");
46
+ if (attrCode && menuTopFiltersAttributeCodes.includes(attrCode)) {
47
+ const attrOptionLabel = value.split(",")[0];
48
+ result = {
49
+ attributeCode: attrCode,
50
+ attributeOption: attrOptionLabel
51
+ };
52
+
53
+ return true;
54
+ }
55
+ });
56
+ }
57
+
58
+ if (pageType == "PRODUCT" && product) {
59
+ product.custom_attributes.map(attr => {
60
+ const attrCode = attr.attribute_metadata?.code;
61
+ if (attrCode && menuTopFiltersAttributeCodes.includes(attrCode)) {
62
+ let label = null;
63
+
64
+ // SELECT / MULTISELECT
65
+ if (attr?.selected_attribute_options?.attribute_option?.length) {
66
+ label = attr.selected_attribute_options.attribute_option.length ?
67
+ attr.selected_attribute_options.attribute_option.map(o => o.label)[0] : '';
68
+ // label = labelArr?.attributeOption?.[0] ?? "";
69
+ }
70
+
71
+ // TEXT / NUMBER
72
+ if (attr?.entered_attribute_value?.value) {
73
+ label = attr.entered_attribute_value.value;
74
+ }
75
+
76
+ result = {
77
+ attributeCode: attrCode,
78
+ attributeOption: label
79
+ };
80
+ }
81
+ });
82
+ }
83
+
84
+ return result;
85
+ }
86
+ }, [storeConfigData, pageType, product]);
87
+
88
+ const [getAgeVerificationConfig, { data, error, loading }] = useLazyQuery(
89
+ getAgeVerificationConfigQuery,
90
+ {
91
+ fetchPolicy: 'cache-and-network',
92
+ nextFetchPolicy: 'cache-first'
93
+ }
94
+ );
95
+
96
+
97
+ useEffect(() => {
98
+ if (activeMenuTopFilterAttribute) {
99
+ getAgeVerificationConfig({
100
+ variables: {
101
+ attributeCode: activeMenuTopFilterAttribute.attributeCode,
102
+ attributeOption: activeMenuTopFilterAttribute.attributeOption
103
+ }
104
+ });
105
+ }
106
+ }, [activeMenuTopFilterAttribute, getAgeVerificationConfig]);
107
+
108
+ const ageVerificationConfig = useMemo(() => {
109
+ if (!data) {
110
+ return null;
111
+ }
112
+
113
+ const getAgeVerificationConfig = data.getAgeVerificationConfig;
114
+ if (!getAgeVerificationConfig) {
115
+ return null;
116
+ }
117
+
118
+ return getAgeVerificationConfig;
119
+ }, [data]);
120
+
121
+ return {
122
+ error,
123
+ loading,
124
+ ageVerificationConfig
125
+ };
126
+ };
@@ -0,0 +1,39 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const GET_PRODUCT_FAQ_LIST_QUERY = gql`
4
+ query getProductFaqList($productId: Int!) {
5
+ productFaqList(product_id: $productId) {
6
+ question_id
7
+ seller_id
8
+ customer_name
9
+ customer_email
10
+ category_id
11
+ store_id
12
+ title
13
+ title_size
14
+ title_color
15
+ title_background
16
+ border_width
17
+ border_color
18
+ border_radius
19
+ body_color
20
+ body_background
21
+ margin_bottom
22
+ margin_left
23
+ icon
24
+ answer
25
+ status
26
+ position
27
+ creation_time
28
+ update_time
29
+ meta_keywords
30
+ meta_description
31
+ page_title
32
+ }
33
+ }
34
+ `;
35
+
36
+ export default {
37
+ getProductFaqListQuery: GET_PRODUCT_FAQ_LIST_QUERY,
38
+ };
39
+
@@ -0,0 +1,48 @@
1
+ import { useLazyQuery } from '@apollo/client';
2
+ import { useCallback } from 'react';
3
+
4
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
5
+ import DEFAULT_OPERATIONS from './productFaq.gql';
6
+
7
+ export const useProductFAQ = props => {
8
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props?.operations);
9
+ const { getProductFaqListQuery } = operations;
10
+
11
+ const [fetchProductFaqList, productFaqListState] = useLazyQuery(
12
+ getProductFaqListQuery,
13
+ {
14
+ fetchPolicy: 'network-only',
15
+ nextFetchPolicy: 'cache-first',
16
+ notifyOnNetworkStatusChange: true,
17
+ errorPolicy: 'all'
18
+ }
19
+ );
20
+
21
+ const loadProductFaqList = useCallback(
22
+ variables => {
23
+ const id = variables && variables.productId;
24
+ const parsedId = typeof id === 'number' ? id : parseInt(id, 10);
25
+ if (isNaN(parsedId)) {
26
+ return Promise.resolve(null);
27
+ }
28
+ return fetchProductFaqList({ variables: { productId: parsedId } });
29
+ },
30
+ [fetchProductFaqList]
31
+ );
32
+
33
+ return {
34
+ loadProductFaqList,
35
+ productFaqListState,
36
+ startPolling:
37
+ productFaqListState && productFaqListState.startPolling
38
+ ? productFaqListState.startPolling
39
+ : undefined,
40
+ stopPolling:
41
+ productFaqListState && productFaqListState.stopPolling
42
+ ? productFaqListState.stopPolling
43
+ : undefined
44
+ };
45
+ };
46
+
47
+ export default useProductFAQ;
48
+
@@ -10,8 +10,6 @@ export const useRFQ = props => {
10
10
  const { pathname } = useLocation();
11
11
  const rfqQuoteId = pathname.split('/')[2];
12
12
 
13
- console.log('rfqQuoteId',rfqQuoteId)
14
-
15
13
  const operations = mergeOperations(DEFAULT_OPERATIONS, props?.operations);
16
14
 
17
15
  const {
@@ -1,163 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import ReactDOM from 'react-dom';
3
- import modalClasses from '@riosst100/pwa-marketplace/src/components/AgeVerification/ageVerificationModal.module.css';
4
- // import { AgeVerificationModalShimmer } from '@riosst100/pwa-marketplace/src/components/SellerCoupon';
5
-
6
- // Reuse day diff logic
7
- const dayDiff = (toDate) => {
8
- if (!toDate) return null;
9
- const end = new Date(toDate);
10
- if (Number.isNaN(end.getTime())) return null;
11
- const now = new Date();
12
- const diffMs = end.setHours(23, 59, 59, 999) - now.getTime();
13
- const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
14
- return diffDays;
15
- };
16
-
17
- // Simple portal root fallback
18
- const getPortalRoot = () => {
19
- let root = document.getElementById('seller-coupon-portal');
20
- if (!root) {
21
- root = document.createElement('div');
22
- root.id = 'seller-coupon-portal';
23
- document.body.appendChild(root);
24
- }
25
- return root;
26
- };
27
-
28
- const AgeVerificationModal = ({
29
- couponData,
30
- couponError,
31
- setCouponModalOpen,
32
- couponModalOpen,
33
- couponLoading,
34
- isCopyAction,
35
- triggerLabel = 'View Seller Coupons',
36
- onSelectCoupon,
37
- onTriggerRender, // optional custom trigger renderer
38
- autoOpen = false, // auto open modal when mounted
39
- closeOnClaim = true // close after claiming
40
- }) => {
41
- // const [open, setOpen] = useState(false);
42
- const [copied, setCopied] = useState(null);
43
-
44
- const items = couponData?.sellerCoupons?.items || [];
45
-
46
- const handleOpen = () => setCouponModalOpen(true);
47
- const handleClose = () => setCouponModalOpen(false);
48
-
49
- useEffect(() => {
50
- if (!couponModalOpen) return;
51
- const onKey = (e) => {
52
- if (e.key === 'Escape') {
53
- handleClose();
54
- }
55
- };
56
- document.addEventListener('keydown', onKey);
57
- return () => document.removeEventListener('keydown', onKey);
58
- }, [couponModalOpen]);
59
-
60
- const handleClaim = async (item) => {
61
- try {
62
- if (navigator?.clipboard?.writeText) {
63
- await navigator.clipboard.writeText(item.code);
64
- }
65
- } catch (_) {
66
- /* ignore */
67
- }
68
- console.log('item.code',item.code)
69
- setCopied(item.code);
70
- setTimeout(() => setCopied(null), 2000);
71
- if (!isCopyAction) {
72
- if (onSelectCoupon) onSelectCoupon(item);
73
- if (closeOnClaim) handleClose();
74
- }
75
- };
76
-
77
- // Auto open behavior
78
- useEffect(() => {
79
- if (autoOpen && !couponModalOpen) {
80
- setCouponModalOpen(true);
81
- }
82
- }, [autoOpen, couponModalOpen]);
83
-
84
- console.log('copied',copied)
85
-
86
- const modal = couponModalOpen ? (
87
- <div className={modalClasses.overlay} role="dialog" aria-modal="true" aria-label="Seller coupons list">
88
- <div className={modalClasses.backdrop} onClick={handleClose} />
89
- <div className={modalClasses.dialog}>
90
- <div className={modalClasses.header}>
91
- <h2 className={modalClasses.title}>{isCopyAction ? 'Store Coupons' : 'Apply Coupon'}</h2>
92
- <button type="button" className={modalClasses.closeBtn} onClick={handleClose} aria-label="Close coupon modal">×</button>
93
- </div>
94
- <div className={modalClasses.body}>
95
- {/* {couponLoading && <AgeVerificationModalShimmer />} */}
96
- {!couponLoading && couponError && <p className={modalClasses.metaText}>Failed to load coupons.</p>}
97
- {!couponLoading && !couponError && !items.length && <p className={modalClasses.metaText}>No coupons available.</p>}
98
- {!couponLoading && !couponError && items.length > 0 && (
99
- <div className={modalClasses.stack}>
100
- {items.map(item => {
101
- const key = item.couponcode_id || item.coupon_id || item.code;
102
- const daysLeft = dayDiff(item.to_date);
103
- const expiryLabel = daysLeft === null
104
- ? 'No expiry'
105
- : daysLeft <= 0
106
- ? 'Ends today'
107
- : `Ends in ${daysLeft} day${daysLeft > 1 ? 's' : ''}`;
108
- const title = item.description || item.name || `Discount ${item.discount_amount || ''}`;
109
- return (
110
- <div key={key} className={modalClasses.card} role="group" aria-label={`Coupon ${item.code}`}>
111
- <div className={modalClasses.perf} aria-hidden="true" />
112
- <div className={modalClasses.content}>
113
- <div className={modalClasses.title}>{title}</div>
114
- <div className={modalClasses.subtext}>Code: <span className={modalClasses.code}>{item.code}</span></div>
115
- <div className={modalClasses.expiry}>{expiryLabel}</div>
116
- </div>
117
- <div className={modalClasses.divider} aria-hidden="true" />
118
- <div className={modalClasses.actions}>
119
- {isCopyAction ? <button
120
- type="button"
121
- className={copied === item.code ? modalClasses.claimedBtn : modalClasses.claimBtn}
122
- onClick={() => handleClaim(item)}
123
- aria-label={`Claim coupon ${item.code}`}
124
- >
125
- {copied === item.code ? 'Copied' : 'Claim'}
126
- </button> :
127
- <button
128
- type="button"
129
- className={copied === item.code ? modalClasses.claimedBtn : modalClasses.claimBtn}
130
- onClick={() => handleClaim(item)}
131
- aria-label={`Apply coupon ${item.code}`}
132
- >
133
- {copied === item.code ? 'Applied' : 'Apply'}
134
- </button>}
135
- </div>
136
- </div>
137
- );
138
- })}
139
- </div>
140
- )}
141
- </div>
142
- </div>
143
- </div>
144
- ) : null;
145
-
146
- const trigger = onTriggerRender
147
- ? onTriggerRender({ couponModalOpen, setCouponModalOpen, handleOpen })
148
- : (
149
- <button
150
- type="button"
151
- className={modalClasses.triggerBtn}
152
- onClick={handleOpen}
153
- aria-haspopup="dialog"
154
- aria-expanded={couponModalOpen}
155
- >
156
- {triggerLabel}
157
- </button>
158
- );
159
-
160
- return <>{onTriggerRender ? trigger : trigger}{couponModalOpen ? ReactDOM.createPortal(modal, getPortalRoot()) : null}</>;
161
- };
162
-
163
- export default AgeVerificationModal;
@@ -1,119 +0,0 @@
1
- import React, { useMemo, useState, useCallback } from 'react';
2
- import classes from '@riosst100/pwa-marketplace/src/components/AgeVerification/sellerCoupon.module.css';
3
- import SellerCouponCheckout from '@riosst100/pwa-marketplace/src/components/SellerCoupon/sellerCouponCheckout';
4
-
5
- const dayDiff = (toDate) => {
6
- if (!toDate) return null;
7
- const end = new Date(toDate);
8
- if (Number.isNaN(end.getTime())) return null;
9
- const now = new Date();
10
- const diffMs = end.setHours(23, 59, 59, 999) - now.getTime();
11
- const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
12
- return diffDays;
13
- };
14
-
15
- const SellerCoupon = ({ couponData, couponError, couponLoading }) => {
16
- if (couponLoading) {
17
- return '';
18
- // return <div className={classes.container}><p className={classes.metaText}>Loading coupons...</p></div>;
19
- }
20
-
21
- if (couponError) {
22
- return '';
23
- // return <div className={classes.container}><p className={classes.metaText}>Failed to load coupons.</p></div>;
24
- }
25
-
26
- const items = couponData?.sellerCoupons?.items || [];
27
-
28
- if (!items.length) {
29
- return '';
30
- // return <div className={classes.container}><p className={classes.metaText}>No coupons available.</p></div>;
31
- }
32
-
33
- const [copied, setCopied] = useState(null);
34
- const [couponModalOpen, setCouponModalOpen] = useState(false);
35
-
36
- const handleClaim = async (code) => {
37
- try {
38
- if (navigator?.clipboard?.writeText) {
39
- await navigator.clipboard.writeText(code);
40
- }
41
- } catch (_) {
42
- // ignore clipboard issues
43
- }
44
- setCopied(code);
45
- setTimeout(() => setCopied(null), 2000);
46
- };
47
-
48
- const handleViewCoupons = useCallback(() => {
49
- setCouponModalOpen(true);
50
- }, [setCouponModalOpen]);
51
-
52
- return (
53
- <section className={classes.container} aria-label="Seller coupons">
54
- <div className={classes.scroller}>
55
- {items.slice(0, 3).map(item => {
56
- const key = item.couponcode_id || item.coupon_id || item.code;
57
- const daysLeft = dayDiff(item.to_date);
58
- const expiryLabel = daysLeft === null
59
- ? 'No expiry'
60
- : daysLeft <= 0
61
- ? 'Ends today'
62
- : `Ends in ${daysLeft} day${daysLeft > 1 ? 's' : ''}`;
63
-
64
- const title = item.description || item.name || `Discount ${item.discount_amount || ''}`;
65
-
66
- return (
67
- <div className={classes.card} key={key} role="group" aria-label={`Coupon ${item.code}`}>
68
- <div className={classes.perf} aria-hidden="true" />
69
- <div className={classes.content}>
70
- <div className={classes.title}>{title}</div>
71
- <div className={classes.subtext}>Code: <span className={classes.code}>{item.code}</span></div>
72
- <div className={classes.expiry}>{expiryLabel}</div>
73
- </div>
74
- <div className={classes.divider} aria-hidden="true" />
75
- <div className={classes.actions}>
76
- <button
77
- type="button"
78
- className={copied === item.code ? classes.claimedBtn : classes.claimBtn}
79
- onClick={() => handleClaim(item.code)}
80
- aria-label={`Claim coupon ${item.code}`}
81
- >
82
- {copied === item.code ? 'Copied' : 'Claim'}
83
- </button>
84
- </div>
85
- </div>
86
- );
87
- })}
88
- {couponModalOpen && (
89
- <SellerCouponCheckout
90
- couponData={couponData}
91
- couponLoading={couponLoading}
92
- couponError={couponError}
93
- autoOpen={true}
94
- closeOnClaim={true}
95
- couponModalOpen={couponModalOpen}
96
- setCouponModalOpen={setCouponModalOpen}
97
- onSelectCoupon={handleClaim}
98
- onTriggerRender={() => null}
99
- isCopyAction={true}
100
- />
101
- )}
102
- <div className={classes.viewAllCard} role="group" aria-label="View All Coupons">
103
- <div className={classes.actions}>
104
- <button
105
- type="button"
106
- className={classes.viewAllBtn}
107
- onClick={handleViewCoupons}
108
- aria-label="View All Coupons"
109
- >
110
- View All Coupons
111
- </button>
112
- </div>
113
- </div>
114
- </div>
115
- </section>
116
- );
117
- };
118
-
119
- export default SellerCoupon;
@@ -1,164 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import ReactDOM from 'react-dom';
3
- import listClasses from '@riosst100/pwa-marketplace/src/components/AgeVerification/sellerCoupon.module.css';
4
- import modalClasses from '@riosst100/pwa-marketplace/src/components/AgeVerification/sellerCouponCheckout.module.css';
5
- import { SellerCouponCheckoutShimmer } from '@riosst100/pwa-marketplace/src/components/SellerCoupon';
6
-
7
- // Reuse day diff logic
8
- const dayDiff = (toDate) => {
9
- if (!toDate) return null;
10
- const end = new Date(toDate);
11
- if (Number.isNaN(end.getTime())) return null;
12
- const now = new Date();
13
- const diffMs = end.setHours(23, 59, 59, 999) - now.getTime();
14
- const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
15
- return diffDays;
16
- };
17
-
18
- // Simple portal root fallback
19
- const getPortalRoot = () => {
20
- let root = document.getElementById('seller-coupon-portal');
21
- if (!root) {
22
- root = document.createElement('div');
23
- root.id = 'seller-coupon-portal';
24
- document.body.appendChild(root);
25
- }
26
- return root;
27
- };
28
-
29
- const SellerCouponCheckout = ({
30
- couponData,
31
- couponError,
32
- setCouponModalOpen,
33
- couponModalOpen,
34
- couponLoading,
35
- isCopyAction,
36
- triggerLabel = 'View Seller Coupons',
37
- onSelectCoupon,
38
- onTriggerRender, // optional custom trigger renderer
39
- autoOpen = false, // auto open modal when mounted
40
- closeOnClaim = true // close after claiming
41
- }) => {
42
- // const [open, setOpen] = useState(false);
43
- const [copied, setCopied] = useState(null);
44
-
45
- const items = couponData?.sellerCoupons?.items || [];
46
-
47
- const handleOpen = () => setCouponModalOpen(true);
48
- const handleClose = () => setCouponModalOpen(false);
49
-
50
- useEffect(() => {
51
- if (!couponModalOpen) return;
52
- const onKey = (e) => {
53
- if (e.key === 'Escape') {
54
- handleClose();
55
- }
56
- };
57
- document.addEventListener('keydown', onKey);
58
- return () => document.removeEventListener('keydown', onKey);
59
- }, [couponModalOpen]);
60
-
61
- const handleClaim = async (item) => {
62
- try {
63
- if (navigator?.clipboard?.writeText) {
64
- await navigator.clipboard.writeText(item.code);
65
- }
66
- } catch (_) {
67
- /* ignore */
68
- }
69
- console.log('item.code',item.code)
70
- setCopied(item.code);
71
- setTimeout(() => setCopied(null), 2000);
72
- if (!isCopyAction) {
73
- if (onSelectCoupon) onSelectCoupon(item);
74
- if (closeOnClaim) handleClose();
75
- }
76
- };
77
-
78
- // Auto open behavior
79
- useEffect(() => {
80
- if (autoOpen && !couponModalOpen) {
81
- setCouponModalOpen(true);
82
- }
83
- }, [autoOpen, couponModalOpen]);
84
-
85
- console.log('copied',copied)
86
-
87
- const modal = couponModalOpen ? (
88
- <div className={modalClasses.overlay} role="dialog" aria-modal="true" aria-label="Seller coupons list">
89
- <div className={modalClasses.backdrop} onClick={handleClose} />
90
- <div className={modalClasses.dialog}>
91
- <div className={modalClasses.header}>
92
- <h2 className={modalClasses.title}>{isCopyAction ? 'Store Coupons' : 'Apply Coupon'}</h2>
93
- <button type="button" className={modalClasses.closeBtn} onClick={handleClose} aria-label="Close coupon modal">×</button>
94
- </div>
95
- <div className={modalClasses.body}>
96
- {couponLoading && <SellerCouponCheckoutShimmer />}
97
- {!couponLoading && couponError && <p className={listClasses.metaText}>Failed to load coupons.</p>}
98
- {!couponLoading && !couponError && !items.length && <p className={listClasses.metaText}>No coupons available.</p>}
99
- {!couponLoading && !couponError && items.length > 0 && (
100
- <div className={modalClasses.stack}>
101
- {items.map(item => {
102
- const key = item.couponcode_id || item.coupon_id || item.code;
103
- const daysLeft = dayDiff(item.to_date);
104
- const expiryLabel = daysLeft === null
105
- ? 'No expiry'
106
- : daysLeft <= 0
107
- ? 'Ends today'
108
- : `Ends in ${daysLeft} day${daysLeft > 1 ? 's' : ''}`;
109
- const title = item.description || item.name || `Discount ${item.discount_amount || ''}`;
110
- return (
111
- <div key={key} className={listClasses.card} role="group" aria-label={`Coupon ${item.code}`}>
112
- <div className={listClasses.perf} aria-hidden="true" />
113
- <div className={listClasses.content}>
114
- <div className={listClasses.title}>{title}</div>
115
- <div className={listClasses.subtext}>Code: <span className={listClasses.code}>{item.code}</span></div>
116
- <div className={listClasses.expiry}>{expiryLabel}</div>
117
- </div>
118
- <div className={listClasses.divider} aria-hidden="true" />
119
- <div className={listClasses.actions}>
120
- {isCopyAction ? <button
121
- type="button"
122
- className={copied === item.code ? listClasses.claimedBtn : listClasses.claimBtn}
123
- onClick={() => handleClaim(item)}
124
- aria-label={`Claim coupon ${item.code}`}
125
- >
126
- {copied === item.code ? 'Copied' : 'Claim'}
127
- </button> :
128
- <button
129
- type="button"
130
- className={copied === item.code ? listClasses.claimedBtn : listClasses.claimBtn}
131
- onClick={() => handleClaim(item)}
132
- aria-label={`Apply coupon ${item.code}`}
133
- >
134
- {copied === item.code ? 'Applied' : 'Apply'}
135
- </button>}
136
- </div>
137
- </div>
138
- );
139
- })}
140
- </div>
141
- )}
142
- </div>
143
- </div>
144
- </div>
145
- ) : null;
146
-
147
- const trigger = onTriggerRender
148
- ? onTriggerRender({ couponModalOpen, setCouponModalOpen, handleOpen })
149
- : (
150
- <button
151
- type="button"
152
- className={modalClasses.triggerBtn}
153
- onClick={handleOpen}
154
- aria-haspopup="dialog"
155
- aria-expanded={couponModalOpen}
156
- >
157
- {triggerLabel}
158
- </button>
159
- );
160
-
161
- return <>{onTriggerRender ? trigger : trigger}{couponModalOpen ? ReactDOM.createPortal(modal, getPortalRoot()) : null}</>;
162
- };
163
-
164
- export default SellerCouponCheckout;