@magento/peregrine 12.4.0 → 12.5.0-alpha.1

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 (64) hide show
  1. package/lib/Apollo/links/authLink.js +19 -0
  2. package/lib/Apollo/links/errorLink.js +64 -0
  3. package/lib/Apollo/links/gqlCacheLink.js +63 -0
  4. package/lib/Apollo/links/index.js +66 -0
  5. package/lib/Apollo/links/mutationQueueLink.js +5 -0
  6. package/lib/Apollo/links/retryLink.js +17 -0
  7. package/lib/Apollo/links/storeLink.js +22 -0
  8. package/lib/Apollo/magentoGqlCacheLink.js +3 -59
  9. package/lib/PeregrineContextProvider/peregrineContextProvider.js +2 -0
  10. package/lib/context/eventing.js +57 -0
  11. package/lib/hooks/useDelayedTransition.js +1 -1
  12. package/lib/talons/AccountInformationPage/useAccountInformationPage.js +15 -1
  13. package/lib/talons/Adapter/useAdapter.js +23 -200
  14. package/lib/talons/AddToCartDialog/addToCartDialog.gql.js +8 -0
  15. package/lib/talons/AddToCartDialog/useAddToCartDialog.js +46 -2
  16. package/lib/talons/AddressBookPage/useAddressBookPage.js +52 -17
  17. package/lib/talons/AuthModal/useAuthModal.js +12 -2
  18. package/lib/talons/Breadcrumbs/useBreadcrumbs.js +7 -2
  19. package/lib/talons/CartPage/ProductListing/EditModal/__fixtures__/configurableProduct.js +39 -4
  20. package/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js +47 -3
  21. package/lib/talons/CartPage/ProductListing/productListingFragments.gql.js +6 -0
  22. package/lib/talons/CartPage/ProductListing/useProduct.js +49 -3
  23. package/lib/talons/CartPage/useCartPage.js +15 -0
  24. package/lib/talons/CheckoutPage/ItemsReview/itemsReviewFragments.gql.js +12 -14
  25. package/lib/talons/CheckoutPage/OrderConfirmationPage/orderConfirmationPageFragments.gql.js +21 -1
  26. package/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js +15 -1
  27. package/lib/talons/CheckoutPage/PaymentInformation/useEditModal.js +10 -1
  28. package/lib/talons/CheckoutPage/PaymentInformation/usePaymentInformation.js +14 -0
  29. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useCustomerForm.js +38 -1
  30. package/lib/talons/CheckoutPage/ShippingInformation/AddressForm/useGuestForm.js +15 -1
  31. package/lib/talons/CheckoutPage/ShippingInformation/useShippingInformation.js +13 -0
  32. package/lib/talons/CheckoutPage/ShippingMethod/useShippingMethod.js +28 -6
  33. package/lib/talons/CheckoutPage/checkoutPage.gql.js +3 -0
  34. package/lib/talons/CheckoutPage/useCheckoutPage.js +79 -3
  35. package/lib/talons/Cms/useCmsPage.js +14 -0
  36. package/lib/talons/CreateAccount/useCreateAccount.js +15 -1
  37. package/lib/talons/FilterModal/helpers.js +8 -2
  38. package/lib/talons/FilterModal/useFilterModal.js +1 -0
  39. package/lib/talons/FilterSidebar/useFilterSidebar.js +1 -0
  40. package/lib/talons/Gallery/useAddToCartButton.js +24 -12
  41. package/lib/talons/Gallery/useGalleryItem.js +85 -1
  42. package/lib/talons/Header/storeSwitcher.gql.js +1 -16
  43. package/lib/talons/Header/useAccountMenu.js +21 -2
  44. package/lib/talons/Header/useStoreSwitcher.js +40 -93
  45. package/lib/talons/MagentoRoute/useMagentoRoute.js +7 -7
  46. package/lib/talons/MiniCart/ProductList/productListFragments.gql.js +4 -0
  47. package/lib/talons/MiniCart/useMiniCart.js +46 -3
  48. package/lib/talons/ProductFullDetail/useProductFullDetail.js +37 -9
  49. package/lib/talons/RootComponents/Category/categoryContent.gql.js +2 -0
  50. package/lib/talons/RootComponents/Category/categoryFragments.gql.js +3 -0
  51. package/lib/talons/RootComponents/Category/useCategory.js +4 -1
  52. package/lib/talons/RootComponents/Category/useCategoryContent.js +35 -13
  53. package/lib/talons/RootComponents/Product/productDetailFragment.gql.js +6 -0
  54. package/lib/talons/RootComponents/Product/useProduct.js +27 -0
  55. package/lib/talons/SearchBar/index.js +1 -0
  56. package/lib/talons/SearchBar/useAutocomplete.js +18 -1
  57. package/lib/talons/SearchBar/useSuggestedProduct.js +99 -0
  58. package/lib/talons/SearchPage/searchPage.gql.js +3 -0
  59. package/lib/talons/SearchPage/useSearchPage.js +56 -28
  60. package/lib/talons/SignIn/useSignIn.js +19 -2
  61. package/lib/talons/WishlistPage/useWishlistItem.js +36 -1
  62. package/lib/talons/WishlistPage/wishlistItemFragments.gql.js +3 -0
  63. package/lib/util/makeUrl.js +1 -1
  64. package/package.json +5 -4
