diff --git a/assets/js/base/components/cart-checkout/totals/coupon/index.tsx b/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
index 40967d36893..93384977e65 100644
--- a/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
+++ b/assets/js/base/components/cart-checkout/totals/coupon/index.tsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
-import { useState, useEffect, useRef } from '@wordpress/element';
+import { useState } from '@wordpress/element';
import Button from '@woocommerce/base-components/button';
import LoadingMask from '@woocommerce/base-components/loading-mask';
import { withInstanceId } from '@wordpress/compose';
@@ -35,35 +35,29 @@ export interface TotalsCouponProps {
/**
* Submit handler
*/
- onSubmit?: ( couponValue: string ) => void;
+ onSubmit?: ( couponValue: string ) => Promise< boolean > | undefined;
}
export const TotalsCoupon = ( {
instanceId,
isLoading = false,
+ onSubmit,
displayCouponForm = false,
- onSubmit = () => void 0,
}: TotalsCouponProps ): JSX.Element => {
const [ couponValue, setCouponValue ] = useState( '' );
const [ isCouponFormHidden, setIsCouponFormHidden ] = useState(
! displayCouponForm
);
- const currentIsLoading = useRef( false );
-
- const validationErrorKey = 'coupon';
const textInputId = `wc-block-components-totals-coupon__input-${ instanceId }`;
-
const formWrapperClass = classnames(
'wc-block-components-totals-coupon__content',
{
'screen-reader-text': isCouponFormHidden,
}
);
-
- const { validationError, validationErrorId } = useSelect( ( select ) => {
+ const { validationErrorId } = useSelect( ( select ) => {
const store = select( VALIDATION_STORE_KEY );
return {
- validationError: store.getValidationError( validationErrorKey ),
validationErrorId: store.getValidationErrorId( textInputId ),
};
} );
@@ -77,18 +71,18 @@ export const TotalsCoupon = ( {
e: React.MouseEvent< HTMLButtonElement, MouseEvent >
) => {
e.preventDefault();
- onSubmit( couponValue );
- };
-
- useEffect( () => {
- if ( currentIsLoading.current !== isLoading ) {
- if ( ! isLoading && couponValue && ! validationError ) {
- setCouponValue( '' );
- setIsCouponFormHidden( true );
- }
- currentIsLoading.current = isLoading;
+ if ( onSubmit !== undefined ) {
+ onSubmit( couponValue ).then( ( result ) => {
+ if ( result ) {
+ setCouponValue( '' );
+ setIsCouponFormHidden( true );
+ }
+ } );
+ } else {
+ setCouponValue( '' );
+ setIsCouponFormHidden( true );
}
- }, [ isLoading, couponValue, validationError ] );
+ };
return (
diff --git a/assets/js/base/components/cart-checkout/totals/coupon/stories/index.tsx b/assets/js/base/components/cart-checkout/totals/coupon/stories/index.tsx
index 79b816e0bb4..e5bae041855 100644
--- a/assets/js/base/components/cart-checkout/totals/coupon/stories/index.tsx
+++ b/assets/js/base/components/cart-checkout/totals/coupon/stories/index.tsx
@@ -31,11 +31,12 @@ const Template: Story< TotalsCouponProps > = ( args ) => {
const onSubmit = ( code: string ) => {
args.onSubmit?.( code );
setArgs( { isLoading: true } );
-
- setTimeout(
- () => setArgs( { isLoading: false } ),
- INTERACTION_TIMEOUT
- );
+ return new Promise( ( resolve ) => {
+ setTimeout( () => {
+ setArgs( { isLoading: false } );
+ resolve( true );
+ }, INTERACTION_TIMEOUT );
+ } );
};
return
;
diff --git a/assets/js/base/context/hooks/cart/test/use-store-cart.js b/assets/js/base/context/hooks/cart/test/use-store-cart.js
index c92712b8d10..17674d13327 100644
--- a/assets/js/base/context/hooks/cart/test/use-store-cart.js
+++ b/assets/js/base/context/hooks/cart/test/use-store-cart.js
@@ -26,6 +26,7 @@ describe( 'useStoreCart', () => {
let registry, renderer;
const receiveCartMock = () => {};
+ const receiveCartContentsMock = () => {};
const previewCartData = {
cartCoupons: previewCart.coupons,
@@ -102,8 +103,9 @@ describe( 'useStoreCart', () => {
hasCalculatedShipping: true,
extensions: {},
errors: [],
- receiveCart: undefined,
paymentRequirements: [],
+ receiveCart: undefined,
+ receiveCartContents: undefined,
};
const mockCartTotals = {
currency_code: 'USD',
@@ -129,8 +131,9 @@ describe( 'useStoreCart', () => {
extensions: {},
isLoadingRates: false,
cartHasCalculatedShipping: true,
- receiveCart: undefined,
paymentRequirements: [],
+ receiveCart: undefined,
+ receiveCartContents: undefined,
};
const getWrappedComponents = ( Component ) => (
@@ -140,8 +143,15 @@ describe( 'useStoreCart', () => {
);
const getTestComponent = ( options ) => () => {
- const { receiveCart, ...results } = useStoreCart( options );
- return
;
+ const { receiveCart, receiveCartContents, ...results } =
+ useStoreCart( options );
+ return (
+
+ );
};
const setUpMocks = () => {
@@ -190,12 +200,16 @@ describe( 'useStoreCart', () => {
);
} );
- const { results, receiveCart } =
+ const { results, receiveCart, receiveCartContents } =
renderer.root.findByType( 'div' ).props; //eslint-disable-line testing-library/await-async-query
- const { receiveCart: defaultReceiveCart, ...remaining } =
- defaultCartData;
+ const {
+ receiveCart: defaultReceiveCart,
+ receiveCartContents: defaultReceiveCartContents,
+ ...remaining
+ } = defaultCartData;
expect( results ).toEqual( remaining );
expect( receiveCart ).toEqual( defaultReceiveCart );
+ expect( receiveCartContents ).toEqual( defaultReceiveCartContents );
} );
it( 'return store data when shouldSelect is true', () => {
@@ -209,11 +223,12 @@ describe( 'useStoreCart', () => {
);
} );
- const { results, receiveCart } =
+ const { results, receiveCart, receiveCartContents } =
renderer.root.findByType( 'div' ).props; //eslint-disable-line testing-library/await-async-query
expect( results ).toEqual( mockStoreCartData );
expect( receiveCart ).toBeUndefined();
+ expect( receiveCartContents ).toBeUndefined();
} );
} );
@@ -225,6 +240,7 @@ describe( 'useStoreCart', () => {
previewCart: {
...previewCart,
receiveCart: receiveCartMock,
+ receiveCartContents: receiveCartContentsMock,
},
},
} );
@@ -239,11 +255,12 @@ describe( 'useStoreCart', () => {
);
} );
- const { results, receiveCart } =
+ const { results, receiveCart, receiveCartContents } =
renderer.root.findByType( 'div' ).props; //eslint-disable-line testing-library/await-async-query
expect( results ).toEqual( previewCartData );
expect( receiveCart ).toEqual( receiveCartMock );
+ expect( receiveCartContents ).toEqual( receiveCartContentsMock );
} );
} );
} );
diff --git a/assets/js/base/context/hooks/cart/use-store-cart-coupons.ts b/assets/js/base/context/hooks/cart/use-store-cart-coupons.ts
index 4ba4807d0da..2923fb7e031 100644
--- a/assets/js/base/context/hooks/cart/use-store-cart-coupons.ts
+++ b/assets/js/base/context/hooks/cart/use-store-cart-coupons.ts
@@ -40,14 +40,12 @@ export const useStoreCartCoupons = ( context = '' ): StoreCartCoupon => {
[ createErrorNotice, createNotice ]
);
- const { applyCoupon, removeCoupon, receiveApplyingCoupon } =
- useDispatch( CART_STORE_KEY );
+ const { applyCoupon, removeCoupon } = useDispatch( CART_STORE_KEY );
const applyCouponWithNotices = ( couponCode: string ) => {
- applyCoupon( couponCode )
- .then( ( result ) => {
+ return applyCoupon( couponCode )
+ .then( () => {
if (
- result === true &&
__experimentalApplyCheckoutFilter( {
filterName: 'showApplyCouponNotice',
defaultValue: true,
@@ -71,6 +69,7 @@ export const useStoreCartCoupons = ( context = '' ): StoreCartCoupon => {
}
);
}
+ return Promise.resolve( true );
} )
.catch( ( error ) => {
setValidationErrors( {
@@ -79,16 +78,14 @@ export const useStoreCartCoupons = ( context = '' ): StoreCartCoupon => {
hidden: false,
},
} );
- // Finished handling the coupon.
- receiveApplyingCoupon( '' );
+ return Promise.resolve( false );
} );
};
const removeCouponWithNotices = ( couponCode: string ) => {
- removeCoupon( couponCode )
- .then( ( result ) => {
+ return removeCoupon( couponCode )
+ .then( () => {
if (
- result === true &&
__experimentalApplyCheckoutFilter( {
filterName: 'showRemoveCouponNotice',
defaultValue: true,
@@ -112,14 +109,14 @@ export const useStoreCartCoupons = ( context = '' ): StoreCartCoupon => {
}
);
}
+ return Promise.resolve( true );
} )
.catch( ( error ) => {
createErrorNotice( error.message, {
id: 'coupon-form',
context,
} );
- // Finished handling the coupon.
- receiveApplyingCoupon( '' );
+ return Promise.resolve( false );
} );
};
diff --git a/assets/js/base/context/hooks/cart/use-store-cart-item-quantity.ts b/assets/js/base/context/hooks/cart/use-store-cart-item-quantity.ts
index 5a0d306ed94..5998e7a3cf5 100644
--- a/assets/js/base/context/hooks/cart/use-store-cart-item-quantity.ts
+++ b/assets/js/base/context/hooks/cart/use-store-cart-item-quantity.ts
@@ -3,7 +3,11 @@
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useCallback, useState, useEffect } from '@wordpress/element';
-import { CART_STORE_KEY, CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
+import {
+ CART_STORE_KEY,
+ CHECKOUT_STORE_KEY,
+ processErrorResponse,
+} from '@woocommerce/block-data';
import { useDebounce } from 'use-debounce';
import { usePrevious } from '@woocommerce/base-hooks';
import {
@@ -84,9 +88,12 @@ export const useStoreCartItemQuantity = (
);
const removeItem = useCallback( () => {
- return cartItemKey
- ? removeItemFromCart( cartItemKey )
- : Promise.resolve( false );
+ if ( cartItemKey ) {
+ return removeItemFromCart( cartItemKey ).catch( ( error ) => {
+ processErrorResponse( error );
+ } );
+ }
+ return Promise.resolve( false );
}, [ cartItemKey, removeItemFromCart ] );
// Observe debounced quantity value, fire action to update server on change.
@@ -97,7 +104,11 @@ export const useStoreCartItemQuantity = (
Number.isFinite( previousDebouncedQuantity ) &&
previousDebouncedQuantity !== debouncedQuantity
) {
- changeCartItemQuantity( cartItemKey, debouncedQuantity );
+ changeCartItemQuantity( cartItemKey, debouncedQuantity ).catch(
+ ( error ) => {
+ processErrorResponse( error );
+ }
+ );
}
}, [
cartItemKey,
diff --git a/assets/js/base/context/hooks/cart/use-store-cart.ts b/assets/js/base/context/hooks/cart/use-store-cart.ts
index ffd38a2cfea..1be0d7a8d45 100644
--- a/assets/js/base/context/hooks/cart/use-store-cart.ts
+++ b/assets/js/base/context/hooks/cart/use-store-cart.ts
@@ -114,6 +114,7 @@ export const defaultCartData: StoreCart = {
cartHasCalculatedShipping: false,
paymentRequirements: EMPTY_PAYMENT_REQUIREMENTS,
receiveCart: () => undefined,
+ receiveCartContents: () => undefined,
extensions: EMPTY_EXTENSIONS,
};
@@ -174,6 +175,10 @@ export const useStoreCart = (
typeof previewCart?.receiveCart === 'function'
? previewCart.receiveCart
: () => undefined,
+ receiveCartContents:
+ typeof previewCart?.receiveCartContents === 'function'
+ ? previewCart.receiveCartContents
+ : () => undefined,
};
}
@@ -185,7 +190,7 @@ export const useStoreCart = (
! store.hasFinishedResolution( 'getCartData' );
const isLoadingRates = store.isCustomerDataUpdating();
- const { receiveCart } = dispatch( storeKey );
+ const { receiveCart, receiveCartContents } = dispatch( storeKey );
const billingAddress = decodeValues( cartData.billingAddress );
const shippingAddress = cartData.needsShipping
? decodeValues( cartData.shippingAddress )
@@ -232,6 +237,7 @@ export const useStoreCart = (
cartHasCalculatedShipping: cartData.hasCalculatedShipping,
paymentRequirements: cartData.paymentRequirements,
receiveCart,
+ receiveCartContents,
};
},
[ shouldSelect ]
diff --git a/assets/js/base/context/hooks/shipping/use-shipping-data.ts b/assets/js/base/context/hooks/shipping/use-shipping-data.ts
index e0d59691793..5ea56ac57b7 100644
--- a/assets/js/base/context/hooks/shipping/use-shipping-data.ts
+++ b/assets/js/base/context/hooks/shipping/use-shipping-data.ts
@@ -1,14 +1,16 @@
/**
* External dependencies
*/
-import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
+import {
+ CART_STORE_KEY as storeKey,
+ processErrorResponse,
+} from '@woocommerce/block-data';
import { useSelect, useDispatch } from '@wordpress/data';
import { isObject } from '@woocommerce/types';
import { useEffect, useRef, useCallback } from '@wordpress/element';
import { deriveSelectedShippingRates } from '@woocommerce/base-utils';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { previewCart } from '@woocommerce/resource-previews';
-import { useThrowError } from '@woocommerce/base-hooks';
/**
* Internal dependencies
@@ -72,12 +74,11 @@ export const useShippingData = (): ShippingData => {
} as {
selectShippingRate: (
newShippingRateId: string,
- packageId?: string | number
+ packageId?: string | number | undefined
) => Promise< unknown >;
};
// Selects a shipping rate, fires an event, and catch any errors.
- const throwError = useThrowError();
const { dispatchCheckoutEvent } = useStoreEvents();
const selectShippingRate = useCallback(
(
@@ -113,16 +114,10 @@ export const useShippingData = (): ShippingData => {
} );
} )
.catch( ( error ) => {
- // Throw an error because an error when selecting a rate is problematic.
- throwError( error );
+ processErrorResponse( error );
} );
},
- [
- dispatchSelectShippingRate,
- dispatchCheckoutEvent,
- throwError,
- selectedRates,
- ]
+ [ dispatchSelectShippingRate, dispatchCheckoutEvent, selectedRates ]
);
return {
diff --git a/assets/js/base/context/providers/cart-checkout/checkout-processor.ts b/assets/js/base/context/providers/cart-checkout/checkout-processor.ts
index 75289e7e1d5..7024107f250 100644
--- a/assets/js/base/context/providers/cart-checkout/checkout-processor.ts
+++ b/assets/js/base/context/providers/cart-checkout/checkout-processor.ts
@@ -84,7 +84,8 @@ const CheckoutProcessor = () => {
select( CART_STORE_KEY ).getCustomerData()
);
- const { cartNeedsPayment, cartNeedsShipping, receiveCart } = useStoreCart();
+ const { cartNeedsPayment, cartNeedsShipping, receiveCartContents } =
+ useStoreCart();
const {
activePaymentMethod,
@@ -275,7 +276,8 @@ const CheckoutProcessor = () => {
)
.then( ( response: CheckoutResponseError ) => {
if ( response.data?.cart ) {
- receiveCart( response.data.cart );
+ // We don't want to receive the address here because it will overwrite fields.
+ receiveCartContents( response.data.cart );
}
processErrorResponse( response );
__internalProcessCheckoutResponse( response );
@@ -304,7 +306,7 @@ const CheckoutProcessor = () => {
shouldCreateAccount,
extensionData,
cartNeedsShipping,
- receiveCart,
+ receiveCartContents,
__internalSetHasError,
__internalProcessCheckoutResponse,
] );
diff --git a/assets/js/base/utils/create-notice.ts b/assets/js/base/utils/create-notice.ts
index 18fb6da2b19..90343f8d786 100644
--- a/assets/js/base/utils/create-notice.ts
+++ b/assets/js/base/utils/create-notice.ts
@@ -24,13 +24,6 @@ export const getNoticeContexts = () => {
return Object.values( noticeContexts );
};
-const hasStoreNoticesContainer = ( container: string ): boolean => {
- const containers = select(
- 'wc/store/store-notices'
- ).getRegisteredContainers();
- return containers.includes( container );
-};
-
/**
* Wrapper for @wordpress/notices createNotice.
*/
@@ -54,19 +47,6 @@ export const createNotice = (
} );
};
-/**
- * Creates a notice only if the Store Notice Container is visible.
- */
-export const createNoticeIfVisible = (
- status: 'error' | 'warning' | 'info' | 'success',
- message: string,
- options: Partial< NoticeOptions >
-) => {
- if ( options?.context && hasStoreNoticesContainer( options.context ) ) {
- createNotice( status, message, options );
- }
-};
-
/**
* Remove notices from all contexts.
*
diff --git a/assets/js/data/cart/actions.ts b/assets/js/data/cart/actions.ts
index 80ae3c95f3a..c30f558ffcd 100644
--- a/assets/js/data/cart/actions.ts
+++ b/assets/js/data/cart/actions.ts
@@ -23,8 +23,10 @@ import { ACTION_TYPES as types } from './action-types';
import { apiFetchWithHeaders } from '../shared-controls';
import { ReturnOrGeneratorYieldUnion } from '../mapped-types';
import { CartDispatchFromMap, CartResolveSelectFromMap } from './index';
+import type { Thunks } from './thunks';
// Thunks are functions that can be dispatched, similar to actions creators
+// @todo Many of the functions that return promises in this file need to be moved to thunks.ts.
export * from './thunks';
/**
@@ -143,6 +145,7 @@ export const itemIsPendingDelete = (
cartItemKey,
isPendingDelete,
} as const );
+
/**
* Returns an action object to mark the cart data in the store as stale.
*
@@ -197,6 +200,7 @@ export const applyExtensionCartUpdate =
return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
}
};
@@ -210,8 +214,8 @@ export const applyExtensionCartUpdate =
export const applyCoupon =
( couponCode: string ) =>
async ( { dispatch } ) => {
- dispatch.receiveApplyingCoupon( couponCode );
try {
+ dispatch.receiveApplyingCoupon( couponCode );
const { response } = await apiFetchWithHeaders( {
path: '/wc/store/v1/cart/apply-coupon',
method: 'POST',
@@ -220,14 +224,14 @@ export const applyCoupon =
},
cache: 'no-store',
} );
- dispatch.receiveApplyingCoupon( '' );
-
dispatch.receiveCart( response );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
+ } finally {
+ dispatch.receiveApplyingCoupon( '' );
}
-
- return true;
};
/**
@@ -240,9 +244,8 @@ export const applyCoupon =
export const removeCoupon =
( couponCode: string ) =>
async ( { dispatch } ) => {
- dispatch.receiveRemovingCoupon( couponCode );
-
try {
+ dispatch.receiveRemovingCoupon( couponCode );
const { response } = await apiFetchWithHeaders( {
path: '/wc/store/v1/cart/remove-coupon',
method: 'POST',
@@ -251,15 +254,14 @@ export const removeCoupon =
},
cache: 'no-store',
} );
-
dispatch.receiveCart( response );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
} finally {
dispatch.receiveRemovingCoupon( '' );
}
-
- return true;
};
/**
@@ -286,11 +288,12 @@ export const addItemToCart =
},
cache: 'no-store',
} );
-
dispatch.receiveCart( response );
triggerAddedToCartEvent( { preserveCartData: true } );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
}
};
@@ -306,9 +309,8 @@ export const addItemToCart =
export const removeItemFromCart =
( cartItemKey: string ) =>
async ( { dispatch }: { dispatch: CartDispatchFromMap } ) => {
- dispatch.itemIsPendingDelete( cartItemKey );
-
try {
+ dispatch.itemIsPendingDelete( cartItemKey );
const { response } = await apiFetchWithHeaders( {
path: `/wc/store/v1/cart/remove-item`,
data: {
@@ -317,10 +319,11 @@ export const removeItemFromCart =
method: 'POST',
cache: 'no-store',
} );
-
dispatch.receiveCart( response );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
} finally {
dispatch.itemIsPendingDelete( cartItemKey, false );
}
@@ -352,8 +355,8 @@ export const changeCartItemQuantity =
if ( cartItem?.quantity === quantity ) {
return;
}
- dispatch.itemIsPendingQuantity( cartItemKey );
try {
+ dispatch.itemIsPendingQuantity( cartItemKey );
const { response } = await apiFetchWithHeaders( {
path: '/wc/store/v1/cart/update-item',
method: 'POST',
@@ -363,10 +366,11 @@ export const changeCartItemQuantity =
},
cache: 'no-store',
} );
-
dispatch.receiveCart( response );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
} finally {
dispatch.itemIsPendingQuantity( cartItemKey, false );
}
@@ -376,8 +380,7 @@ export const changeCartItemQuantity =
* Selects a shipping rate.
*
* @param {string} rateId The id of the rate being selected.
- * @param {number | string} [packageId] The key of the packages that we will
- * select within.
+ * @param {number | string} [packageId] The key of the packages that we will select within.
*/
export const selectShippingRate =
( rateId: string, packageId = 0 ) =>
@@ -393,14 +396,14 @@ export const selectShippingRate =
},
cache: 'no-store',
} );
-
dispatch.receiveCart( response );
+ return response as CartResponse;
} catch ( error ) {
dispatch.receiveError( error );
+ return Promise.reject( error );
} finally {
dispatch.shippingRatesBeingSelected( false );
}
- return true;
};
/**
@@ -428,9 +431,8 @@ export const updateCustomerData =
editing = true
) =>
async ( { dispatch }: { dispatch: CartDispatchFromMap } ) => {
- dispatch.updatingCustomerData( true );
-
try {
+ dispatch.updatingCustomerData( true );
const { response } = await apiFetchWithHeaders( {
path: '/wc/store/v1/cart/update-customer',
method: 'POST',
@@ -442,35 +444,35 @@ export const updateCustomerData =
} else {
dispatch.receiveCart( response );
}
- dispatch.updatingCustomerData( false );
+ return response;
} catch ( error ) {
dispatch.receiveError( error );
- dispatch.updatingCustomerData( false );
-
return Promise.reject( error );
+ } finally {
+ dispatch.updatingCustomerData( false );
}
- return Promise.resolve( true );
};
-export type CartAction = ReturnOrGeneratorYieldUnion<
+type Actions =
+ | typeof addItemToCart
+ | typeof applyCoupon
+ | typeof changeCartItemQuantity
+ | typeof itemIsPendingDelete
+ | typeof itemIsPendingQuantity
+ | typeof receiveApplyingCoupon
| typeof receiveCartContents
+ | typeof receiveCartItem
+ | typeof receiveRemovingCoupon
+ | typeof removeCoupon
+ | typeof removeItemFromCart
+ | typeof selectShippingRate
| typeof setBillingAddress
- | typeof setShippingAddress
+ | typeof setCartData
| typeof setErrorData
- | typeof receiveApplyingCoupon
- | typeof receiveRemovingCoupon
- | typeof receiveCartItem
- | typeof itemIsPendingQuantity
- | typeof itemIsPendingDelete
- | typeof updatingCustomerData
- | typeof shippingRatesBeingSelected
| typeof setIsCartDataStale
+ | typeof setShippingAddress
+ | typeof shippingRatesBeingSelected
| typeof updateCustomerData
- | typeof removeItemFromCart
- | typeof changeCartItemQuantity
- | typeof addItemToCart
- | typeof setCartData
- | typeof applyCoupon
- | typeof removeCoupon
- | typeof selectShippingRate
->;
+ | typeof updatingCustomerData;
+
+export type CartAction = ReturnOrGeneratorYieldUnion< Actions | Thunks >;
diff --git a/assets/js/data/cart/notify-errors.ts b/assets/js/data/cart/notify-errors.ts
index 2fe2a6e0a6f..ab076483172 100644
--- a/assets/js/data/cart/notify-errors.ts
+++ b/assets/js/data/cart/notify-errors.ts
@@ -1,38 +1,11 @@
/**
* External dependencies
*/
-import { __ } from '@wordpress/i18n';
import { ApiErrorResponse, isApiErrorResponse } from '@woocommerce/types';
-import { createNotice, DEFAULT_ERROR_MESSAGE } from '@woocommerce/base-utils';
+import { createNotice } from '@woocommerce/base-utils';
import { decodeEntities } from '@wordpress/html-entities';
import { dispatch } from '@wordpress/data';
-/**
- * This function is used to notify the user of cart errors.
- */
-export const notifyErrors = ( error: ApiErrorResponse | null = null ) => {
- if ( error === null || ! isApiErrorResponse( error ) ) {
- return;
- }
-
- let errorMessage = error.message || DEFAULT_ERROR_MESSAGE;
-
- // Replace the generic invalid JSON message with something more user friendly.
- if ( error.code === 'invalid_json' ) {
- errorMessage = __(
- 'Something went wrong. Please contact us for assistance.',
- 'woo-gutenberg-products-block'
- );
- }
-
- // Create a new notice with a consistent error ID.
- createNotice( 'error', errorMessage, {
- id: 'woocommerce_cart_data_request_error',
- context: 'wc/cart',
- isDismissible: true,
- } );
-};
-
/**
* This function is used to notify the user of cart item errors/conflicts
*/
diff --git a/assets/js/data/cart/thunks.ts b/assets/js/data/cart/thunks.ts
index 82bcb3a040c..5528668326b 100644
--- a/assets/js/data/cart/thunks.ts
+++ b/assets/js/data/cart/thunks.ts
@@ -2,8 +2,8 @@
* External dependencies
*/
import {
- CartResponse,
Cart,
+ CartResponse,
ApiErrorResponse,
isApiErrorResponse,
} from '@woocommerce/types';
@@ -13,7 +13,7 @@ import { camelCase, mapKeys } from 'lodash';
* Internal dependencies
*/
import { notifyQuantityChanges } from './notify-quantity-changes';
-import { notifyErrors, notifyCartErrors } from './notify-errors';
+import { notifyCartErrors } from './notify-errors';
import { CartDispatchFromMap, CartSelectFromMap } from './index';
/**
@@ -46,7 +46,7 @@ export const receiveCart =
};
/**
- * A thunk used in updating the store with cart errors retrieved from a request. This also notifies the shopper of any errors that occur.
+ * A thunk used in updating the store with cart errors retrieved from a request.
*/
export const receiveError =
( response: ApiErrorResponse | null = null ) =>
@@ -57,7 +57,7 @@ export const receiveError =
if ( response.data?.cart ) {
dispatch.receiveCart( response?.data?.cart );
}
-
- notifyErrors( response );
}
};
+
+export type Thunks = typeof receiveCart | typeof receiveError;
diff --git a/assets/js/data/utils/process-error-response.ts b/assets/js/data/utils/process-error-response.ts
index bd6fb6fcd58..bc4b22fd42b 100644
--- a/assets/js/data/utils/process-error-response.ts
+++ b/assets/js/data/utils/process-error-response.ts
@@ -1,11 +1,7 @@
/**
* External dependencies
*/
-import {
- createNotice,
- createNoticeIfVisible,
- DEFAULT_ERROR_MESSAGE,
-} from '@woocommerce/base-utils';
+import { createNotice, DEFAULT_ERROR_MESSAGE } from '@woocommerce/base-utils';
import { decodeEntities } from '@wordpress/html-entities';
import {
objectHasProp,
@@ -82,6 +78,35 @@ export const getErrorDetails = (
);
};
+/**
+ * Gets appropriate error context from error code.
+ */
+const getErrorContextFromCode = ( code: string ): string => {
+ switch ( code ) {
+ case 'woocommerce_rest_missing_email_address':
+ case 'woocommerce_rest_invalid_email_address':
+ return noticeContexts.CONTACT_INFORMATION;
+ default:
+ return noticeContexts.CART;
+ }
+};
+
+/**
+ * Gets appropriate error context from error param name.
+ */
+const getErrorContextFromParam = ( param: string ): string | undefined => {
+ switch ( param ) {
+ case 'invalid_email':
+ return noticeContexts.CONTACT_INFORMATION;
+ case 'billing_address':
+ return noticeContexts.BILLING_ADDRESS;
+ case 'shipping_address':
+ return noticeContexts.SHIPPING_ADDRESS;
+ default:
+ return undefined;
+ }
+};
+
/**
* Processes the response for an invalid param error, with response code rest_invalid_param.
*/
@@ -92,28 +117,13 @@ const processInvalidParamResponse = (
const errorDetails = getErrorDetails( response );
errorDetails.forEach( ( { code, message, id, param } ) => {
- switch ( code ) {
- case 'invalid_email':
- createNotice( 'error', message, {
- id,
- context: context || noticeContexts.CONTACT_INFORMATION,
- } );
- return;
- }
- switch ( param ) {
- case 'billing_address':
- createNoticeIfVisible( 'error', message, {
- id,
- context: context || noticeContexts.BILLING_ADDRESS,
- } );
- break;
- case 'shipping_address':
- createNoticeIfVisible( 'error', message, {
- id,
- context: context || noticeContexts.SHIPPING_ADDRESS,
- } );
- break;
- }
+ createNotice( 'error', message, {
+ id,
+ context:
+ context ||
+ getErrorContextFromParam( param ) ||
+ getErrorContextFromCode( code ),
+ } );
} );
};
@@ -123,27 +133,27 @@ const processInvalidParamResponse = (
* This is where we can handle specific error codes and display notices in specific contexts.
*/
export const processErrorResponse = (
- response: ApiErrorResponse,
- context: string | undefined
+ response: ApiErrorResponse | null,
+ context?: string | undefined
) => {
if ( ! isApiErrorResponse( response ) ) {
return;
}
- switch ( response.code ) {
- case 'woocommerce_rest_missing_email_address':
- case 'woocommerce_rest_invalid_email_address':
- createNotice( 'error', response.message, {
- id: response.code,
- context: context || noticeContexts.CONTACT_INFORMATION,
- } );
- break;
- case 'rest_invalid_param':
- processInvalidParamResponse( response, context );
- break;
- default:
- createNotice( 'error', response.message || DEFAULT_ERROR_MESSAGE, {
- id: response.code,
- context: context || noticeContexts.CHECKOUT,
- } );
+
+ if ( response.code === 'rest_invalid_param' ) {
+ return processInvalidParamResponse( response, context );
+ }
+
+ let errorMessage =
+ decodeEntities( response.message ) || DEFAULT_ERROR_MESSAGE;
+
+ // Replace the generic invalid JSON message with something more user friendly.
+ if ( response.code === 'invalid_json' ) {
+ errorMessage = DEFAULT_ERROR_MESSAGE;
}
+
+ createNotice( 'error', errorMessage, {
+ id: response.code,
+ context: context || getErrorContextFromCode( response.code ),
+ } );
};
diff --git a/assets/js/types/type-defs/hooks.ts b/assets/js/types/type-defs/hooks.ts
index d57ddff68b4..eb967a9e492 100644
--- a/assets/js/types/type-defs/hooks.ts
+++ b/assets/js/types/type-defs/hooks.ts
@@ -31,8 +31,8 @@ export interface StoreCartItemQuantity {
export interface StoreCartCoupon {
appliedCoupons: CartResponseCouponItem[];
isLoading: boolean;
- applyCoupon: ( coupon: string ) => void;
- removeCoupon: ( coupon: string ) => void;
+ applyCoupon: ( coupon: string ) => Promise< boolean >;
+ removeCoupon: ( coupon: string ) => Promise< boolean >;
isApplyingCoupon: boolean;
isRemovingCoupon: boolean;
}
@@ -58,6 +58,7 @@ export interface StoreCart {
cartHasCalculatedShipping: boolean;
paymentRequirements: string[];
receiveCart: ( cart: CartResponse ) => void;
+ receiveCartContents: ( cart: CartResponse ) => void;
}
export type Query = {