HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/store-notices-container.tar
index.tsx000064400000005757151555132530006436 0ustar00/**
 * External dependencies
 */
import { useSelect, useDispatch } from '@wordpress/data';
import {
	PAYMENT_STORE_KEY,
	STORE_NOTICES_STORE_KEY,
} from '@woocommerce/block-data';
import { getNoticeContexts } from '@woocommerce/base-utils';
import type { Notice } from '@wordpress/notices';
import { useMemo, useEffect } from '@wordpress/element';
import type { NoticeType } from '@woocommerce/types';

/**
 * Internal dependencies
 */
import './style.scss';
import StoreNotices from './store-notices';
import SnackbarNotices from './snackbar-notices';
import type { StoreNoticesContainerProps } from './types';

const formatNotices = ( notices: Notice[], context: string ): NoticeType[] => {
	return notices.map( ( notice ) => ( {
		...notice,
		context,
	} ) ) as NoticeType[];
};

const StoreNoticesContainer = ( {
	className = '',
	context = '',
	additionalNotices = [],
}: StoreNoticesContainerProps ): JSX.Element | null => {
	const { registerContainer, unregisterContainer } = useDispatch(
		STORE_NOTICES_STORE_KEY
	);
	const { suppressNotices, registeredContainers } = useSelect(
		( select ) => ( {
			suppressNotices:
				select( PAYMENT_STORE_KEY ).isExpressPaymentMethodActive(),
			registeredContainers: select(
				STORE_NOTICES_STORE_KEY
			).getRegisteredContainers(),
		} )
	);
	const contexts = useMemo< string[] >(
		() => ( Array.isArray( context ) ? context : [ context ] ),
		[ context ]
	);
	// Find sub-contexts that have not been registered. We will show notices from those contexts here too.
	const allContexts = getNoticeContexts();
	const unregisteredSubContexts = allContexts.filter(
		( subContext: string ) =>
			contexts.some( ( _context: string ) =>
				subContext.includes( _context + '/' )
			) && ! registeredContainers.includes( subContext )
	);

	// Get notices from the current context and any sub-contexts and append the name of the context to the notice
	// objects for later reference.
	const notices = useSelect< NoticeType[] >( ( select ) => {
		const { getNotices } = select( 'core/notices' );

		return [
			...unregisteredSubContexts.flatMap( ( subContext: string ) =>
				formatNotices( getNotices( subContext ), subContext )
			),
			...contexts.flatMap( ( subContext: string ) =>
				formatNotices(
					getNotices( subContext ).concat( additionalNotices ),
					subContext
				)
			),
		].filter( Boolean ) as NoticeType[];
	} );

	// Register the container context with the parent.
	useEffect( () => {
		contexts.map( ( _context ) => registerContainer( _context ) );
		return () => {
			contexts.map( ( _context ) => unregisterContainer( _context ) );
		};
	}, [ contexts, registerContainer, unregisterContainer ] );

	if ( suppressNotices ) {
		return null;
	}

	return (
		<>
			<StoreNotices
				className={ className }
				notices={ notices.filter(
					( notice ) => notice.type === 'default'
				) }
			/>
			<SnackbarNotices
				className={ className }
				notices={ notices.filter(
					( notice ) => notice.type === 'snackbar'
				) }
			/>
		</>
	);
};

export default StoreNoticesContainer;
snackbar-notices.tsx000064400000001633151555132530010542 0ustar00/**
 * External dependencies
 */
import classnames from 'classnames';
import SnackbarList from '@woocommerce/base-components/snackbar-list';
import { useDispatch } from '@wordpress/data';
import type { NoticeType } from '@woocommerce/types';

const SnackbarNotices = ( {
	className,
	notices,
}: {
	className: string;
	notices: NoticeType[];
} ): JSX.Element | null => {
	const { removeNotice } = useDispatch( 'core/notices' );

	return (
		<SnackbarList
			className={ classnames(
				className,
				'wc-block-components-notices__snackbar'
			) }
			notices={ notices }
			onRemove={ ( noticeId: string ) => {
				notices.forEach( ( notice ) => {
					if ( notice.explicitDismiss && notice.id === noticeId ) {
						removeNotice( notice.id, notice.context );
					} else if ( ! notice.explicitDismiss ) {
						removeNotice( notice.id, notice.context );
					}
				} );
			} }
		/>
	);
};

export default SnackbarNotices;
store-notices.tsx000064400000010713151555132530010111 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import { useRef, useEffect, RawHTML } from '@wordpress/element';
import { sanitizeHTML } from '@woocommerce/utils';
import { useDispatch } from '@wordpress/data';
import { usePrevious } from '@woocommerce/base-hooks';
import { decodeEntities } from '@wordpress/html-entities';
import type { NoticeType } from '@woocommerce/types';
import type { NoticeBannerProps } from '@woocommerce/base-components/notice-banner';

