Remove `useStoreNotices` and interact directly with data store instead (https://github.com/woocommerce/woocommerce-blocks/pull/6159)
* Make useStoreNotices interact directly with the store * Get/set error notices directly in store in paymentMethodDataContext * Add hasNoticesOfType util * Remove useStoreNotices and interact directly with data store * Create/remove notices directly in store * Remove tests for useStoreNotices * Add tests for notices util * Use setIsSuppressed from useStoreNoticesContext * remove useStoreNotices hook * Update context typedef to define only isSuppressed and setIsSuppressed * Remove all values from StoreNoticesContext besides setIsSuppressed * Wrap Cart and Checkout blocks in StoreNoticesProvider (for isSuppressed) * Make StoreNoticesContainer a named export This is required so we can import it from @wooommerce/base-context * Change addErrorNotice to createErrorNotice to match store action * Remove unnecessary StoreNoticeProviders and pass only context to container * Accept a context in StoreNoticesContainer * Pass relevant context to StoreNoticesContainer * Add function to remove notices by status * Prevent checkout from breaking when removing notices during processing * Prevent TS error about not included path * Add StoreNoticesContainer to single product block * Add StoreNoticesContainer to All Products Block * Ensure errors are shown when using All Products & Single Product Blocks * Add a context arg to removeNoticesByStatus * Use correct contexts for all products and single product block * Update tests to reflect new context argument * Re-add missing block file for order-summary * Remove block file for order-summary * Send context to useStoreCartCoupons to show errors correctly
This commit is contained in:
parent
d5503b6e46
commit
cab947bc2b
|
@ -76,7 +76,9 @@ const AddToCartButton = () => {
|
|||
isProcessing={ isProcessing }
|
||||
isDone={ addedToCart }
|
||||
onClick={ () => {
|
||||
dispatchActions.submitForm();
|
||||
dispatchActions.submitForm(
|
||||
`woocommerce/single-product/${ product?.id || 0 }`
|
||||
);
|
||||
dispatchStoreEvent( 'cart-add-item', {
|
||||
product,
|
||||
listName: parentName,
|
||||
|
|
|
@ -104,7 +104,10 @@ const AddToCartButton = ( {
|
|||
is_in_stock: isInStock,
|
||||
} = product;
|
||||
const { dispatchStoreEvent } = useStoreEvents();
|
||||
const { cartQuantity, addingToCart, addToCart } = useStoreAddToCart( id );
|
||||
const { cartQuantity, addingToCart, addToCart } = useStoreAddToCart(
|
||||
id,
|
||||
`woocommerce/single-product/${ id || 0 }`
|
||||
);
|
||||
|
||||
const addedToCart = Number.isFinite( cartQuantity ) && cartQuantity > 0;
|
||||
const allowAddToCart = ! hasOptions && isPurchasable && isInStock;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import type { StoreCartCoupon } from '@woocommerce/types';
|
||||
|
@ -15,7 +15,6 @@ import type { StoreCartCoupon } from '@woocommerce/types';
|
|||
import { useStoreCart } from './use-store-cart';
|
||||
import { useStoreSnackbarNotices } from '../use-store-snackbar-notices';
|
||||
import { useValidationContext } from '../../providers/validation';
|
||||
import { useStoreNotices } from '../use-store-notices';
|
||||
|
||||
/**
|
||||
* This is a custom hook for loading the Store API /cart/coupons endpoint and an
|
||||
|
@ -25,9 +24,9 @@ import { useStoreNotices } from '../use-store-notices';
|
|||
* @return {StoreCartCoupon} An object exposing data and actions from/for the
|
||||
* store api /cart/coupons endpoint.
|
||||
*/
|
||||
export const useStoreCartCoupons = (): StoreCartCoupon => {
|
||||
export const useStoreCartCoupons = ( context = '' ): StoreCartCoupon => {
|
||||
const { cartCoupons, cartIsLoading } = useStoreCart();
|
||||
const { addErrorNotice } = useStoreNotices();
|
||||
const { createErrorNotice } = useDispatch( 'core/notices' );
|
||||
const { addSnackbarNotice } = useStoreSnackbarNotices();
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
|
||||
|
@ -100,8 +99,9 @@ export const useStoreCartCoupons = (): StoreCartCoupon => {
|
|||
}
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
addErrorNotice( error.message, {
|
||||
createErrorNotice( error.message, {
|
||||
id: 'coupon-form',
|
||||
context,
|
||||
} );
|
||||
// Finished handling the coupon.
|
||||
receiveApplyingCoupon( '' );
|
||||
|
@ -115,7 +115,7 @@ export const useStoreCartCoupons = (): StoreCartCoupon => {
|
|||
isRemovingCoupon,
|
||||
};
|
||||
},
|
||||
[ addErrorNotice, addSnackbarNotice ]
|
||||
[ createErrorNotice, addSnackbarNotice ]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,6 @@ export * from './cart';
|
|||
export * from './collections';
|
||||
export * from './shipping';
|
||||
export * from './payment-methods';
|
||||
export * from './use-store-notices';
|
||||
export * from './use-store-events';
|
||||
export * from './use-query-state';
|
||||
export * from './use-store-products';
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, act } from '@testing-library/react';
|
||||
import { StoreNoticesProvider } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useStoreNotices } from '../use-store-notices';
|
||||
|
||||
describe( 'useStoreNotices', () => {
|
||||
function setup() {
|
||||
const returnVal = {};
|
||||
|
||||
function TestComponent() {
|
||||
Object.assign( returnVal, useStoreNotices() );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render(
|
||||
<StoreNoticesProvider>
|
||||
<TestComponent />
|
||||
</StoreNoticesProvider>
|
||||
);
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
test( 'allows adding and removing notices and checking if there are notices of a specific type', () => {
|
||||
const storeNoticesData = setup();
|
||||
|
||||
// Assert initial state.
|
||||
expect( storeNoticesData.notices ).toEqual( [] );
|
||||
expect( storeNoticesData.hasNoticesOfType( 'default' ) ).toBe( false );
|
||||
|
||||
// Add error notice.
|
||||
act( () => {
|
||||
storeNoticesData.addErrorNotice( 'Error notice' );
|
||||
} );
|
||||
|
||||
expect( storeNoticesData.notices.length ).toBe( 1 );
|
||||
expect( storeNoticesData.hasNoticesOfType( 'default' ) ).toBe( true );
|
||||
|
||||
expect( storeNoticesData.notices.length ).toBe( 1 );
|
||||
expect( storeNoticesData.hasNoticesOfType( 'default' ) ).toBe( true );
|
||||
|
||||
// Remove error notice.
|
||||
act( () => {
|
||||
storeNoticesData.removeNotices( 'error' );
|
||||
} );
|
||||
|
||||
expect( storeNoticesData.notices.length ).toBe( 0 );
|
||||
expect( storeNoticesData.hasNoticesOfType( 'default' ) ).toBe( false );
|
||||
|
||||
// Remove all remaining notices.
|
||||
act( () => {
|
||||
storeNoticesData.removeNotices();
|
||||
} );
|
||||
|
||||
expect( storeNoticesData.notices.length ).toBe( 0 );
|
||||
expect( storeNoticesData.hasNoticesOfType( 'default' ) ).toBe( false );
|
||||
} );
|
||||
} );
|
|
@ -11,7 +11,6 @@ import type { CartItem } from '@woocommerce/types';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { useStoreCart } from './cart/use-store-cart';
|
||||
import { useStoreNotices } from './use-store-notices';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/hooks').StoreCartItemAddToCart} StoreCartItemAddToCart
|
||||
|
@ -52,7 +51,7 @@ const getQuantityFromCartItems = (
|
|||
export const useStoreAddToCart = ( productId: number ): StoreAddToCart => {
|
||||
const { addItemToCart } = useDispatch( storeKey );
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
const { addErrorNotice, removeNotice } = useStoreNotices();
|
||||
const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
const [ addingToCart, setAddingToCart ] = useState( false );
|
||||
const currentCartItemQuantity = useRef(
|
||||
|
@ -66,9 +65,9 @@ export const useStoreAddToCart = ( productId: number ): StoreAddToCart => {
|
|||
removeNotice( 'add-to-cart' );
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
addErrorNotice( decodeEntities( error.message ), {
|
||||
context: 'wc/all-products',
|
||||
createErrorNotice( decodeEntities( error.message ), {
|
||||
id: 'add-to-cart',
|
||||
context: 'wc/all-products',
|
||||
isDismissible: true,
|
||||
} );
|
||||
} )
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useMemo, useRef, useEffect } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useStoreNoticesContext } from '../providers/store-notices/context';
|
||||
|
||||
type WPNoticeAction = {
|
||||
label: string;
|
||||
url?: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
type WPNotice = {
|
||||
id: string;
|
||||
status: 'success' | 'info' | 'error' | 'warning';
|
||||
content: string;
|
||||
spokenMessage: string;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
__unstableHTML: string;
|
||||
isDismissible: boolean;
|
||||
type: 'default' | 'snackbar';
|
||||
speak: boolean;
|
||||
actions: WPNoticeAction[];
|
||||
};
|
||||
|
||||
type NoticeOptions = {
|
||||
id: string;
|
||||
type?: string;
|
||||
isDismissible?: boolean;
|
||||
};
|
||||
|
||||
type NoticeCreator = ( text: string, noticeProps: NoticeOptions ) => void;
|
||||
|
||||
export const useStoreNotices = (): {
|
||||
notices: WPNotice[];
|
||||
hasNoticesOfType: ( type: string ) => boolean;
|
||||
removeNotices: ( status: string | null ) => void;
|
||||
removeNotice: ( id: string, context?: string ) => void;
|
||||
addDefaultNotice: NoticeCreator;
|
||||
addErrorNotice: NoticeCreator;
|
||||
addWarningNotice: NoticeCreator;
|
||||
addInfoNotice: NoticeCreator;
|
||||
addSuccessNotice: NoticeCreator;
|
||||
setIsSuppressed: ( isSuppressed: boolean ) => void;
|
||||
} => {
|
||||
const {
|
||||
notices,
|
||||
createNotice,
|
||||
removeNotice,
|
||||
setIsSuppressed,
|
||||
} = useStoreNoticesContext();
|
||||
// Added to a ref so the surface for notices doesn't change frequently
|
||||
// and thus can be used as dependencies on effects.
|
||||
const currentNotices = useRef( notices );
|
||||
|
||||
// Update notices ref whenever they change
|
||||
useEffect( () => {
|
||||
currentNotices.current = notices;
|
||||
}, [ notices ] );
|
||||
|
||||
const noticesApi = useMemo(
|
||||
() => ( {
|
||||
hasNoticesOfType: ( type: 'default' | 'snackbar' ): boolean => {
|
||||
return currentNotices.current.some(
|
||||
( notice ) => notice.type === type
|
||||
);
|
||||
},
|
||||
removeNotices: ( status = null ) => {
|
||||
currentNotices.current.forEach( ( notice ) => {
|
||||
if ( status === null || notice.status === status ) {
|
||||
removeNotice( notice.id );
|
||||
}
|
||||
} );
|
||||
},
|
||||
removeNotice,
|
||||
} ),
|
||||
[ removeNotice ]
|
||||
);
|
||||
|
||||
const noticeCreators = useMemo(
|
||||
() => ( {
|
||||
addDefaultNotice: ( text: string, noticeProps = {} ) =>
|
||||
void createNotice( 'default', text, {
|
||||
...noticeProps,
|
||||
} ),
|
||||
addErrorNotice: ( text: string, noticeProps = {} ) =>
|
||||
void createNotice( 'error', text, {
|
||||
...noticeProps,
|
||||
} ),
|
||||
addWarningNotice: ( text: string, noticeProps = {} ) =>
|
||||
void createNotice( 'warning', text, {
|
||||
...noticeProps,
|
||||
} ),
|
||||
addInfoNotice: ( text: string, noticeProps = {} ) =>
|
||||
void createNotice( 'info', text, {
|
||||
...noticeProps,
|
||||
} ),
|
||||
addSuccessNotice: ( text: string, noticeProps = {} ) =>
|
||||
void createNotice( 'success', text, {
|
||||
...noticeProps,
|
||||
} ),
|
||||
} ),
|
||||
[ createNotice ]
|
||||
);
|
||||
|
||||
return {
|
||||
notices,
|
||||
...noticesApi,
|
||||
...noticeCreators,
|
||||
setIsSuppressed,
|
||||
};
|
||||
};
|
|
@ -14,7 +14,7 @@ import {
|
|||
productIsPurchasable,
|
||||
productSupportsAddToCartForm,
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -29,8 +29,8 @@ import {
|
|||
reducer as emitReducer,
|
||||
} from './event-emit';
|
||||
import { useValidationContext } from '../../validation';
|
||||
import { useStoreNotices } from '../../../hooks/use-store-notices';
|
||||
import { useEmitResponse } from '../../../hooks/use-emit-response';
|
||||
import { removeNoticesByStatus } from '../../../../../utils/notices';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/add-to-cart-form').AddToCartFormDispatchActions} AddToCartFormDispatchActions
|
||||
|
@ -99,7 +99,7 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
);
|
||||
const [ observers, observerDispatch ] = useReducer( emitReducer, {} );
|
||||
const currentObservers = useShallowEqual( observers );
|
||||
const { addErrorNotice, removeNotices } = useStoreNotices();
|
||||
const { createErrorNotice } = useDispatch( 'core/notices' );
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
const {
|
||||
isSuccessResponse,
|
||||
|
@ -167,7 +167,7 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
const status = addToCartFormState.status;
|
||||
|
||||
if ( status === STATUS.BEFORE_PROCESSING ) {
|
||||
removeNotices( 'error' );
|
||||
removeNoticesByStatus( 'error', 'wc/add-to-cart' );
|
||||
emitEvent(
|
||||
currentObservers,
|
||||
EMIT_TYPES.ADD_TO_CART_BEFORE_PROCESSING,
|
||||
|
@ -178,7 +178,10 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
response.forEach(
|
||||
( { errorMessage, validationErrors } ) => {
|
||||
if ( errorMessage ) {
|
||||
addErrorNotice( errorMessage );
|
||||
createErrorNotice(
|
||||
errorMessage,
|
||||
'wc/add-to-cart'
|
||||
);
|
||||
}
|
||||
if ( validationErrors ) {
|
||||
setValidationErrors( validationErrors );
|
||||
|
@ -195,10 +198,10 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
}, [
|
||||
addToCartFormState.status,
|
||||
setValidationErrors,
|
||||
addErrorNotice,
|
||||
removeNotices,
|
||||
createErrorNotice,
|
||||
dispatch,
|
||||
currentObservers,
|
||||
product?.id,
|
||||
] );
|
||||
|
||||
/**
|
||||
|
@ -227,7 +230,7 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
? { context: messageContext }
|
||||
: undefined;
|
||||
handled = true;
|
||||
addErrorNotice( message, errorOptions );
|
||||
createErrorNotice( message, errorOptions );
|
||||
}
|
||||
} );
|
||||
return handled;
|
||||
|
@ -248,8 +251,11 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
'Something went wrong. Please contact us to get assistance.',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
addErrorNotice( message, {
|
||||
createErrorNotice( message, {
|
||||
id: 'add-to-cart',
|
||||
context: `woocommerce/single-product/${
|
||||
product?.id || 0
|
||||
}`,
|
||||
} );
|
||||
}
|
||||
dispatch( actions.setIdle() );
|
||||
|
@ -277,11 +283,12 @@ export const AddToCartFormStateContextProvider = ( {
|
|||
addToCartFormState.hasError,
|
||||
addToCartFormState.processingResponse,
|
||||
dispatchActions,
|
||||
addErrorNotice,
|
||||
createErrorNotice,
|
||||
isErrorResponse,
|
||||
isFailResponse,
|
||||
isSuccessResponse,
|
||||
currentObservers,
|
||||
product?.id,
|
||||
] );
|
||||
|
||||
const supportsFormElements = productSupportsAddToCartForm( product );
|
||||
|
|
|
@ -6,6 +6,7 @@ import triggerFetch from '@wordpress/api-fetch';
|
|||
import { useEffect, useCallback, useState } from '@wordpress/element';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { triggerAddedToCartEvent } from '@woocommerce/base-utils';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -13,7 +14,6 @@ import { triggerAddedToCartEvent } from '@woocommerce/base-utils';
|
|||
import { useAddToCartFormContext } from '../../form-state';
|
||||
import { useValidationContext } from '../../../validation';
|
||||
import { useStoreCart } from '../../../../hooks/cart/use-store-cart';
|
||||
import { useStoreNotices } from '../../../../hooks/use-store-notices';
|
||||
|
||||
/**
|
||||
* FormSubmit.
|
||||
|
@ -34,7 +34,7 @@ const FormSubmit = () => {
|
|||
hasValidationErrors,
|
||||
showAllValidationErrors,
|
||||
} = useValidationContext();
|
||||
const { addErrorNotice, removeNotice } = useStoreNotices();
|
||||
const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' );
|
||||
const { receiveCart } = useStoreCart();
|
||||
const [ isSubmitting, setIsSubmitting ] = useState( false );
|
||||
const doSubmit = ! hasError && isProcessing;
|
||||
|
@ -63,7 +63,10 @@ const FormSubmit = () => {
|
|||
// Triggers form submission to the API.
|
||||
const submitFormCallback = useCallback( () => {
|
||||
setIsSubmitting( true );
|
||||
removeNotice( 'add-to-cart' );
|
||||
removeNotice(
|
||||
'add-to-cart',
|
||||
`woocommerce/single-product/${ product?.id || 0 }`
|
||||
);
|
||||
|
||||
const fetchData = {
|
||||
id: product.id || 0,
|
||||
|
@ -87,20 +90,23 @@ const FormSubmit = () => {
|
|||
if ( ! fetchResponse.ok ) {
|
||||
// We received an error response.
|
||||
if ( response.body && response.body.message ) {
|
||||
addErrorNotice(
|
||||
createErrorNotice(
|
||||
decodeEntities( response.body.message ),
|
||||
{
|
||||
id: 'add-to-cart',
|
||||
context: `woocommerce/single-product/${
|
||||
product?.id || 0
|
||||
}`,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
addErrorNotice(
|
||||
createErrorNotice(
|
||||
__(
|
||||
'Something went wrong. Please contact us to get assistance.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
{
|
||||
id: 'add-to-cart',
|
||||
id: 'dadd-to-cart',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -126,7 +132,7 @@ const FormSubmit = () => {
|
|||
} );
|
||||
}, [
|
||||
product,
|
||||
addErrorNotice,
|
||||
createErrorNotice,
|
||||
removeNotice,
|
||||
receiveCart,
|
||||
dispatchActions,
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
emptyHiddenAddressFields,
|
||||
formatStoreApiErrorMessage,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -25,8 +26,7 @@ import { useCustomerDataContext } from './customer';
|
|||
import { usePaymentMethodDataContext } from './payment-methods';
|
||||
import { useValidationContext } from '../validation';
|
||||
import { useStoreCart } from '../../hooks/cart/use-store-cart';
|
||||
import { useStoreNotices } from '../../hooks/use-store-notices';
|
||||
|
||||
import { useStoreNoticesContext } from '../store-notices';
|
||||
/**
|
||||
* CheckoutProcessor component.
|
||||
*
|
||||
|
@ -58,7 +58,8 @@ const CheckoutProcessor = () => {
|
|||
paymentMethods,
|
||||
shouldSavePayment,
|
||||
} = usePaymentMethodDataContext();
|
||||
const { addErrorNotice, removeNotice, setIsSuppressed } = useStoreNotices();
|
||||
const { setIsSuppressed } = useStoreNoticesContext();
|
||||
const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' );
|
||||
const currentBillingData = useRef( billingData );
|
||||
const currentShippingAddress = useRef( shippingAddress );
|
||||
const currentRedirectUrl = useRef( redirectUrl );
|
||||
|
@ -232,13 +233,13 @@ const CheckoutProcessor = () => {
|
|||
if ( response.data?.cart ) {
|
||||
receiveCart( response.data.cart );
|
||||
}
|
||||
addErrorNotice(
|
||||
createErrorNotice(
|
||||
formatStoreApiErrorMessage( response ),
|
||||
{ id: 'checkout' }
|
||||
);
|
||||
response?.additional_errors?.forEach?.(
|
||||
( additionalError ) => {
|
||||
addErrorNotice( additionalError.message, {
|
||||
createErrorNotice( additionalError.message, {
|
||||
id: additionalError.error_code,
|
||||
} );
|
||||
}
|
||||
|
@ -246,7 +247,7 @@ const CheckoutProcessor = () => {
|
|||
dispatchActions.setAfterProcessing( response );
|
||||
} );
|
||||
} catch {
|
||||
addErrorNotice(
|
||||
createErrorNotice(
|
||||
sprintf(
|
||||
// Translators: %s Error text.
|
||||
__(
|
||||
|
@ -278,7 +279,7 @@ const CheckoutProcessor = () => {
|
|||
extensionData,
|
||||
cartNeedsShipping,
|
||||
dispatchActions,
|
||||
addErrorNotice,
|
||||
createErrorNotice,
|
||||
receiveCart,
|
||||
] );
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { usePrevious } from '@woocommerce/base-hooks';
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
import { isObject } from '@woocommerce/types';
|
||||
import { isObject, isString } from '@woocommerce/types';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -38,10 +39,10 @@ import {
|
|||
reducer as emitReducer,
|
||||
} from './event-emit';
|
||||
import { useValidationContext } from '../../validation';
|
||||
import { useStoreNotices } from '../../../hooks/use-store-notices';
|
||||
import { useStoreEvents } from '../../../hooks/use-store-events';
|
||||
import { useCheckoutNotices } from '../../../hooks/use-checkout-notices';
|
||||
import { useEmitResponse } from '../../../hooks/use-emit-response';
|
||||
import { removeNoticesByStatus } from '../../../../../utils/notices';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/contexts').CheckoutDataContext} CheckoutDataContext
|
||||
|
@ -76,7 +77,8 @@ export const CheckoutStateProvider = ( {
|
|||
DEFAULT_STATE.redirectUrl = redirectUrl;
|
||||
const [ checkoutState, dispatch ] = useReducer( reducer, DEFAULT_STATE );
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
const { addErrorNotice, removeNotices } = useStoreNotices();
|
||||
const { createErrorNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const isCalculating = checkoutState.calculatingCount > 0;
|
||||
const {
|
||||
|
@ -162,7 +164,7 @@ export const CheckoutStateProvider = ( {
|
|||
useEffect( () => {
|
||||
const status = checkoutState.status;
|
||||
if ( status === STATUS.BEFORE_PROCESSING ) {
|
||||
removeNotices( 'error' );
|
||||
removeNoticesByStatus( 'error' );
|
||||
emitEvent(
|
||||
currentObservers.current,
|
||||
EMIT_TYPES.CHECKOUT_VALIDATION_BEFORE_PROCESSING,
|
||||
|
@ -172,7 +174,7 @@ export const CheckoutStateProvider = ( {
|
|||
if ( Array.isArray( response ) ) {
|
||||
response.forEach(
|
||||
( { errorMessage, validationErrors } ) => {
|
||||
addErrorNotice( errorMessage );
|
||||
createErrorNotice( errorMessage );
|
||||
setValidationErrors( validationErrors );
|
||||
}
|
||||
);
|
||||
|
@ -187,8 +189,7 @@ export const CheckoutStateProvider = ( {
|
|||
}, [
|
||||
checkoutState.status,
|
||||
setValidationErrors,
|
||||
addErrorNotice,
|
||||
removeNotices,
|
||||
createErrorNotice,
|
||||
dispatch,
|
||||
] );
|
||||
|
||||
|
@ -210,12 +211,15 @@ export const CheckoutStateProvider = ( {
|
|||
isErrorResponse( response ) ||
|
||||
isFailResponse( response )
|
||||
) {
|
||||
if ( response.message ) {
|
||||
const errorOptions = response.messageContext
|
||||
? { context: response.messageContext }
|
||||
: undefined;
|
||||
if ( response.message && isString( response.message ) ) {
|
||||
const errorOptions =
|
||||
response.messageContext &&
|
||||
isString( response.messageContent )
|
||||
? // The `as string` is OK here because of the type guard above.
|
||||
{ context: response.messageContext as string }
|
||||
: undefined;
|
||||
errorResponse = response;
|
||||
addErrorNotice( response.message, errorOptions );
|
||||
createErrorNotice( response.message, errorOptions );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
@ -271,7 +275,7 @@ export const CheckoutStateProvider = ( {
|
|||
'Something went wrong. Please contact us to get assistance.',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
addErrorNotice( message, {
|
||||
createErrorNotice( message, {
|
||||
id: 'checkout',
|
||||
} );
|
||||
}
|
||||
|
@ -311,11 +315,16 @@ export const CheckoutStateProvider = ( {
|
|||
if ( successResponse && ! errorResponse ) {
|
||||
dispatch( actions.setComplete( successResponse ) );
|
||||
} else if ( isObject( errorResponse ) ) {
|
||||
if ( errorResponse.message ) {
|
||||
const errorOptions = errorResponse.messageContext
|
||||
? { context: errorResponse.messageContext }
|
||||
: undefined;
|
||||
addErrorNotice(
|
||||
if (
|
||||
errorResponse.message &&
|
||||
isString( errorResponse.message )
|
||||
) {
|
||||
const errorOptions =
|
||||
errorResponse.messageContext &&
|
||||
isString( errorResponse.messageContext )
|
||||
? { context: errorResponse.messageContext }
|
||||
: undefined;
|
||||
createErrorNotice(
|
||||
errorResponse.message,
|
||||
errorOptions
|
||||
);
|
||||
|
@ -346,7 +355,7 @@ export const CheckoutStateProvider = ( {
|
|||
previousStatus,
|
||||
previousHasError,
|
||||
dispatchActions,
|
||||
addErrorNotice,
|
||||
createErrorNotice,
|
||||
isErrorResponse,
|
||||
isFailResponse,
|
||||
isSuccessResponse,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
useMemo,
|
||||
} from '@wordpress/element';
|
||||
import { objectHasProp } from '@woocommerce/types';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -39,7 +40,6 @@ import {
|
|||
reducer as emitReducer,
|
||||
} from './event-emit';
|
||||
import { useValidationContext } from '../../validation';
|
||||
import { useStoreNotices } from '../../../hooks/use-store-notices';
|
||||
import { useEmitResponse } from '../../../hooks/use-emit-response';
|
||||
import { getCustomerPaymentMethods } from './utils';
|
||||
|
||||
|
@ -70,7 +70,9 @@ export const PaymentMethodDataProvider = ( {
|
|||
} = useCheckoutContext();
|
||||
const { isEditor, getPreviewData } = useEditorContext();
|
||||
const { setValidationErrors } = useValidationContext();
|
||||
const { addErrorNotice, removeNotice } = useStoreNotices();
|
||||
const { createErrorNotice: addErrorNotice, removeNotice } = useDispatch(
|
||||
'core/notices'
|
||||
);
|
||||
const {
|
||||
isSuccessResponse,
|
||||
isErrorResponse,
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
ExpressPaymentMethodConfigInstance,
|
||||
} from '@woocommerce/type-defs/payments';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -23,7 +24,6 @@ import { useDebouncedCallback } from 'use-debounce';
|
|||
import { useEditorContext } from '../../editor-context';
|
||||
import { useCustomerDataContext } from '../customer';
|
||||
import { useStoreCart } from '../../../hooks/cart/use-store-cart';
|
||||
import { useStoreNotices } from '../../../hooks/use-store-notices';
|
||||
import { useEmitResponse } from '../../../hooks/use-emit-response';
|
||||
import type { PaymentMethodsDispatcherType } from './types';
|
||||
import { useShippingData } from '../../../hooks/shipping/use-shipping-data';
|
||||
|
@ -68,7 +68,7 @@ const usePaymentMethodRegistration = (
|
|||
selectedShippingMethods,
|
||||
paymentRequirements,
|
||||
} );
|
||||
const { addErrorNotice } = useStoreNotices();
|
||||
const { createErrorNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
useEffect( () => {
|
||||
canPayArgument.current = {
|
||||
|
@ -142,7 +142,7 @@ const usePaymentMethodRegistration = (
|
|||
),
|
||||
paymentMethod.paymentMethodId
|
||||
);
|
||||
addErrorNotice( `${ errorText } ${ e }`, {
|
||||
createErrorNotice( `${ errorText } ${ e }`, {
|
||||
context: noticeContext,
|
||||
id: `wc-${ paymentMethod.paymentMethodId }-registration-error`,
|
||||
} );
|
||||
|
@ -157,7 +157,7 @@ const usePaymentMethodRegistration = (
|
|||
// That's why we track "is initialized" state here.
|
||||
setIsInitialized( true );
|
||||
}, [
|
||||
addErrorNotice,
|
||||
createErrorNotice,
|
||||
dispatcher,
|
||||
isEditor,
|
||||
noticeContext,
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Notice } from 'wordpress-components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import { useStoreNoticesContext } from '../context';
|
||||
|
||||
const getWooClassName = ( { status = 'default' } ) => {
|
||||
switch ( status ) {
|
||||
|
@ -23,10 +25,23 @@ const getWooClassName = ( { status = 'default' } ) => {
|
|||
return '';
|
||||
};
|
||||
|
||||
const StoreNoticesContainer = ( { className, notices, removeNotice } ) => {
|
||||
const regularNotices = notices.filter(
|
||||
( notice ) => notice.type !== 'snackbar'
|
||||
);
|
||||
export const StoreNoticesContainer = ( {
|
||||
className,
|
||||
context = 'default',
|
||||
additionalNotices = [],
|
||||
} ) => {
|
||||
const { isSuppressed } = useStoreNoticesContext();
|
||||
|
||||
const { notices } = useSelect( ( select ) => {
|
||||
const store = select( 'core/notices' );
|
||||
return {
|
||||
notices: store.getNotices( context ),
|
||||
};
|
||||
} );
|
||||
const { removeNotice } = useDispatch( 'core/notices' );
|
||||
const regularNotices = notices
|
||||
.filter( ( notice ) => notice.type !== 'snackbar' )
|
||||
.concat( additionalNotices );
|
||||
|
||||
if ( ! regularNotices.length ) {
|
||||
return null;
|
||||
|
@ -34,7 +49,7 @@ const StoreNoticesContainer = ( { className, notices, removeNotice } ) => {
|
|||
|
||||
const wrapperClass = classnames( className, 'wc-block-components-notices' );
|
||||
|
||||
return (
|
||||
return isSuppressed ? null : (
|
||||
<div className={ wrapperClass }>
|
||||
{ regularNotices.map( ( props ) => (
|
||||
<Notice
|
||||
|
@ -46,7 +61,7 @@ const StoreNoticesContainer = ( { className, notices, removeNotice } ) => {
|
|||
) }
|
||||
onRemove={ () => {
|
||||
if ( props.isDismissible ) {
|
||||
removeNotice( props.id );
|
||||
removeNotice( props.id, context );
|
||||
}
|
||||
} }
|
||||
>
|
||||
|
@ -69,5 +84,3 @@ StoreNoticesContainer.propTypes = {
|
|||
} )
|
||||
),
|
||||
};
|
||||
|
||||
export default StoreNoticesContainer;
|
||||
|
|
|
@ -2,20 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useStoreEvents } from '../../hooks/use-store-events';
|
||||
import { useEditorContext } from '../editor-context';
|
||||
import StoreNoticesContainer from './components/store-notices-container';
|
||||
import { createContext, useContext, useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/contexts').NoticeContext} NoticeContext
|
||||
|
@ -23,11 +10,8 @@ import StoreNoticesContainer from './components/store-notices-container';
|
|||
*/
|
||||
|
||||
const StoreNoticesContext = createContext( {
|
||||
notices: [],
|
||||
createNotice: ( status, text, props ) => void { status, text, props },
|
||||
removeNotice: ( id, ctxt ) => void { id, ctxt },
|
||||
setIsSuppressed: ( val ) => void { val },
|
||||
context: 'wc/core',
|
||||
isSuppressed: false,
|
||||
} );
|
||||
|
||||
/**
|
||||
|
@ -42,89 +26,24 @@ export const useStoreNoticesContext = () => {
|
|||
/**
|
||||
* Provides an interface for blocks to add notices to the frontend UI.
|
||||
*
|
||||
* Statuses map to https://github.com/WordPress/gutenberg/tree/master/packages/components/src/notice
|
||||
* - Default (no status)
|
||||
* - Error
|
||||
* - Warning
|
||||
* - Info
|
||||
* - Success
|
||||
*
|
||||
* @param {Object} props Incoming props for the component.
|
||||
* @param {JSX.Element} props.children The Elements wrapped by this component.
|
||||
* @param {string} [props.className] CSS class used.
|
||||
* @param {boolean} [props.createNoticeContainer] Whether to create a notice container or not.
|
||||
* @param {string} [props.context] The notice context for notices being rendered.
|
||||
*/
|
||||
export const StoreNoticesProvider = ( {
|
||||
children,
|
||||
className = '',
|
||||
createNoticeContainer = true,
|
||||
context = 'wc/core',
|
||||
} ) => {
|
||||
const { createNotice, removeNotice } = useDispatch( 'core/notices' );
|
||||
export const StoreNoticesProvider = ( { children } ) => {
|
||||
const [ isSuppressed, setIsSuppressed ] = useState( false );
|
||||
const { dispatchStoreEvent } = useStoreEvents();
|
||||
const { isEditor } = useEditorContext();
|
||||
|
||||
const createNoticeWithContext = useCallback(
|
||||
( status = 'default', content = '', options = {} ) => {
|
||||
createNotice( status, content, {
|
||||
...options,
|
||||
context: options.context || context,
|
||||
} );
|
||||
dispatchStoreEvent( 'store-notice-create', {
|
||||
status,
|
||||
content,
|
||||
options,
|
||||
} );
|
||||
},
|
||||
[ createNotice, dispatchStoreEvent, context ]
|
||||
);
|
||||
|
||||
const removeNoticeWithContext = useCallback(
|
||||
( id, ctxt = context ) => {
|
||||
removeNotice( id, ctxt );
|
||||
},
|
||||
[ removeNotice, context ]
|
||||
);
|
||||
|
||||
const { notices } = useSelect(
|
||||
( select ) => {
|
||||
return {
|
||||
notices: select( 'core/notices' ).getNotices( context ),
|
||||
};
|
||||
},
|
||||
[ context ]
|
||||
);
|
||||
|
||||
const contextValue = {
|
||||
notices,
|
||||
createNotice: createNoticeWithContext,
|
||||
removeNotice: removeNoticeWithContext,
|
||||
context,
|
||||
setIsSuppressed,
|
||||
isSuppressed,
|
||||
};
|
||||
|
||||
const noticeOutput = isSuppressed ? null : (
|
||||
<StoreNoticesContainer
|
||||
className={ className }
|
||||
notices={ contextValue.notices }
|
||||
removeNotice={ contextValue.removeNotice }
|
||||
isEditor={ isEditor }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<StoreNoticesContext.Provider value={ contextValue }>
|
||||
{ createNoticeContainer && noticeOutput }
|
||||
{ children }
|
||||
</StoreNoticesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
StoreNoticesProvider.propTypes = {
|
||||
className: PropTypes.string,
|
||||
createNoticeContainer: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
context: PropTypes.string,
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"../../settings/blocks/index.ts",
|
||||
"../../base/hooks/index.js",
|
||||
"../../base/utils/",
|
||||
"../../utils",
|
||||
"../../data/",
|
||||
"../../types/",
|
||||
"../components",
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
useExpressPaymentMethods,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreNoticesContainer,
|
||||
useCheckoutContext,
|
||||
usePaymentMethodDataContext,
|
||||
} from '@woocommerce/base-context';
|
||||
|
@ -57,11 +57,10 @@ const CartExpressPayment = () => {
|
|||
>
|
||||
<div className="wc-block-components-express-payment wc-block-components-express-payment--cart">
|
||||
<div className="wc-block-components-express-payment__content">
|
||||
<StoreNoticesProvider
|
||||
<StoreNoticesContainer
|
||||
context={ noticeContexts.EXPRESS_PAYMENTS }
|
||||
>
|
||||
<ExpressPaymentMethods />
|
||||
</StoreNoticesProvider>
|
||||
/>
|
||||
<ExpressPaymentMethods />
|
||||
</div>
|
||||
</div>
|
||||
</LoadingMask>
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
useExpressPaymentMethods,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreNoticesContainer,
|
||||
useCheckoutContext,
|
||||
usePaymentMethodDataContext,
|
||||
useEditorContext,
|
||||
|
@ -44,9 +44,9 @@ const CheckoutExpressPayment = () => {
|
|||
// when a payment method fails to register.
|
||||
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
|
||||
return (
|
||||
<StoreNoticesProvider
|
||||
<StoreNoticesContainer
|
||||
context={ noticeContexts.EXPRESS_PAYMENTS }
|
||||
></StoreNoticesProvider>
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -81,17 +81,16 @@ const CheckoutExpressPayment = () => {
|
|||
</Title>
|
||||
</div>
|
||||
<div className="wc-block-components-express-payment__content">
|
||||
<StoreNoticesProvider
|
||||
<StoreNoticesContainer
|
||||
context={ noticeContexts.EXPRESS_PAYMENTS }
|
||||
>
|
||||
<p>
|
||||
{ __(
|
||||
'In a hurry? Use one of our express checkout options:',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</p>
|
||||
<ExpressPaymentMethods />
|
||||
</StoreNoticesProvider>
|
||||
/>
|
||||
<p>
|
||||
{ __(
|
||||
'In a hurry? Use one of our express checkout options:',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</p>
|
||||
<ExpressPaymentMethods />
|
||||
</div>
|
||||
</div>
|
||||
</LoadingMask>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Component } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import { StoreNoticesContainer } from '@woocommerce/base-context';
|
||||
import { noticeContexts } from '@woocommerce/base-context/hooks';
|
||||
|
||||
class PaymentMethodErrorBoundary extends Component {
|
||||
state = { errorMessage: '', hasError: false };
|
||||
|
@ -44,7 +45,12 @@ class PaymentMethodErrorBoundary extends Component {
|
|||
status: 'error',
|
||||
},
|
||||
];
|
||||
return <StoreNoticesContainer notices={ notices } />;
|
||||
return (
|
||||
<StoreNoticesContainer
|
||||
additionalNotices={ notices }
|
||||
context={ noticeContexts.PAYMENTS }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
usePaymentMethods,
|
||||
usePaymentMethodInterface,
|
||||
useEmitResponse,
|
||||
useStoreNotices,
|
||||
useStoreEvents,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { cloneElement, useCallback } from '@wordpress/element';
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
} from '@woocommerce/base-context';
|
||||
import classNames from 'classnames';
|
||||
import RadioControlAccordion from '@woocommerce/base-components/radio-control-accordion';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -39,7 +39,7 @@ const PaymentMethodOptions = () => {
|
|||
...paymentMethodInterface
|
||||
} = usePaymentMethodInterface();
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
const { removeNotice } = useStoreNotices();
|
||||
const { removeNotice } = useDispatch( 'core/notices' );
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
const { isEditor } = useEditorContext();
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import RadioControl from '@woocommerce/base-components/radio-control';
|
|||
import {
|
||||
usePaymentMethodInterface,
|
||||
usePaymentMethods,
|
||||
useStoreNotices,
|
||||
useStoreEvents,
|
||||
useEmitResponse,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* @typedef {import('@woocommerce/type-defs/contexts').CustomerPaymentMethod} CustomerPaymentMethod
|
||||
|
@ -61,7 +61,7 @@ const SavedPaymentMethodOptions = () => {
|
|||
const { paymentMethods } = usePaymentMethods();
|
||||
const paymentMethodInterface = usePaymentMethodInterface();
|
||||
const { noticeContexts } = useEmitResponse();
|
||||
const { removeNotice } = useStoreNotices();
|
||||
const { removeNotice } = useDispatch( 'core/notices' );
|
||||
const { dispatchCheckoutEvent } = useStoreEvents();
|
||||
|
||||
const options = useMemo( () => {
|
||||
|
|
|
@ -5,7 +5,10 @@ import { __ } from '@wordpress/i18n';
|
|||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import LoadingMask from '@woocommerce/base-components/loading-mask';
|
||||
import { ValidationContextProvider } from '@woocommerce/base-context';
|
||||
import {
|
||||
ValidationContextProvider,
|
||||
StoreNoticesContainer,
|
||||
} from '@woocommerce/base-context';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils';
|
||||
|
@ -84,7 +87,8 @@ const Block = ( { attributes, children, scrollToTop } ) => (
|
|||
showErrorMessage={ CURRENT_USER_IS_ADMIN }
|
||||
>
|
||||
<StoreSnackbarNoticesProvider context="wc/cart">
|
||||
<StoreNoticesProvider context="wc/cart">
|
||||
<StoreNoticesProvider>
|
||||
<StoreNoticesContainer context="wc/cart" />
|
||||
<SlotFillProvider>
|
||||
<CartProvider>
|
||||
<Cart attributes={ attributes }>{ children }</Cart>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { TotalsWrapper } from '@woocommerce/blocks-checkout';
|
|||
const Block = ( { className }: { className: string } ): JSX.Element | null => {
|
||||
const couponsEnabled = getSetting( 'couponsEnabled', true );
|
||||
|
||||
const { applyCoupon, isApplyingCoupon } = useStoreCartCoupons();
|
||||
const { applyCoupon, isApplyingCoupon } = useStoreCartCoupons( 'wc/cart' );
|
||||
|
||||
if ( ! couponsEnabled ) {
|
||||
return null;
|
||||
|
|
|
@ -27,7 +27,7 @@ const DiscountSlotFill = (): JSX.Element => {
|
|||
|
||||
const Block = ( { className }: { className: string } ): JSX.Element => {
|
||||
const { cartTotals, cartCoupons } = useStoreCart();
|
||||
const { removeCoupon, isRemovingCoupon } = useStoreCartCoupons();
|
||||
const { removeCoupon, isRemovingCoupon } = useStoreCartCoupons( 'wc/cart' );
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
*/
|
||||
import classnames from 'classnames';
|
||||
import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { useStoreCart, useStoreNotices } from '@woocommerce/base-context/hooks';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -21,17 +22,17 @@ const FrontendBlock = ( {
|
|||
} ): JSX.Element | null => {
|
||||
const { cartItems, cartIsLoading, cartItemErrors } = useStoreCart();
|
||||
const { hasDarkControls } = useCartBlockContext();
|
||||
const { addErrorNotice } = useStoreNotices();
|
||||
const { createErrorNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
// Ensures any cart errors listed in the API response get shown.
|
||||
useEffect( () => {
|
||||
cartItemErrors.forEach( ( error ) => {
|
||||
addErrorNotice( decodeEntities( error.message ), {
|
||||
createErrorNotice( decodeEntities( error.message ), {
|
||||
isDismissible: true,
|
||||
id: error.code,
|
||||
} );
|
||||
} );
|
||||
}, [ addErrorNotice, cartItemErrors ] );
|
||||
}, [ createErrorNotice, cartItemErrors ] );
|
||||
|
||||
if ( cartIsLoading || cartItems.length >= 1 ) {
|
||||
return (
|
||||
|
|
|
@ -4,15 +4,17 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import { createInterpolateElement, useEffect } from '@wordpress/element';
|
||||
import { useStoreCart, useStoreNotices } from '@woocommerce/base-context/hooks';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
useCheckoutContext,
|
||||
useValidationContext,
|
||||
ValidationContextProvider,
|
||||
StoreNoticesProvider,
|
||||
CheckoutProvider,
|
||||
} from '@woocommerce/base-context';
|
||||
import { StoreSnackbarNoticesProvider } from '@woocommerce/base-context/providers';
|
||||
import {
|
||||
StoreSnackbarNoticesProvider,
|
||||
StoreNoticesContainer,
|
||||
} from '@woocommerce/base-context/providers';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { CURRENT_USER_IS_ADMIN, getSetting } from '@woocommerce/settings';
|
||||
|
@ -28,6 +30,8 @@ import CheckoutOrderError from './checkout-order-error';
|
|||
import { LOGIN_TO_CHECKOUT_URL, isLoginRequired, reloadPage } from './utils';
|
||||
import type { Attributes } from './types';
|
||||
import { CheckoutBlockContext } from './context';
|
||||
import { hasNoticesOfType } from '../../utils/notices';
|
||||
import { StoreNoticesProvider } from '../../base/context/providers';
|
||||
|
||||
const LoginPrompt = () => {
|
||||
return (
|
||||
|
@ -102,7 +106,6 @@ const ScrollOnError = ( {
|
|||
}: {
|
||||
scrollToTop: ( props: Record< string, unknown > ) => void;
|
||||
} ): null => {
|
||||
const { hasNoticesOfType } = useStoreNotices();
|
||||
const {
|
||||
hasError: checkoutHasError,
|
||||
isIdle: checkoutIsIdle,
|
||||
|
@ -115,7 +118,7 @@ const ScrollOnError = ( {
|
|||
const hasErrorsToDisplay =
|
||||
checkoutIsIdle &&
|
||||
checkoutHasError &&
|
||||
( hasValidationErrors || hasNoticesOfType( 'default' ) );
|
||||
( hasValidationErrors || hasNoticesOfType( 'wc/checkout', 'default' ) );
|
||||
|
||||
useEffect( () => {
|
||||
let scrollToTopTimeout: number;
|
||||
|
@ -144,48 +147,59 @@ const Block = ( {
|
|||
attributes: Attributes;
|
||||
children: React.ReactChildren;
|
||||
scrollToTop: ( props: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element => (
|
||||
<BlockErrorBoundary
|
||||
header={ __( 'Something went wrong…', 'woo-gutenberg-products-block' ) }
|
||||
text={ createInterpolateElement(
|
||||
__(
|
||||
'The checkout has encountered an unexpected error. <button>Try reloading the page</button>. If the error persists, please get in touch with us so we can assist.',
|
||||
} ): JSX.Element => {
|
||||
return (
|
||||
<BlockErrorBoundary
|
||||
header={ __(
|
||||
'Something went wrong…',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
{
|
||||
button: (
|
||||
<button
|
||||
className="wc-block-link-button"
|
||||
onClick={ reloadPage }
|
||||
/>
|
||||
) }
|
||||
text={ createInterpolateElement(
|
||||
__(
|
||||
'The checkout has encountered an unexpected error. <button>Try reloading the page</button>. If the error persists, please get in touch with us so we can assist.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
}
|
||||
) }
|
||||
showErrorMessage={ CURRENT_USER_IS_ADMIN }
|
||||
>
|
||||
<StoreSnackbarNoticesProvider context="wc/checkout">
|
||||
<StoreNoticesProvider context="wc/checkout">
|
||||
<ValidationContextProvider>
|
||||
{ /* SlotFillProvider need to be defined before CheckoutProvider so fills have the SlotFill context ready when they mount. */ }
|
||||
<SlotFillProvider>
|
||||
<CheckoutProvider>
|
||||
<SidebarLayout
|
||||
className={ classnames( 'wc-block-checkout', {
|
||||
'has-dark-controls':
|
||||
attributes.hasDarkControls,
|
||||
} ) }
|
||||
>
|
||||
<Checkout attributes={ attributes }>
|
||||
{ children }
|
||||
</Checkout>
|
||||
<ScrollOnError scrollToTop={ scrollToTop } />
|
||||
</SidebarLayout>
|
||||
</CheckoutProvider>
|
||||
</SlotFillProvider>
|
||||
</ValidationContextProvider>
|
||||
</StoreNoticesProvider>
|
||||
</StoreSnackbarNoticesProvider>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
{
|
||||
button: (
|
||||
<button
|
||||
className="wc-block-link-button"
|
||||
onClick={ reloadPage }
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
showErrorMessage={ CURRENT_USER_IS_ADMIN }
|
||||
>
|
||||
<StoreSnackbarNoticesProvider context="wc/checkout">
|
||||
<StoreNoticesProvider>
|
||||
<StoreNoticesContainer context="wc/checkout" />
|
||||
<ValidationContextProvider>
|
||||
{ /* SlotFillProvider need to be defined before CheckoutProvider so fills have the SlotFill context ready when they mount. */ }
|
||||
<SlotFillProvider>
|
||||
<CheckoutProvider>
|
||||
<SidebarLayout
|
||||
className={ classnames(
|
||||
'wc-block-checkout',
|
||||
{
|
||||
'has-dark-controls':
|
||||
attributes.hasDarkControls,
|
||||
}
|
||||
) }
|
||||
>
|
||||
<Checkout attributes={ attributes }>
|
||||
{ children }
|
||||
</Checkout>
|
||||
<ScrollOnError
|
||||
scrollToTop={ scrollToTop }
|
||||
/>
|
||||
</SidebarLayout>
|
||||
</CheckoutProvider>
|
||||
</SlotFillProvider>
|
||||
</ValidationContextProvider>
|
||||
</StoreNoticesProvider>
|
||||
</StoreSnackbarNoticesProvider>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default withScrollToTop( Block );
|
||||
|
|
|
@ -13,7 +13,9 @@ const Block = ( {
|
|||
} ): JSX.Element | null => {
|
||||
const couponsEnabled = getSetting( 'couponsEnabled', true );
|
||||
|
||||
const { applyCoupon, isApplyingCoupon } = useStoreCartCoupons();
|
||||
const { applyCoupon, isApplyingCoupon } = useStoreCartCoupons(
|
||||
'wc/checkout'
|
||||
);
|
||||
|
||||
if ( ! couponsEnabled ) {
|
||||
return null;
|
||||
|
|
|
@ -27,7 +27,9 @@ const DiscountSlotFill = (): JSX.Element => {
|
|||
|
||||
const Block = ( { className = '' }: { className?: string } ): JSX.Element => {
|
||||
const { cartTotals, cartCoupons } = useStoreCart();
|
||||
const { removeCoupon, isRemovingCoupon } = useStoreCartCoupons();
|
||||
const { removeCoupon, isRemovingCoupon } = useStoreCartCoupons(
|
||||
'wc/checkout'
|
||||
);
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,16 +5,14 @@ import classnames from 'classnames';
|
|||
import { useStoreCart, useEmitResponse } from '@woocommerce/base-context/hooks';
|
||||
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
||||
import { FormStep } from '@woocommerce/base-components/cart-checkout';
|
||||
import {
|
||||
useCheckoutContext,
|
||||
StoreNoticesProvider,
|
||||
} from '@woocommerce/base-context';
|
||||
import { useCheckoutContext } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import attributes from './attributes';
|
||||
import { StoreNoticesContainer } from '../../../../base/context/providers';
|
||||
|
||||
const FrontendBlock = ( {
|
||||
title,
|
||||
|
@ -48,9 +46,8 @@ const FrontendBlock = ( {
|
|||
description={ description }
|
||||
showStepNumber={ showStepNumber }
|
||||
>
|
||||
<StoreNoticesProvider context={ noticeContexts.PAYMENTS }>
|
||||
<Block />
|
||||
</StoreNoticesProvider>
|
||||
<StoreNoticesContainer context={ noticeContexts.PAYMENTS } />
|
||||
<Block />
|
||||
{ children }
|
||||
</FormStep>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,10 @@ import PropTypes from 'prop-types';
|
|||
import { ProductListContainer } from '@woocommerce/base-components/product-list';
|
||||
import { InnerBlockLayoutContextProvider } from '@woocommerce/shared-context';
|
||||
import { gridBlockPreview } from '@woocommerce/resource-previews';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreNoticesContainer,
|
||||
} from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* The All Products Block.
|
||||
|
@ -36,6 +40,9 @@ class Block extends Component {
|
|||
parentName="woocommerce/all-products"
|
||||
parentClassName="wc-block-grid"
|
||||
>
|
||||
<StoreNoticesProvider>
|
||||
<StoreNoticesContainer context={ 'wc/all-products' } />
|
||||
</StoreNoticesProvider>
|
||||
<ProductListContainer
|
||||
attributes={ attributes }
|
||||
urlParameterSuffix={ urlParameterSuffix }
|
||||
|
|
|
@ -7,7 +7,10 @@ import {
|
|||
InnerBlockLayoutContextProvider,
|
||||
ProductDataContextProvider,
|
||||
} from '@woocommerce/shared-context';
|
||||
import { StoreNoticesProvider } from '@woocommerce/base-context';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreNoticesContainer,
|
||||
} from '@woocommerce/base-context';
|
||||
import { useStoreEvents } from '@woocommerce/base-context/hooks';
|
||||
|
||||
/**
|
||||
|
@ -46,9 +49,10 @@ const Block = ( { isLoading, product, children } ) => {
|
|||
product={ product }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<StoreNoticesProvider context={ noticeContext }>
|
||||
<div className={ className }>{ children }</div>
|
||||
<StoreNoticesProvider>
|
||||
<StoreNoticesContainer context={ noticeContext } />
|
||||
</StoreNoticesProvider>
|
||||
<div className={ className }>{ children }</div>
|
||||
</ProductDataContextProvider>
|
||||
</InnerBlockLayoutContextProvider>
|
||||
);
|
||||
|
|
|
@ -214,13 +214,8 @@
|
|||
/**
|
||||
* @typedef NoticeContext
|
||||
*
|
||||
* @property {Array<StoreNoticeObject>} notices An array of notice objects.
|
||||
* @property {function(string,string,any):undefined} createNotice Creates a notice for the given arguments.
|
||||
* @property {function(string, any):undefined} createSnackbarNotice Creates a snackbar notice type.
|
||||
* @property {function(string,string=):undefined} removeNotice Removes a notice with the given id and context
|
||||
* @property {string} context The current context identifier for the notice
|
||||
* provider
|
||||
* @property {function(boolean):void} setIsSuppressed Consumers can use this setter to suppress
|
||||
* @property {function(boolean):void} setIsSuppressed Consumers can use this setter to suppress notices.
|
||||
* @property {boolean} isSuppressed Whether notices should be hidden/suppressed.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
import { Notice } from '@wordpress/notices';
|
||||
|
||||
export const hasNoticesOfType = (
|
||||
context = '',
|
||||
type: 'default' | 'snackbar'
|
||||
): boolean => {
|
||||
const notices: Notice[] = select( 'core/notices' ).getNotices( context );
|
||||
return notices.some( ( notice: Notice ) => notice.type === type );
|
||||
};
|
||||
|
||||
export const removeNoticesByStatus = ( status: string, context = '' ): void => {
|
||||
const notices = select( 'core/notices' ).getNotices();
|
||||
const { removeNotice } = dispatch( 'core/notices' );
|
||||
const noticesOfType = notices.filter(
|
||||
( notice ) => notice.status === status
|
||||
);
|
||||
noticesOfType.forEach( ( notice ) => removeNotice( notice.id, context ) );
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select, dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { hasNoticesOfType, removeNoticesByStatus } from '../notices';
|
||||
|
||||
jest.mock( '@wordpress/data' );
|
||||
|
||||
describe( 'Notice utils', () => {
|
||||
beforeEach( () => {
|
||||
jest.resetAllMocks();
|
||||
} );
|
||||
describe( 'hasNoticesOfType', () => {
|
||||
it( 'Correctly returns if there are notices of a given type in the core data store', () => {
|
||||
select.mockReturnValue( {
|
||||
getNotices: jest.fn().mockReturnValue( [
|
||||
{
|
||||
id: 'coupon-form',
|
||||
status: 'error',
|
||||
content:
|
||||
'Coupon cannot be removed because it is not already applied to the cart.',
|
||||
spokenMessage:
|
||||
'Coupon cannot be removed because it is not already applied to the cart.',
|
||||
isDismissible: true,
|
||||
actions: [],
|
||||
type: 'default',
|
||||
icon: null,
|
||||
explicitDismiss: false,
|
||||
},
|
||||
] ),
|
||||
} );
|
||||
const hasSnackbarNotices = hasNoticesOfType(
|
||||
'wc/cart',
|
||||
'snackbar'
|
||||
);
|
||||
const hasDefaultNotices = hasNoticesOfType( 'wc/cart', 'default' );
|
||||
expect( hasDefaultNotices ).toBe( true );
|
||||
expect( hasSnackbarNotices ).toBe( false );
|
||||
} );
|
||||
|
||||
it( 'Handles notices being empty', () => {
|
||||
select.mockReturnValue( {
|
||||
getNotices: jest.fn().mockReturnValue( [] ),
|
||||
} );
|
||||
const hasDefaultNotices = hasNoticesOfType( 'wc/cart', 'default' );
|
||||
expect( hasDefaultNotices ).toBe( false );
|
||||
} );
|
||||
} );
|
||||
describe( 'removeNoticesByStatus', () => {
|
||||
it( 'Correctly removes notices of a given status', () => {
|
||||
select.mockReturnValue( {
|
||||
getNotices: jest.fn().mockReturnValue( [
|
||||
{
|
||||
id: 'coupon-form',
|
||||
status: 'error',
|
||||
content:
|
||||
'Coupon cannot be removed because it is not already applied to the cart.',
|
||||
spokenMessage:
|
||||
'Coupon cannot be removed because it is not already applied to the cart.',
|
||||
isDismissible: true,
|
||||
actions: [],
|
||||
type: 'default',
|
||||
icon: null,
|
||||
explicitDismiss: false,
|
||||
},
|
||||
{
|
||||
id: 'address-form',
|
||||
status: 'error',
|
||||
content: 'Address invalid',
|
||||
spokenMessage: 'Address invalid',
|
||||
isDismissible: true,
|
||||
actions: [],
|
||||
type: 'default',
|
||||
icon: null,
|
||||
explicitDismiss: false,
|
||||
},
|
||||
{
|
||||
id: 'some-warning',
|
||||
status: 'warning',
|
||||
content: 'Warning notice.',
|
||||
spokenMessage: 'Warning notice.',
|
||||
isDismissible: true,
|
||||
actions: [],
|
||||
type: 'default',
|
||||
icon: null,
|
||||
explicitDismiss: false,
|
||||
},
|
||||
] ),
|
||||
} );
|
||||
dispatch.mockReturnValue( {
|
||||
removeNotice: jest.fn(),
|
||||
} );
|
||||
removeNoticesByStatus( 'error' );
|
||||
expect( dispatch().removeNotice ).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'coupon-form',
|
||||
''
|
||||
);
|
||||
expect( dispatch().removeNotice ).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'address-form',
|
||||
''
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Handles notices being empty', () => {
|
||||
select.mockReturnValue( {
|
||||
getNotices: jest.fn().mockReturnValue( [] ),
|
||||
} );
|
||||
|
||||
dispatch.mockReturnValue( {
|
||||
removeNotice: jest.fn(),
|
||||
} );
|
||||
removeNoticesByStatus( 'empty' );
|
||||
expect( dispatch().removeNotice ).not.toBeCalled();
|
||||
} );
|
||||
} );
|
||||
} );
|
Loading…
Reference in New Issue