@@ -2,6 +2,7 @@ import { useEffect, useMemo } from 'react';
2
2
  import useFieldState from '@magento/peregrine/lib/hooks/hook-wrappers/useInformedFieldStateWrapper';
3
3
  import { useLazyQuery } from '@apollo/client';
4
4
  import debounce from 'lodash.debounce';
5
+ import { useEventingContext } from '../../context/eventing';
5
6
 
6
7
  /**
7
8
  * @typedef { import("graphql").DocumentNode } DocumentNode
@@ -21,6 +22,8 @@ export const useAutocomplete = props => {
21
22
  visible
22
23
  } = props;
23
24
 
25
+ const [, { dispatch }] = useEventingContext();
26
+
24
27
  // Prepare to run the queries.
25
28
  const [runSearch, productResult] = useLazyQuery(getAutocompleteResults, {
26
29
  fetchPolicy: 'cache-and-network',
@@ -44,12 +47,25 @@ export const useAutocomplete = props => {
44
47
  useEffect(() => {
45
48
  if (valid && visible) {
46
49
  debouncedRunQuery(value);
50
+
51
+ if (value) {
52
+ dispatch({
53
+ type: 'SEARCHBAR_REQUEST',
54
+ payload: {
55
+ query: value,
56
+ currentPage: 1, // Same value used in GQL query
57
+ pageSize: 3, // Same value used in GQL query
58
+ refinements: []
59
+ }
60
+ });
61
+ }
47
62
  }
48
- }, [debouncedRunQuery, valid, value, visible]);
63
+ }, [debouncedRunQuery, valid, value, visible, dispatch]);
49
64
 
50
65
  const { data, error, loading } = productResult;
51
66
 
52
67
  // Handle results.
68
+ const categories = data && data.products?.aggregations[1]?.options;
53
69
  const products = data && data.products;
54
70
  const filters = data && data.products.aggregations;
55
71
  const hasResult = products && products.items;
@@ -73,6 +89,7 @@ export const useAutocomplete = props => {
73
89
  }
74
90
 
75
91
  return {
92
+ categories,
76
93
  displayResult,
77
94
  filters,
78
95
  messageType,
@@ -0,0 +1,99 @@
1
+ import { useCallback, useMemo, useEffect } from 'react';
2
+ import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
3
+ import resourceUrl from '../../util/makeUrl';
4
+
5
+ /**
6
+ * Return props necessary to render a SuggestedProduct component.
7
+ *
8
+ * @param {Object} props
9
+ * @param {Object} props.price_range - price range
10
+ * @param {String} props.url_key - url key
11
+ * @param {String} props.url_suffix - url suffix
12
+ * @param {String} props.sku - product sky
13
+ * @param {Function} props.onNavigate - callback to fire on link click
14
+ */
15
+ export const useSuggestedProduct = props => {
16
+ const [, { dispatch }] = useEventingContext();
17
+ const {
18
+ name,
19
+ price,
20
+ price_range,
21
+ onNavigate,
22
+ url_key,
23
+ url_suffix,
24
+ sku
25
+ } = props;
26
+
27
+ const finalPrice = price_range?.maximum_price?.final_price?.value;
28
+ const discountAmount = price_range?.maximum_price?.discount?.amount_off;
29
+ const currencyCode = price_range?.maximum_price?.final_price?.currency;
30
+
31
+ const handleClick = useCallback(() => {
32
+ dispatch({
33
+ type: 'PRODUCT_CLICK',
34
+ payload: {
35
+ name,
36
+ sku,
37
+ priceTotal: finalPrice,
38
+ discountAmount,
39
+ currencyCode,
40
+ selectedOptions: null
41
+ }
42
+ });
43
+ if (typeof onNavigate === 'function') {
44
+ onNavigate();
45
+ }
46
+ }, [
47
+ name,
48
+ currencyCode,
49
+ discountAmount,
50
+ dispatch,
51
+ finalPrice,
52
+ onNavigate,
53
+ sku
54
+ ]);
55
+
56
+ useEffect(() => {
57
+ if (sku !== null) {
58
+ dispatch({
59
+ type: 'PRODUCT_IMPRESSION',
60
+ payload: {
61
+ name,
62
+ sku,
63
+ priceTotal: finalPrice,
64
+ discountAmount,
65
+ currencyCode,
66
+ selectedOptions: null
67
+ }
68
+ });
69
+ }
70
+ }, [name, currencyCode, discountAmount, dispatch, finalPrice, sku]);
71
+
72
+ // fall back to deprecated field if price range is unavailable
73
+ const priceProps = useMemo(() => {
74
+ return {
75
+ currencyCode:
76
+ price_range?.maximum_price?.final_price?.currency ||
77
+ price.regularPrice.amount.currency,
78
+ value:
79
+ price_range?.maximum_price?.final_price?.value ||
80
+ price.regularPrice.amount.value
81
+ };
82
+ }, [
83
+ price.regularPrice.amount.currency,
84
+ price.regularPrice.amount.value,
85
+ price_range?.maximum_price?.final_price?.currency,
86
+ price_range?.maximum_price?.final_price?.value
87
+ ]);
88
+
89
+ const uri = useMemo(() => resourceUrl(`/${url_key}${url_suffix || ''}`), [
90
+ url_key,
91
+ url_suffix
92
+ ]);
93
+
94
+ return {
95
+ priceProps,
96
+ handleClick,
97
+ uri
98
+ };
99
+ };
@@ -56,6 +56,9 @@ export const PRODUCT_SEARCH = gql`
56
56
  currency
57
57
  value
58
58
  }
59
+ discount {
60
+ amount_off
61
+ }
59
62
  }
60
63
  }
61
64
  sku
@@ -11,6 +11,7 @@ import { useSort } from '../../hooks/useSort';
11
11
  import { getFiltersFromSearch, getFilterInput } from '../FilterModal/helpers';
12
12
 
13
13
  import DEFAULT_OPERATIONS from './searchPage.gql';
14
+ import { useEventingContext } from '../../context/eventing';
14
15
 
15
16
  /**
16
17
  * Return props necessary to render a SearchPage component.
@@ -19,6 +20,7 @@ import DEFAULT_OPERATIONS from './searchPage.gql';
19
20
  * @param {String} props.query - graphql query used for executing search
20
21
  */
21
22
  export const useSearchPage = (props = {}) => {
23
+ const [, { dispatch }] = useEventingContext();
22
24
  const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
23
25
 
24
26
  const {
@@ -133,17 +135,50 @@ export const useSearchPage = (props = {}) => {
133
135
  setPageLoading(isBackgroundLoading);
134
136
  }, [isBackgroundLoading, setPageLoading]);
135
137
 
138
+ //Handle initial redirect to add page to query param to prevent double query and dispatch and further pagination
139
+ const searched = useRef(false);
140
+ // Reset the current page back to one (1) when the search string, filters
141
+ // or sort criteria change.
142
+ useEffect(() => {
143
+ // We don't want to compare page value.
144
+ const prevSearch = new URLSearchParams(previousSearch.current);
145
+ const nextSearch = new URLSearchParams(search);
146
+ prevSearch.delete('page');
147
+ nextSearch.delete('page');
148
+
149
+ if (
150
+ prevSearch.toString() !== nextSearch.toString() ||
151
+ previousSort.current.sortAttribute.toString() !==
152
+ currentSort.sortAttribute.toString() ||
153
+ previousSort.current.sortDirection.toString() !==
154
+ currentSort.sortDirection.toString()
155
+ ) {
156
+ // The search term changed.
157
+ setCurrentPage(1, true);
158
+ // And update the ref.
159
+ previousSearch.current = search;
160
+ previousSort.current = currentSort;
161
+ searched.current = false;
162
+ }
163
+ }, [currentSort, search, setCurrentPage]);
164
+
136
165
  useEffect(() => {
137
166
  // Wait until we have the type map to fetch product data.
138
167
  if (!filterTypeMap.size || !pageSize) {
139
168
  return;
140
169
  }
141
170
  const filters = getFiltersFromSearch(search);
142
-
143
- // Construct the filter arg object.
171
+ // Construct the filter arg object and dispatcher refinement data.
144
172
  const newFilters = {};
173
+ const refinementData = [];
145
174
  filters.forEach((values, key) => {
146
- newFilters[key] = getFilterInput(values, filterTypeMap.get(key));
175
+ const filterMapType = filterTypeMap.get(key);
176
+ newFilters[key] = getFilterInput(values, filterMapType);
177
+ refinementData.push({
178
+ attribute: key,
179
+ value: values,
180
+ isRange: filterMapType === 'FilterRangeTypeInput'
181
+ });
147
182
  });
148
183
 
149
184
  runQuery({
@@ -155,6 +190,22 @@ export const useSearchPage = (props = {}) => {
155
190
  sort: { [sortAttribute]: sortDirection }
156
191
  }
157
192
  });
193
+ if (!searched.current) {
194
+ dispatch({
195
+ type: 'SEARCH_REQUEST',
196
+ payload: {
197
+ query: inputText,
198
+ refinements: refinementData,
199
+ sort: {
200
+ attribute: sortAttribute,
201
+ order: sortDirection
202
+ },
203
+ pageSize: Number(pageSize),
204
+ currentPage: Number(currentPage)
205
+ }
206
+ });
207
+ searched.current = true;
208
+ }
158
209
  }, [
159
210
  currentPage,
160
211
  filterTypeMap,
@@ -163,7 +214,8 @@ export const useSearchPage = (props = {}) => {
163
214
  pageSize,
164
215
  search,
165
216
  sortDirection,
166
- sortAttribute
217
+ sortAttribute,
218
+ dispatch
167
219
  ]);
168
220
 
169
221
  // Set the total number of pages whenever the data changes.
@@ -179,30 +231,6 @@ export const useSearchPage = (props = {}) => {
179
231
  };
180
232
  }, [data, setTotalPages]);
181
233
 
182
- // Reset the current page back to one (1) when the search string, filters
183
- // or sort criteria change.
184
- useEffect(() => {
185
- // We don't want to compare page value.
186
- const prevSearch = new URLSearchParams(previousSearch.current);
187
- const nextSearch = new URLSearchParams(search);
188
- prevSearch.delete('page');
189
- nextSearch.delete('page');
190
-
191
- if (
192
- prevSearch.toString() !== nextSearch.toString() ||
193
- previousSort.current.sortAttribute.toString() !==
194
- currentSort.sortAttribute.toString() ||
195
- previousSort.current.sortDirection.toString() !==
196
- currentSort.sortDirection.toString()
197
- ) {
198
- // The search term changed.
199
- setCurrentPage(1, true);
200
- // And update the ref.
201
- previousSearch.current = search;
202
- previousSort.current = currentSort;
203
- }
204
- }, [currentSort, search, setCurrentPage]);
205
-
206
234
  useEffect(() => {
207
235
  if (inputText) {
208
236
  getSortMethods({
@@ -9,6 +9,7 @@ import { useAwaitQuery } from '../../hooks/useAwaitQuery';
9
9
  import { retrieveCartId } from '../../store/actions/cart';
10
10
 
11
11
  import DEFAULT_OPERATIONS from './signIn.gql';
12
+ import { useEventingContext } from '../../context/eventing';
12
13
 
13
14
  export const useSignIn = props => {
14
15
  const {
@@ -39,6 +40,8 @@ export const useSignIn = props => {
39
40
  { getUserDetails, setToken }
40
41
  ] = useUserContext();
41
42
 
43
+ const [, { dispatch }] = useEventingContext();
44
+
42
45
  const [signIn, { error: signInError }] = useMutation(signInMutation, {
43
46
  fetchPolicy: 'no-cache'
44
47
  });
@@ -101,7 +104,20 @@ export const useSignIn = props => {
101
104
  });
102
105
 
103
106
  // Ensure old stores are updated with any new data.
104
- getUserDetails({ fetchUserDetails });
107
+
108
+ await getUserDetails({ fetchUserDetails });
109
+
110
+ const { data } = await fetchUserDetails({
111
+ fetchPolicy: 'cache-only'
112
+ });
113
+
114
+ dispatch({
115
+ type: 'USER_SIGN_IN',
116
+ payload: {
117
+ ...data.customer
118
+ }
119
+ });
120
+
105
121
  getCartDetails({ fetchCartId, fetchCartDetails });
106
122
  } catch (error) {
107
123
  if (process.env.NODE_ENV !== 'production') {
@@ -124,7 +140,8 @@ export const useSignIn = props => {
124
140
  getUserDetails,
125
141
  fetchUserDetails,
126
142
  getCartDetails,
127
- fetchCartDetails
143
+ fetchCartDetails,
144
+ dispatch
128
145
  ]
129
146
  );
130
147
 
@@ -4,6 +4,7 @@ import { useMutation } from '@apollo/client';
4
4
  import { useCartContext } from '@magento/peregrine/lib/context/cart';
5
5
  import mergeOperations from '../../util/shallowMerge';
6
6
  import defaultOperations from './wishlistItem.gql';
7
+ import { useEventingContext } from '../../context/eventing';
7
8
 
8
9
  const SUPPORTED_PRODUCT_TYPES = ['SimpleProduct', 'ConfigurableProduct'];
9
10
 
@@ -29,6 +30,8 @@ const mergeSupportedProductTypes = (supportedProductTypes = []) => {
29
30
  export const useWishlistItem = props => {
30
31
  const { item, onOpenAddToCartDialog, wishlistId } = props;
31
32
 
33
+ const [, { dispatch }] = useEventingContext();
34
+
32
35
  const {
33
36
  configurable_options: selectedConfigurableOptions = [],
34
37
  id: itemId,
@@ -162,6 +165,36 @@ export const useWishlistItem = props => {
162
165
  ) {
163
166
  try {
164
167
  await addWishlistItemToCart();
168
+
169
+ const selectedOptionsLabels =
170
+ selectedConfigurableOptions?.length > 0
171
+ ? selectedConfigurableOptions?.map(
172
+ ({ option_label, value_label }) => ({
173
+ attribute: option_label,
174
+ value: value_label
175
+ })
176
+ )
177
+ : null;
178
+
179
+ dispatch({
180
+ type: 'CART_ADD_ITEM',
181
+ payload: {
182
+ cartId,
183
+ sku: item.product.sku,
184
+ name: item.product.name,
185
+ priceTotal:
186
+ item.product.price_range.maximum_price.final_price
187
+ .value,
188
+ currencyCode:
189
+ item.product.price_range.maximum_price.final_price
190
+ .currency,
191
+ discountAmount:
192
+ item.product.price_range.maximum_price.discount
193
+ .amount_off,
194
+ selectedOptions: selectedOptionsLabels,
195
+ quantity: 1
196
+ }
197
+ });
165
198
  } catch (error) {
166
199
  console.error(error);
167
200
  }
@@ -170,10 +203,12 @@ export const useWishlistItem = props => {
170
203
  }
171
204
  }, [
172
205
  addWishlistItemToCart,
206
+ cartId,
173
207
  configurableOptions.length,
208
+ dispatch,
174
209
  item,
175
210
  onOpenAddToCartDialog,
176
- selectedConfigurableOptions.length
211
+ selectedConfigurableOptions
177
212
  ]);
178
213
 
179
214
  const handleRemoveProductFromWishlist = useCallback(async () => {
@@ -17,6 +17,9 @@ export const WishlistItemFragment = gql`
17
17
  currency
