Improve the dismissal behavior of the incompatible gateways notice (https://github.com/woocommerce/woocommerce-blocks/pull/8299)

* Fix notice persistence after dismissal

- This fix applied to the `incompatible payment gateway notice`.
- We used the same dismissal logic in the `sidebar compatibility notice`

* Get incompatible payments when initialized

We initially get the list of `globalPaymentMethods` shared from
the back-end as incompatible payments, because the front-end
`availablePaymentMethods` is empty before the
`paymentMethodsInitialized` state

* Introduce advanced notice dismissal handling

We want to display a dismissed incompatible gateways notice, when the
list of incompatible gateways is updated (e.g., a new incompatible
gateway is enabled)

* Use the full block name for the `Cart` & `Checkout`

* Update variable name for comprehension

* Fix TS errors

* Remove unused imports
This commit is contained in:
Saad Tarhi 2023-02-24 11:57:24 +01:00 committed by GitHub
parent c915123afc
commit 39b6c1c320
5 changed files with 137 additions and 29 deletions

View File

@ -2,11 +2,12 @@
* External dependencies
*/
import { useEffect, useState } from '@wordpress/element';
import { Dispatch, SetStateAction } from 'react';
export const useLocalStorageState = < T >(
key: string,
initialValue: T
): [ T, ( arg0: T ) => void ] => {
): [ T, Dispatch< SetStateAction< T > > ] => {
const [ state, setState ] = useState< T >( () => {
const valueInLocalStorage = window.localStorage.getItem( key );
if ( valueInLocalStorage ) {

View File

@ -98,6 +98,11 @@ const withSidebarNotices = createHigherOrderComponent(
toggleDismissedStatus={
toggleIncompatiblePaymentGatewaysNoticeDismissedStatus
}
block={
isCheckout
? 'woocommerce/checkout'
: 'woocommerce/cart'
}
/>
{ isIncompatiblePaymentGatewaysNoticeDismissed ? (

View File

@ -111,13 +111,24 @@ export const getPaymentMethodData = ( state: PaymentState ) => {
};
export const getIncompatiblePaymentMethods = ( state: PaymentState ) => {
const {
availablePaymentMethods,
availableExpressPaymentMethods,
paymentMethodsInitialized,
expressPaymentMethodsInitialized,
} = state;
if ( ! paymentMethodsInitialized || ! expressPaymentMethodsInitialized ) {
return {};
}
return Object.fromEntries(
Object.entries( globalPaymentMethods ).filter( ( [ k ] ) => {
return ! (
k in
{
...state.availablePaymentMethods,
...state.availableExpressPaymentMethods,
...availablePaymentMethods,
...availableExpressPaymentMethods,
}
);
} )

View File

@ -3,48 +3,36 @@
*/
import { _n } from '@wordpress/i18n';
import { Notice, ExternalLink } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import {
useState,
createInterpolateElement,
useEffect,
} from '@wordpress/element';
import { createInterpolateElement, useEffect } from '@wordpress/element';
import { Alert } from '@woocommerce/icons';
import { Icon } from '@wordpress/icons';
/**
* Internal dependencies
*/
import { STORE_KEY as PAYMENT_STORE_KEY } from '../../data/payment/constants';
import { useIncompatiblePaymentGatewaysNotice } from './use-incompatible-payment-gateways-notice';
import './editor.scss';
interface PaymentGatewaysNoticeProps {
toggleDismissedStatus: ( status: boolean ) => void;
block: 'woocommerce/cart' | 'woocommerce/checkout';
}
export function IncompatiblePaymentGatewaysNotice( {
toggleDismissedStatus,
block,
}: PaymentGatewaysNoticeProps ) {
// Everything below works the same for Cart/Checkout
const { incompatiblePaymentMethods } = useSelect( ( select ) => {
const { getIncompatiblePaymentMethods } = select( PAYMENT_STORE_KEY );
return {
incompatiblePaymentMethods: getIncompatiblePaymentMethods(),
};
}, [] );
const [ settingStatus, setStatus ] = useState( 'pristine' );
const numberOfIncompatiblePaymentMethods = Object.keys(
incompatiblePaymentMethods
).length;
const isNoticeDismissed =
numberOfIncompatiblePaymentMethods === 0 ||
settingStatus === 'dismissed';
const [
isVisible,
dismissNotice,
incompatiblePaymentMethods,
numberOfIncompatiblePaymentMethods,
] = useIncompatiblePaymentGatewaysNotice( block );
useEffect( () => {
toggleDismissedStatus( isNoticeDismissed );
}, [ isNoticeDismissed, toggleDismissedStatus ] );
toggleDismissedStatus( ! isVisible );
}, [ isVisible, toggleDismissedStatus ] );
if ( isNoticeDismissed ) {
if ( ! isVisible ) {
return null;
}
@ -68,7 +56,7 @@ export function IncompatiblePaymentGatewaysNotice( {
<Notice
className="wc-blocks-incompatible-extensions-notice"
status={ 'warning' }
onRemove={ () => setStatus( 'dismissed' ) }
onRemove={ dismissNotice }
spokenMessage={ noticeContent }
>
<div className="wc-blocks-incompatible-extensions-notice__content">

View File

@ -0,0 +1,103 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import { useLocalStorageState } from '@woocommerce/base-hooks';
/**
* Internal dependencies
*/
import { STORE_KEY as PAYMENT_STORE_KEY } from '../../data/payment/constants';
type StoredIncompatibleGateway = { [ k: string ]: string[] };
const initialDismissedNotices: React.SetStateAction<
StoredIncompatibleGateway[]
> = [];
const areEqual = ( array1: string[], array2: string[] ) => {
if ( array1.length !== array2.length ) {
return false;
}
const uniqueCollectionValues = new Set( [ ...array1, ...array2 ] );
return uniqueCollectionValues.size === array1.length;
};
export const useIncompatiblePaymentGatewaysNotice = (
blockName: string
): [ boolean, () => void, { [ k: string ]: string }, number ] => {
const [ dismissedNotices, setDismissedNotices ] = useLocalStorageState<
StoredIncompatibleGateway[]
>(
`wc-blocks_dismissed_incompatible_payment_gateways_notices`,
initialDismissedNotices
);
const [ isVisible, setIsVisible ] = useState( false );
const { incompatiblePaymentMethods } = useSelect( ( select ) => {
const { getIncompatiblePaymentMethods } = select( PAYMENT_STORE_KEY );
return {
incompatiblePaymentMethods: getIncompatiblePaymentMethods(),
};
}, [] );
const incompatiblePaymentMethodsIDs = Object.keys(
incompatiblePaymentMethods
);
const numberOfIncompatiblePaymentMethods =
incompatiblePaymentMethodsIDs.length;
const isDismissedNoticeUpToDate = dismissedNotices.some(
( notice ) =>
Object.keys( notice ).includes( blockName ) &&
areEqual(
notice[ blockName as keyof object ],
incompatiblePaymentMethodsIDs
)
);
const shouldBeDismissed =
numberOfIncompatiblePaymentMethods === 0 || isDismissedNoticeUpToDate;
const dismissNotice = () => {
const dismissedNoticesSet = new Set( dismissedNotices );
dismissedNoticesSet.add( {
[ blockName ]: incompatiblePaymentMethodsIDs,
} );
setDismissedNotices( [ ...dismissedNoticesSet ] );
};
// This ensures the modal is not loaded on first render. This is required so
// Gutenberg doesn't steal the focus from the Guide and focuses the block.
useEffect( () => {
setIsVisible( ! shouldBeDismissed );
if ( ! shouldBeDismissed && ! isDismissedNoticeUpToDate ) {
setDismissedNotices( ( previousDismissedNotices ) =>
previousDismissedNotices.reduce(
( acc: StoredIncompatibleGateway[], curr ) => {
if ( Object.keys( curr ).includes( blockName ) ) {
return acc;
}
acc.push( curr );
return acc;
},
[]
)
);
}
}, [
shouldBeDismissed,
isDismissedNoticeUpToDate,
setDismissedNotices,
blockName,
] );
return [
isVisible,
dismissNotice,
incompatiblePaymentMethods,
numberOfIncompatiblePaymentMethods,
];
};