/** * External dependencies */ import { useState, useEffect } from '@wordpress/element'; import { useLocalStorageState } from '@woocommerce/base-hooks'; /** * Internal dependencies */ import { useIncompatiblePaymentGatewaysNotice } from './use-incompatible-payment-gateways-notice'; import { useIncompatibleExtensionNotice } from './use-incompatible-extensions-notice'; type StoredIncompatibleExtension = { [ k: string ]: string[] }; const initialDismissedNotices: React.SetStateAction< StoredIncompatibleExtension[] > = []; const areEqual = ( array1: string[], array2: string[] ) => { if ( array1.length !== array2.length ) { return false; } const uniqueCollectionValues = new Set( [ ...array1, ...array2 ] ); return uniqueCollectionValues.size === array1.length; }; const sortAlphabetically = ( obj: { [ key: string ]: string; } ): { [ key: string ]: string } => Object.fromEntries( Object.entries( obj ).sort( ( [ , a ], [ , b ] ) => a.localeCompare( b ) ) ); export const useCombinedIncompatibilityNotice = ( blockName: string ): [ boolean, () => void, { [ k: string ]: string }, number ] => { const [ incompatibleExtensions, incompatibleExtensionSlugs, incompatibleExtensionCount, ] = useIncompatibleExtensionNotice(); const [ incompatiblePaymentMethods, incompatiblePaymentMethodSlugs, incompatiblePaymentMethodCount, ] = useIncompatiblePaymentGatewaysNotice(); const allIncompatibleItems = { ...incompatibleExtensions, ...incompatiblePaymentMethods, }; const allIncompatibleItemSlugs = [ ...incompatibleExtensionSlugs, ...incompatiblePaymentMethodSlugs, ]; const allIncompatibleItemCount = incompatibleExtensionCount + incompatiblePaymentMethodCount; const [ dismissedNotices, setDismissedNotices ] = useLocalStorageState< StoredIncompatibleExtension[] >( `wc-blocks_dismissed_incompatible_extensions_notices`, initialDismissedNotices ); const [ isVisible, setIsVisible ] = useState( false ); const isDismissedNoticeUpToDate = dismissedNotices.some( ( notice ) => Object.keys( notice ).includes( blockName ) && areEqual( notice[ blockName as keyof object ], allIncompatibleItemSlugs ) ); const shouldBeDismissed = allIncompatibleItemCount === 0 || isDismissedNoticeUpToDate; const dismissNotice = () => { const dismissedNoticesSet = new Set( dismissedNotices ); dismissedNoticesSet.add( { [ blockName ]: allIncompatibleItemSlugs, } ); 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: StoredIncompatibleExtension[], curr ) => { if ( Object.keys( curr ).includes( blockName ) ) { return acc; } acc.push( curr ); return acc; }, [] ) ); } }, [ shouldBeDismissed, isDismissedNoticeUpToDate, setDismissedNotices, blockName, ] ); return [ isVisible, dismissNotice, sortAlphabetically( allIncompatibleItems ), allIncompatibleItemCount, ]; };