@magento/peregrine 14.4.1 → 14.5.1-alpha.5

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.
@@ -1,7 +1,12 @@
1
1
  import { createActions } from 'redux-actions';
2
2
 
3
3
  const prefix = 'USER';
4
- const actionTypes = ['RESET', 'SET_TOKEN', 'CLEAR_TOKEN'];
4
+ const actionTypes = [
5
+ 'RESET',
6
+ 'SET_TOKEN',
7
+ 'CLEAR_TOKEN',
8
+ 'SET_USER_ON_ORDER_SUCCESS'
9
+ ];
5
10
 
6
11
  const actionMap = {
7
12
  SIGN_IN: {
@@ -89,3 +89,9 @@ export const clearToken = () =>
89
89
  // Remove from store
90
90
  dispatch(actions.clearToken());
91
91
  };
92
+
93
+ export const setUserOnOrderSuccess = successFlag =>
94
+ async function thunk(dispatch) {
95
+ // Dispatch the action to update the state
96
+ dispatch(actions.setUserOnOrderSuccess(successFlag));
97
+ };
@@ -30,7 +30,8 @@ const initialState = {
30
30
  isResettingPassword: false,
31
31
  isSignedIn: isSignedIn(),
32
32
  resetPasswordError: null,
33
- token: getToken()
33
+ token: getToken(),
34
+ userOnOrderSuccess: false // Add userOnOrderSuccess state
34
35
  };
35
36
 
36
37
  const reducerMap = {
@@ -48,6 +49,12 @@ const reducerMap = {
48
49
  token: null
49
50
  };
50
51
  },
52
+ [actions.setUserOnOrderSuccess]: (state, { payload }) => {
53
+ return {
54
+ ...state,
55
+ userOnOrderSuccess: payload // Update the state with the new flag value
56
+ };
57
+ },
51
58
  [actions.getDetails.request]: state => {
52
59
  return {
53
60
  ...state,
@@ -1,102 +1,129 @@
1
- export default {
2
- cart: {
3
- id: 'GXtkt675mPd3gYuvhWLd5iw5ekVoDj1b',
4
- total_quantity: 7,
5
- items: [
1
+ export default [
2
+ {
3
+ id: '29568',
4
+ product: {
5
+ id: 1093,
6
+ name: 'Jillian Top',
7
+ thumbnail: {
8
+ url:
9
+ 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg',
10
+ __typename: 'ProductImage'
11
+ },
12
+ __typename: 'ConfigurableProduct'
13
+ },
14
+ quantity: 3,
15
+ configurable_options: [
16
+ {
17
+ configurable_product_option_uid: 179,
18
+ option_label: 'Fashion Color',
19
+ configurable_product_option_value_uid: 18,
20
+ value_label: 'Peach',
21
+ __typename: 'SelectedConfigurableOption'
22
+ },
23
+ {
24
+ configurable_product_option_uid: 182,
25
+ option_label: 'Fashion Size',
26
+ configurable_product_option_value_uid: 27,
27
+ value_label: 'M',
28
+ __typename: 'SelectedConfigurableOption'
29
+ }
30
+ ],
31
+ __typename: 'ConfigurableCartItem'
32
+ },
33
+ {
34
+ id: '29570',
35
+ product: {
36
+ id: 1115,
37
+ name: 'Juno Sweater',
38
+ thumbnail: {
39
+ url:
40
+ 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/s/vsw02-pe_main_2.jpg',
41
+ __typename: 'ProductImage'
42
+ },
43
+ __typename: 'ConfigurableProduct'
44
+ },
45
+ quantity: 1,
46
+ configurable_options: [
47
+ {
48
+ configurable_product_option_uid: 179,
49
+ option_label: 'Fashion Color',
50
+ configurable_product_option_value_uid: 21,
51
+ value_label: 'Rain',
52
+ __typename: 'SelectedConfigurableOption'
53
+ },
6
54
  {
7
- id: '29568',
8
- product: {
9
- id: 1093,
10
- name: 'Jillian Top',
11
- thumbnail: {
12
- url:
13
- 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg',
14
- __typename: 'ProductImage'
15
- },
16
- __typename: 'ConfigurableProduct'
17
- },
18
- quantity: 3,
19
- configurable_options: [
20
- {
21
- configurable_product_option_uid: 179,
22
- option_label: 'Fashion Color',
23
- configurable_product_option_value_uid: 18,
24
- value_label: 'Peach',
25
- __typename: 'SelectedConfigurableOption'
26
- },
27
- {
28
- configurable_product_option_uid: 182,
29
- option_label: 'Fashion Size',
30
- configurable_product_option_value_uid: 27,
31
- value_label: 'M',
32
- __typename: 'SelectedConfigurableOption'
33
- }
34
- ],
35
- __typename: 'ConfigurableCartItem'
55
+ configurable_product_option_uid: 182,
56
+ option_label: 'Fashion Size',
57
+ configurable_product_option_value_uid: 29,
58
+ value_label: 'XS',
59
+ __typename: 'SelectedConfigurableOption'
60
+ }
61
+ ],
62
+ __typename: 'ConfigurableCartItem'
63
+ },
64
+ {
65
+ id: '29572',
66
+ product: {
67
+ id: 1152,
68
+ name: 'Angelina Tank Dress',
69
+ thumbnail: {
70
+ url:
71
+ 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/d/vd01-ll_main_2.jpg',
72
+ __typename: 'ProductImage'
73
+ },
74
+ __typename: 'ConfigurableProduct'
75
+ },
76
+ quantity: 3,
77
+ configurable_options: [
78
+ {
79
+ configurable_product_option_uid: 179,
80
+ option_label: 'Fashion Color',
81
+ configurable_product_option_value_uid: 20,
82
+ value_label: 'Lilac',
83
+ __typename: 'SelectedConfigurableOption'
84
+ },
85
+ {
86
+ configurable_product_option_uid: 182,
87
+ option_label: 'Fashion Size',
88
+ configurable_product_option_value_uid: 26,
89
+ value_label: 'L',
90
+ __typename: 'SelectedConfigurableOption'
91
+ }
92
+ ],
93
+ __typename: 'ConfigurableCartItem'
94
+ }
95
+ ];
96
+
97
+ export const singleItem = [
98
+ {
99
+ id: '29568',
100
+ product: {
101
+ id: 1093,
102
+ name: 'Jillian Top',
103
+ thumbnail: {
104
+ url:
105
+ 'https://master-7rqtwti-c5v7sxvquxwl4.us-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/t/vt12-kh_main_2.jpg',
106
+ __typename: 'ProductImage'
36
107
  },
108
+ __typename: 'ConfigurableProduct'
109
+ },
110
+ quantity: 3,
111
+ configurable_options: [
37
112
  {
38
- id: '29570',
39
- product: {
40
- id: 1115,
41
- name: 'Juno Sweater',
42
- thumbnail: {
43
- url:
44
- 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/s/vsw02-pe_main_2.jpg',
45
- __typename: 'ProductImage'
46
- },
47
- __typename: 'ConfigurableProduct'
48
- },
49
- quantity: 1,
50
- configurable_options: [
51
- {
52
- configurable_product_option_uid: 179,
53
- option_label: 'Fashion Color',
54
- configurable_product_option_value_uid: 21,
55
- value_label: 'Rain',
56
- __typename: 'SelectedConfigurableOption'
57
- },
58
- {
59
- configurable_product_option_uid: 182,
60
- option_label: 'Fashion Size',
61
- configurable_product_option_value_uid: 29,
62
- value_label: 'XS',
63
- __typename: 'SelectedConfigurableOption'
64
- }
65
- ],
66
- __typename: 'ConfigurableCartItem'
113
+ configurable_product_option_uid: 179,
114
+ option_label: 'Fashion Color',
115
+ configurable_product_option_value_uid: 18,
116
+ value_label: 'Peach',
117
+ __typename: 'SelectedConfigurableOption'
67
118
  },
68
119
  {
69
- id: '29572',
70
- product: {
71
- id: 1152,
72
- name: 'Angelina Tank Dress',
73
- thumbnail: {
74
- url:
75
- 'https://master-7rqtwti-c5v7sxvquxwl4.eu-4.magentosite.cloud/media/catalog/product/cache/d3ba9f7bcd3b0724e976dc5144b29c7d/v/d/vd01-ll_main_2.jpg',
76
- __typename: 'ProductImage'
77
- },
78
- __typename: 'ConfigurableProduct'
79
- },
80
- quantity: 3,
81
- configurable_options: [
82
- {
83
- configurable_product_option_uid: 179,
84
- option_label: 'Fashion Color',
85
- configurable_product_option_value_uid: 20,
86
- value_label: 'Lilac',
87
- __typename: 'SelectedConfigurableOption'
88
- },
89
- {
90
- configurable_product_option_uid: 182,
91
- option_label: 'Fashion Size',
92
- configurable_product_option_value_uid: 26,
93
- value_label: 'L',
94
- __typename: 'SelectedConfigurableOption'
95
- }
96
- ],
97
- __typename: 'ConfigurableCartItem'
120
+ configurable_product_option_uid: 182,
121
+ option_label: 'Fashion Size',
122
+ configurable_product_option_value_uid: 27,
123
+ value_label: 'M',
124
+ __typename: 'SelectedConfigurableOption'
98
125
  }
99
126
  ],
100
- __typename: 'Cart'
127
+ __typename: 'ConfigurableCartItem'
101
128
  }
102
- };
129
+ ];
@@ -1,7 +1,6 @@
1
1
  import { useEffect, useState, useCallback, useMemo } from 'react';
2
- import { useLazyQuery, useQuery } from '@apollo/client';
2
+ import { useQuery } from '@apollo/client';
3
3
 
4
- import { useCartContext } from '../../../context/cart';
5
4
  import mergeOperations from '../../../util/shallowMerge';
6
5
  import DEFAULT_OPERATIONS from './itemsReview.gql';
7
6
 
@@ -9,9 +8,9 @@ export const useItemsReview = props => {
9
8
  const [showAllItems, setShowAllItems] = useState(false);
10
9
  const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
11
10
 
12
- const { getItemsInCart, getConfigurableThumbnailSource } = operations;
11
+ const { getConfigurableThumbnailSource } = operations;
13
12
 
14
- const [{ cartId }] = useCartContext();
13
+ const { items: itemsData } = props;
15
14
 
16
15
  const { data: configurableThumbnailSourceData } = useQuery(
17
16
  getConfigurableThumbnailSource,
@@ -27,48 +26,29 @@ export const useItemsReview = props => {
27
26
  }
28
27
  }, [configurableThumbnailSourceData]);
29
28
 
30
- const [
31
- fetchItemsInCart,
32
- { data: queryData, error, loading }
33
- ] = useLazyQuery(getItemsInCart, {
34
- fetchPolicy: 'cache-and-network'
35
- });
36
-
37
- // If static data was provided, use that instead of query data.
38
- const data = props.data || queryData;
39
-
40
29
  const setShowAllItemsFlag = useCallback(() => setShowAllItems(true), [
41
30
  setShowAllItems
42
31
  ]);
43
32
 
44
- useEffect(() => {
45
- if (cartId && !props.data) {
46
- fetchItemsInCart({
47
- variables: {
48
- cartId
49
- }
50
- });
51
- }
52
- }, [cartId, fetchItemsInCart, props.data]);
53
-
54
33
  useEffect(() => {
55
34
  /**
56
35
  * If there are 2 or less than 2 items in cart
57
36
  * set show all items to `true`.
58
37
  */
59
- if (data && data.cart && data.cart.items.length <= 2) {
38
+ if (itemsData && itemsData.length <= 2) {
60
39
  setShowAllItems(true);
61
40
  }
62
- }, [data]);
41
+ }, [itemsData]);
63
42
 
64
- const items = data ? data.cart.items : [];
43
+ const items = itemsData || [];
65
44
 
66
- const totalQuantity = data ? +data.cart.total_quantity : 0;
45
+ const totalQuantity = items.reduce(
46
+ (previousValue, currentValue) => previousValue + currentValue.quantity,
47
+ 0
48
+ );
67
49
 
68
50
  return {
69
- isLoading: !!loading,
70
51
  items,
71
- hasErrors: !!error,
72
52
  totalQuantity,
73
53
  showAllItems,
74
54
  setShowAllItems: setShowAllItemsFlag,
@@ -0,0 +1,30 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const GET_ORDER_CONFIRMATION_DETAILS = gql`
4
+ query getOrderConfirmationDetails($orderNumber: String!) {
5
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
6
+ customer {
7
+ email
8
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
9
+ orders(filter: { number: { eq: $orderNumber } }) {
10
+ items {
11
+ id
12
+ shipping_address {
13
+ firstname
14
+ lastname
15
+ street
16
+ city
17
+ region
18
+ postcode
19
+ country_code
20
+ }
21
+ shipping_method
22
+ }
23
+ }
24
+ }
25
+ }
26
+ `;
27
+
28
+ export default {
29
+ getOrderConfirmationDetailsQuery: GET_ORDER_CONFIRMATION_DETAILS
30
+ };
@@ -9,6 +9,7 @@ import { useGoogleReCaptcha } from '../../../hooks/useGoogleReCaptcha';
9
9
 
10
10
  import DEFAULT_OPERATIONS from './createAccount.gql';
11
11
  import { useEventingContext } from '../../../context/eventing';
12
+ import { useHistory } from 'react-router-dom';
12
13
 
13
14
  /**
14
15
  * Returns props necessary to render CreateAccount component. In particular this
@@ -95,6 +96,7 @@ export const useCreateAccount = props => {
95
96
  formAction: 'createAccount'
96
97
  });
97
98
 
99
+ const history = useHistory();
98
100
  const handleSubmit = useCallback(
99
101
  async formValues => {
100
102
  setIsSubmitting(true);
@@ -158,6 +160,8 @@ export const useCreateAccount = props => {
158
160
  if (onSubmit) {
159
161
  onSubmit();
160
162
  }
163
+
164
+ history.push('/account-information');
161
165
  } catch (error) {
162
166
  if (process.env.NODE_ENV !== 'production') {
163
167
  console.error(error);
@@ -179,7 +183,8 @@ export const useCreateAccount = props => {
179
183
  removeCart,
180
184
  setToken,
181
185
  signIn,
182
- dispatch
186
+ dispatch,
187
+ history
183
188
  ]
184
189
  );
185
190
 
@@ -1,6 +1,16 @@
1
+ import { useEffect } from 'react';
1
2
  import { useUserContext } from '../../../context/user';
3
+ import { setUserOnOrderSuccess } from '../../../store/actions/user/asyncActions';
4
+ import { useLazyQuery } from '@apollo/client';
2
5
 
3
- export const flatten = data => {
6
+ import mergeOperations from '../../../util/shallowMerge';
7
+ import DEFAULT_OPERATIONS from './orderConfirmationPage.gql';
8
+ import { useDispatch } from 'react-redux';
9
+
10
+ export const flattenGuestCartData = data => {
11
+ if (!data) {
12
+ return;
13
+ }
4
14
  const { cart } = data;
5
15
  const { shipping_addresses } = cart;
6
16
  const address = shipping_addresses[0];
@@ -18,17 +28,79 @@ export const flatten = data => {
18
28
  postcode: address.postcode,
19
29
  region: address.region.label,
20
30
  shippingMethod,
31
+ street: address.street
32
+ };
33
+ };
34
+
35
+ export const flattenCustomerOrderData = data => {
36
+ if (!data) {
37
+ return;
38
+ }
39
+
40
+ const { customer } = data;
41
+ const order = customer?.orders?.items?.[0];
42
+ if (!order || !order.shipping_address) {
43
+ // Return an empty response if no valid order or shipping address exists
44
+ return;
45
+ }
46
+ const { shipping_address: address } = order;
47
+
48
+ return {
49
+ city: address.city,
50
+ country: address.country_code,
51
+ email: customer.email,
52
+ firstname: address.firstname,
53
+ lastname: address.lastname,
54
+ postcode: address.postcode,
55
+ region: address.region,
21
56
  street: address.street,
22
- totalItemQuantity: cart.total_quantity
57
+ shippingMethod: order.shipping_method
23
58
  };
24
59
  };
25
60
 
26
61
  export const useOrderConfirmationPage = props => {
27
- const { data } = props;
62
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
63
+ const { getOrderConfirmationDetailsQuery } = operations;
64
+
28
65
  const [{ isSignedIn }] = useUserContext();
29
66
 
67
+ const [
68
+ fetchOrderConfirmationDetails,
69
+ { data: queryData, error, loading }
70
+ ] = useLazyQuery(getOrderConfirmationDetailsQuery);
71
+
72
+ const flatData =
73
+ flattenGuestCartData(props.data) || flattenCustomerOrderData(queryData);
74
+
75
+ const dispatch = useDispatch();
76
+
77
+ useEffect(() => {
78
+ if (props.orderNumber && !props.data) {
79
+ const orderNumber = props.orderNumber;
80
+ fetchOrderConfirmationDetails({
81
+ variables: {
82
+ orderNumber
83
+ }
84
+ });
85
+ }
86
+
87
+ dispatch(setUserOnOrderSuccess(true));
88
+
89
+ return () => {
90
+ // Reset the flag when leaving the page
91
+ dispatch(setUserOnOrderSuccess(false));
92
+ };
93
+ }, [
94
+ props.orderNumber,
95
+ props.data,
96
+ fetchOrderConfirmationDetails,
97
+ dispatch
98
+ ]);
99
+
30
100
  return {
31
- flatData: flatten(data),
32
- isSignedIn
101
+ flatData,
102
+ isSignedIn,
103
+ error,
104
+ loading
33
105
  };
34
106
  };
@@ -7,6 +7,8 @@ import {
7
7
  } from '@apollo/client';
8
8
  import { useEventingContext } from '../../context/eventing';
9
9
 
10
+ import { useHistory } from 'react-router-dom';
11
+
10
12
  import { useUserContext } from '../../context/user';
11
13
  import { useCartContext } from '../../context/cart';
12
14
 
@@ -68,8 +70,8 @@ export const CHECKOUT_STEP = {
68
70
  * }
69
71
  */
70
72
  export const useCheckoutPage = (props = {}) => {
73
+ const history = useHistory();
71
74
  const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
72
-
73
75
  const {
74
76
  createCartMutation,
75
77
  getCheckoutDetailsQuery,
@@ -249,6 +251,7 @@ export const useCheckoutPage = (props = {}) => {
249
251
  });
250
252
  setPlaceOrderButtonClicked(true);
251
253
  setIsPlacingOrder(true);
254
+ localStorage.setItem('orderCount', '1');
252
255
  }, [cartId, getOrderDetails]);
253
256
 
254
257
  const handlePlaceOrderEnterKeyPress = useCallback(() => {
@@ -383,6 +386,16 @@ export const useCheckoutPage = (props = {}) => {
383
386
  isPlacingOrder,
384
387
  reviewOrderButtonClicked
385
388
  ]);
389
+ useEffect(() => {
390
+ if (isSignedIn && placeOrderData) {
391
+ history.push('/order-confirmation', {
392
+ orderNumber: placeOrderData.placeOrder.order.order_number,
393
+ items: cartItems
394
+ });
395
+ } else if (!isSignedIn && placeOrderData) {
396
+ history.push('/checkout');
397
+ }
398
+ }, [isSignedIn, placeOrderData, cartItems, history]);
386
399
 
387
400
  return {
388
401
  activeContent,
@@ -24,6 +24,7 @@ export const CREATE_ACCOUNT = gql`
24
24
  # eslint-disable-next-line @graphql-eslint/require-id-when-available
25
25
  customer {
26
26
  email
27
+ is_confirmed
27
28
  }
28
29
  }
29
30
  }
@@ -10,6 +10,12 @@ import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha';
10
10
 
11
11
  import DEFAULT_OPERATIONS from './createAccount.gql';
12
12
  import { useEventingContext } from '../../context/eventing';
13
+ import { useHistory, useLocation } from 'react-router-dom';
14
+
15
+ /**
16
+ * Routes to redirect from if used to create an account.
17
+ */
18
+ const REDIRECT_FOR_ROUTES = ['/checkout', '/order-confirmation'];
13
19
 
14
20
  /**
15
21
  * Returns props necessary to render CreateAccount component. In particular this
@@ -47,7 +53,7 @@ export const useCreateAccount = props => {
47
53
  { createCart, removeCart, getCartDetails }
48
54
  ] = useCartContext();
49
55
  const [
50
- { isGettingDetails },
56
+ { isGettingDetails, userOnOrderSuccess },
51
57
  { getUserDetails, setToken }
52
58
  ] = useUserContext();
53
59
 
@@ -112,6 +118,9 @@ export const useCreateAccount = props => {
112
118
  };
113
119
  }, [handleCancel]);
114
120
 
121
+ const history = useHistory();
122
+ const location = useLocation();
123
+
115
124
  const handleSubmit = useCallback(
116
125
  async formValues => {
117
126
  setIsSubmitting(true);
@@ -188,6 +197,13 @@ export const useCreateAccount = props => {
188
197
  if (onSubmit) {
189
198
  onSubmit();
190
199
  }
200
+
201
+ if (
202
+ userOnOrderSuccess &&
203
+ REDIRECT_FOR_ROUTES.includes(location.pathname)
204
+ ) {
205
+ history.push('/account-information');
206
+ }
191
207
  } catch (error) {
192
208
  if (process.env.NODE_ENV !== 'production') {
193
209
  console.error(error);
@@ -213,7 +229,10 @@ export const useCreateAccount = props => {
213
229
  getCartDetails,
214
230
  fetchCartDetails,
215
231
  onSubmit,
216
- dispatch
232
+ dispatch,
233
+ history,
234
+ location.pathname,
235
+ userOnOrderSuccess
217
236
  ]
218
237
  );
219
238
 
@@ -54,7 +54,7 @@ export const getStateFromSearch = (initialValue, filterKeys, filterItems) => {
54
54
 
55
55
  if (existingFilter) {
56
56
  items.add(existingFilter);
57
- } else {
57
+ } else if (group !== 'price') {
58
58
  console.warn(
59
59
  `Existing filter ${value} not found in possible filters`
60
60
  );
@@ -1,13 +1,20 @@
1
1
  import { useCallback, useState, useEffect, useMemo } from 'react';
2
+ import { useLocation } from 'react-router-dom';
2
3
 
3
4
  export const useFilterBlock = props => {
4
- const { filterState, items, initialOpen } = props;
5
+ const { filterState, items, initialOpen, group } = props;
6
+ const location = useLocation();
5
7
 
6
8
  const hasSelected = useMemo(() => {
9
+ const params = new URLSearchParams(location.search);
10
+ //expansion of price filter dropdown
11
+ if (group == 'price') {
12
+ return params.get('price[filter]') ? true : false;
13
+ }
7
14
  return items.some(item => {
8
15
  return filterState && filterState.has(item);
9
16
  });
10
- }, [filterState, items]);
17
+ }, [filterState, items, group, location.search]);
11
18
 
12
19
  const [isExpanded, setExpanded] = useState(hasSelected || initialOpen);
13
20
 
@@ -182,9 +182,10 @@ export const useFilterSidebar = props => {
182
182
  }, [handleClose]);
183
183
 
184
184
  const handleReset = useCallback(() => {
185
- filterApi.clear();
186
- setIsApplying(true);
187
- }, [filterApi, setIsApplying]);
185
+ //filterApi.clear();
186
+ //setIsApplying(true);
187
+ history.replace({ search: 'page=1' });
188
+ }, [history]);
188
189
 
189
190
  const handleKeyDownActions = useCallback(
190
191
  event => {
@@ -14,7 +14,23 @@ export const useFormError = props => {
14
14
  defaultMessage:
15
15
  'An error has occurred. Please check the input and try again.'
16
16
  });
17
- return deriveErrorMessage(errors, defaultErrorMessage);
17
+
18
+ const firstError = errors
19
+ .filter(error => error !== null || undefined)
20
+ .map(error => (Array.isArray(error) ? error[0] : error))
21
+ .find(message => message);
22
+ var graphqlErrorMessage;
23
+
24
+ if (firstError) {
25
+ graphqlErrorMessage = formatMessage({
26
+ id: 'formError.responseError',
27
+ defaultMessage: firstError.message
28
+ });
29
+ }
30
+
31
+ return graphqlErrorMessage
32
+ ? deriveErrorMessage(errors, graphqlErrorMessage)
33
+ : deriveErrorMessage(errors, defaultErrorMessage);
18
34
  }, [errors, formatMessage, allowErrorMessages]);
19
35
 
20
36
  return {
@@ -11,7 +11,7 @@ export const GET_CONFIGURABLE_THUMBNAIL_SOURCE = gql`
11
11
  `;
12
12
 
13
13
  export const GET_PRODUCT_THUMBNAILS_BY_URL_KEY = gql`
14
- query GetProductThumbnailsByURLKey($urlKeys: [String!]!) {
14
+ query GetProductThumbnailsByURLKey($urlKeys: [String]!) {
15
15
  products(filter: { url_key: { in: $urlKeys } }) {
16
16
  # eslint-disable-next-line @graphql-eslint/require-id-when-available
17
17
  items {
@@ -1,10 +1,8 @@
1
1
  import { gql } from '@apollo/client';
2
2
 
3
3
  export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql`
4
- query getProductFiltersByCategory(
5
- $categoryIdFilter: FilterEqualTypeInput!
6
- ) {
7
- products(filter: { category_uid: $categoryIdFilter }) {
4
+ query getProductFiltersByCategory($filters: ProductAttributeFilterInput!) {
5
+ products(filter: $filters) {
8
6
  aggregations {
9
7
  label
10
8
  count
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useState, useMemo } from 'react';
2
2
  import { useLazyQuery, useQuery } from '@apollo/client';
3
3
 
4
4
  import mergeOperations from '../../../util/shallowMerge';
@@ -29,6 +29,92 @@ export const useCategoryContent = props => {
29
29
  getCategoryAvailableSortMethodsQuery
30
30
  } = operations;
31
31
 
32
+ const [
33
+ getFiltersAttributeCode,
34
+ { data: filterAttributeData }
35
+ ] = useLazyQuery(getProductFiltersByCategoryQuery, {
36
+ fetchPolicy: 'cache-and-network',
37
+ nextFetchPolicy: 'cache-first'
38
+ });
39
+
40
+ useEffect(() => {
41
+ if (categoryId) {
42
+ getFiltersAttributeCode({
43
+ variables: {
44
+ filters: {
45
+ category_uid: { eq: categoryId }
46
+ }
47
+ }
48
+ });
49
+ }
50
+ }, [categoryId, getFiltersAttributeCode]);
51
+
52
+ const availableFilterData = filterAttributeData
53
+ ? filterAttributeData.products?.aggregations
54
+ : null;
55
+ const availableFilters = availableFilterData
56
+ ?.map(eachitem => eachitem.attribute_code)
57
+ ?.sort();
58
+
59
+ const handlePriceFilter = priceFilter => {
60
+ if (priceFilter && priceFilter.size > 0) {
61
+ for (const price of priceFilter) {
62
+ const [from, to] = price.value.split('_');
63
+ return { price: { from, to } };
64
+ }
65
+ }
66
+ return {};
67
+ };
68
+
69
+ const [filterOptions, setFilterOptions] = useState();
70
+
71
+ const selectedFilters = useMemo(() => {
72
+ const filters = {};
73
+ if (filterOptions) {
74
+ for (const [group, items] of filterOptions.entries()) {
75
+ availableFilters?.map(eachitem => {
76
+ if (eachitem === group && group !== 'price') {
77
+ const sampleArray = [];
78
+ for (const item of items) {
79
+ sampleArray.push(item.value);
80
+ }
81
+ filters[group] = sampleArray;
82
+ }
83
+ });
84
+ }
85
+ }
86
+
87
+ if (filterOptions && filterOptions.has('price')) {
88
+ const priceFilter = filterOptions.get('price');
89
+ const priceRange = handlePriceFilter(priceFilter);
90
+ if (priceRange.price) {
91
+ filters.price = priceRange.price;
92
+ }
93
+ }
94
+
95
+ return filters;
96
+ }, [filterOptions, availableFilters]);
97
+
98
+ const dynamicQueryVariables = useMemo(() => {
99
+ const generateDynamicFiltersQuery = filterParams => {
100
+ let filterConditions = {
101
+ category_uid: { eq: categoryId }
102
+ };
103
+
104
+ Object.keys(filterParams).forEach(key => {
105
+ let filter = {};
106
+ if (key !== 'price') {
107
+ filter = { [key]: { in: filterParams[key] } };
108
+ }
109
+ filterConditions = { ...filterConditions, ...filter };
110
+ });
111
+
112
+ return filterConditions;
113
+ };
114
+
115
+ return generateDynamicFiltersQuery(selectedFilters);
116
+ }, [selectedFilters, categoryId]);
117
+
32
118
  const placeholderItems = Array.from({ length: pageSize }).fill(null);
33
119
 
34
120
  const [getFilters, { data: filterData }] = useLazyQuery(
@@ -60,18 +146,26 @@ export const useCategoryContent = props => {
60
146
  );
61
147
 
62
148
  const [, { dispatch }] = useEventingContext();
63
-
149
+ const [previousFilters, setPreviousFilters] = useState(null);
64
150
  useEffect(() => {
65
- if (categoryId) {
151
+ if (
152
+ categoryId &&
153
+ JSON.stringify(selectedFilters) !== JSON.stringify(previousFilters)
154
+ ) {
66
155
  getFilters({
67
156
  variables: {
68
- categoryIdFilter: {
69
- eq: categoryId
70
- }
157
+ filters: dynamicQueryVariables
71
158
  }
72
159
  });
160
+ setPreviousFilters(selectedFilters);
73
161
  }
74
- }, [categoryId, getFilters]);
162
+ }, [
163
+ categoryId,
164
+ selectedFilters,
165
+ dynamicQueryVariables,
166
+ previousFilters,
167
+ getFilters
168
+ ]);
75
169
 
76
170
  useEffect(() => {
77
171
  if (categoryId) {
@@ -85,7 +179,7 @@ export const useCategoryContent = props => {
85
179
  }
86
180
  }, [categoryId, getSortMethods]);
87
181
 
88
- const filters = filterData ? filterData.products.aggregations : null;
182
+ const filters = filterData ? filterData.products?.aggregations : null;
89
183
  const items = data ? data.products.items : placeholderItems;
90
184
  const totalPagesFromData = data
91
185
  ? data.products.page_info.total_pages
@@ -104,7 +198,7 @@ export const useCategoryContent = props => {
104
198
  : null;
105
199
 
106
200
  useEffect(() => {
107
- if (!categoryLoading && categoryData.categories.items.length > 0) {
201
+ if (!categoryLoading && categoryData?.categories.items.length > 0) {
108
202
  dispatch({
109
203
  type: 'CATEGORY_PAGE_VIEW',
110
204
  payload: {
@@ -122,6 +216,8 @@ export const useCategoryContent = props => {
122
216
  categoryName,
123
217
  categoryDescription,
124
218
  filters,
219
+ filterOptions,
220
+ setFilterOptions,
125
221
  items,
126
222
  totalCount,
127
223
  totalPagesFromData
@@ -10,10 +10,15 @@ import { retrieveCartId } from '../../store/actions/cart';
10
10
 
11
11
  import DEFAULT_OPERATIONS from './signIn.gql';
12
12
  import { useEventingContext } from '../../context/eventing';
13
+ import { useHistory, useLocation } from 'react-router-dom';
14
+
15
+ /**
16
+ * Routes to redirect from if used to create an account.
17
+ */
18
+ const REDIRECT_FOR_ROUTES = ['/checkout', '/order-confirmation'];
13
19
 
14
20
  export const useSignIn = props => {
15
21
  const {
16
- handleTriggerClick,
17
22
  getCartDetailsQuery,
18
23
  setDefaultUsername,
19
24
  showCreateAccount,
@@ -40,7 +45,7 @@ export const useSignIn = props => {
40
45
 
41
46
  const userContext = useUserContext();
42
47
  const [
43
- { isGettingDetails, getDetailsError },
48
+ { isGettingDetails, getDetailsError, userOnOrderSuccess },
44
49
  { getUserDetails, setToken }
45
50
  ] = userContext;
46
51
 
@@ -83,10 +88,12 @@ export const useSignIn = props => {
83
88
  const formApiRef = useRef(null);
84
89
  const setFormApi = useCallback(api => (formApiRef.current = api), []);
85
90
 
91
+ const history = useHistory();
92
+ const location = useLocation();
93
+
86
94
  const handleSubmit = useCallback(
87
95
  async ({ email, password }) => {
88
96
  setIsSigningIn(true);
89
- handleTriggerClick();
90
97
 
91
98
  try {
92
99
  // Get source cart id (guest cart id).
@@ -144,6 +151,13 @@ export const useSignIn = props => {
144
151
  });
145
152
 
146
153
  getCartDetails({ fetchCartId, fetchCartDetails });
154
+
155
+ if (
156
+ userOnOrderSuccess &&
157
+ REDIRECT_FOR_ROUTES.includes(location.pathname)
158
+ ) {
159
+ history.push('/order-history');
160
+ }
147
161
  } catch (error) {
148
162
  if (process.env.NODE_ENV !== 'production') {
149
163
  console.error(error);
@@ -168,7 +182,9 @@ export const useSignIn = props => {
168
182
  getCartDetails,
169
183
  fetchCartDetails,
170
184
  dispatch,
171
- handleTriggerClick
185
+ history,
186
+ location.pathname,
187
+ userOnOrderSuccess
172
188
  ]
173
189
  );
174
190
 
@@ -1,6 +1,6 @@
1
1
  import makeUrl from './makeUrl';
2
2
  import resolveLinkProps from './resolveLinkProps';
3
-
3
+ import DOMPurify from 'dompurify';
4
4
  /**
5
5
  * Modifies html string images to use makeUrl as source and resolves links to use internal path.
6
6
  *
@@ -9,7 +9,7 @@ import resolveLinkProps from './resolveLinkProps';
9
9
  */
10
10
  const htmlStringImgUrlConverter = htmlString => {
11
11
  const temporaryElement = document.createElement('div');
12
- temporaryElement.innerHTML = htmlString;
12
+ temporaryElement.innerHTML = DOMPurify.sanitize(htmlString);
13
13
  for (const imgElement of temporaryElement.getElementsByTagName('img')) {
14
14
  imgElement.src = makeUrl(imgElement.src, {
15
15
  type: 'image-wysiwyg',
@@ -1,4 +1,4 @@
1
1
  // 4x5 transparent svg
2
2
  // svg source = <svg xmlns='http://www.w3.org/2000/svg' width='4' height='5'><rect width='4' height='5' style='fill: none' /></svg>
3
3
  export const transparentPlaceholder =
4
- 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc0JyBoZWlnaHQ9JzUnPjxyZWN0IHdpZHRoPSc0JyBoZWlnaHQ9JzUnIHN0eWxlPSdmaWxsOiBub25lJyAvPjwvc3ZnPg==';
4
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0IiBoZWlnaHQ9IjUiIHZpZXdCb3g9Ii0yNSAtMjUgMzEyIDMxMiI+CiAgICA8ZyBpZD0iTGF5ZXJfMi0yIj4KICAgICAgICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0iZmlsbDogI2YwZjBmMDsgZmlsbC1ydWxlOiBldmVub2RkOyIgZD0iTTM2LDE4OS40di0xMDkuMWMwLTAuNywwLjQtMS42LDEuMS0ybDk0LjctNTcuMWMwLjctMC41LDEuNy0wLjUsMi41LDBsOTEuNSw1N2MwLjcsMC40LDEuMSwxLjEsMS4xLDJ2MTA3LjljMCwwLjctMC40LDEuNS0xLjEsMmwtMTkuMywxMi42Yy0xLjUsMS0zLjYsMC0zLjYtMnYtMTA1LjljMC0wLjctMC40LTEuNi0xLjEtMmwtNjcuNS00MS4zYy0wLjctMC41LTEuNi0wLjUtMi4zLDBsLTY5LjYsNDEuM2MtMC43LDAuNC0xLjEsMS4xLTEuMSwydjEwNS45YzAsMS45LTIsMy0zLjYsMmwtMjEuNS0xMy41aDB2LjJaTTg2LDExMC45djEwNi4zYzAsMC45LDAuNCwxLjYsMS4xLDJsNDQuNSwyNi43YzAuNywwLjUsMS43LDAuNCwyLjUsMGw0Mi41LTI2LjdjMC43LTAuNCwxLjEtMS4xLDEuMS0ydi0xMDUuOGMwLTAuNy0wLjQtMS41LTEuMS0ybC0yOC43LTE3LjljLTEuNS0xLTMuNiwwLjEtMy42LDJ2MTIxLjZjMCwwLjctMC40LDEuNS0xLjEsMmwtOS4zLDUuOWMtMC43LDAuNS0xLjYsMC41LTIuMywwbC0xMS4xLTUuOWMtMC43LTAuNC0xLjItMS4yLTEuMi0yLjF2LTEyMS43YzAtMS43LTItMy0zLjUtMmwtMjguOCwxNi4yYy0wLjcsMC40LTEuMSwxLjEtMS4xLDJ2MS41aC4xWiIvPgogICAgPC9nPgo8L3N2Zz4K';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magento/peregrine",
3
- "version": "14.4.1",
3
+ "version": "14.5.1-alpha.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },