@magento/peregrine 12.4.0 → 12.5.0

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
@@ -0,0 +1,19 @@
1
+ import { setContext } from '@apollo/client/link/context';
2
+ import { BrowserPersistence } from '@magento/peregrine/lib/util';
3
+
4
+ const storage = new BrowserPersistence();
5
+
6
+ export default function createAuthLink() {
7
+ return setContext((_, { headers }) => {
8
+ // get the authentication token from local storage if it exists.
9
+ const token = storage.getItem('signin_token');
10
+
11
+ // return the headers to the context so httpLink can read them
12
+ return {
13
+ headers: {
14
+ ...headers,
15
+ authorization: token ? `Bearer ${token}` : ''
16
+ }
17
+ };
18
+ });
19
+ }
@@ -0,0 +1,64 @@
1
+ import { onError } from '@apollo/client/link/error';
2
+ import getWithPath from 'lodash.get';
3
+ import setWithPath from 'lodash.set';
4
+
5
+ export default function createErrorLink() {
6
+ return onError(handler => {
7
+ const { graphQLErrors, networkError, response } = handler;
8
+
9
+ if (graphQLErrors) {
10
+ graphQLErrors.forEach(({ message, locations, path }) =>
11
+ console.log(
12
+ `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
13
+ )
14
+ );
15
+ }
16
+
17
+ if (networkError) {
18
+ console.log(`[Network error]: ${networkError}`);
19
+ }
20
+
21
+ if (response) {
22
+ const { data, errors } = response;
23
+ let pathToCartItems;
24
+
25
+ // It's within the GraphQL spec to receive data and errors, where
26
+ // errors are merely informational and not intended to block. Almost
27
+ // all existing components were not built with this in mind, so we
28
+ // build special handling of this error message so we can deal with
29
+ // it at the time we deem appropriate.
30
+ errors.forEach(({ message, path }, index) => {
31
+ if (
32
+ message === 'Some of the products are out of stock.' ||
33
+ message ===
34
+ 'There are no source items with the in stock status' ||
35
+ message === 'The requested qty is not available'
36
+ ) {
37
+ if (!pathToCartItems) {
38
+ pathToCartItems = path.slice(0, -1);
39
+ }
40
+
41
+ // Set the error to null to be cleaned up later
42
+ response.errors[index] = null;
43
+ }
44
+ });
45
+
46
+ // indicator that we have some cleanup to perform on the response
47
+ if (pathToCartItems) {
48
+ const cartItems = getWithPath(data, pathToCartItems);
49
+ const filteredCartItems = cartItems.filter(
50
+ cartItem => cartItem !== null
51
+ );
52
+ setWithPath(data, pathToCartItems, filteredCartItems);
53
+
54
+ const filteredErrors = response.errors.filter(
55
+ error => error !== null
56
+ );
57
+ // If all errors were stock related and set to null, reset the error response so it doesn't throw
58
+ response.errors = filteredErrors.length
59
+ ? filteredErrors
60
+ : undefined;
61
+ }
62
+ }
63
+ });
64
+ }
@@ -0,0 +1,63 @@
1
+ import { ApolloLink } from '@apollo/client';
2
+ import { BrowserPersistence } from '@magento/peregrine/lib/util';
3
+
4
+ // The name of the header to exchange with the server.
5
+ const CACHE_ID_HEADER = 'x-magento-cache-id';
6
+ // The key in local storage where we save the cache id
7
+ const LOCAL_STORAGE_KEY = 'magento_cache_id';
8
+ const storage = new BrowserPersistence();
9
+
10
+ /**
11
+ * The Magento GraphQL Cache Link class is an ApolloLink that is responsible for
12
+ * Venia sending the proper `x-magento-cache-id` header with each of its GraphQL requests.
13
+ */
14
+ export class MagentoGQLCacheLink extends ApolloLink {
15
+ // The links get reinstantiated on refresh.
16
+ // If we have an existing cache id value from a previous browsing session, use it.
17
+ #cacheId = storage.getItem(LOCAL_STORAGE_KEY) || null;
18
+
19
+ // Any time the cache id needs to be set, update both our internal variable and
20
+ // the value in local storage.
21
+ setCacheId(value) {
22
+ this.#cacheId = value;
23
+ storage.setItem(LOCAL_STORAGE_KEY, value);
24
+ }
25
+
26
+ request(operation, forward) {
27
+ // Attach the cache header to each outgoing request.
28
+ operation.setContext(previousContext => {
29
+ const { headers } = previousContext;
30
+ const withCacheHeader = {
31
+ ...headers,
32
+ [CACHE_ID_HEADER]: this.#cacheId
33
+ };
34
+
35
+ return {
36
+ ...previousContext,
37
+ headers: withCacheHeader
38
+ };
39
+ });
40
+
41
+ // Update the cache id from each response.
42
+ const updateCacheId = data => {
43
+ const context = operation.getContext();
44
+ const { response } = context;
45
+
46
+ const responseCacheId = response.headers.get(CACHE_ID_HEADER);
47
+
48
+ if (responseCacheId) {
49
+ this.setCacheId(responseCacheId);
50
+ }
51
+
52
+ // Purposefully don't modify the result,
53
+ // no other link needs to know about the cache id.
54
+ return data;
55
+ };
56
+
57
+ return forward(operation).map(updateCacheId);
58
+ }
59
+ }
60
+
61
+ export default function createGqlCacheLink() {
62
+ return new MagentoGQLCacheLink();
63
+ }
@@ -0,0 +1,66 @@
1
+ import { createHttpLink } from '@apollo/client';
2
+
3
+ import createAuthLink from '@magento/peregrine/lib/Apollo/links/authLink';
4
+ import createErrorLink from '@magento/peregrine/lib/Apollo/links/errorLink';
5
+ import createGqlCacheLink from '@magento/peregrine/lib/Apollo/links/gqlCacheLink';
6
+ import createMutationQueueLink from '@magento/peregrine/lib/Apollo/links/mutationQueueLink';
7
+ import createRetryLink from '@magento/peregrine/lib/Apollo/links/retryLink';
8
+ import createStoreLink from '@magento/peregrine/lib/Apollo/links/storeLink';
9
+ import shrinkQuery from '@magento/peregrine/lib/util/shrinkQuery';
10
+
11
+ /**
12
+ * Intercept and shrink URLs from GET queries.
13
+ *
14
+ * Using GET makes it possible to use edge caching in Magento Cloud, but risks
15
+ * exceeding URL limits with default usage of Apollo's http link.
16
+ *
17
+ * `shrinkQuery` encodes the URL in a more efficient way.
18
+ *
19
+ * @param {*} uri
20
+ * @param {*} options
21
+ */
22
+ export const customFetchToShrinkQuery = (uri, options) => {
23
+ // TODO: add `ismorphic-fetch` or equivalent to avoid this error
24
+ if (typeof globalThis.fetch !== 'function') {
25
+ console.error('This environment does not define `fetch`.');
26
+ return () => {};
27
+ }
28
+
29
+ const resource = options.method === 'GET' ? shrinkQuery(uri) : uri;
30
+
31
+ return globalThis.fetch(resource, options);
32
+ };
33
+
34
+ const getLinks = apiBase => {
35
+ const authLink = createAuthLink();
36
+ const storeLink = createStoreLink();
37
+ const errorLink = createErrorLink();
38
+ const retryLink = createRetryLink();
39
+ const gqlCacheLink = createGqlCacheLink();
40
+ const mutationQueueLink = createMutationQueueLink();
41
+
42
+ // Warning: `useGETForQueries` risks exceeding URL length limits.
43
+ // These limits in practice are typically set at or behind where TLS
44
+ // terminates. For Magento Cloud & Fastly, 8kb is the maximum by default.
45
+ // https://docs.fastly.com/en/guides/resource-limits#request-and-response-limits
46
+ const httpLink = createHttpLink({
47
+ fetch: customFetchToShrinkQuery,
48
+ useGETForQueries: true,
49
+ uri: apiBase
50
+ });
51
+
52
+ // preserve this array order, it's important
53
+ // as the terminating link, `httpLink` must be last
54
+ const links = new Map()
55
+ .set('MUTATION_QUEUE', mutationQueueLink)
56
+ .set('RETRY', retryLink)
57
+ .set('AUTH', authLink)
58
+ .set('GQL_CACHE', gqlCacheLink)
59
+ .set('STORE', storeLink)
60
+ .set('ERROR', errorLink)
61
+ .set('HTTP', httpLink);
62
+
63
+ return links;
64
+ };
65
+
66
+ export default getLinks;
@@ -0,0 +1,5 @@
1
+ import MutationQueueLink from '@adobe/apollo-link-mutation-queue';
2
+
3
+ export default function createMutationQueueLink() {
4
+ return new MutationQueueLink();
5
+ }
@@ -0,0 +1,17 @@
1
+ import { RetryLink } from '@apollo/client/link/retry';
2
+
3
+ const isServer = !globalThis.document;
4
+
5
+ export default function createRetryLink() {
6
+ return new RetryLink({
7
+ delay: {
8
+ initial: 300,
9
+ max: Infinity,
10
+ jitter: true
11
+ },
12
+ attempts: {
13
+ max: 5,
14
+ retryIf: error => error && !isServer && navigator.onLine
15
+ }
16
+ });
17
+ }
@@ -0,0 +1,22 @@
1
+ import { setContext } from '@apollo/client/link/context';
2
+ import { BrowserPersistence } from '@magento/peregrine/lib/util';
3
+
4
+ const storage = new BrowserPersistence();
5
+
6
+ export default function createStoreLink() {
7
+ return setContext((_, { headers }) => {
8
+ const storeCurrency = storage.getItem('store_view_currency') || null;
9
+ const storeCode = storage.getItem('store_view_code') || STORE_VIEW_CODE;
10
+
11
+ // return the headers to the context so httpLink can read them
12
+ return {
13
+ headers: {
14
+ ...headers,
15
+ store: storeCode,
16
+ ...(storeCurrency && {
17
+ 'Content-Currency': storeCurrency
18
+ })
19
+ }
20
+ };
21
+ });
22
+ }
@@ -1,59 +1,3 @@
1
- import { ApolloLink } from '@apollo/client';
2
- import { BrowserPersistence } from '@magento/peregrine/lib/util';
3
-
4
- // The name of the header to exchange with the server.
5
- const CACHE_ID_HEADER = 'x-magento-cache-id';
6
- // The key in local storage where we save the cache id
7
- const LOCAL_STORAGE_KEY = 'magento_cache_id';
8
- const storage = new BrowserPersistence();
9
-
10
- /**
11
- * The Magento GraphQL Cache Link class is an ApolloLink that is responsible for
12
- * Venia sending the proper `x-magento-cache-id` header with each of its GraphQL requests.
13
- */
14
- export default class MagentoGQLCacheLink extends ApolloLink {
15
- // The links get reinstantiated on refresh.
16
- // If we have an existing cache id value from a previous browsing session, use it.
17
- #cacheId = storage.getItem(LOCAL_STORAGE_KEY) || null;
18
-
19
- // Any time the cache id needs to be set, update both our internal variable and
20
- // the value in local storage.
21
- setCacheId(value) {
22
- this.#cacheId = value;
23
- storage.setItem(LOCAL_STORAGE_KEY, value);
24
- }
25
-
26
- request(operation, forward) {
27
- // Attach the cache header to each outgoing request.
28
- operation.setContext(previousContext => {
29
- const { headers } = previousContext;
30
- const withCacheHeader = {
31
- ...headers,
32
- [CACHE_ID_HEADER]: this.#cacheId
33
- };
34
-
35
- return {
36
- ...previousContext,
37
- headers: withCacheHeader
38
- };
39
- });
40
-
41
- // Update the cache id from each response.
42
- const updateCacheId = data => {
43
- const context = operation.getContext();
44
- const { response } = context;
45
-
46
- const responseCacheId = response.headers.get(CACHE_ID_HEADER);
47
-
48
- if (responseCacheId) {
49
- this.setCacheId(responseCacheId);
50
- }
51
-
52
- // Purposefully don't modify the result,
53
- // no other link needs to know about the cache id.
54
- return data;
55
- };
56
-
57
- return forward(operation).map(updateCacheId);
58
- }
59
- }
1
+ export {
2
+ MagentoGQLCacheLink as default
3
+ } from '@magento/peregrine/lib/Apollo/links/gqlCacheLink';
@@ -5,6 +5,7 @@ import CartContextProvider from '../context/cart';
5
5
  import CatalogContextProvider from '../context/catalog';
6
6
  import CheckoutContextProvider from '../context/checkout';
7
7
  import ErrorContextProvider from '../context/unhandledErrors';
8
+ import EventingContextProvider from '../context/eventing';
8
9
  import RootComponentsProvider from '../context/rootComponents';
9
10
  import UserContextProvider from '../context/user';
10
11
 
@@ -15,6 +16,7 @@ import UserContextProvider from '../context/user';
15
16
  */
16
17
  const contextProviders = [
17
18
  ErrorContextProvider,
19
+ EventingContextProvider,
18
20
  AppContextProvider,
19
21
  UserContextProvider,
20
22
  CatalogContextProvider,
@@ -0,0 +1,57 @@
1
+ import React, {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useRef
7
+ } from 'react';
8
+ import { Observable } from 'zen-observable-ts';
9
+
10
+ const EVENT_NAME = 'eventing';
11
+ const EventingContext = createContext();
12
+
13
+ const EventingContextProvider = props => {
14
+ const { children } = props;
15
+ const cacheRef = useRef([]);
16
+
17
+ const state = useMemo(
18
+ () =>
19
+ new Observable(observer => {
20
+ for (const element of cacheRef.current) {
21
+ observer.next(element);
22
+ }
23
+
24
+ document.addEventListener(EVENT_NAME, event => {
25
+ observer.next(event.detail);
26
+ });
27
+ }),
28
+ []
29
+ );
30
+
31
+ const dispatch = useCallback(detail => {
32
+ const event = new CustomEvent(EVENT_NAME, { detail });
33
+
34
+ cacheRef.current.push(detail);
35
+ document.dispatchEvent(event);
36
+ }, []);
37
+
38
+ const contextValue = useMemo(
39
+ () => [
40
+ state,
41
+ {
42
+ dispatch,
43
+ subscribe: state.subscribe
44
+ }
45
+ ],
46
+ [dispatch, state]
47
+ );
48
+
49
+ return (
50
+ <EventingContext.Provider value={contextValue}>
51
+ {children}
52
+ </EventingContext.Provider>
53
+ );
54
+ };
55
+
56
+ export default EventingContextProvider;
57
+ export const useEventingContext = () => useContext(EventingContext);
@@ -44,7 +44,7 @@ const useDelayedTransition = () => {
44
44
 
45
45
  if (process.env.USE_STORE_CODE_IN_URL === 'true') {
46
46
  const storeCodes = AVAILABLE_STORE_VIEWS.map(
47
- store => `\/?${store.code}`
47
+ store => `\/?${store.store_code}`
48
48
  ).join('|');
49
49
  const regex = new RegExp(`^${storeCodes}`);
50
50
  currentPath = currentPath.replace(regex, '');
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from 'react';
2
2
  import { useMutation, useQuery } from '@apollo/client';
3
3
  import { useUserContext } from '../../context/user';
4
4
  import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha';
5
+ import { useEventingContext } from '../../context/eventing';
5
6
 
6
7
  export const useAccountInformationPage = props => {
7
8
  const {
@@ -17,6 +18,8 @@ export const useAccountInformationPage = props => {
17
18
 
18
19
  const [isUpdateMode, setIsUpdateMode] = useState(false);
19
20
 
21
+ const [, { dispatch }] = useEventingContext();
22
+
20
23
  // Use local state to determine whether to display errors or not.
21
24
  // Could be replaced by a "reset mutation" function from apollo client.
22
25
  // https://github.com/apollographql/apollo-feature-requests/issues/170
@@ -116,6 +119,16 @@ export const useAccountInformationPage = props => {
116
119
  ...recaptchaDataForChangeCustomerPassword
117
120
  });
118
121
  }
122
+
123
+ dispatch({
124
+ type: 'USER_ACCOUNT_UPDATE',
125
+ payload: {
126
+ email,
127
+ firstName: firstname,
128
+ lastName: lastname
129
+ }
130
+ });
131
+
119
132
  // After submission, close the form if there were no errors.
120
133
  handleCancel(false);
121
134
  } catch {
@@ -133,7 +146,8 @@ export const useAccountInformationPage = props => {
133
146
  handleCancel,
134
147
  setCustomerInformation,
135
148
  generateReCaptchaData,
136
- changeCustomerPassword
149
+ changeCustomerPassword,
150
+ dispatch
137
151
  ]
138
152
  );
139
153