@magento/peregrine 12.5.1-beta.1 → 12.6.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.
- package/lib/talons/AddToCartDialog/useAddToCartDialog.js +77 -11
- package/lib/talons/CartPage/ProductListing/EditModal/productFormFragment.gql.js +1 -0
- package/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js +80 -4
- package/lib/talons/CartPage/ProductListing/productListingFragments.gql.js +4 -0
- package/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js +24 -1
- package/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js +39 -3
- package/lib/talons/ProductFullDetail/useProductFullDetail.js +60 -1
- package/lib/talons/ProductOptions/useOptions.js +7 -3
- package/lib/talons/WishlistPage/wishlistItemFragments.gql.js +15 -0
- package/lib/util/createProductVariants.js +91 -0
- package/lib/util/findAllMatchingVariants.js +34 -0
- package/lib/util/getCombinations.js +12 -0
- package/lib/util/getOutOfStockIndexes.js +13 -0
- package/lib/util/getOutOfStockVariants.js +115 -0
- package/lib/util/getOutOfStockVariantsWithInitialSelection.js +63 -0
- package/lib/util/isProductConfigurable.js +1 -1
- package/package.json +1 -1
|
@@ -5,10 +5,12 @@ import mergeOperations from '../../util/shallowMerge';
|
|
|
5
5
|
import { useCartContext } from '../../context/cart';
|
|
6
6
|
import defaultOperations from './addToCartDialog.gql';
|
|
7
7
|
import { useEventingContext } from '../../context/eventing';
|
|
8
|
+
import { isProductConfigurable } from '@magento/peregrine/lib/util/isProductConfigurable';
|
|
9
|
+
import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants';
|
|
8
10
|
|
|
9
11
|
export const useAddToCartDialog = props => {
|
|
10
12
|
const { item, onClose } = props;
|
|
11
|
-
const sku = item && item.product
|
|
13
|
+
const sku = item && item.product?.sku;
|
|
12
14
|
|
|
13
15
|
const [, { dispatch }] = useEventingContext();
|
|
14
16
|
|
|
@@ -18,9 +20,58 @@ export const useAddToCartDialog = props => {
|
|
|
18
20
|
const [currentImage, setCurrentImage] = useState();
|
|
19
21
|
const [currentPrice, setCurrentPrice] = useState();
|
|
20
22
|
const [currentDiscount, setCurrentDiscount] = useState();
|
|
23
|
+
const [singleOptionSelection, setSingleOptionSelection] = useState();
|
|
24
|
+
const [multipleOptionSelections, setMultipleOptionSelections] = useState(
|
|
25
|
+
new Map()
|
|
26
|
+
);
|
|
21
27
|
|
|
22
28
|
const [{ cartId }] = useCartContext();
|
|
23
29
|
|
|
30
|
+
const optionCodes = useMemo(() => {
|
|
31
|
+
const optionCodeMap = new Map();
|
|
32
|
+
if (item) {
|
|
33
|
+
item.product?.configurable_options.forEach(option => {
|
|
34
|
+
optionCodeMap.set(option.attribute_id, option.attribute_code);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return optionCodeMap;
|
|
38
|
+
}, [item]);
|
|
39
|
+
|
|
40
|
+
// Check if display out of stock products option is selected in the Admin Dashboard
|
|
41
|
+
const isOutOfStockProductDisplayed = useMemo(() => {
|
|
42
|
+
if (item) {
|
|
43
|
+
let totalVariants = 1;
|
|
44
|
+
const { product } = item;
|
|
45
|
+
const isConfigurable = isProductConfigurable(product);
|
|
46
|
+
if (product?.configurable_options && isConfigurable) {
|
|
47
|
+
for (const option of product.configurable_options) {
|
|
48
|
+
const length = option.values.length;
|
|
49
|
+
totalVariants = totalVariants * length;
|
|
50
|
+
}
|
|
51
|
+
return product.variants.length === totalVariants;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [item]);
|
|
55
|
+
|
|
56
|
+
const outOfStockVariants = useMemo(() => {
|
|
57
|
+
if (item) {
|
|
58
|
+
const product = item.product;
|
|
59
|
+
return getOutOfStockVariants(
|
|
60
|
+
product,
|
|
61
|
+
optionCodes,
|
|
62
|
+
singleOptionSelection,
|
|
63
|
+
multipleOptionSelections,
|
|
64
|
+
isOutOfStockProductDisplayed
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}, [
|
|
68
|
+
item,
|
|
69
|
+
optionCodes,
|
|
70
|
+
singleOptionSelection,
|
|
71
|
+
multipleOptionSelections,
|
|
72
|
+
isOutOfStockProductDisplayed
|
|
73
|
+
]);
|
|
74
|
+
|
|
24
75
|
const selectedOptionsArray = useMemo(() => {
|
|
25
76
|
if (item) {
|
|
26
77
|
const existingOptionsMap = item.configurable_options.reduce(
|
|
@@ -39,14 +90,14 @@ export const useAddToCartDialog = props => {
|
|
|
39
90
|
|
|
40
91
|
const selectedOptions = [];
|
|
41
92
|
mergedOptionsMap.forEach((selectedValueId, attributeId) => {
|
|
42
|
-
const configurableOption = item.product
|
|
93
|
+
const configurableOption = item.product?.configurable_options.find(
|
|
43
94
|
option => option.attribute_id_v2 === attributeId
|
|
44
95
|
);
|
|
45
|
-
const configurableOptionValue = configurableOption
|
|
96
|
+
const configurableOptionValue = configurableOption?.values.find(
|
|
46
97
|
optionValue => optionValue.value_index === selectedValueId
|
|
47
98
|
);
|
|
48
99
|
|
|
49
|
-
selectedOptions.push(configurableOptionValue
|
|
100
|
+
selectedOptions.push(configurableOptionValue?.uid);
|
|
50
101
|
});
|
|
51
102
|
|
|
52
103
|
return selectedOptions;
|
|
@@ -107,13 +158,27 @@ export const useAddToCartDialog = props => {
|
|
|
107
158
|
setCurrentImage();
|
|
108
159
|
setCurrentPrice();
|
|
109
160
|
setUserSelectedOptions(new Map());
|
|
161
|
+
setMultipleOptionSelections(new Map());
|
|
110
162
|
}, [onClose]);
|
|
111
163
|
|
|
112
|
-
const handleOptionSelection = useCallback(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
const handleOptionSelection = useCallback(
|
|
165
|
+
(optionId, value) => {
|
|
166
|
+
setUserSelectedOptions(existing =>
|
|
167
|
+
new Map(existing).set(parseInt(optionId), value)
|
|
168
|
+
);
|
|
169
|
+
// Create a new Map to keep track of user single selection with key as String
|
|
170
|
+
const nextSingleOptionSelection = new Map();
|
|
171
|
+
nextSingleOptionSelection.set(optionId, value);
|
|
172
|
+
setSingleOptionSelection(nextSingleOptionSelection);
|
|
173
|
+
// Create a new Map to keep track of multiple selections with key as String
|
|
174
|
+
const nextMultipleOptionSelections = new Map([
|
|
175
|
+
...multipleOptionSelections
|
|
176
|
+
]);
|
|
177
|
+
nextMultipleOptionSelections.set(optionId, value);
|
|
178
|
+
setMultipleOptionSelections(nextMultipleOptionSelections);
|
|
179
|
+
},
|
|
180
|
+
[multipleOptionSelections]
|
|
181
|
+
);
|
|
117
182
|
|
|
118
183
|
const handleAddToCart = useCallback(async () => {
|
|
119
184
|
try {
|
|
@@ -192,7 +257,7 @@ export const useAddToCartDialog = props => {
|
|
|
192
257
|
if (item) {
|
|
193
258
|
return {
|
|
194
259
|
onSelectionChange: handleOptionSelection,
|
|
195
|
-
options: item.product
|
|
260
|
+
options: item.product?.configurable_options,
|
|
196
261
|
selectedValues: item.configurable_options
|
|
197
262
|
};
|
|
198
263
|
}
|
|
@@ -202,7 +267,7 @@ export const useAddToCartDialog = props => {
|
|
|
202
267
|
if (item) {
|
|
203
268
|
return {
|
|
204
269
|
disabled:
|
|
205
|
-
item.product
|
|
270
|
+
item.product?.configurable_options.length !==
|
|
206
271
|
selectedOptionsArray.length || isAddingToCart,
|
|
207
272
|
onClick: handleAddToCart,
|
|
208
273
|
priority: 'high'
|
|
@@ -215,6 +280,7 @@ export const useAddToCartDialog = props => {
|
|
|
215
280
|
configurableOptionProps,
|
|
216
281
|
formErrors: [addProductToCartError],
|
|
217
282
|
handleOnClose,
|
|
283
|
+
outOfStockVariants,
|
|
218
284
|
imageProps,
|
|
219
285
|
isFetchingProductDetail,
|
|
220
286
|
priceProps
|
|
@@ -6,6 +6,7 @@ import { useCartContext } from '../../../../context/cart';
|
|
|
6
6
|
import { findMatchingVariant } from '../../../../util/findMatchingProductVariant';
|
|
7
7
|
import DEFAULT_OPERATIONS from './productForm.gql';
|
|
8
8
|
import { useEventingContext } from '../../../../context/eventing';
|
|
9
|
+
import { getOutOfStockVariantsWithInitialSelection } from '../../../../util/getOutOfStockVariantsWithInitialSelection';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* This talon contains logic for a product edit form.
|
|
@@ -32,6 +33,18 @@ import { useEventingContext } from '../../../../context/eventing';
|
|
|
32
33
|
* @example <caption>Importing into your project</caption>
|
|
33
34
|
* import { useProductForm } from '@magento/peregrine/lib/talons/CartPage/ProductListing/EditModal/useProductForm';
|
|
34
35
|
*/
|
|
36
|
+
|
|
37
|
+
// Get initial selections
|
|
38
|
+
function deriveOptionSelectionsFromProduct(cartItem) {
|
|
39
|
+
if (cartItem) {
|
|
40
|
+
const initialOptionSelections = new Map();
|
|
41
|
+
for (const { id, value_id } of cartItem.configurable_options) {
|
|
42
|
+
initialOptionSelections.set(String(id), value_id);
|
|
43
|
+
}
|
|
44
|
+
return initialOptionSelections;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
export const useProductForm = props => {
|
|
36
49
|
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
37
50
|
|
|
@@ -52,11 +65,28 @@ export const useProductForm = props => {
|
|
|
52
65
|
const [, { dispatch }] = useEventingContext();
|
|
53
66
|
|
|
54
67
|
const [{ cartId }] = useCartContext();
|
|
68
|
+
|
|
69
|
+
const derivedOptionSelections = useMemo(() => {
|
|
70
|
+
if (cartItem) {
|
|
71
|
+
return deriveOptionSelectionsFromProduct(cartItem);
|
|
72
|
+
}
|
|
73
|
+
}, [cartItem]);
|
|
74
|
+
|
|
55
75
|
const [optionSelections, setOptionSelections] = useState(new Map());
|
|
76
|
+
const [multipleOptionSelections, setMultipleOptionSelections] = useState(
|
|
77
|
+
derivedOptionSelections ? derivedOptionSelections : new Map()
|
|
78
|
+
);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (cartItem) {
|
|
81
|
+
setMultipleOptionSelections(derivedOptionSelections);
|
|
82
|
+
}
|
|
83
|
+
}, [derivedOptionSelections, cartItem]);
|
|
56
84
|
|
|
57
85
|
const handleClose = useCallback(() => {
|
|
86
|
+
setMultipleOptionSelections(new Map());
|
|
87
|
+
setOptionSelections(new Map());
|
|
58
88
|
setActiveEditItem(null);
|
|
59
|
-
}, [setActiveEditItem]);
|
|
89
|
+
}, [setActiveEditItem, setMultipleOptionSelections, setOptionSelections]);
|
|
60
90
|
|
|
61
91
|
const [
|
|
62
92
|
updateItemQuantity,
|
|
@@ -106,20 +136,41 @@ export const useProductForm = props => {
|
|
|
106
136
|
option => option.id == optionId
|
|
107
137
|
);
|
|
108
138
|
|
|
109
|
-
if (initialSelection
|
|
139
|
+
if (initialSelection?.value_id === selection) {
|
|
110
140
|
nextOptionSelections.delete(optionId);
|
|
111
141
|
} else {
|
|
112
142
|
nextOptionSelections.set(optionId, selection);
|
|
113
143
|
}
|
|
114
144
|
|
|
115
145
|
setOptionSelections(nextOptionSelections);
|
|
146
|
+
|
|
147
|
+
// Create a new Map to only keep track of user multiple selections with key as String
|
|
148
|
+
// without considering initialSelection.value_id
|
|
149
|
+
const nextMultipleOptionSelections = new Map([
|
|
150
|
+
...multipleOptionSelections
|
|
151
|
+
]);
|
|
152
|
+
nextMultipleOptionSelections.set(optionId, selection);
|
|
153
|
+
setMultipleOptionSelections(nextMultipleOptionSelections);
|
|
116
154
|
},
|
|
117
|
-
[cartItem, optionSelections]
|
|
155
|
+
[cartItem, optionSelections, multipleOptionSelections]
|
|
118
156
|
);
|
|
119
157
|
|
|
120
158
|
const configItem =
|
|
121
159
|
!loading && !error && data ? data.products.items[0] : null;
|
|
122
160
|
|
|
161
|
+
// Check if display out of stock products option is selected in the Admin Dashboard
|
|
162
|
+
const isOutOfStockProductDisplayed = useMemo(() => {
|
|
163
|
+
let totalVariants = 1;
|
|
164
|
+
|
|
165
|
+
if (configItem && configItem.configurable_options) {
|
|
166
|
+
for (const option of configItem.configurable_options) {
|
|
167
|
+
const length = option.values.length;
|
|
168
|
+
totalVariants = totalVariants * length;
|
|
169
|
+
}
|
|
170
|
+
return configItem.variants.length === totalVariants;
|
|
171
|
+
}
|
|
172
|
+
}, [configItem]);
|
|
173
|
+
|
|
123
174
|
const configurableOptionCodes = useMemo(() => {
|
|
124
175
|
const optionCodeMap = new Map();
|
|
125
176
|
|
|
@@ -149,6 +200,25 @@ export const useProductForm = props => {
|
|
|
149
200
|
}
|
|
150
201
|
}, [cartItem, configItem, configurableOptionCodes, optionSelections]);
|
|
151
202
|
|
|
203
|
+
const outOfStockVariants = useMemo(() => {
|
|
204
|
+
if (cartItem && configItem) {
|
|
205
|
+
const product = cartItem.product;
|
|
206
|
+
return getOutOfStockVariantsWithInitialSelection(
|
|
207
|
+
product,
|
|
208
|
+
configurableOptionCodes,
|
|
209
|
+
multipleOptionSelections,
|
|
210
|
+
configItem,
|
|
211
|
+
isOutOfStockProductDisplayed
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}, [
|
|
215
|
+
cartItem,
|
|
216
|
+
configurableOptionCodes,
|
|
217
|
+
multipleOptionSelections,
|
|
218
|
+
configItem,
|
|
219
|
+
isOutOfStockProductDisplayed
|
|
220
|
+
]);
|
|
221
|
+
|
|
152
222
|
const configurableThumbnailSource = useMemo(() => {
|
|
153
223
|
return storeConfigData?.storeConfig?.configurable_thumbnail_source;
|
|
154
224
|
}, [storeConfigData]);
|
|
@@ -164,7 +234,10 @@ export const useProductForm = props => {
|
|
|
164
234
|
try {
|
|
165
235
|
const quantity = formValues.quantity;
|
|
166
236
|
|
|
167
|
-
if (
|
|
237
|
+
if (
|
|
238
|
+
(selectedVariant && optionSelections.size) ||
|
|
239
|
+
(selectedVariant && multipleOptionSelections.size)
|
|
240
|
+
) {
|
|
168
241
|
await updateConfigurableOptions({
|
|
169
242
|
variables: {
|
|
170
243
|
cartId,
|
|
@@ -176,6 +249,7 @@ export const useProductForm = props => {
|
|
|
176
249
|
});
|
|
177
250
|
|
|
178
251
|
setOptionSelections(new Map());
|
|
252
|
+
setMultipleOptionSelections(new Map());
|
|
179
253
|
} else if (quantity !== cartItem.quantity) {
|
|
180
254
|
await updateItemQuantity({
|
|
181
255
|
variables: {
|
|
@@ -234,6 +308,7 @@ export const useProductForm = props => {
|
|
|
234
308
|
dispatch,
|
|
235
309
|
handleClose,
|
|
236
310
|
optionSelections.size,
|
|
311
|
+
multipleOptionSelections.size,
|
|
237
312
|
selectedVariant,
|
|
238
313
|
updateConfigurableOptions,
|
|
239
314
|
updateItemQuantity
|
|
@@ -254,6 +329,7 @@ export const useProductForm = props => {
|
|
|
254
329
|
errors,
|
|
255
330
|
handleOptionSelection,
|
|
256
331
|
handleSubmit,
|
|
332
|
+
outOfStockVariants,
|
|
257
333
|
isLoading: !!loading,
|
|
258
334
|
isSaving,
|
|
259
335
|
isDialogOpen: cartItem !== null,
|
|
@@ -24,10 +24,13 @@ export const ProductListingFragment = gql`
|
|
|
24
24
|
variants {
|
|
25
25
|
attributes {
|
|
26
26
|
uid
|
|
27
|
+
code
|
|
28
|
+
value_index
|
|
27
29
|
}
|
|
28
30
|
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
29
31
|
product {
|
|
30
32
|
uid
|
|
33
|
+
stock_status
|
|
31
34
|
small_image {
|
|
32
35
|
url
|
|
33
36
|
}
|
|
@@ -61,6 +64,7 @@ export const ProductListingFragment = gql`
|
|
|
61
64
|
option_label
|
|
62
65
|
configurable_product_option_value_uid
|
|
63
66
|
value_label
|
|
67
|
+
value_id
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -8,10 +8,33 @@ export const GET_PAYMENT_METHODS = gql`
|
|
|
8
8
|
code
|
|
9
9
|
title
|
|
10
10
|
}
|
|
11
|
+
selected_payment_method {
|
|
12
|
+
code
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const SET_PAYMENT_METHOD_ON_CART = gql`
|
|
19
|
+
mutation setPaymentMethodOnCart(
|
|
20
|
+
$cartId: String!
|
|
21
|
+
$paymentMethod: PaymentMethodInput!
|
|
22
|
+
) {
|
|
23
|
+
setPaymentMethodOnCart(
|
|
24
|
+
input: { cart_id: $cartId, payment_method: $paymentMethod }
|
|
25
|
+
) {
|
|
26
|
+
cart {
|
|
27
|
+
id
|
|
28
|
+
selected_payment_method {
|
|
29
|
+
code
|
|
30
|
+
title
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
}
|
|
12
34
|
}
|
|
13
35
|
`;
|
|
14
36
|
|
|
15
37
|
export default {
|
|
16
|
-
getPaymentMethodsQuery: GET_PAYMENT_METHODS
|
|
38
|
+
getPaymentMethodsQuery: GET_PAYMENT_METHODS,
|
|
39
|
+
setPaymentMethodOnCartMutation: SET_PAYMENT_METHOD_ON_CART
|
|
17
40
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useMutation, useQuery } from '@apollo/client';
|
|
2
3
|
import useFieldState from '@magento/peregrine/lib/hooks/hook-wrappers/useInformedFieldStateWrapper';
|
|
3
4
|
import DEFAULT_OPERATIONS from './paymentMethods.gql';
|
|
4
5
|
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
@@ -7,7 +8,12 @@ import { useCartContext } from '../../../context/cart';
|
|
|
7
8
|
|
|
8
9
|
export const usePaymentMethods = props => {
|
|
9
10
|
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
10
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
getPaymentMethodsQuery,
|
|
13
|
+
setPaymentMethodOnCartMutation
|
|
14
|
+
} = operations;
|
|
15
|
+
|
|
16
|
+
const [setPaymentMethod] = useMutation(setPaymentMethodOnCartMutation);
|
|
11
17
|
|
|
12
18
|
const [{ cartId }] = useCartContext();
|
|
13
19
|
|
|
@@ -23,13 +29,43 @@ export const usePaymentMethods = props => {
|
|
|
23
29
|
const availablePaymentMethods =
|
|
24
30
|
(data && data.cart.available_payment_methods) || [];
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
// If there is one payment method, select it by default.
|
|
33
|
+
// If more than one, none should be selected by default.
|
|
34
|
+
const defaultPaymentCode =
|
|
27
35
|
(availablePaymentMethods.length && availablePaymentMethods[0].code) ||
|
|
28
36
|
null;
|
|
37
|
+
const selectedPaymentCode =
|
|
38
|
+
(data && data.cart.selected_payment_method.code) || null;
|
|
39
|
+
|
|
40
|
+
const initialSelectedMethod =
|
|
41
|
+
availablePaymentMethods.length > 1
|
|
42
|
+
? selectedPaymentCode
|
|
43
|
+
: defaultPaymentCode;
|
|
44
|
+
|
|
45
|
+
const handlePaymentMethodSelection = useCallback(
|
|
46
|
+
element => {
|
|
47
|
+
const value = element.target.value;
|
|
48
|
+
|
|
49
|
+
setPaymentMethod({
|
|
50
|
+
variables: {
|
|
51
|
+
cartId,
|
|
52
|
+
paymentMethod: {
|
|
53
|
+
code: value,
|
|
54
|
+
braintree: {
|
|
55
|
+
payment_method_nonce: value,
|
|
56
|
+
is_active_payment_token_enabler: false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
[cartId, setPaymentMethod]
|
|
63
|
+
);
|
|
29
64
|
|
|
30
65
|
return {
|
|
31
66
|
availablePaymentMethods,
|
|
32
67
|
currentSelectedPaymentMethod,
|
|
68
|
+
handlePaymentMethodSelection,
|
|
33
69
|
initialSelectedMethod,
|
|
34
70
|
isLoading: loading
|
|
35
71
|
};
|
|
@@ -12,10 +12,12 @@ import { deriveErrorMessage } from '../../util/deriveErrorMessage';
|
|
|
12
12
|
import mergeOperations from '../../util/shallowMerge';
|
|
13
13
|
import defaultOperations from './productFullDetail.gql';
|
|
14
14
|
import { useEventingContext } from '../../context/eventing';
|
|
15
|
+
import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants';
|
|
15
16
|
|
|
16
17
|
const INITIAL_OPTION_CODES = new Map();
|
|
17
18
|
const INITIAL_OPTION_SELECTIONS = new Map();
|
|
18
19
|
const OUT_OF_STOCK_CODE = 'OUT_OF_STOCK';
|
|
20
|
+
const IN_STOCK_CODE = 'IN_STOCK';
|
|
19
21
|
|
|
20
22
|
const deriveOptionCodesFromProduct = product => {
|
|
21
23
|
// If this is a simple product it has no option codes.
|
|
@@ -85,6 +87,19 @@ const getIsOutOfStock = (product, optionCodes, optionSelections) => {
|
|
|
85
87
|
}
|
|
86
88
|
return stock_status === OUT_OF_STOCK_CODE;
|
|
87
89
|
};
|
|
90
|
+
const getIsAllOutOfStock = product => {
|
|
91
|
+
const { stock_status, variants } = product;
|
|
92
|
+
const isConfigurable = isProductConfigurable(product);
|
|
93
|
+
|
|
94
|
+
if (isConfigurable) {
|
|
95
|
+
const inStockItem = variants.find(item => {
|
|
96
|
+
return item.product.stock_status === IN_STOCK_CODE;
|
|
97
|
+
});
|
|
98
|
+
return !inStockItem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return stock_status === OUT_OF_STOCK_CODE;
|
|
102
|
+
};
|
|
88
103
|
|
|
89
104
|
const getMediaGalleryEntries = (product, optionCodes, optionSelections) => {
|
|
90
105
|
let value = [];
|
|
@@ -291,6 +306,8 @@ export const useProductFullDetail = props => {
|
|
|
291
306
|
derivedOptionSelections
|
|
292
307
|
);
|
|
293
308
|
|
|
309
|
+
const [singleOptionSelection, setSingleOptionSelection] = useState();
|
|
310
|
+
|
|
294
311
|
const derivedOptionCodes = useMemo(
|
|
295
312
|
() => deriveOptionCodesFromProduct(product),
|
|
296
313
|
[product]
|
|
@@ -307,6 +324,41 @@ export const useProductFullDetail = props => {
|
|
|
307
324
|
[product, optionCodes, optionSelections]
|
|
308
325
|
);
|
|
309
326
|
|
|
327
|
+
// Check if display out of stock products option is selected in the Admin Dashboard
|
|
328
|
+
const isOutOfStockProductDisplayed = useMemo(() => {
|
|
329
|
+
let totalVariants = 1;
|
|
330
|
+
const isConfigurable = isProductConfigurable(product);
|
|
331
|
+
if (product.configurable_options && isConfigurable) {
|
|
332
|
+
for (const option of product.configurable_options) {
|
|
333
|
+
const length = option.values.length;
|
|
334
|
+
totalVariants = totalVariants * length;
|
|
335
|
+
}
|
|
336
|
+
return product.variants.length === totalVariants;
|
|
337
|
+
}
|
|
338
|
+
}, [product]);
|
|
339
|
+
|
|
340
|
+
const isEverythingOutOfStock = useMemo(() => getIsAllOutOfStock(product), [
|
|
341
|
+
product
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
const outOfStockVariants = useMemo(
|
|
345
|
+
() =>
|
|
346
|
+
getOutOfStockVariants(
|
|
347
|
+
product,
|
|
348
|
+
optionCodes,
|
|
349
|
+
singleOptionSelection,
|
|
350
|
+
optionSelections,
|
|
351
|
+
isOutOfStockProductDisplayed
|
|
352
|
+
),
|
|
353
|
+
[
|
|
354
|
+
product,
|
|
355
|
+
optionCodes,
|
|
356
|
+
singleOptionSelection,
|
|
357
|
+
optionSelections,
|
|
358
|
+
isOutOfStockProductDisplayed
|
|
359
|
+
]
|
|
360
|
+
);
|
|
361
|
+
|
|
310
362
|
const mediaGalleryEntries = useMemo(
|
|
311
363
|
() => getMediaGalleryEntries(product, optionCodes, optionSelections),
|
|
312
364
|
[product, optionCodes, optionSelections]
|
|
@@ -344,7 +396,7 @@ export const useProductFullDetail = props => {
|
|
|
344
396
|
optionSelections.forEach((value, key) => {
|
|
345
397
|
const values = attributeIdToValuesMap.get(key);
|
|
346
398
|
|
|
347
|
-
const selectedValue = values
|
|
399
|
+
const selectedValue = values?.find(
|
|
348
400
|
item => item.value_index === value
|
|
349
401
|
);
|
|
350
402
|
|
|
@@ -481,6 +533,10 @@ export const useProductFullDetail = props => {
|
|
|
481
533
|
const nextOptionSelections = new Map([...optionSelections]);
|
|
482
534
|
nextOptionSelections.set(optionId, selection);
|
|
483
535
|
setOptionSelections(nextOptionSelections);
|
|
536
|
+
// Create a new Map to keep track of single selections with key as String
|
|
537
|
+
const nextSingleOptionSelection = new Map();
|
|
538
|
+
nextSingleOptionSelection.set(optionId, selection);
|
|
539
|
+
setSingleOptionSelection(nextSingleOptionSelection);
|
|
484
540
|
},
|
|
485
541
|
[optionSelections]
|
|
486
542
|
);
|
|
@@ -542,8 +598,11 @@ export const useProductFullDetail = props => {
|
|
|
542
598
|
handleAddToCart,
|
|
543
599
|
handleSelectionChange,
|
|
544
600
|
isOutOfStock,
|
|
601
|
+
isEverythingOutOfStock,
|
|
602
|
+
outOfStockVariants,
|
|
545
603
|
isAddToCartDisabled:
|
|
546
604
|
isOutOfStock ||
|
|
605
|
+
isEverythingOutOfStock ||
|
|
547
606
|
isMissingOptions ||
|
|
548
607
|
isAddConfigurableLoading ||
|
|
549
608
|
isAddSimpleLoading ||
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
export const useOptions = props => {
|
|
4
|
-
const { onSelectionChange, selectedValues } = props;
|
|
4
|
+
const { onSelectionChange, selectedValues, options } = props;
|
|
5
5
|
const handleSelectionChange = useCallback(
|
|
6
6
|
(optionId, selection) => {
|
|
7
7
|
if (onSelectionChange) {
|
|
@@ -12,10 +12,14 @@ export const useOptions = props => {
|
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
const selectedValueMap = new Map();
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
// Map the option with correct option_label
|
|
17
|
+
for (const { id, value_label } of selectedValues) {
|
|
18
|
+
const option_label = options.find(
|
|
19
|
+
option => option.attribute_id === String(id)
|
|
20
|
+
).label;
|
|
16
21
|
selectedValueMap.set(option_label, value_label);
|
|
17
22
|
}
|
|
18
|
-
|
|
19
23
|
return {
|
|
20
24
|
handleSelectionChange,
|
|
21
25
|
selectedValueMap
|
|
@@ -48,6 +48,21 @@ export const WishlistItemFragment = gql`
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
variants {
|
|
52
|
+
attributes {
|
|
53
|
+
uid
|
|
54
|
+
code
|
|
55
|
+
value_index
|
|
56
|
+
}
|
|
57
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
58
|
+
product {
|
|
59
|
+
uid
|
|
60
|
+
stock_status
|
|
61
|
+
small_image {
|
|
62
|
+
url
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
# TODO: Use configurable_product_option_uid for ConfigurableWishlistItem when available in 2.4.5
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuild the array of variants with out of stock items data added.
|
|
3
|
+
* Since when admin selects in the Admin dashboard to not to display out of stock products
|
|
4
|
+
* the variants data that are needed to find disabled swatches only show the the in stock ones, missing the out of stock ones
|
|
5
|
+
* We rebuild the variants here to display all the variants and mark the stock status accordingly
|
|
6
|
+
* This returns an array of objects
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const createProductVariants = product => {
|
|
10
|
+
const OUT_OF_STOCK_CODE = 'OUT_OF_STOCK';
|
|
11
|
+
const IN_STOCK_CODE = 'IN_STOCK';
|
|
12
|
+
|
|
13
|
+
if (product && product.configurable_options) {
|
|
14
|
+
const { variants } = product;
|
|
15
|
+
// Compute the permutation of all possible arrays of given arrays
|
|
16
|
+
// For example, if array = [[1,2],[10,20],[100,200,300]]
|
|
17
|
+
// the result is [[1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100], [1, 20, 200],
|
|
18
|
+
// [1, 20, 300], [2, 10, 100], [2, 10, 200], [2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]]
|
|
19
|
+
const cartesian = (...array) =>
|
|
20
|
+
array.reduce((array, current) =>
|
|
21
|
+
array.flatMap(cur => current.map(n => [cur, n].flat()))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const configurableOptionsValueIndexes = product.configurable_options.map(
|
|
25
|
+
option => option.values.map(value => value.value_index)
|
|
26
|
+
);
|
|
27
|
+
// Get all possible variants for current options
|
|
28
|
+
const allPossibleItems = cartesian(...configurableOptionsValueIndexes);
|
|
29
|
+
|
|
30
|
+
const variantsValueIndexes = variants.map(variant =>
|
|
31
|
+
variant.attributes.map(attribute => attribute.value_index)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const newVariantsArray = [];
|
|
35
|
+
const len = allPossibleItems.length;
|
|
36
|
+
let foundMatch;
|
|
37
|
+
let currentValueIndex = [];
|
|
38
|
+
for (let i = 0; i < len; i++) {
|
|
39
|
+
currentValueIndex = allPossibleItems[i];
|
|
40
|
+
for (const option of variantsValueIndexes) {
|
|
41
|
+
// If found the same item option in the current variants array, meaning the item is in stock
|
|
42
|
+
// If not found a match, meaning the item is out of stock, which is why it's not in the current variants array
|
|
43
|
+
// with the not to display out of stock products selected in Admin dashboard
|
|
44
|
+
foundMatch =
|
|
45
|
+
option.length > 1
|
|
46
|
+
? Array.from(currentValueIndex)
|
|
47
|
+
.sort()
|
|
48
|
+
.toString() === option.sort().toString()
|
|
49
|
+
: currentValueIndex.toString() === option.toString();
|
|
50
|
+
if (foundMatch) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const newAttributes = [];
|
|
56
|
+
// If there are more than 1 group of swatches
|
|
57
|
+
if (currentValueIndex.length && currentValueIndex.length > 1) {
|
|
58
|
+
for (const index of Array.from(currentValueIndex)) {
|
|
59
|
+
const code = product.configurable_options.find(option =>
|
|
60
|
+
option.values.find(value => value.value_index === index)
|
|
61
|
+
);
|
|
62
|
+
newAttributes.push({
|
|
63
|
+
value_index: index,
|
|
64
|
+
code: code.attribute_code
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// If there's only one group of swatches
|
|
68
|
+
} else {
|
|
69
|
+
const code = product.configurable_options.find(option =>
|
|
70
|
+
option.values.find(
|
|
71
|
+
value => value.value_index === currentValueIndex
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
newAttributes.push({
|
|
75
|
+
value_index: currentValueIndex,
|
|
76
|
+
code: code.attribute_code
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
newVariantsArray.push({
|
|
80
|
+
key: i,
|
|
81
|
+
attributes: Array.from(newAttributes),
|
|
82
|
+
product: {
|
|
83
|
+
stock_status: foundMatch ? IN_STOCK_CODE : OUT_OF_STOCK_CODE
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return newVariantsArray;
|
|
88
|
+
} else {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find all the products/variants contains current option selections
|
|
3
|
+
* @return {Array} variants
|
|
4
|
+
*/
|
|
5
|
+
export const findAllMatchingVariants = ({
|
|
6
|
+
variants,
|
|
7
|
+
optionCodes,
|
|
8
|
+
singleOptionSelection
|
|
9
|
+
}) => {
|
|
10
|
+
return variants?.filter(({ attributes, product }) => {
|
|
11
|
+
const customAttributes = (attributes || []).reduce(
|
|
12
|
+
(map, { code, value_index }) => new Map(map).set(code, value_index),
|
|
13
|
+
new Map()
|
|
14
|
+
);
|
|
15
|
+
for (const [id, value] of singleOptionSelection) {
|
|
16
|
+
const code = optionCodes.get(id);
|
|
17
|
+
|
|
18
|
+
const matchesStandardAttribute = product[code] === value;
|
|
19
|
+
|
|
20
|
+
const matchesCustomAttribute = customAttributes.get(code) === value;
|
|
21
|
+
|
|
22
|
+
// if any option selection fails to match any standard attribute
|
|
23
|
+
// and also fails to match any custom attribute
|
|
24
|
+
// then this isn't the correct variant
|
|
25
|
+
if (!matchesStandardAttribute && !matchesCustomAttribute) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// otherwise, every option selection matched
|
|
31
|
+
// and these are the correct variants
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the combination of k elements in the array.
|
|
3
|
+
* For example: array is [1,2,3]. k=2.
|
|
4
|
+
* The results are [[1,2],[1,3],[2,3]]
|
|
5
|
+
* @return {Array}
|
|
6
|
+
*/
|
|
7
|
+
export function getCombinations(array, k, prefix = []) {
|
|
8
|
+
if (k == 0) return [prefix];
|
|
9
|
+
return array.flatMap((value, index) =>
|
|
10
|
+
getCombinations(array.slice(index + 1), k - 1, [...prefix, value])
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the value_index of out of stock variants
|
|
3
|
+
* @return {Array} indexes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const getOutOfStockIndexes = items => {
|
|
7
|
+
const OUT_OF_STOCK_CODE = 'OUT_OF_STOCK';
|
|
8
|
+
return items
|
|
9
|
+
?.filter(item => item.product.stock_status === OUT_OF_STOCK_CODE)
|
|
10
|
+
.map(option =>
|
|
11
|
+
option.attributes.map(attribute => attribute.value_index)
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find out of stock variants/options of current option selections
|
|
3
|
+
* @return {Array} variants
|
|
4
|
+
*/
|
|
5
|
+
import { isProductConfigurable } from '@magento/peregrine/lib/util/isProductConfigurable';
|
|
6
|
+
import { findAllMatchingVariants } from '@magento/peregrine/lib/util/findAllMatchingVariants';
|
|
7
|
+
import { getOutOfStockIndexes } from '@magento/peregrine/lib/util/getOutOfStockIndexes';
|
|
8
|
+
import { createProductVariants } from '@magento/peregrine/lib/util/createProductVariants';
|
|
9
|
+
import { getCombinations } from '@magento/peregrine/lib/util/getCombinations';
|
|
10
|
+
|
|
11
|
+
const OUT_OF_STOCK_CODE = 'OUT_OF_STOCK';
|
|
12
|
+
|
|
13
|
+
export const getOutOfStockVariants = (
|
|
14
|
+
product,
|
|
15
|
+
optionCodes,
|
|
16
|
+
singleOptionSelection,
|
|
17
|
+
optionSelections,
|
|
18
|
+
isOutOfStockProductDisplayed
|
|
19
|
+
) => {
|
|
20
|
+
const isConfigurable = isProductConfigurable(product);
|
|
21
|
+
const singeOptionSelected =
|
|
22
|
+
singleOptionSelection && singleOptionSelection.size === 1;
|
|
23
|
+
const outOfStockIndexes = [];
|
|
24
|
+
|
|
25
|
+
if (isConfigurable) {
|
|
26
|
+
let variants = product.variants;
|
|
27
|
+
const variantsIfOutOfStockProductsNotDisplayed = createProductVariants(
|
|
28
|
+
product
|
|
29
|
+
);
|
|
30
|
+
//If out of stock products is set to not displayed, use the variants created
|
|
31
|
+
variants = isOutOfStockProductDisplayed
|
|
32
|
+
? variants
|
|
33
|
+
: variantsIfOutOfStockProductsNotDisplayed;
|
|
34
|
+
|
|
35
|
+
const numberOfVariations = variants[0].attributes.length;
|
|
36
|
+
|
|
37
|
+
// If only one pair of variations, display out of stock variations before option selection
|
|
38
|
+
if (numberOfVariations === 1) {
|
|
39
|
+
const outOfStockOptions = variants.filter(
|
|
40
|
+
variant => variant.product.stock_status === OUT_OF_STOCK_CODE
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const outOfStockIndex = outOfStockOptions.map(option =>
|
|
44
|
+
option.attributes.map(attribute => attribute.value_index)
|
|
45
|
+
);
|
|
46
|
+
return outOfStockIndex;
|
|
47
|
+
} else {
|
|
48
|
+
if (singeOptionSelected) {
|
|
49
|
+
const optionsSelected =
|
|
50
|
+
Array.from(optionSelections.values()).filter(
|
|
51
|
+
value => !!value
|
|
52
|
+
).length > 1;
|
|
53
|
+
const selectedIndexes = Array.from(
|
|
54
|
+
optionSelections.values()
|
|
55
|
+
).flat();
|
|
56
|
+
|
|
57
|
+
const items = findAllMatchingVariants({
|
|
58
|
+
optionCodes,
|
|
59
|
+
singleOptionSelection,
|
|
60
|
+
variants
|
|
61
|
+
});
|
|
62
|
+
const outOfStockItemsIndexes = getOutOfStockIndexes(items);
|
|
63
|
+
|
|
64
|
+
// For all the out of stock options associated with current selection, display out of stock swatches
|
|
65
|
+
// when the number of matching indexes of selected indexes and out of stock indexes are not smaller than the total groups of swatches minus 1
|
|
66
|
+
for (const indexes of outOfStockItemsIndexes) {
|
|
67
|
+
const sameIndexes = indexes.filter(num =>
|
|
68
|
+
selectedIndexes.includes(num)
|
|
69
|
+
);
|
|
70
|
+
const differentIndexes = indexes.filter(
|
|
71
|
+
num => !selectedIndexes.includes(num)
|
|
72
|
+
);
|
|
73
|
+
if (sameIndexes.length >= optionCodes.size - 1) {
|
|
74
|
+
outOfStockIndexes.push(differentIndexes);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Display all possible out of stock swatches with current selections, when all groups of swatches are selected
|
|
78
|
+
if (
|
|
79
|
+
optionsSelected &&
|
|
80
|
+
!selectedIndexes.includes(undefined) &&
|
|
81
|
+
selectedIndexes.length === optionCodes.size
|
|
82
|
+
) {
|
|
83
|
+
const selectedIndexesCombinations = getCombinations(
|
|
84
|
+
selectedIndexes,
|
|
85
|
+
selectedIndexes.length - 1
|
|
86
|
+
);
|
|
87
|
+
// Find out of stock items and indexes for each combination
|
|
88
|
+
const oosIndexes = [];
|
|
89
|
+
for (const option of selectedIndexesCombinations) {
|
|
90
|
+
// Map the option indexes to their optionCodes
|
|
91
|
+
const curOption = new Map(
|
|
92
|
+
[...optionSelections].filter(
|
|
93
|
+
([key, val]) => (
|
|
94
|
+
option.includes(key), option.includes(val)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
const curItems = findAllMatchingVariants({
|
|
99
|
+
optionCodes: optionCodes,
|
|
100
|
+
singleOptionSelection: curOption,
|
|
101
|
+
variants: variants
|
|
102
|
+
});
|
|
103
|
+
const outOfStockIndex = getOutOfStockIndexes(curItems)
|
|
104
|
+
?.flat()
|
|
105
|
+
.filter(idx => !selectedIndexes.includes(idx));
|
|
106
|
+
oosIndexes.push(outOfStockIndex);
|
|
107
|
+
}
|
|
108
|
+
return oosIndexes;
|
|
109
|
+
}
|
|
110
|
+
return outOfStockIndexes;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find out of stock variants/options of current option selections with initial selctions
|
|
3
|
+
* @return {Array} variants
|
|
4
|
+
*/
|
|
5
|
+
import { findAllMatchingVariants } from '@magento/peregrine/lib/util/findAllMatchingVariants';
|
|
6
|
+
import { getOutOfStockIndexes } from '@magento/peregrine/lib/util/getOutOfStockIndexes';
|
|
7
|
+
import { createProductVariants } from '@magento/peregrine/lib/util/createProductVariants';
|
|
8
|
+
import { getCombinations } from '@magento/peregrine/lib/util/getCombinations';
|
|
9
|
+
|
|
10
|
+
export const getOutOfStockVariantsWithInitialSelection = (
|
|
11
|
+
product,
|
|
12
|
+
configurableOptionCodes,
|
|
13
|
+
multipleOptionSelections,
|
|
14
|
+
configItem,
|
|
15
|
+
isOutOfStockProductDisplayed
|
|
16
|
+
) => {
|
|
17
|
+
if (configItem && product) {
|
|
18
|
+
let variants = product.variants;
|
|
19
|
+
const variantsIfOutOfStockProductsNotDisplayed = createProductVariants(
|
|
20
|
+
configItem
|
|
21
|
+
);
|
|
22
|
+
//If out of stock products is set to not displayed, use the variants created
|
|
23
|
+
variants = isOutOfStockProductDisplayed
|
|
24
|
+
? variants
|
|
25
|
+
: variantsIfOutOfStockProductsNotDisplayed;
|
|
26
|
+
if (
|
|
27
|
+
multipleOptionSelections &&
|
|
28
|
+
multipleOptionSelections.size === configurableOptionCodes.size
|
|
29
|
+
) {
|
|
30
|
+
const selectedIndexes = Array.from(
|
|
31
|
+
multipleOptionSelections.values()
|
|
32
|
+
).flat();
|
|
33
|
+
|
|
34
|
+
const selectedIndexesCombinations = getCombinations(
|
|
35
|
+
selectedIndexes,
|
|
36
|
+
selectedIndexes.length - 1
|
|
37
|
+
);
|
|
38
|
+
const oosIndexes = [];
|
|
39
|
+
for (const option of selectedIndexesCombinations) {
|
|
40
|
+
const curOption = new Map(
|
|
41
|
+
[...multipleOptionSelections].filter(
|
|
42
|
+
([key, val]) => (
|
|
43
|
+
option.includes(key), option.includes(val)
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
const curItems = findAllMatchingVariants({
|
|
48
|
+
optionCodes: configurableOptionCodes,
|
|
49
|
+
singleOptionSelection: curOption,
|
|
50
|
+
variants: variants
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const outOfStockIndex = getOutOfStockIndexes(curItems)
|
|
54
|
+
?.flat()
|
|
55
|
+
.filter(idx => !selectedIndexes.includes(idx));
|
|
56
|
+
|
|
57
|
+
oosIndexes.push(outOfStockIndex);
|
|
58
|
+
}
|
|
59
|
+
return oosIndexes;
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
};
|