diff --git a/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx index 8a15b75b55f..03614c7b3bd 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/utils/render-parent-block.tsx @@ -2,6 +2,7 @@ * External dependencies */ import { renderFrontend } from '@woocommerce/base-utils'; +import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; import { Fragment, Suspense, @@ -13,6 +14,7 @@ import { getRegisteredBlocks, hasInnerBlocks, } from '@woocommerce/blocks-checkout'; +import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; /** * This file contains logic used on the frontend to convert DOM elements (saved by the block editor) to React @@ -53,7 +55,7 @@ const renderForcedBlocks = ( block: string, blockMap: Record< string, React.ReactNode >, // Current children from the parent (siblings of the forced block) - blockChildren: HTMLCollection | null, + blockChildren: NodeListOf< ChildNode > | null, // Wrapper for inner components. blockWrapper?: React.ElementType ) => { @@ -63,9 +65,9 @@ const renderForcedBlocks = ( const currentBlocks = blockChildren ? ( Array.from( blockChildren ) - .map( ( element: Element ) => - element instanceof HTMLElement - ? element?.dataset.blockName || null + .map( ( node: Node ) => + node instanceof HTMLElement + ? node?.dataset.blockName || null : null ) .filter( Boolean ) as string[] ) @@ -80,7 +82,7 @@ const renderForcedBlocks = ( const InnerBlockComponentWrapper = blockWrapper ? blockWrapper : Fragment; return ( - + <> { forcedBlocks.map( ( { blockName, component }, @@ -90,16 +92,36 @@ const renderForcedBlocks = ( ? component : getBlockComponentFromMap( blockName, blockMap ); return ForcedComponent ? ( - + + + + + ) : null; } ) } - + ); }; +interface renderInnerBlocksProps { + // Block (parent) being rendered. Used for inner block component mapping. + block: string; + // Map of block names to block components for children. + blockMap: Record< string, React.ReactNode >; + // Wrapper for inner components. + blockWrapper?: React.ElementType | undefined; + // Elements from the DOM being converted to components. + children: HTMLCollection | NodeList; + // Depth within the DOM hierarchy. + depth?: number; +} + /** * Recursively replace block markup in the DOM with React Components. */ @@ -114,30 +136,19 @@ const renderInnerBlocks = ( { children, // Current depth of the children. Used to ensure keys are unique. depth = 1, -}: { - // Block (parent) being rendered. Used for inner block component mapping. - block: string; - // Map of block names to block components for children. - blockMap: Record< string, React.ReactNode >; - // Wrapper for inner components. - blockWrapper?: React.ElementType; - // Elements from the DOM being converted to components. - children: HTMLCollection | NodeList; - // Depth within the DOM hierarchy. - depth?: number; -} ): ( JSX.Element | null )[] | null => { +}: renderInnerBlocksProps ): ( JSX.Element | null )[] | null => { if ( ! children || children.length === 0 ) { return null; } - return Array.from( children ).map( ( element: Element, index: number ) => { + return Array.from( children ).map( ( node: Node, index: number ) => { /** * This will grab the blockName from the data- attributes stored in block markup. Without a blockName, we cannot * convert the HTMLElement to a React component. */ const { blockName = '', ...componentProps } = { key: `${ block }_${ depth }_${ index }`, - ...( element instanceof HTMLElement ? element.dataset : {} ), - className: element.className || '', + ...( node instanceof HTMLElement ? node.dataset : {} ), + className: node instanceof Element ? node?.className : '', }; const InnerBlockComponent = getBlockComponentFromMap( @@ -153,7 +164,9 @@ const renderInnerBlocks = ( { */ if ( ! InnerBlockComponent ) { const parsedElement = parse( - element?.outerHTML || element?.textContent || '' + ( node instanceof Element && node?.outerHTML ) || + node?.textContent || + '' ); // Returns text nodes without manipulation. @@ -166,11 +179,11 @@ const renderInnerBlocks = ( { return null; } - const renderedChildren = element.childNodes.length + const renderedChildren = node.childNodes.length ? renderInnerBlocks( { block, blockMap, - children: element.childNodes, + children: node.childNodes, depth: depth + 1, blockWrapper, } ) @@ -195,39 +208,45 @@ const renderInnerBlocks = ( { key={ `${ block }_${ depth }_${ index }_suspense` } fallback={
} > - - - { - /** - * Within this Inner Block Component we also need to recursively render it's children. This - * is done here with a depth+1. The same block map and parent is used, but we pass new - * children from this element. - */ - renderInnerBlocks( { - block, - blockMap, - children: element.children, - depth: depth + 1, - blockWrapper, - } ) - } - { - /** - * In addition to the inner blocks, we may also need to render FORCED blocks which have not - * yet been added to the inner block template. We do this by comparing the current children - * to the list of registered forced blocks. - * - * @see registerCheckoutBlock - */ - renderForcedBlocks( - blockName, - blockMap, - element.children, - blockWrapper - ) - } - - + { /* Prevent third party components from breaking the entire checkout */ } + + + + { + /** + * Within this Inner Block Component we also need to recursively render it's children. This + * is done here with a depth+1. The same block map and parent is used, but we pass new + * children from this element. + */ + renderInnerBlocks( { + block, + blockMap, + children: node.childNodes, + depth: depth + 1, + blockWrapper, + } ) + } + { + /** + * In addition to the inner blocks, we may also need to render FORCED blocks which have not + * yet been added to the inner block template. We do this by comparing the current children + * to the list of registered forced blocks. + * + * @see registerCheckoutBlock + */ + renderForcedBlocks( + blockName, + blockMap, + node.childNodes, + blockWrapper + ) + } + + + ); } ); diff --git a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/block-error.tsx b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/block-error.tsx index 49b5443de15..e09f18fd4c9 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/block-error.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/block-error.tsx @@ -18,8 +18,9 @@ const BlockError = ( { errorMessage, errorMessagePrefix = __( 'Error:', 'woo-gutenberg-products-block' ), button, -}: BlockErrorProps ): JSX.Element => { - return ( + showErrorBlock = true, +}: BlockErrorProps ): React.ReactNode => { + return showErrorBlock ? (
{ imageUrl && (
- ); + ) : null; }; export default BlockError; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/index.tsx index 47df319e96b..cbc7f085bb9 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/index.tsx @@ -42,6 +42,7 @@ class BlockErrorBoundary extends Component< BlockErrorBoundaryProps > { header, imageUrl, showErrorMessage = true, + showErrorBlock = true, text, errorMessagePrefix, renderError, @@ -55,6 +56,7 @@ class BlockErrorBoundary extends Component< BlockErrorBoundaryProps > { } return ( React.ReactNode; + renderError?: ( props: RenderErrorProps ) => React.ReactNode; + showErrorMessage?: boolean; } export interface DerivedStateReturn {