@riosst100/pwa-marketplace 3.0.0 → 3.0.3
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/componentOverrideMapping.js +2 -0
- package/src/components/CheckoutHeader/checkoutHeader.js +162 -0
- package/src/components/CheckoutHeader/checkoutHeader.module.css +171 -0
- package/src/components/CheckoutHeader/index.js +1 -0
- package/src/components/FavoriteSeller/AddToListButton/addToListButton.module.css +2 -2
- package/src/components/FilterTop/FilterBlockList/filterTopItem.module.css +2 -0
- package/src/components/OrderDetail/components/itemsOrdered.js +6 -1
- package/src/components/RMAPage/components/productItem.css +15 -0
- package/src/components/RMAPage/components/productItem.js +43 -13
- package/src/components/RMAPage/components/productItem.module.css +15 -0
- package/src/components/SellerCoupon/index.js +1 -0
- package/src/components/SellerCoupon/sellerCoupon.js +84 -0
- package/src/components/SellerCoupon/sellerCoupon.module.css +110 -0
- package/src/components/SellerCoupon/sellerCouponCheckout.js +147 -0
- package/src/components/SellerCoupon/sellerCouponCheckout.module.css +85 -0
- package/src/components/SellerDetail/sellerDetail.js +10 -2
- package/src/components/SellerProducts/sellerProducts.js +4 -2
- package/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/usePriceSummary.js +10 -1
- package/src/overwrites/peregrine/lib/talons/CartPage/useCartPage.js +121 -0
- package/src/overwrites/peregrine/lib/talons/CheckoutPage/checkoutPage.extended.gql.js +17 -1
- package/src/overwrites/peregrine/lib/talons/Header/useCartTrigger.js +82 -0
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +17 -1
- package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +11 -11
- package/src/overwrites/venia-ui/lib/components/CartPage/PriceSummary/priceSummary.js +24 -6
- package/src/overwrites/venia-ui/lib/components/CartPage/ProductListing/productListing.js +6 -2
- package/src/overwrites/venia-ui/lib/components/CartPage/ProductListingBySeller/productListingBySeller.js +3 -1
- package/src/overwrites/venia-ui/lib/components/CartPage/cartPage.js +20 -4
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderSummary/orderSummary.js +1 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.js +1 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js +1 -1
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/PriceAdjustments/priceAdjustments.js +74 -6
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingInformation/shippingInformation.js +3 -3
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.js +53 -2
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.module.css +1 -1
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterItemRadio.js +1 -2
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterItemRadioGroup.js +10 -27
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/subFilterItemRadioGroup.js +1 -1
- package/src/overwrites/venia-ui/lib/components/Footer/footer.js +14 -4
- package/src/overwrites/venia-ui/lib/components/Footer/footer.module.css +16 -4
- package/src/overwrites/venia-ui/lib/components/Gallery/item.js +9 -8
- package/src/overwrites/venia-ui/lib/components/Header/accountTrigger.js +3 -3
- package/src/overwrites/venia-ui/lib/components/Header/cartTrigger.js +9 -6
- package/src/overwrites/venia-ui/lib/components/Header/cartTrigger.module.css +1 -1
- package/src/overwrites/venia-ui/lib/components/Header/header.js +2 -0
- package/src/overwrites/venia-ui/lib/components/Header/wishlistTrigger.js +4 -4
- package/src/overwrites/venia-ui/lib/components/Main/main.js +9 -4
- package/src/overwrites/venia-ui/lib/components/Main/main.module.css +2 -2
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +100 -72
- package/src/overwrites/venia-ui/lib/components/ToastContainer/toast.module.css +3 -3
- package/src/talons/Header/useCartTrigger.js +82 -0
- package/src/talons/Seller/seller.gql.js +42 -1
- package/src/talons/Seller/useSeller.js +25 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.container{
|
|
2
|
+
margin:16px 0;
|
|
3
|
+
padding:0 8px;
|
|
4
|
+
}
|
|
5
|
+
.heading{
|
|
6
|
+
font-size:18px;
|
|
7
|
+
font-weight:600;
|
|
8
|
+
margin:0 0 8px;
|
|
9
|
+
color:#111
|
|
10
|
+
}
|
|
11
|
+
.metaText{
|
|
12
|
+
color:#666;
|
|
13
|
+
margin:0;
|
|
14
|
+
}
|
|
15
|
+
.scroller{
|
|
16
|
+
display:flex;
|
|
17
|
+
gap:16px;
|
|
18
|
+
overflow-x:auto;
|
|
19
|
+
padding:8px 4px 12px;
|
|
20
|
+
scroll-snap-type:x mandatory;
|
|
21
|
+
}
|
|
22
|
+
.card{
|
|
23
|
+
position:relative;
|
|
24
|
+
background:#fff8f1;
|
|
25
|
+
border:1px solid #ffb891;
|
|
26
|
+
border-left:none;
|
|
27
|
+
min-width:260px;
|
|
28
|
+
max-width:320px;
|
|
29
|
+
display:flex;
|
|
30
|
+
align-items:stretch;
|
|
31
|
+
padding:0 12px 0 24px;
|
|
32
|
+
scroll-snap-align:start;
|
|
33
|
+
overflow:hidden;
|
|
34
|
+
--border-color:#ffb891;
|
|
35
|
+
}
|
|
36
|
+
.perf{
|
|
37
|
+
position:absolute;
|
|
38
|
+
inset:0 auto 0 0;
|
|
39
|
+
width:4px;
|
|
40
|
+
background:#fff;
|
|
41
|
+
/* border-top-left-radius:8px;
|
|
42
|
+
border-bottom-left-radius:8px; */
|
|
43
|
+
pointer-events:none;
|
|
44
|
+
}
|
|
45
|
+
.card::before{
|
|
46
|
+
content:"";
|
|
47
|
+
position:absolute;
|
|
48
|
+
top:0;bottom:0;left:0px;
|
|
49
|
+
z-index:9;
|
|
50
|
+
width:1px;
|
|
51
|
+
background:repeating-linear-gradient(to bottom,#ffb891 0,#ffb891 0px,transparent 6px,transparent 10px);
|
|
52
|
+
}
|
|
53
|
+
.perf:before{
|
|
54
|
+
content:"";
|
|
55
|
+
position:absolute;
|
|
56
|
+
inset:0;
|
|
57
|
+
background:
|
|
58
|
+
radial-gradient(circle at 0 .375rem, transparent 0, transparent .1875rem, var(--border-color) .1875rem, var(--border-color) .25rem, transparent .25rem) repeat-y;
|
|
59
|
+
background-size:1rem 0.66rem;
|
|
60
|
+
background-position:0 0;
|
|
61
|
+
}
|
|
62
|
+
.content{
|
|
63
|
+
flex:1 1 auto;
|
|
64
|
+
padding:10px 12px 10px 0;
|
|
65
|
+
display:flex;
|
|
66
|
+
flex-direction:column;
|
|
67
|
+
justify-content:center;
|
|
68
|
+
}
|
|
69
|
+
.title{
|
|
70
|
+
color:#f76b1c;
|
|
71
|
+
font-weight:700;
|
|
72
|
+
margin-bottom:4px;
|
|
73
|
+
}
|
|
74
|
+
.subtext{
|
|
75
|
+
color:#444;
|
|
76
|
+
font-size:13px;
|
|
77
|
+
margin-bottom:4px;
|
|
78
|
+
}
|
|
79
|
+
.code{
|
|
80
|
+
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
81
|
+
font-weight:600;
|
|
82
|
+
}
|
|
83
|
+
.expiry{
|
|
84
|
+
color:#a94442;
|
|
85
|
+
font-size:12px;
|
|
86
|
+
}
|
|
87
|
+
.actions{
|
|
88
|
+
display:flex;
|
|
89
|
+
align-items:center;
|
|
90
|
+
justify-content:center;
|
|
91
|
+
padding:10px 0;
|
|
92
|
+
}
|
|
93
|
+
.claimBtn{
|
|
94
|
+
background: #f76b1c;
|
|
95
|
+
color:#fff;
|
|
96
|
+
border:none;
|
|
97
|
+
border-radius:6px;
|
|
98
|
+
padding:8px 18px;
|
|
99
|
+
font-weight:600;
|
|
100
|
+
cursor:pointer;
|
|
101
|
+
}
|
|
102
|
+
.claimBtn:hover{
|
|
103
|
+
background:#f26313;
|
|
104
|
+
}
|
|
105
|
+
.divider{
|
|
106
|
+
width:1px;
|
|
107
|
+
background:repeating-linear-gradient(to bottom,#ffb891 0,#ffb891 5px,transparent 5px,transparent 10px);
|
|
108
|
+
align-self:stretch;
|
|
109
|
+
margin:0 12px 0 4px;
|
|
110
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import listClasses from './sellerCoupon.module.css';
|
|
4
|
+
import modalClasses from './sellerCouponCheckout.module.css';
|
|
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 SellerCouponCheckout = ({
|
|
29
|
+
couponData,
|
|
30
|
+
couponError,
|
|
31
|
+
couponLoading,
|
|
32
|
+
triggerLabel = 'View Seller Coupons',
|
|
33
|
+
onSelectCoupon,
|
|
34
|
+
onTriggerRender, // optional custom trigger renderer
|
|
35
|
+
autoOpen = false, // auto open modal when mounted
|
|
36
|
+
closeOnClaim = true // close after claiming
|
|
37
|
+
}) => {
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [copied, setCopied] = useState(null);
|
|
40
|
+
|
|
41
|
+
const items = couponData?.sellerCoupons?.items || [];
|
|
42
|
+
|
|
43
|
+
const handleOpen = () => setOpen(true);
|
|
44
|
+
const handleClose = () => setOpen(false);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!open) return;
|
|
48
|
+
const onKey = (e) => {
|
|
49
|
+
if (e.key === 'Escape') {
|
|
50
|
+
handleClose();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
document.addEventListener('keydown', onKey);
|
|
54
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
55
|
+
}, [open]);
|
|
56
|
+
|
|
57
|
+
const handleClaim = async (item) => {
|
|
58
|
+
try {
|
|
59
|
+
if (navigator?.clipboard?.writeText) {
|
|
60
|
+
await navigator.clipboard.writeText(item.code);
|
|
61
|
+
}
|
|
62
|
+
} catch (_) {
|
|
63
|
+
/* ignore */
|
|
64
|
+
}
|
|
65
|
+
setCopied(item.code);
|
|
66
|
+
setTimeout(() => setCopied(null), 2000);
|
|
67
|
+
if (onSelectCoupon) onSelectCoupon(item);
|
|
68
|
+
if (closeOnClaim) handleClose();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Auto open behavior
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (autoOpen && !open) {
|
|
74
|
+
setOpen(true);
|
|
75
|
+
}
|
|
76
|
+
}, [autoOpen, open]);
|
|
77
|
+
|
|
78
|
+
const modal = open ? (
|
|
79
|
+
<div className={modalClasses.overlay} role="dialog" aria-modal="true" aria-label="Seller coupons list">
|
|
80
|
+
<div className={modalClasses.backdrop} onClick={handleClose} />
|
|
81
|
+
<div className={modalClasses.dialog}>
|
|
82
|
+
<div className={modalClasses.header}>
|
|
83
|
+
<h2 className={modalClasses.title}>Seller Coupons</h2>
|
|
84
|
+
<button type="button" className={modalClasses.closeBtn} onClick={handleClose} aria-label="Close coupon modal">×</button>
|
|
85
|
+
</div>
|
|
86
|
+
<div className={modalClasses.body}>
|
|
87
|
+
{couponLoading && <p className={listClasses.metaText}>Loading coupons...</p>}
|
|
88
|
+
{!couponLoading && couponError && <p className={listClasses.metaText}>Failed to load coupons.</p>}
|
|
89
|
+
{!couponLoading && !couponError && !items.length && <p className={listClasses.metaText}>No coupons available.</p>}
|
|
90
|
+
{!couponLoading && !couponError && items.length > 0 && (
|
|
91
|
+
<div className={modalClasses.stack}>
|
|
92
|
+
{items.map(item => {
|
|
93
|
+
const key = item.couponcode_id || item.coupon_id || item.code;
|
|
94
|
+
const daysLeft = dayDiff(item.to_date);
|
|
95
|
+
const expiryLabel = daysLeft === null
|
|
96
|
+
? 'No expiry'
|
|
97
|
+
: daysLeft <= 0
|
|
98
|
+
? 'Ends today'
|
|
99
|
+
: `Ends in ${daysLeft} day${daysLeft > 1 ? 's' : ''}`;
|
|
100
|
+
const title = item.description || item.name || `Discount ${item.discount_amount || ''}`;
|
|
101
|
+
return (
|
|
102
|
+
<div key={key} className={listClasses.card} role="group" aria-label={`Coupon ${item.code}`}>
|
|
103
|
+
<div className={listClasses.perf} aria-hidden="true" />
|
|
104
|
+
<div className={listClasses.content}>
|
|
105
|
+
<div className={listClasses.title}>{title}</div>
|
|
106
|
+
<div className={listClasses.subtext}>Code: <span className={listClasses.code}>{item.code}</span></div>
|
|
107
|
+
<div className={listClasses.expiry}>{expiryLabel}</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div className={listClasses.divider} aria-hidden="true" />
|
|
110
|
+
<div className={listClasses.actions}>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
className={listClasses.claimBtn}
|
|
114
|
+
onClick={() => handleClaim(item)}
|
|
115
|
+
aria-label={`Claim coupon ${item.code}`}
|
|
116
|
+
>
|
|
117
|
+
{copied === item.code ? 'Copied' : 'Claim'}
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
) : null;
|
|
129
|
+
|
|
130
|
+
const trigger = onTriggerRender
|
|
131
|
+
? onTriggerRender({ open, setOpen, handleOpen })
|
|
132
|
+
: (
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
className={modalClasses.triggerBtn}
|
|
136
|
+
onClick={handleOpen}
|
|
137
|
+
aria-haspopup="dialog"
|
|
138
|
+
aria-expanded={open}
|
|
139
|
+
>
|
|
140
|
+
{triggerLabel}
|
|
141
|
+
</button>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return <>{onTriggerRender ? trigger : trigger}{open ? ReactDOM.createPortal(modal, getPortalRoot()) : null}</>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default SellerCouponCheckout;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
.overlay{
|
|
2
|
+
position:fixed;
|
|
3
|
+
inset:0;
|
|
4
|
+
z-index:1000;
|
|
5
|
+
display:flex;
|
|
6
|
+
align-items:flex-start;
|
|
7
|
+
justify-content:center;
|
|
8
|
+
padding:5vh 24px;
|
|
9
|
+
font-family:inherit
|
|
10
|
+
}
|
|
11
|
+
.backdrop{
|
|
12
|
+
position:absolute;
|
|
13
|
+
inset:0;
|
|
14
|
+
background:rgba(0,0,0,.4);
|
|
15
|
+
backdrop-filter:blur(2px)
|
|
16
|
+
}
|
|
17
|
+
.dialog{
|
|
18
|
+
position:relative;
|
|
19
|
+
background:#fff;
|
|
20
|
+
border-radius:12px;
|
|
21
|
+
box-shadow:0 8px 32px rgba(0,0,0,.25);
|
|
22
|
+
width:clamp(320px,80vw,370px);
|
|
23
|
+
max-height:90vh;
|
|
24
|
+
display:flex;
|
|
25
|
+
flex-direction:column
|
|
26
|
+
}
|
|
27
|
+
.header{
|
|
28
|
+
display:flex;
|
|
29
|
+
align-items:center;
|
|
30
|
+
justify-content:space-between;
|
|
31
|
+
padding:16px 20px;
|
|
32
|
+
border-bottom:1px solid #eee
|
|
33
|
+
}
|
|
34
|
+
.title{
|
|
35
|
+
font-size:20px;
|
|
36
|
+
font-weight:600;
|
|
37
|
+
margin:0;
|
|
38
|
+
color:#333
|
|
39
|
+
}
|
|
40
|
+
.closeBtn{
|
|
41
|
+
background:none;
|
|
42
|
+
border:none;
|
|
43
|
+
font-size:24px;
|
|
44
|
+
line-height:1;
|
|
45
|
+
cursor:pointer;
|
|
46
|
+
color:#777;
|
|
47
|
+
padding:4px 8px;
|
|
48
|
+
border-radius:6px
|
|
49
|
+
}
|
|
50
|
+
.closeBtn:hover{
|
|
51
|
+
background:#f5f5f5;
|
|
52
|
+
color:#222
|
|
53
|
+
}
|
|
54
|
+
.body{
|
|
55
|
+
padding:16px 20px;
|
|
56
|
+
overflow:auto
|
|
57
|
+
}
|
|
58
|
+
.triggerBtn{
|
|
59
|
+
background:#f76b1c;
|
|
60
|
+
color:#fff;
|
|
61
|
+
border:none;
|
|
62
|
+
padding:10px 18px;
|
|
63
|
+
border-radius:8px;
|
|
64
|
+
font-weight:600;
|
|
65
|
+
cursor:pointer
|
|
66
|
+
}
|
|
67
|
+
.triggerBtn:hover{
|
|
68
|
+
background:#f26313
|
|
69
|
+
}
|
|
70
|
+
/* Vertical stack wrapper for existing sellerCoupon card styles */
|
|
71
|
+
.stack{
|
|
72
|
+
display:flex;
|
|
73
|
+
flex-direction:column;
|
|
74
|
+
gap:16px;
|
|
75
|
+
padding:4px 2px 12px;
|
|
76
|
+
}
|
|
77
|
+
@media (max-width:640px){
|
|
78
|
+
.dialog{
|
|
79
|
+
width:92vw;
|
|
80
|
+
padding-bottom:8px
|
|
81
|
+
}
|
|
82
|
+
.grid{
|
|
83
|
+
grid-template-columns:repeat(auto-fill,minmax(160px,1fr))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -36,7 +36,7 @@ const SellerDetail = props => {
|
|
|
36
36
|
mapSeller
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
const { error, loading, seller, favoriteSellerButtonProps } = talonProps;
|
|
39
|
+
const { error, loading, seller, favoriteSellerButtonProps, couponData, couponError, couponLoading } = talonProps;
|
|
40
40
|
const history = useHistory();
|
|
41
41
|
|
|
42
42
|
if (loading && !seller)
|
|
@@ -77,7 +77,14 @@ const SellerDetail = props => {
|
|
|
77
77
|
{
|
|
78
78
|
id: 'product-tab',
|
|
79
79
|
title: 'All Products',
|
|
80
|
-
content:
|
|
80
|
+
content:
|
|
81
|
+
<SellerProducts
|
|
82
|
+
sellerId={seller?.seller_id}
|
|
83
|
+
seller={seller}
|
|
84
|
+
couponData={couponData}
|
|
85
|
+
couponLoading={couponLoading}
|
|
86
|
+
couponError={couponError}
|
|
87
|
+
/>
|
|
81
88
|
},
|
|
82
89
|
{
|
|
83
90
|
id: 'store-information',
|
|
@@ -207,3 +214,4 @@ const SellerDetail = props => {
|
|
|
207
214
|
|
|
208
215
|
export default SellerDetail;
|
|
209
216
|
|
|
217
|
+
|
|
@@ -19,6 +19,7 @@ import NonSportCardsSets from '@riosst100/pwa-marketplace/src/components/NonSpor
|
|
|
19
19
|
import LegoSets from '@riosst100/pwa-marketplace/src/components/LegoSets/legoSets';
|
|
20
20
|
import TrainsSets from '@riosst100/pwa-marketplace/src/components/TrainsSets/trainsSets';
|
|
21
21
|
import SetsData from '@riosst100/pwa-marketplace/src/components/SetsData/setsData';
|
|
22
|
+
import SellerCoupon from '@riosst100/pwa-marketplace/src/components/SellerCoupon'
|
|
22
23
|
|
|
23
24
|
const MESSAGES = new Map().set(
|
|
24
25
|
'NOT_FOUND',
|
|
@@ -26,8 +27,7 @@ const MESSAGES = new Map().set(
|
|
|
26
27
|
);
|
|
27
28
|
|
|
28
29
|
const SellerProducts = props => {
|
|
29
|
-
const { sellerId, seller } = props;
|
|
30
|
-
|
|
30
|
+
const { sellerId, seller, couponData, couponLoading, couponError } = props;
|
|
31
31
|
const uid = null;
|
|
32
32
|
|
|
33
33
|
const { formatMessage } = useIntl();
|
|
@@ -83,6 +83,7 @@ const SellerProducts = props => {
|
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
85
|
<Fragment>
|
|
86
|
+
<SellerCoupon couponData={couponData} couponError={couponError} couponLoading={couponLoading} />
|
|
86
87
|
{showSubcategory ? (
|
|
87
88
|
<SubCategoryPage
|
|
88
89
|
categoryId={uid}
|
|
@@ -147,3 +148,4 @@ SellerProducts.defaultProps = {
|
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
export default SellerProducts;
|
|
151
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
2
|
import { useHistory, useRouteMatch } from 'react-router-dom';
|
|
3
3
|
import { setCheckoutState } from '@magento/peregrine/lib/store/actions/cart';
|
|
4
4
|
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
@@ -55,6 +55,8 @@ export const usePriceSummary = (props = {}) => {
|
|
|
55
55
|
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
56
56
|
const { getPriceSummaryQuery, initCheckoutSplitCartMutation, createCartMutation } = operations;
|
|
57
57
|
|
|
58
|
+
const { setIsProceedToCheckout } = props;
|
|
59
|
+
|
|
58
60
|
const apolloClient = useApolloClient();
|
|
59
61
|
|
|
60
62
|
const [{ cartId }, { createSellerCart, createCart, removeCart }] = useCartContext();
|
|
@@ -84,6 +86,12 @@ export const usePriceSummary = (props = {}) => {
|
|
|
84
86
|
|
|
85
87
|
const [fetchCartId] = useMutation(createCartMutation);
|
|
86
88
|
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (initCheckoutSplitCartLoading) {
|
|
91
|
+
setIsProceedToCheckout(true);
|
|
92
|
+
}
|
|
93
|
+
}, [initCheckoutSplitCartLoading])
|
|
94
|
+
|
|
87
95
|
const handleProceedToCheckout = useCallback(async(props) => {
|
|
88
96
|
const { sellerUrl } = props;
|
|
89
97
|
|
|
@@ -125,6 +133,7 @@ export const usePriceSummary = (props = {}) => {
|
|
|
125
133
|
hasItems: data && !!data.cart.items.length,
|
|
126
134
|
isCheckout,
|
|
127
135
|
isLoading: !!loading,
|
|
136
|
+
initCheckoutSplitCartLoading: initCheckoutSplitCartLoading,
|
|
128
137
|
flatData: flattenData(data)
|
|
129
138
|
};
|
|
130
139
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useLazyQuery } from '@apollo/client';
|
|
3
|
+
|
|
4
|
+
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
5
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
6
|
+
import DEFAULT_OPERATIONS from '@magento/peregrine/lib/talons/CartPage/cartPage.gql';
|
|
7
|
+
import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
|
|
8
|
+
import { useUserContext } from '@magento/peregrine/lib/context/user';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This talon contains logic for a cart page component.
|
|
12
|
+
* It performs effects and returns prop data for rendering the component.
|
|
13
|
+
*
|
|
14
|
+
* This talon performs the following effects:
|
|
15
|
+
*
|
|
16
|
+
* - Manages the updating state of the cart while cart details data is being fetched
|
|
17
|
+
*
|
|
18
|
+
* @function
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} props
|
|
21
|
+
* @param {CartPageQueries} props.queries GraphQL queries
|
|
22
|
+
*
|
|
23
|
+
* @returns {CartPageTalonProps}
|
|
24
|
+
*
|
|
25
|
+
* @example <caption>Importing into your project</caption>
|
|
26
|
+
* import { useCartPage } from '@magento/peregrine/lib/talons/CartPage/useCartPage';
|
|
27
|
+
*/
|
|
28
|
+
export const useCartPage = (props = {}) => {
|
|
29
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
30
|
+
const { getCartDetailsQuery } = operations;
|
|
31
|
+
|
|
32
|
+
const [{ cartId }] = useCartContext();
|
|
33
|
+
const [{ isSignedIn }] = useUserContext();
|
|
34
|
+
|
|
35
|
+
const [isProceedToCheckout, setIsProceedToCheckout] = useState(false);
|
|
36
|
+
|
|
37
|
+
const [isCartUpdating, setIsCartUpdating] = useState(false);
|
|
38
|
+
const [wishlistSuccessProps, setWishlistSuccessProps] = useState(null);
|
|
39
|
+
|
|
40
|
+
const [fetchCartDetails, { called, data, loading }] = useLazyQuery(
|
|
41
|
+
getCartDetailsQuery,
|
|
42
|
+
{
|
|
43
|
+
fetchPolicy: 'cache-and-network',
|
|
44
|
+
nextFetchPolicy: 'cache-first',
|
|
45
|
+
errorPolicy: 'all'
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const hasItems = !!data?.cart?.total_quantity;
|
|
50
|
+
const shouldShowLoadingIndicator = called && loading && !hasItems;
|
|
51
|
+
|
|
52
|
+
const cartItems = useMemo(() => {
|
|
53
|
+
return data?.cart?.items || [];
|
|
54
|
+
}, [data]);
|
|
55
|
+
|
|
56
|
+
const onAddToWishlistSuccess = useCallback(successToastProps => {
|
|
57
|
+
setWishlistSuccessProps(successToastProps);
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const [, { dispatch }] = useEventingContext();
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!called && cartId) {
|
|
64
|
+
fetchCartDetails({ variables: { cartId } });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Let the cart page know it is updating while we're waiting on network data.
|
|
68
|
+
setIsCartUpdating(loading);
|
|
69
|
+
}, [fetchCartDetails, called, cartId, loading]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (called && cartId && !loading) {
|
|
73
|
+
dispatch({
|
|
74
|
+
type: 'CART_PAGE_VIEW',
|
|
75
|
+
payload: {
|
|
76
|
+
cart_id: cartId,
|
|
77
|
+
products: cartItems
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}, [called, cartItems, cartId, loading, dispatch]);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
cartItems,
|
|
85
|
+
hasItems,
|
|
86
|
+
isCartUpdating,
|
|
87
|
+
isGuestCheckout: !isSignedIn,
|
|
88
|
+
fetchCartDetails,
|
|
89
|
+
onAddToWishlistSuccess,
|
|
90
|
+
setIsCartUpdating,
|
|
91
|
+
shouldShowLoadingIndicator,
|
|
92
|
+
wishlistSuccessProps,
|
|
93
|
+
isProceedToCheckout,
|
|
94
|
+
setIsProceedToCheckout
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** JSDoc type definitions */
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* GraphQL formatted string queries used in this talon.
|
|
102
|
+
*
|
|
103
|
+
* @typedef {Object} CartPageQueries
|
|
104
|
+
*
|
|
105
|
+
* @property {GraphQLAST} getCartDetailsQuery Query for getting the cart details.
|
|
106
|
+
*
|
|
107
|
+
* @see [cartPage.gql.js]{@link https://github.com/magento/pwa-studio/blob/develop/packages/venia-ui/lib/components/CartPage/cartPage.gql.js}
|
|
108
|
+
* for queries used in Venia
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Props data to use when rendering a cart page component.
|
|
113
|
+
*
|
|
114
|
+
* @typedef {Object} CartPageTalonProps
|
|
115
|
+
*
|
|
116
|
+
* @property {Array<Object>} cartItems An array of item objects in the cart.
|
|
117
|
+
* @property {boolean} hasItems True if the cart has items. False otherwise.
|
|
118
|
+
* @property {boolean} isCartUpdating True if the cart is updating. False otherwise.
|
|
119
|
+
* @property {function} setIsCartUpdating Callback function for setting the updating state of the cart page.
|
|
120
|
+
* @property {boolean} shouldShowLoadingIndicator True if the loading indicator should be rendered. False otherwise.
|
|
121
|
+
*/
|
|
@@ -2,6 +2,17 @@ import { gql } from '@apollo/client';
|
|
|
2
2
|
import { CheckoutPageFragment } from '@magento/peregrine/lib/talons/CheckoutPage/checkoutPageFragments.gql';
|
|
3
3
|
import { ItemsReviewFragment } from '@magento/peregrine/lib/talons/CheckoutPage/ItemsReview/itemsReviewFragments.gql';
|
|
4
4
|
|
|
5
|
+
// Local fragment to include seller_url for each item.
|
|
6
|
+
export const ITEM_SELLER_FRAGMENT = gql`
|
|
7
|
+
fragment ItemSellerFragment on Cart {
|
|
8
|
+
items {
|
|
9
|
+
seller {
|
|
10
|
+
seller_url
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
5
16
|
export const CREATE_CART = gql`
|
|
6
17
|
mutation createCart {
|
|
7
18
|
cartId: createEmptyCart
|
|
@@ -52,9 +63,11 @@ export const GET_ORDER_DETAILS = gql`
|
|
|
52
63
|
}
|
|
53
64
|
}
|
|
54
65
|
...ItemsReviewFragment
|
|
66
|
+
...ItemSellerFragment
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
${ItemsReviewFragment}
|
|
70
|
+
${ITEM_SELLER_FRAGMENT}
|
|
58
71
|
`;
|
|
59
72
|
|
|
60
73
|
export const GET_CHECKOUT_DETAILS = gql`
|
|
@@ -63,10 +76,12 @@ export const GET_CHECKOUT_DETAILS = gql`
|
|
|
63
76
|
id
|
|
64
77
|
...CheckoutPageFragment
|
|
65
78
|
...ItemsReviewFragment
|
|
79
|
+
...ItemSellerFragment
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
${CheckoutPageFragment}
|
|
69
83
|
${ItemsReviewFragment}
|
|
84
|
+
${ITEM_SELLER_FRAGMENT}
|
|
70
85
|
`;
|
|
71
86
|
|
|
72
87
|
export const GET_CUSTOMER = gql`
|
|
@@ -84,5 +99,6 @@ export default {
|
|
|
84
99
|
getCheckoutDetailsQuery: GET_CHECKOUT_DETAILS,
|
|
85
100
|
getCustomerQuery: GET_CUSTOMER,
|
|
86
101
|
getOrderDetailsQuery: GET_ORDER_DETAILS,
|
|
87
|
-
placeOrderMutation: PLACE_ORDER
|
|
102
|
+
placeOrderMutation: PLACE_ORDER,
|
|
103
|
+
itemSellerFragment: ITEM_SELLER_FRAGMENT
|
|
88
104
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect } from 'react';
|
|
2
|
+
import { useQuery } from '@apollo/client';
|
|
3
|
+
import { useHistory, useLocation } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
6
|
+
import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Routes to hide the mini cart on.
|
|
10
|
+
*/
|
|
11
|
+
// const DENIED_MINI_CART_ROUTES = ['/checkout'];
|
|
12
|
+
const DENIED_MINI_CART_ROUTES = [];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {DocumentNode} props.queries.getItemCountQuery query to get the total cart items count
|
|
17
|
+
*
|
|
18
|
+
* @returns {
|
|
19
|
+
* itemCount: Number,
|
|
20
|
+
* miniCartIsOpen: Boolean,
|
|
21
|
+
* handleLinkClick: Function,
|
|
22
|
+
* handleTriggerClick: Function,
|
|
23
|
+
* miniCartRef: Function,
|
|
24
|
+
* hideCartTrigger: Function,
|
|
25
|
+
* setMiniCartIsOpen: Function
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export const useCartTrigger = props => {
|
|
29
|
+
const {
|
|
30
|
+
queries: { getItemCountQuery }
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const [{ cartId }] = useCartContext();
|
|
34
|
+
const history = useHistory();
|
|
35
|
+
const location = useLocation();
|
|
36
|
+
const [isHidden, setIsHidden] = useState(() =>
|
|
37
|
+
DENIED_MINI_CART_ROUTES.includes(location.pathname)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
elementRef: miniCartRef,
|
|
42
|
+
expanded: miniCartIsOpen,
|
|
43
|
+
setExpanded: setMiniCartIsOpen,
|
|
44
|
+
triggerRef: miniCartTriggerRef
|
|
45
|
+
} = useDropdown();
|
|
46
|
+
|
|
47
|
+
const { data } = useQuery(getItemCountQuery, {
|
|
48
|
+
fetchPolicy: 'cache-and-network',
|
|
49
|
+
variables: {
|
|
50
|
+
cartId
|
|
51
|
+
},
|
|
52
|
+
skip: !cartId,
|
|
53
|
+
errorPolicy: 'all'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const itemCount = data?.cart?.total_summary_quantity_including_config || 0;
|
|
57
|
+
|
|
58
|
+
const handleTriggerClick = useCallback(() => {
|
|
59
|
+
// Open the mini cart.
|
|
60
|
+
setMiniCartIsOpen(isOpen => !isOpen);
|
|
61
|
+
}, [setMiniCartIsOpen]);
|
|
62
|
+
|
|
63
|
+
const handleLinkClick = useCallback(() => {
|
|
64
|
+
// Send the user to the cart page.
|
|
65
|
+
history.push('/cart');
|
|
66
|
+
}, [history]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setIsHidden(DENIED_MINI_CART_ROUTES.includes(location.pathname));
|
|
70
|
+
}, [location]);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
handleLinkClick,
|
|
74
|
+
handleTriggerClick,
|
|
75
|
+
itemCount,
|
|
76
|
+
miniCartIsOpen,
|
|
77
|
+
miniCartRef,
|
|
78
|
+
hideCartTrigger: isHidden,
|
|
79
|
+
setMiniCartIsOpen,
|
|
80
|
+
miniCartTriggerRef
|
|
81
|
+
};
|
|
82
|
+
};
|