/**
 * Internal dependencies
 */
import StoreNotice from '../store-notice';

const StoreNotices = ( {
	className,
	notices,
}: {
	className: string;
	notices: NoticeType[];
} ): 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 &&
			inputs.indexOf( activeElement.tagName.toLowerCase() ) !== -1 &&
			activeElement.getAttribute( 'type' ) !== 'radio'
		) {
			return;
		}

		const newNoticeIds = noticeIds.filter(
			( value ) =>
				! previousNoticeIds || ! previousNoticeIds.includes( value )
		);

		if ( newNoticeIds.length && containerRef?.scrollIntoView ) {
			containerRef.scrollIntoView( {
				behavior: 'smooth',
			} );
		}
	}, [ noticeIds, previousNoticeIds, ref ] );

	// Group notices by whether or not they are dismissible. Dismissible notices can be grouped.
	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' ),
		default: dismissibleNotices.filter(
			( { status } ) => status === 'default'
		),
	};

	return (
		<div
			ref={ ref }
			className={ classnames( className, 'wc-block-components-notices' ) }
		>
			{ nonDismissibleNotices.map( ( notice ) => (
				<StoreNotice
					key={ notice.id + '-' + notice.context }
					{ ...notice }
				>
					<RawHTML>
						{ sanitizeHTML( decodeEntities( notice.content ) ) }
					</RawHTML>
				</StoreNotice>
			) ) }
			{ Object.entries( dismissibleNoticeGroups ).map(
				( [ status, noticeGroup ] ) => {
					if ( ! noticeGroup.length ) {
						return null;
					}
					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 }`,
						status: 'error',
						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',
											'woo-gutenberg-products-block'
									  )
									: ''
							}
						>
							<ul>
								{ uniqueNotices.map( ( notice ) => (
									<li
										key={ notice.id + '-' + notice.context }
									>
										<RawHTML>{ notice.content }</RawHTML>
									</li>
								) ) }
							</ul>
						</StoreNotice>
					);
				}
			) }
		</div>
	);
};

export default StoreNotices;
style.scss000064400000002676151555132530006621 0ustar00.wc-block-components-notices {
	display: block;
	margin: 1.5em 0;
	&:first-child {
		margin-top: 0;
	}
	&:empty {
		margin: 0;
	}
	.wc-block-components-notices__notice {
		margin: 0;
		display: flex;
		flex-wrap: nowrap;
		a {
			text-decoration: underline;
		}
		.components-notice__dismiss {
			background: transparent none;
			padding: 0;
			margin: 0 0 0 auto;
			border: 0;
			outline: 0;
			color: currentColor;
			svg {
				fill: currentColor;
				vertical-align: text-top;
			}
		}
		.components-notice__content > div:not(.components-notice__actions) {
			*:first-child {
				margin-top: 0;
			}
			*:last-child {
				margin-bottom: 0;
			}
		}
		.components-notice__content {
			ul {
				margin: 0;
				padding: 0;
				list-style: none;
			}
			li + li {
				margin: 0.25em 0 0 0;
			}
		}
	}
	.wc-block-components-notices__notice + .wc-block-components-notices__notice {
		margin-top: 1em;
	}
}

// @todo Either move notice style fixes to Woo core, or take full control over notice component styling in blocks.
.theme-twentytwentyone,
.theme-twentytwenty {
	.wc-block-components-notices__notice {
		padding: 1.5rem 3rem;
	}
}

.wc-block-components-notices__snackbar {
	position: fixed;
	bottom: 20px;
	left: 16px;
	width: auto;

	@include breakpoint("<782px") {
		position: fixed;
		top: 10px;
		left: 0;
		bottom: auto;
	}

	.components-snackbar-list__notice-container {
		@include breakpoint("<782px") {
			margin-left: 10px;
			margin-right: 10px;
		}
	}
}
test/index.tsx000064400000013340151555132530007400 0ustar00/**
 * External dependencies
 */
import { store as noticesStore } from '@wordpress/notices';
import { dispatch, select } from '@wordpress/data';
import { act, render, screen, waitFor } from '@testing-library/react';

/**
 * Internal dependencies
 */
import StoreNoticesContainer from '../index';

describe( 'StoreNoticesContainer', () => {
	it( 'Shows notices from the correct context', async () => {
		dispatch( noticesStore ).createErrorNotice( 'Custom test error', {
			id: 'custom-test-error',
			context: 'test-context',
		} );
		render( <StoreNoticesContainer context="test-context" /> );
		expect( screen.getAllByText( /Custom test error/i ) ).toHaveLength( 2 );
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-test-error',
				'test-context'
			)
		);
		await waitFor( () => {
			return (
				select( noticesStore ).getNotices( 'test-context' ).length === 0
			);
		} );
	} );

	it( 'Does not show notices from other contexts', async () => {
		dispatch( noticesStore ).createErrorNotice( 'Custom test error 2', {
			id: 'custom-test-error-2',
			context: 'test-context',
		} );
		render( <StoreNoticesContainer context="other-context" /> );
		expect( screen.queryAllByText( /Custom test error 2/i ) ).toHaveLength(
			0
		);
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-test-error-2',
				'test-context'
			)
		);
		await waitFor( () => {
			return (
				select( noticesStore ).getNotices( 'test-context' ).length === 0
			);
		} );
	} );

	it( 'Does not show snackbar notices', async () => {
		dispatch( noticesStore ).createErrorNotice( 'Custom test error 2', {
			id: 'custom-test-error-2',
			context: 'test-context',
			type: 'snackbar',
		} );
		render( <StoreNoticesContainer context="other-context" /> );
		expect( screen.queryAllByText( /Custom test error 2/i ) ).toHaveLength(
			0
		);
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-test-error-2',
				'test-context'
			)
		);
		await waitFor( () => {
			return (
				select( noticesStore ).getNotices( 'test-context' ).length === 0
			);
		} );
	} );

	it( 'Shows additional notices', () => {
		render(
			<StoreNoticesContainer
				additionalNotices={ [
					{
						id: 'additional-test-error',
						status: 'error',
						spokenMessage: 'Additional test error',
						isDismissible: false,
						content: 'Additional test error',
						actions: [],
						speak: false,
						__unstableHTML: '',
						type: 'default',
					},
				] }
			/>
		);
		// Also counts the spokenMessage.
		expect( screen.getAllByText( /Additional test error/i ) ).toHaveLength(
			2
		);
	} );

	it( 'Shows notices from unregistered sub-contexts', async () => {
		dispatch( noticesStore ).createErrorNotice(
			'Custom first sub-context error',
			{
				id: 'custom-subcontext-test-error',
				context: 'wc/checkout/shipping-address',
			}
		);
		dispatch( noticesStore ).createErrorNotice(
			'Custom second sub-context error',
			{
				id: 'custom-subcontext-test-error',
				context: 'wc/checkout/billing-address',
			}
		);
		render( <StoreNoticesContainer context="wc/checkout" /> );
		// This should match against 2 messages, one for each sub-context.
		expect(
			screen.getAllByText( /Custom first sub-context error/i )
		).toHaveLength( 2 );
		expect(
			screen.getAllByText( /Custom second sub-context error/i )
		).toHaveLength( 2 );
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/shipping-address'
			)
		);
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/billing-address'
			)
		);
	} );

	it( 'Shows notices from several contexts', async () => {
		dispatch( noticesStore ).createErrorNotice( 'Custom shipping error', {
			id: 'custom-subcontext-test-error',
			context: 'wc/checkout/shipping-address',
		} );
		dispatch( noticesStore ).createErrorNotice( 'Custom billing error', {
			id: 'custom-subcontext-test-error',
			context: 'wc/checkout/billing-address',
		} );
		render(
			<StoreNoticesContainer
				context={ [
					'wc/checkout/billing-address',
					'wc/checkout/shipping-address',
				] }
			/>
		);
		// This should match against 4 elements; A written and spoken message for each error.
		expect( screen.getAllByText( /Custom shipping error/i ) ).toHaveLength(
			2
		);
		expect( screen.getAllByText( /Custom billing error/i ) ).toHaveLength(
			2
		);
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/shipping-address'
			)
		);
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/billing-address'
			)
		);
	} );

	it( 'Combine same notices from several contexts', async () => {
		dispatch( noticesStore ).createErrorNotice( 'Custom generic error', {
			id: 'custom-subcontext-test-error',
			context: 'wc/checkout/shipping-address',
		} );
		dispatch( noticesStore ).createErrorNotice( 'Custom generic error', {
			id: 'custom-subcontext-test-error',
			context: 'wc/checkout/billing-address',
		} );
		render(
			<StoreNoticesContainer
				context={ [
					'wc/checkout/billing-address',
					'wc/checkout/shipping-address',
				] }
			/>
		);
		// This should match against 2 elements; A written and spoken message.
		expect( screen.getAllByText( /Custom generic error/i ) ).toHaveLength(
			2
		);
		// Clean up notices.
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/shipping-address'
			)
		);
		await act( () =>
			dispatch( noticesStore ).removeNotice(
				'custom-subcontext-test-error',
				'wc/checkout/billing-address'
			)
		);
	} );
} );
types.ts000064400000000514151555132530006265 0ustar00/**
 * External dependencies
 */
import type { NoticeType } from '@woocommerce/types';

export interface StoreNoticesContainerProps {
	className?: string | undefined;
	context?: string | string[];
	// List of additional notices that were added inline and not stored in the `core/notices` store.
	additionalNotices?: NoticeType[];
}