18
18
  value
19
19
  }
20
+ discount {
21
+ amount_off
22
+ }
20
23
  }
21
24
  }
22
25
  sku
@@ -13,7 +13,7 @@ const storeSecureBaseMediaUrl = {};
13
13
 
14
14
  // Fallback to global secure_base_media_url set at build time
15
15
  AVAILABLE_STORE_VIEWS.forEach(store => {
16
- storeSecureBaseMediaUrl[store.code] = store.secure_base_media_url;
16
+ storeSecureBaseMediaUrl[store.store_code] = store.secure_base_media_url;
17
17
  });
18
18
 
19
19
  let mediaBackend =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magento/peregrine",
3
- "version": "12.4.0",
3
+ "version": "12.5.0-alpha.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -17,10 +17,11 @@
17
17
  },
18
18
  "homepage": "https://github.com/magento/pwa-studio/tree/main/packages/peregrine#readme",
19
19
  "dependencies": {
20
- "fast-glob": "~3.2.4"
20
+ "fast-glob": "~3.2.4",
21
+ "zen-observable-ts": "^1.2.0"
21
22
  },
22
23
  "devDependencies": {
23
- "@apollo/client": "~3.4.0",
24
+ "@apollo/client": "~3.5.0",
24
25
  "@magento/eslint-config": "~1.5.0",
25
26
  "@testing-library/react-hooks": "~5.0.3",
26
27
  "intl": "~1.2.5",
@@ -36,7 +37,7 @@
36
37
  "wait-for-expect": "~1.2.0"
37
38
  },
38
39
  "peerDependencies": {
39
- "@apollo/client": "~3.4.0",
40
+ "@apollo/client": "~3.5.0",
40
41
  "@babel/runtime": "~7.15.3",
41
42
  "informed": "~3.29.0",
42
43
  "react": "~17.0.1",