Skip to content

Commit f873f87

Browse files
[Backport to release/10.1] Prevent added to cart notice to appear when adding variable products to cart via the Add to Cart + Options block (#60058)
* Cherry-pick fd1def2 with unresolved conflicts from #59921 * Fix conflicts * Fix variation ID not being updated --------- Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
1 parent 2634064 commit f873f87

File tree

8 files changed

+252
-114
lines changed

8 files changed

+252
-114
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: fix
3+
4+
Prevent added to cart notice to appear when adding variable products to cart via the Add to Cart + Options block

plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/frontend.ts

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
* External dependencies
33
*/
44
import { store, getContext, useLayoutEffect } from '@wordpress/interactivity';
5-
import type {
6-
OptimisticCartItem,
7-
SelectedAttributes,
8-
Store as WooCommerce,
9-
} from '@woocommerce/stores/woocommerce/cart';
5+
import type { Store as WooCommerce } from '@woocommerce/stores/woocommerce/cart';
106

117
/**
128
* Internal dependencies
139
*/
10+
import { doesCartItemMatchAttributes } from '../../../../base/utils/variations/does-cart-item-match-attributes';
1411
import type { AddToCartWithOptionsStore } from '../../../../blocks/add-to-cart-with-options/frontend';
1512

1613
// Stores are locked to prevent 3PD usage until the API is stable.
@@ -56,41 +53,6 @@ const { state: addToCartWithOptionsState } = store< AddToCartWithOptionsStore >(
5653
{ lock: universalLock }
5754
);
5855

59-
const isCartItemMatched = (
60-
cartItem: OptimisticCartItem,
61-
selectedItem: SelectedAttributes[]
62-
) => {
63-
if (
64-
! Array.isArray( cartItem.variation ) ||
65-
! Array.isArray( selectedItem )
66-
) {
67-
return false;
68-
}
69-
70-
// In case the attributes list length is different in both the objects.
71-
if ( cartItem.variation.length !== selectedItem.length ) {
72-
return false;
73-
}
74-
75-
return cartItem.variation.every(
76-
( {
77-
// eslint-disable-next-line
78-
raw_attribute,
79-
value,
80-
}: {
81-
raw_attribute: string;
82-
value: string;
83-
} ) =>
84-
selectedItem.some( ( item: SelectedAttributes ) => {
85-
return (
86-
item.attribute === raw_attribute &&
87-
( item.value.toLowerCase() === value.toLowerCase() ||
88-
( item.value && value === '' ) ) // Handle "any" attribute type
89-
);
90-
} )
91-
);
92-
};
93-
9456
const productButtonStore = {
9557
state: {
9658
get quantity(): number {
@@ -106,7 +68,7 @@ const productButtonStore = {
10668
const selectedAttributes =
10769
addToCartWithOptionsState?.selectedAttributes;
10870
const selectedVariableProduct = products.find( ( item ) =>
109-
isCartItemMatched( item, selectedAttributes )
71+
doesCartItemMatchAttributes( item, selectedAttributes )
11072
);
11173

11274
return selectedVariableProduct?.quantity || 0;

plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,41 @@ const getInfoNoticesFromCartUpdates = (
151151
];
152152
};
153153

154+
// Same as the one in /assets/js/base/utils/variations/does-cart-item-match-attributes.ts.
155+
const doesCartItemMatchAttributes = (
156+
cartItem: OptimisticCartItem,
157+
selectedAttributes: SelectedAttributes[]
158+
) => {
159+
if (
160+
! Array.isArray( cartItem.variation ) ||
161+
! Array.isArray( selectedAttributes )
162+
) {
163+
return false;
164+
}
165+
166+
if ( cartItem.variation.length !== selectedAttributes.length ) {
167+
return false;
168+
}
169+
170+
return cartItem.variation.every(
171+
( {
172+
// eslint-disable-next-line
173+
raw_attribute,
174+
value,
175+
}: {
176+
raw_attribute: string;
177+
value: string;
178+
} ) =>
179+
selectedAttributes.some( ( item: SelectedAttributes ) => {
180+
return (
181+
item.attribute === raw_attribute &&
182+
( item.value.toLowerCase() === value.toLowerCase() ||
183+
( item.value && value === '' ) ) // Handle "any" attribute type
184+
);
185+
} )
186+
);
187+
};
188+
154189
let pendingRefresh = false;
155190
let refreshTimeout = 3000;
156191

@@ -223,9 +258,28 @@ const { state, actions } = store< Store >(
223258
},
224259

225260
*addCartItem( { id, quantity, variation }: OptimisticCartItem ) {
226-
let item = state.cart.items.find(
227-
( { id: productId } ) => id === productId
228-
);
261+
let item = state.cart.items.find( ( cartItem ) => {
262+
if ( cartItem.type === 'variation' ) {
263+
// If it's a variation, check that attributes match.
264+
// While different variations have different attributes,
265+
// some variations might accept 'Any' value for an attribute,
266+
// in which case, we need to check that the attributes match.
267+
if (
268+
id !== cartItem.id ||
269+
! cartItem.variation ||
270+
! variation ||
271+
cartItem.variation.length !== variation.length
272+
) {
273+
return false;
274+
}
275+
return doesCartItemMatchAttributes(
276+
cartItem,
277+
variation
278+
);
279+
}
280+
281+
return id === cartItem.id;
282+
} );
229283
const endpoint = item ? 'update-item' : 'add-item';
230284
const previousCart = JSON.stringify( state.cart );
231285
const quantityChanges: QuantityChanges = {};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import type {
5+
OptimisticCartItem,
6+
SelectedAttributes,
7+
} from '@woocommerce/stores/woocommerce/cart';
8+
9+
export const doesCartItemMatchAttributes = (
10+
cartItem: OptimisticCartItem,
11+
selectedAttributes: SelectedAttributes[]
12+
) => {
13+
if (
14+
! Array.isArray( cartItem.variation ) ||
15+
! Array.isArray( selectedAttributes )
16+
) {
17+
return false;
18+
}
19+
20+
if ( cartItem.variation.length !== selectedAttributes.length ) {
21+
return false;
22+
}
23+
24+
return cartItem.variation.every(
25+
( {
26+
// eslint-disable-next-line
27+
raw_attribute,
28+
value,
29+
}: {
30+
raw_attribute: string;
31+
value: string;
32+
} ) =>
33+
selectedAttributes.some( ( item: SelectedAttributes ) => {
34+
return (
35+
item.attribute === raw_attribute &&
36+
( item.value.toLowerCase() === value.toLowerCase() ||
37+
( item.value && value === '' ) ) // Handle "any" attribute type
38+
);
39+
} )
40+
);
41+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { SelectedAttributes } from '@woocommerce/stores/woocommerce/cart';
5+
6+
export type AvailableVariation = {
7+
attributes: Record< string, string >;
8+
variation_id: number;
9+
price_html: string;
10+
is_in_stock: boolean;
11+
};
12+
13+
export const getMatchedVariation = (
14+
availableVariations: AvailableVariation[],
15+
selectedAttributes: SelectedAttributes[]
16+
) => {
17+
if (
18+
! Array.isArray( availableVariations ) ||
19+
! Array.isArray( selectedAttributes ) ||
20+
availableVariations.length === 0 ||
21+
selectedAttributes.length === 0
22+
) {
23+
return null;
24+
}
25+
return (
26+
availableVariations.find( ( availableVariation ) => {
27+
return Object.entries( availableVariation.attributes ).every(
28+
( [ attributeName, attributeValue ] ) => {
29+
const attributeMatched = selectedAttributes.some(
30+
( variationAttribute ) => {
31+
const isSameAttribute =
32+
variationAttribute.attribute === attributeName;
33+
if ( ! isSameAttribute ) {
34+
return false;
35+
}
36+
37+
return (
38+
variationAttribute.value === attributeValue ||
39+
( variationAttribute.value &&
40+
attributeValue === '' )
41+
);
42+
}
43+
);
44+
45+
return attributeMatched;
46+
}
47+
);
48+
} ) || null
49+
);
50+
};

0 commit comments

Comments
 (0)