@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.
Files changed (55) 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/componentOverrideMapping.js +2 -0
  5. package/src/components/CheckoutHeader/checkoutHeader.js +162 -0
  6. package/src/components/CheckoutHeader/checkoutHeader.module.css +171 -0
  7. package/src/components/CheckoutHeader/index.js +1 -0
  8. package/src/components/FavoriteSeller/AddToListButton/addToListButton.module.css +2 -2
  9. package/src/components/FilterTop/FilterBlockList/filterTopItem.module.css +2 -0
  10. package/src/components/OrderDetail/components/itemsOrdered.js +6 -1
  11. package/src/components/RMAPage/components/productItem.css +15 -0
  12. package/src/components/RMAPage/components/productItem.js +43 -13
  13. package/src/components/RMAPage/components/productItem.module.css +15 -0
  14. package/src/components/SellerCoupon/index.js +1 -0
  15. package/src/components/SellerCoupon/sellerCoupon.js +84 -0
  16. package/src/components/SellerCoupon/sellerCoupon.module.css +110 -0
  17. package/src/components/SellerCoupon/sellerCouponCheckout.js +147 -0
  18. package/src/components/SellerCoupon/sellerCouponCheckout.module.css +85 -0
  19. package/src/components/SellerDetail/sellerDetail.js +10 -2
  20. package/src/components/SellerProducts/sellerProducts.js +4 -2
  21. package/src/overwrites/peregrine/lib/talons/CartPage/PriceSummary/usePriceSummary.js +10 -1
  22. package/src/overwrites/peregrine/lib/talons/CartPage/useCartPage.js +121 -0
  23. package/src/overwrites/peregrine/lib/talons/CheckoutPage/checkoutPage.extended.gql.js +17 -1
  24. package/src/overwrites/peregrine/lib/talons/Header/useCartTrigger.js +82 -0
  25. package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +17 -1
  26. package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +11 -11
  27. package/src/overwrites/venia-ui/lib/components/CartPage/PriceSummary/priceSummary.js +24 -6
  28. package/src/overwrites/venia-ui/lib/components/CartPage/ProductListing/productListing.js +6 -2
  29. package/src/overwrites/venia-ui/lib/components/CartPage/ProductListingBySeller/productListingBySeller.js +3 -1
  30. package/src/overwrites/venia-ui/lib/components/CartPage/cartPage.js +20 -4
  31. package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderSummary/orderSummary.js +1 -1
  32. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.js +1 -1
  33. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js +1 -1
  34. package/src/overwrites/venia-ui/lib/components/CheckoutPage/PriceAdjustments/priceAdjustments.js +74 -6
  35. package/src/overwrites/venia-ui/lib/components/CheckoutPage/ShippingInformation/shippingInformation.js +3 -3
  36. package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.js +53 -2
  37. package/src/overwrites/venia-ui/lib/components/CheckoutPage/checkoutPage.module.css +1 -1
  38. package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterItemRadio.js +1 -2
  39. package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterItemRadioGroup.js +10 -27
  40. package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/subFilterItemRadioGroup.js +1 -1
  41. package/src/overwrites/venia-ui/lib/components/Footer/footer.js +14 -4
  42. package/src/overwrites/venia-ui/lib/components/Footer/footer.module.css +16 -4
  43. package/src/overwrites/venia-ui/lib/components/Gallery/item.js +9 -8
  44. package/src/overwrites/venia-ui/lib/components/Header/accountTrigger.js +3 -3
  45. package/src/overwrites/venia-ui/lib/components/Header/cartTrigger.js +9 -6
  46. package/src/overwrites/venia-ui/lib/components/Header/cartTrigger.module.css +1 -1
  47. package/src/overwrites/venia-ui/lib/components/Header/header.js +2 -0
  48. package/src/overwrites/venia-ui/lib/components/Header/wishlistTrigger.js +4 -4
  49. package/src/overwrites/venia-ui/lib/components/Main/main.js +9 -4
  50. package/src/overwrites/venia-ui/lib/components/Main/main.module.css +2 -2
  51. package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +100 -72
  52. package/src/overwrites/venia-ui/lib/components/ToastContainer/toast.module.css +3 -3
  53. package/src/talons/Header/useCartTrigger.js +82 -0
  54. package/src/talons/Seller/seller.gql.js +42 -1
  55. 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: <SellerProducts sellerId={seller?.seller_id} seller={seller} />
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
+ };