Add block error boundary to JS-rendered blocks (https://github.com/woocommerce/woocommerce-blocks/pull/1166)
* Add block error boundary to All Products and Reviews blocks * Add block error boundary to Price Filter and Attributes Filter blocks * Add image * Make block error component use props instead of hardcoded values * Add props to BlockErrorBoundary * Change 'text' prop to 'content' * Add docs to proptypes * Replace 'content' prop with 'text'
This commit is contained in:
parent
2fde5d6890
commit
0739e4c536
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 296 KiB |
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { WC_BLOCKS_ASSET_URL } from '@woocommerce/block-settings';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const BlockError = ( {
|
||||
imageUrl = `${ WC_BLOCKS_ASSET_URL }img/block-error.svg`,
|
||||
header = __( 'Oops!', 'woo-gutenberg-products-block' ),
|
||||
text = __(
|
||||
'There was an error with loading this content.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
errorMessage,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="wc-block-error">
|
||||
{ imageUrl && (
|
||||
<img
|
||||
className="wc-block-error__image"
|
||||
src={ imageUrl }
|
||||
alt=""
|
||||
/>
|
||||
) }
|
||||
<div className="wc-block-error__content">
|
||||
{ header && (
|
||||
<p className="wc-block-error__header">{ header }</p>
|
||||
) }
|
||||
{ text && <p className="wc-block-error__text">{ text }</p> }
|
||||
{ errorMessage && (
|
||||
<p className="wc-block-error__message">{ errorMessage }</p>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BlockError.propTypes = {
|
||||
/**
|
||||
* Error message to display below the content.
|
||||
*/
|
||||
errorMessage: PropTypes.string,
|
||||
/**
|
||||
* Text to display as the heading of the error block.
|
||||
* If it's `null` or an empty string, no header will be displayed.
|
||||
* If it's not defined, the default header will be used.
|
||||
*/
|
||||
header: PropTypes.string,
|
||||
/**
|
||||
* URL of the image to display.
|
||||
* If it's `null` or an empty string, no image will be displayed.
|
||||
* If it's not defined, the default image will be used.
|
||||
*/
|
||||
imageUrl: PropTypes.string,
|
||||
/**
|
||||
* Text to display in the error block below the header.
|
||||
* If it's `null` or an empty string, nothing will be displayed.
|
||||
* If it's not defined, the default text will be used.
|
||||
*/
|
||||
text: PropTypes.string,
|
||||
};
|
||||
|
||||
export default BlockError;
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import BlockError from './block-error';
|
||||
import './style.scss';
|
||||
|
||||
class BlockErrorBoundary extends Component {
|
||||
state = { hasError: false };
|
||||
|
||||
static getDerivedStateFromError( error ) {
|
||||
return { errorMessage: error.message, hasError: true };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { header, imageUrl, showErrorMessage, text } = this.props;
|
||||
const { errorMessage, hasError } = this.state;
|
||||
|
||||
if ( hasError ) {
|
||||
return (
|
||||
<BlockError
|
||||
errorMessage={ showErrorMessage ? errorMessage : null }
|
||||
header={ header }
|
||||
imageUrl={ imageUrl }
|
||||
text={ text }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
BlockErrorBoundary.propTypes = {
|
||||
/**
|
||||
* Text to display as the heading of the error block.
|
||||
* If it's `null` or an empty string, no header will be displayed.
|
||||
* If it's not defined, the default header will be used.
|
||||
*/
|
||||
header: PropTypes.string,
|
||||
/**
|
||||
* URL of the image to display.
|
||||
* If it's `null` or an empty string, no image will be displayed.
|
||||
* If it's not defined, the default image will be used.
|
||||
*/
|
||||
imageUrl: PropTypes.string,
|
||||
/**
|
||||
* Whether to display the JS error message.
|
||||
*/
|
||||
showErrorMessage: PropTypes.bool,
|
||||
/**
|
||||
* Text to display in the error block below the header.
|
||||
* If it's `null` or an empty string, nothing will be displayed.
|
||||
* If it's not defined, the default text will be used.
|
||||
*/
|
||||
text: PropTypes.string,
|
||||
};
|
||||
|
||||
BlockErrorBoundary.defaultProps = {
|
||||
showErrorMessage: false,
|
||||
};
|
||||
|
||||
export default BlockErrorBoundary;
|
|
@ -0,0 +1,30 @@
|
|||
.wc-block-error {
|
||||
display: flex;
|
||||
background-color: #f3f3f4;
|
||||
border-left: 4px solid #6d6d6d;
|
||||
padding: $gap-larger $gap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wc-block-error__header {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wc-block-error__text,
|
||||
.wc-block-error__message {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@include breakpoint( ">480px" ) {
|
||||
.wc-block-error {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wc-block-error__image + .wc-block-error__content {
|
||||
margin-left: $gap;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@wordpress/element';
|
||||
import { sortBy } from 'lodash';
|
||||
import CheckboxList from '@woocommerce/base-components/checkbox-list';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -173,7 +174,7 @@ const AttributeFilterBlock = ( { attributes, isPreview = false } ) => {
|
|||
const TagName = `h${ attributes.headingLevel }`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockErrorBoundary>
|
||||
{ ! isPreview && attributes.heading && (
|
||||
<TagName>{ attributes.heading }</TagName>
|
||||
) }
|
||||
|
@ -185,7 +186,7 @@ const AttributeFilterBlock = ( { attributes, isPreview = false } ) => {
|
|||
isLoading={ attributeTermsLoading }
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,9 +6,10 @@ import {
|
|||
useQueryStateByKey,
|
||||
useQueryStateByContext,
|
||||
} from '@woocommerce/base-hooks';
|
||||
import { useCallback, Fragment } from '@wordpress/element';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import PriceSlider from '@woocommerce/base-components/price-slider';
|
||||
import { CURRENCY } from '@woocommerce/settings';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
/**
|
||||
* Component displaying a price filter.
|
||||
|
@ -62,7 +63,7 @@ const PriceFilterBlock = ( { attributes, isPreview = false } ) => {
|
|||
const TagName = `h${ attributes.headingLevel }`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockErrorBoundary>
|
||||
{ ! isPreview && attributes.heading && (
|
||||
<TagName>{ attributes.heading }</TagName>
|
||||
) }
|
||||
|
@ -81,7 +82,7 @@ const PriceFilterBlock = ( { attributes, isPreview = false } ) => {
|
|||
isLoading={ isLoading }
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import ProductListContainer from '@woocommerce/base-components/product-list/container';
|
||||
import { InnerBlockConfigurationProvider } from '@woocommerce/base-context/inner-block-configuration-context';
|
||||
import { ProductLayoutContextProvider } from '@woocommerce/base-context/product-layout-context';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
const layoutContextConfig = {
|
||||
layoutStyleClassPrefix: 'wc-block-grid',
|
||||
|
@ -33,14 +34,16 @@ class Block extends Component {
|
|||
* wc-block-{$this->block_name},
|
||||
*/
|
||||
return (
|
||||
<InnerBlockConfigurationProvider value={ parentBlockConfig }>
|
||||
<ProductLayoutContextProvider value={ layoutContextConfig }>
|
||||
<ProductListContainer
|
||||
attributes={ attributes }
|
||||
urlParameterSuffix={ urlParameterSuffix }
|
||||
/>
|
||||
</ProductLayoutContextProvider>
|
||||
</InnerBlockConfigurationProvider>
|
||||
<BlockErrorBoundary>
|
||||
<InnerBlockConfigurationProvider value={ parentBlockConfig }>
|
||||
<ProductLayoutContextProvider value={ layoutContextConfig }>
|
||||
<ProductListContainer
|
||||
attributes={ attributes }
|
||||
urlParameterSuffix={ urlParameterSuffix }
|
||||
/>
|
||||
</ProductLayoutContextProvider>
|
||||
</InnerBlockConfigurationProvider>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import LoadMoreButton from '@woocommerce/base-components/load-more-button';
|
|||
import ReviewList from '@woocommerce/base-components/review-list';
|
||||
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
|
||||
import withReviews from '@woocommerce/base-hocs/with-reviews';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
/**
|
||||
* Block rendered in the editor.
|
||||
|
@ -51,20 +52,26 @@ class EditorBlock extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<Disabled>
|
||||
{ attributes.showOrderby && ENABLE_REVIEW_RATING && (
|
||||
<ReviewSortSelect readOnly value={ attributes.orderby } />
|
||||
) }
|
||||
<ReviewList attributes={ attributes } reviews={ reviews } />
|
||||
{ attributes.showLoadMore && totalReviews > reviews.length && (
|
||||
<LoadMoreButton
|
||||
screenReaderLabel={ __(
|
||||
'Load more reviews',
|
||||
'woo-gutenberg-products-block'
|
||||
<BlockErrorBoundary>
|
||||
<Disabled>
|
||||
{ attributes.showOrderby && ENABLE_REVIEW_RATING && (
|
||||
<ReviewSortSelect
|
||||
readOnly
|
||||
value={ attributes.orderby }
|
||||
/>
|
||||
) }
|
||||
<ReviewList attributes={ attributes } reviews={ reviews } />
|
||||
{ attributes.showLoadMore &&
|
||||
totalReviews > reviews.length && (
|
||||
<LoadMoreButton
|
||||
screenReaderLabel={ __(
|
||||
'Load more reviews',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</Disabled>
|
||||
</Disabled>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ENABLE_REVIEW_RATING } from '@woocommerce/block-settings';
|
||||
import LoadMoreButton from '@woocommerce/base-components/load-more-button';
|
||||
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
|
||||
import ReviewList from '@woocommerce/base-components/review-list';
|
||||
import withReviews from '@woocommerce/base-hocs/with-reviews';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
/**
|
||||
* Block rendered in the frontend.
|
||||
|
@ -27,7 +27,7 @@ const FrontendBlock = ( {
|
|||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockErrorBoundary>
|
||||
{ attributes.showOrderby !== 'false' && ENABLE_REVIEW_RATING && (
|
||||
<ReviewSortSelect
|
||||
defaultValue={ orderby }
|
||||
|
@ -45,7 +45,7 @@ const FrontendBlock = ( {
|
|||
) }
|
||||
/>
|
||||
) }
|
||||
</Fragment>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -22,3 +22,4 @@ export const HAS_TAGS = getSetting( 'hasTags', true );
|
|||
export const HOME_URL = getSetting( 'homeUrl', '' );
|
||||
export const PRODUCT_COUNT = getSetting( 'productCount', 0 );
|
||||
export const ATTRIBUTES = getSetting( 'attributes', [] );
|
||||
export const WC_BLOCKS_ASSET_URL = getSetting( 'wcBlocksAssetUrl', '' );
|
||||
|
|
|
@ -117,6 +117,7 @@ class Assets {
|
|||
'enableReviewRating' => 'yes' === get_option( 'woocommerce_enable_review_rating' ),
|
||||
'productCount' => array_sum( (array) $product_counts ),
|
||||
'attributes' => wc_get_attribute_taxonomies(),
|
||||
'wcBlocksAssetUrl' => plugins_url( 'assets/', __DIR__ ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue