2022-12-19 15:30:13 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2023-04-05 12:43:03 +00:00
|
|
|
import { __ } from '@wordpress/i18n';
|
2022-12-19 15:30:13 +00:00
|
|
|
import classnames from 'classnames';
|
2023-04-05 12:43:03 +00:00
|
|
|
import { useRef, useEffect, RawHTML } from '@wordpress/element';
|
2022-12-19 15:30:13 +00:00
|
|
|
import { sanitizeHTML } from '@woocommerce/utils';
|
|
|
|
import { useDispatch } from '@wordpress/data';
|
|
|
|
import { usePrevious } from '@woocommerce/base-hooks';
|
|
|
|
import { decodeEntities } from '@wordpress/html-entities';
|
2023-04-05 12:43:03 +00:00
|
|
|
import type { NoticeType } from '@woocommerce/types';
|
|
|
|
import type { NoticeBannerProps } from '@woocommerce/base-components/notice-banner';
|
2022-12-19 15:30:13 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2023-04-05 12:43:03 +00:00
|
|
|
import StoreNotice from '../store-notice';
|
2022-12-19 15:30:13 +00:00
|
|
|
|
|
|
|
const StoreNotices = ( {
|
|
|
|
className,
|
|
|
|
notices,
|
|
|
|
}: {
|
|
|
|
className: string;
|
2023-04-05 12:43:03 +00:00
|
|
|
notices: NoticeType[];
|
2022-12-19 15:30:13 +00:00
|
|
|
} ): JSX.Element => {
|
|
|
|
const ref = useRef< HTMLDivElement >( null );
|
|
|
|
const { removeNotice } = useDispatch( 'core/notices' );
|
|
|
|
const noticeIds = notices.map( ( notice ) => notice.id );
|
|
|
|
const previousNoticeIds = usePrevious( noticeIds );
|
|
|
|
|
|
|
|
useEffect( () => {
|
|
|
|
// Scroll to container when an error is added here.
|
|
|
|
const containerRef = ref.current;
|
|
|
|
|
|
|
|
if ( ! containerRef ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not scroll if input has focus.
|
|
|
|
const activeElement = containerRef.ownerDocument.activeElement;
|
|
|
|
const inputs = [ 'input', 'select', 'button', 'textarea' ];
|
|
|
|
|
|
|
|
if (
|
|
|
|
activeElement &&
|
2023-01-19 16:40:52 +00:00
|
|
|
inputs.indexOf( activeElement.tagName.toLowerCase() ) !== -1 &&
|
|
|
|
activeElement.getAttribute( 'type' ) !== 'radio'
|
2022-12-19 15:30:13 +00:00
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const newNoticeIds = noticeIds.filter(
|
|
|
|
( value ) =>
|
|
|
|
! previousNoticeIds || ! previousNoticeIds.includes( value )
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( newNoticeIds.length && containerRef?.scrollIntoView ) {
|
|
|
|
containerRef.scrollIntoView( {
|
|
|
|
behavior: 'smooth',
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
}, [ noticeIds, previousNoticeIds, ref ] );
|
|
|
|
|
2023-01-19 16:40:52 +00:00
|
|
|
// Group notices by whether or not they are dismissible. Dismissible notices can be grouped.
|
2022-12-22 14:15:33 +00:00
|
|
|
const dismissibleNotices = notices.filter(
|
|
|
|
( { isDismissible } ) => !! isDismissible
|
|
|
|
);
|
|
|
|
const nonDismissibleNotices = notices.filter(
|
|
|
|
( { isDismissible } ) => ! isDismissible
|
|
|
|
);
|
|
|
|
|
|
|
|
// Group dismissibleNotices by status. They will be combined into a single notice.
|
|
|
|
const dismissibleNoticeGroups = {
|
|
|
|
error: dismissibleNotices.filter(
|
|
|
|
( { status } ) => status === 'error'
|
|
|
|
),
|
|
|
|
success: dismissibleNotices.filter(
|
|
|
|
( { status } ) => status === 'success'
|
|
|
|
),
|
|
|
|
warning: dismissibleNotices.filter(
|
|
|
|
( { status } ) => status === 'warning'
|
|
|
|
),
|
|
|
|
info: dismissibleNotices.filter( ( { status } ) => status === 'info' ),
|
2023-04-05 12:43:03 +00:00
|
|
|
default: dismissibleNotices.filter(
|
|
|
|
( { status } ) => status === 'default'
|
|
|
|
),
|
2022-12-19 15:30:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={ ref }
|
|
|
|
className={ classnames( className, 'wc-block-components-notices' ) }
|
|
|
|
>
|
2022-12-22 14:15:33 +00:00
|
|
|
{ nonDismissibleNotices.map( ( notice ) => (
|
2023-04-05 12:43:03 +00:00
|
|
|
<StoreNotice
|
2023-01-19 16:40:52 +00:00
|
|
|
key={ notice.id + '-' + notice.context }
|
2022-12-22 14:15:33 +00:00
|
|
|
{ ...notice }
|
|
|
|
>
|
2023-04-05 12:43:03 +00:00
|
|
|
<RawHTML>
|
|
|
|
{ sanitizeHTML( decodeEntities( notice.content ) ) }
|
|
|
|
</RawHTML>
|
|
|
|
</StoreNotice>
|
2022-12-22 14:15:33 +00:00
|
|
|
) ) }
|
|
|
|
{ Object.entries( dismissibleNoticeGroups ).map(
|
2022-12-19 15:30:13 +00:00
|
|
|
( [ status, noticeGroup ] ) => {
|
|
|
|
if ( ! noticeGroup.length ) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-04-05 12:43:03 +00:00
|
|
|
const uniqueNotices = noticeGroup
|
|
|
|
.filter(
|
|
|
|
(
|
|
|
|
notice: NoticeType,
|
|
|
|
noticeIndex: number,
|
|
|
|
noticesArray: NoticeType[]
|
|
|
|
) =>
|
|
|
|
noticesArray.findIndex(
|
|
|
|
( _notice: NoticeType ) =>
|
|
|
|
_notice.content === notice.content
|
|
|
|
) === noticeIndex
|
|
|
|
)
|
|
|
|
.map( ( notice ) => ( {
|
|
|
|
...notice,
|
|
|
|
content: sanitizeHTML(
|
|
|
|
decodeEntities( notice.content )
|
|
|
|
),
|
|
|
|
} ) );
|
|
|
|
const noticeProps: Omit< NoticeBannerProps, 'children' > & {
|
|
|
|
key: string;
|
|
|
|
} = {
|
|
|
|
key: `store-notice-${ status }`,
|
2023-11-27 13:07:18 +00:00
|
|
|
status,
|
2023-04-05 12:43:03 +00:00
|
|
|
onRemove: () => {
|
|
|
|
noticeGroup.forEach( ( notice ) => {
|
|
|
|
removeNotice( notice.id, notice.context );
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return uniqueNotices.length === 1 ? (
|
|
|
|
<StoreNotice { ...noticeProps }>
|
|
|
|
<RawHTML>{ noticeGroup[ 0 ].content }</RawHTML>
|
|
|
|
</StoreNotice>
|
|
|
|
) : (
|
|
|
|
<StoreNotice
|
|
|
|
{ ...noticeProps }
|
|
|
|
summary={
|
|
|
|
status === 'error'
|
|
|
|
? __(
|
|
|
|
'Please fix the following errors before continuing',
|
2023-12-12 23:05:20 +00:00
|
|
|
'woocommerce'
|
2023-04-05 12:43:03 +00:00
|
|
|
)
|
|
|
|
: ''
|
|
|
|
}
|
2022-12-19 15:30:13 +00:00
|
|
|
>
|
2023-04-05 12:43:03 +00:00
|
|
|
<ul>
|
|
|
|
{ uniqueNotices.map( ( notice ) => (
|
|
|
|
<li
|
|
|
|
key={ notice.id + '-' + notice.context }
|
|
|
|
>
|
|
|
|
<RawHTML>{ notice.content }</RawHTML>
|
|
|
|
</li>
|
|
|
|
) ) }
|
|
|
|
</ul>
|
|
|
|
</StoreNotice>
|
2022-12-19 15:30:13 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
) }
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default StoreNotices;
|