@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.
- package/package.json +1 -1
- package/src/components/AboutUs/aboutUs.css +111 -0
- package/src/components/AboutUs/aboutUs.js +6 -2
- package/src/components/AgeVerification/ageVerification.js +121 -0
- package/src/components/AgeVerification/index.js +1 -2
- package/src/components/AgeVerificationModal/ageVerificationModal.js +83 -0
- package/src/components/{AgeVerification → AgeVerificationModal}/ageVerificationModal.module.css +77 -10
- package/src/components/AgeVerificationModal/index.js +2 -0
- package/src/components/Messages/messages.js +0 -10
- package/src/components/PrivacyPolicy/index.js +1 -0
- package/src/components/PrivacyPolicy/privacyPolicy.css +125 -0
- package/src/components/PrivacyPolicy/privacyPolicy.js +13 -0
- package/src/components/RFQPage/quoteDetail.js +2 -3
- package/src/components/ShowMore/showMore.js +8 -5
- package/src/components/TermsOfUse/index.js +1 -0
- package/src/components/TermsOfUse/termsOfUse.css +113 -0
- package/src/components/TermsOfUse/termsOfUse.js +13 -0
- package/src/intercept.js +23 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategory.js +0 -4
- package/src/overwrites/venia-ui/lib/RootComponents/Category/category.js +2 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +1 -1
- package/src/overwrites/venia-ui/lib/RootComponents/Product/product.js +2 -0
- package/src/overwrites/venia-ui/lib/components/Adapter/adapter.js +0 -23
- package/src/overwrites/venia-ui/lib/components/Footer/footer.js +2 -2
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/productFAQ.js +82 -0
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +49 -42
- package/src/talons/AgeVerification/ageVerification.gql.js +31 -0
- package/src/talons/AgeVerification/useAgeVerification.js +126 -0
- package/src/talons/ProductFAQ/productFaq.gql.js +39 -0
- package/src/talons/ProductFAQ/useProductFAQ.js +48 -0
- package/src/talons/RFQ/useRFQ.js +0 -2
- package/src/components/AgeVerification/ageVerificationModal.js +0 -163
- package/src/components/AgeVerification/sellerCoupon.js +0 -119
- package/src/components/AgeVerification/sellerCouponCheckout.js +0 -164
- /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
|
+
|
package/src/talons/RFQ/useRFQ.js
CHANGED
|
@@ -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;
|
/package/src/components/{AgeVerification → AgeVerificationModal}/ageVerificationModal.shimmer.js
RENAMED
|
File without changes
|