Create withReviews base HOC (https://github.com/woocommerce/woocommerce-blocks/pull/877)
* Create withReviews base HOC * Add tests * Make Reviews by Category use withReviews HOC * Move componentDidUpdate and debounce dependency to decouple HOC from components * Spaces * Rename 'delayMethod' to 'delayFunction' * Refactor withReviews HOC * Update tests * Minor fixes * Undo fix being handled in woocommerce/woocommerce-blocks#884 * Remove hardcoded from withReviews * Update delay comment * Use callbacks instead of announceUpdates prop * Move props check to a 'shouldReplaceReviews' method * Fix productId propType * Move per_page and offset args to 'getArgs' * Update withReviews displayName * Fix tests * Add callback propsTypes * Use is-shallow-equal
This commit is contained in:
parent
0b9559e2db
commit
46934d2946
|
@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
||||||
*/
|
*/
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
const ReviewOrderSelect = ( { componentId, onChange, readOnly, value } ) => {
|
const ReviewOrderSelect = ( { componentId, defaultValue, onChange, readOnly, value } ) => {
|
||||||
const selectId = `wc-block-review-order-select__select-${ componentId }`;
|
const selectId = `wc-block-review-order-select__select-${ componentId }`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -25,6 +25,7 @@ const ReviewOrderSelect = ( { componentId, onChange, readOnly, value } ) => {
|
||||||
<select // eslint-disable-line jsx-a11y/no-onchange
|
<select // eslint-disable-line jsx-a11y/no-onchange
|
||||||
id={ selectId }
|
id={ selectId }
|
||||||
className="wc-block-review-order-select__select"
|
className="wc-block-review-order-select__select"
|
||||||
|
defaultValue={ defaultValue }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
readOnly={ readOnly }
|
readOnly={ readOnly }
|
||||||
value={ value }
|
value={ value }
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import TestRenderer from 'react-test-renderer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import withReviews from '../with-reviews';
|
||||||
|
import * as mockUtils from '../../../blocks/reviews/utils';
|
||||||
|
|
||||||
|
jest.mock( '../../../blocks/reviews/utils', () => ( {
|
||||||
|
getOrderArgs: () => ( {
|
||||||
|
order: 'desc',
|
||||||
|
orderby: 'date_gmt',
|
||||||
|
} ),
|
||||||
|
getReviews: jest.fn(),
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
const mockReviews = [
|
||||||
|
{ reviewer: 'Alice', review: 'Lorem ipsum', rating: 2 },
|
||||||
|
{ reviewer: 'Bob', review: 'Dolor sit amet', rating: 3 },
|
||||||
|
{ reviewer: 'Carol', review: 'Consectetur adipiscing elit', rating: 5 },
|
||||||
|
];
|
||||||
|
const defaultArgs = {
|
||||||
|
offset: 0,
|
||||||
|
order: 'desc',
|
||||||
|
orderby: 'date_gmt',
|
||||||
|
per_page: 2,
|
||||||
|
product_id: 1,
|
||||||
|
};
|
||||||
|
const TestComponent = withReviews( ( props ) => {
|
||||||
|
return <div
|
||||||
|
error={ props.error }
|
||||||
|
getReviews={ props.getReviews }
|
||||||
|
appendReviews={ props.appendReviews }
|
||||||
|
onChangeArgs={ props.onChangeArgs }
|
||||||
|
isLoading={ props.isLoading }
|
||||||
|
reviews={ props.reviews }
|
||||||
|
totalReviews={ props.totalReviews }
|
||||||
|
/>;
|
||||||
|
} );
|
||||||
|
const render = () => {
|
||||||
|
return TestRenderer.create(
|
||||||
|
<TestComponent
|
||||||
|
order="desc"
|
||||||
|
orderby="date_gmt"
|
||||||
|
productId={ 1 }
|
||||||
|
reviewsToDisplay={ 2 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe( 'withReviews Component', () => {
|
||||||
|
let renderer;
|
||||||
|
afterEach( () => {
|
||||||
|
mockUtils.getReviews.mockReset();
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'lifecycle events', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
mockUtils.getReviews.mockImplementationOnce(
|
||||||
|
() => Promise.resolve( { reviews: mockReviews.slice( 0, 2 ), totalReviews: mockReviews.length } )
|
||||||
|
).mockImplementationOnce(
|
||||||
|
() => Promise.resolve( { reviews: mockReviews.slice( 2, 3 ), totalReviews: mockReviews.length } )
|
||||||
|
);
|
||||||
|
renderer = render();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'getReviews is called on mount with default args', () => {
|
||||||
|
const { getReviews } = mockUtils;
|
||||||
|
|
||||||
|
expect( getReviews ).toHaveBeenCalledWith( defaultArgs );
|
||||||
|
expect( getReviews ).toHaveBeenCalledTimes( 1 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'getReviews is called on component update', () => {
|
||||||
|
const { getReviews } = mockUtils;
|
||||||
|
renderer.update(
|
||||||
|
<TestComponent
|
||||||
|
order="desc"
|
||||||
|
orderby="date_gmt"
|
||||||
|
productId={ 1 }
|
||||||
|
reviewsToDisplay={ 3 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( getReviews ).toHaveBeenNthCalledWith( 2, { ...defaultArgs, offset: 2, per_page: 1 } );
|
||||||
|
expect( getReviews ).toHaveBeenCalledTimes( 2 );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'when the API returns product data', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
mockUtils.getReviews.mockImplementation(
|
||||||
|
() => Promise.resolve( { reviews: mockReviews.slice( 0, 2 ), totalReviews: mockReviews.length } )
|
||||||
|
);
|
||||||
|
renderer = render();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'sets reviews based on API response', () => {
|
||||||
|
const props = renderer.root.findByType( 'div' ).props;
|
||||||
|
|
||||||
|
expect( props.error ).toBeNull();
|
||||||
|
expect( props.isLoading ).toBe( false );
|
||||||
|
expect( props.reviews ).toEqual( mockReviews.slice( 0, 2 ) );
|
||||||
|
expect( props.totalReviews ).toEqual( mockReviews.length );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'when the API returns an error', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
mockUtils.getReviews.mockImplementation(
|
||||||
|
() => Promise.reject( { message: 'There was an error.' } )
|
||||||
|
);
|
||||||
|
renderer = render();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'sets the error prop', () => {
|
||||||
|
const props = renderer.root.findByType( 'div' ).props;
|
||||||
|
|
||||||
|
expect( props.error ).toEqual( { apiMessage: 'There was an error.' } );
|
||||||
|
expect( props.isLoading ).toBe( false );
|
||||||
|
expect( props.reviews ).toEqual( [] );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,170 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getReviews } from '../../blocks/reviews/utils';
|
||||||
|
|
||||||
|
const withReviews = ( OriginalComponent ) => {
|
||||||
|
class WrappedComponent extends Component {
|
||||||
|
constructor() {
|
||||||
|
super( ...arguments );
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
reviews: [],
|
||||||
|
totalReviews: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setError = this.setError.bind( this );
|
||||||
|
this.delayedAppendReviews = this.props.delayFunction( this.appendReviews );
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.replaceReviews();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate( prevProps ) {
|
||||||
|
if ( prevProps.reviewsToDisplay < this.props.reviewsToDisplay ) {
|
||||||
|
// Since this attribute might be controlled via something with
|
||||||
|
// short intervals between value changes, this allows for optionally
|
||||||
|
// delaying review fetches via the provided delay function.
|
||||||
|
this.delayedAppendReviews();
|
||||||
|
} else if (
|
||||||
|
this.shouldReplaceReviews( prevProps, this.props )
|
||||||
|
) {
|
||||||
|
this.replaceReviews();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldReplaceReviews( prevProps, nextProps ) {
|
||||||
|
return (
|
||||||
|
prevProps.orderby !== nextProps.orderby ||
|
||||||
|
prevProps.order !== nextProps.order ||
|
||||||
|
prevProps.productId !== nextProps.productId ||
|
||||||
|
! isShallowEqual( prevProps.categoryIds, nextProps.categoryIds )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getArgs( reviewsToSkip ) {
|
||||||
|
const { categoryIds, order, orderby, productId, reviewsToDisplay } = this.props;
|
||||||
|
const args = {
|
||||||
|
order,
|
||||||
|
orderby,
|
||||||
|
per_page: reviewsToDisplay - reviewsToSkip,
|
||||||
|
offset: reviewsToSkip,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( categoryIds && categoryIds.length ) {
|
||||||
|
args.category_id = Array.isArray( categoryIds ) ? categoryIds.join( ',' ) : categoryIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( productId ) {
|
||||||
|
args.product_id = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceReviews() {
|
||||||
|
const { onReviewsReplaced } = this.props;
|
||||||
|
|
||||||
|
this.updateListOfReviews().then( onReviewsReplaced );
|
||||||
|
}
|
||||||
|
|
||||||
|
appendReviews() {
|
||||||
|
const { onReviewsAppended, reviewsToDisplay } = this.props;
|
||||||
|
const { reviews } = this.state;
|
||||||
|
|
||||||
|
// Given that this function is delayed, props might have been updated since
|
||||||
|
// it was called so we need to check again if fetching new reviews is necessary.
|
||||||
|
if ( reviewsToDisplay <= reviews.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateListOfReviews( reviews ).then( onReviewsAppended );
|
||||||
|
}
|
||||||
|
|
||||||
|
updateListOfReviews( oldReviews = [] ) {
|
||||||
|
const { reviewsToDisplay } = this.props;
|
||||||
|
const { totalReviews } = this.state;
|
||||||
|
const reviewsToLoad = Math.min( totalReviews, reviewsToDisplay ) - oldReviews.length;
|
||||||
|
|
||||||
|
this.setState( {
|
||||||
|
loading: true,
|
||||||
|
reviews: oldReviews.concat( Array( reviewsToLoad ).fill( {} ) ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
return getReviews( this.getArgs( oldReviews.length ) )
|
||||||
|
.then( ( { reviews: newReviews, totalReviews: newTotalReviews } ) => {
|
||||||
|
this.setState( {
|
||||||
|
reviews: oldReviews.filter( ( review ) => Object.keys( review ).length ).concat( newReviews ),
|
||||||
|
totalReviews: newTotalReviews,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
} );
|
||||||
|
|
||||||
|
return { newReviews };
|
||||||
|
} )
|
||||||
|
.catch( this.setError );
|
||||||
|
}
|
||||||
|
|
||||||
|
setError( apiError ) {
|
||||||
|
const { onReviewsLoadError } = this.props;
|
||||||
|
const error = typeof apiError === 'object' && apiError.hasOwnProperty( 'message' ) ? {
|
||||||
|
apiMessage: apiError.message,
|
||||||
|
} : {
|
||||||
|
apiMessage: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState( { reviews: [], loading: false, error } );
|
||||||
|
|
||||||
|
onReviewsLoadError();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { reviewsToDisplay } = this.props;
|
||||||
|
const { error, loading, reviews, totalReviews } = this.state;
|
||||||
|
|
||||||
|
return <OriginalComponent
|
||||||
|
{ ...this.props }
|
||||||
|
error={ error }
|
||||||
|
isLoading={ loading }
|
||||||
|
reviews={ reviews.slice( 0, reviewsToDisplay ) }
|
||||||
|
totalReviews={ totalReviews }
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedComponent.propTypes = {
|
||||||
|
order: PropTypes.oneOf( [ 'asc', 'desc' ] ).isRequired,
|
||||||
|
orderby: PropTypes.string.isRequired,
|
||||||
|
reviewsToDisplay: PropTypes.number.isRequired,
|
||||||
|
categoryIds: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array ] ),
|
||||||
|
delayFunction: PropTypes.func,
|
||||||
|
onReviewsAppended: PropTypes.func,
|
||||||
|
onReviewsLoadError: PropTypes.func,
|
||||||
|
onReviewsReplaced: PropTypes.func,
|
||||||
|
productId: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
|
||||||
|
};
|
||||||
|
|
||||||
|
WrappedComponent.defaultProps = {
|
||||||
|
delayFunction: ( f ) => f,
|
||||||
|
onReviewsAppended: () => {},
|
||||||
|
onReviewsLoadError: () => {},
|
||||||
|
onReviewsReplaced: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { displayName = OriginalComponent.name || 'Component' } = OriginalComponent;
|
||||||
|
WrappedComponent.displayName = `WithReviews( ${ displayName } )`;
|
||||||
|
|
||||||
|
return WrappedComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withReviews;
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import LoadMoreButton from '../../base/components/load-more-button';
|
||||||
|
import ReviewOrderSelect from '../../base/components/review-order-select';
|
||||||
|
import ReviewList from '../../base/components/review-list';
|
||||||
|
import withComponentId from '../../base/hocs/with-component-id';
|
||||||
|
import withReviews from '../../base/hocs/with-reviews';
|
||||||
|
import { ENABLE_REVIEW_RATING } from '../../constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block rendered in the frontend.
|
||||||
|
*/
|
||||||
|
const FrontendBlock = ( { attributes, componentId, onAppendReviews, onChangeOrderby, reviews, totalReviews } ) => {
|
||||||
|
const { orderby } = attributes;
|
||||||
|
|
||||||
|
if ( 0 === reviews.length ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{ ( attributes.showOrderby && ENABLE_REVIEW_RATING ) && (
|
||||||
|
<ReviewOrderSelect
|
||||||
|
componentId={ componentId }
|
||||||
|
defaultValue={ orderby }
|
||||||
|
onChange={ onChangeOrderby }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
<ReviewList
|
||||||
|
attributes={ attributes }
|
||||||
|
componentId={ componentId }
|
||||||
|
reviews={ reviews }
|
||||||
|
/>
|
||||||
|
{ ( attributes.showLoadMore && totalReviews > reviews.length ) && (
|
||||||
|
<LoadMoreButton
|
||||||
|
onClick={ onAppendReviews }
|
||||||
|
screenReaderLabel={ __( 'Load more reviews', 'woo-gutenberg-products-block' ) }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FrontendBlock.propTypes = {
|
||||||
|
/**
|
||||||
|
* The attributes for this block.
|
||||||
|
*/
|
||||||
|
attributes: PropTypes.object.isRequired,
|
||||||
|
onAppendReviews: PropTypes.func,
|
||||||
|
onChangeArgs: PropTypes.func,
|
||||||
|
// from withComponentId
|
||||||
|
componentId: PropTypes.number,
|
||||||
|
// from withReviewsattributes
|
||||||
|
reviews: PropTypes.array,
|
||||||
|
totalReviews: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withComponentId( withReviews( FrontendBlock ) );
|
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||||
|
import { speak } from '@wordpress/a11y';
|
||||||
|
import { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getOrderArgs } from './utils';
|
||||||
|
import FrontendBlock from './frontend-block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container of the block rendered in the frontend.
|
||||||
|
*/
|
||||||
|
class FrontendContainerBlock extends Component {
|
||||||
|
constructor() {
|
||||||
|
super( ...arguments );
|
||||||
|
const { attributes } = this.props;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
orderby: attributes.orderby,
|
||||||
|
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onAppendReviews = this.onAppendReviews.bind( this );
|
||||||
|
this.onChangeOrderby = this.onChangeOrderby.bind( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
onAppendReviews() {
|
||||||
|
const { attributes } = this.props;
|
||||||
|
const { reviewsToDisplay } = this.state;
|
||||||
|
|
||||||
|
this.setState( {
|
||||||
|
reviewsToDisplay: reviewsToDisplay + parseInt( attributes.reviewsOnLoadMore, 10 ),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeOrderby( event ) {
|
||||||
|
const { attributes } = this.props;
|
||||||
|
|
||||||
|
this.setState( {
|
||||||
|
orderby: event.target.value,
|
||||||
|
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
onReviewsAppended( { newReviews } ) {
|
||||||
|
speak(
|
||||||
|
sprintf(
|
||||||
|
_n(
|
||||||
|
'%d review loaded.',
|
||||||
|
'%d reviews loaded.',
|
||||||
|
newReviews.length,
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
newReviews.length,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onReviewsReplaced() {
|
||||||
|
speak( __( 'Reviews list updated.', 'woo-gutenberg-products-block' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
onReviewsLoadError() {
|
||||||
|
speak( __( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { attributes } = this.props;
|
||||||
|
const { categoryIds, productId } = attributes;
|
||||||
|
const { reviewsToDisplay } = this.state;
|
||||||
|
const { order, orderby } = getOrderArgs( this.state.orderby );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FrontendBlock
|
||||||
|
attributes={ attributes }
|
||||||
|
categoryIds={ categoryIds }
|
||||||
|
onAppendReviews={ this.onAppendReviews }
|
||||||
|
onChangeOrderby={ this.onChangeOrderby }
|
||||||
|
onReviewsAppended={ this.onReviewsAppended }
|
||||||
|
onReviewsLoadError={ this.onReviewsLoadError }
|
||||||
|
onReviewsReplaced={ this.onReviewsReplaced }
|
||||||
|
order={ order }
|
||||||
|
orderby={ orderby }
|
||||||
|
productId={ productId }
|
||||||
|
reviewsToDisplay={ reviewsToDisplay }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrontendContainerBlock.propTypes = {
|
||||||
|
/**
|
||||||
|
* The attributes for this block.
|
||||||
|
*/
|
||||||
|
attributes: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FrontendContainerBlock;
|
|
@ -18,6 +18,7 @@ import { SearchListItem } from '@woocommerce/components';
|
||||||
import { Fragment } from '@wordpress/element';
|
import { Fragment } from '@wordpress/element';
|
||||||
import { compose } from '@wordpress/compose';
|
import { compose } from '@wordpress/compose';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -26,7 +27,7 @@ import EditorBlock from './editor-block.js';
|
||||||
import ProductCategoryControl from '../../../components/product-category-control';
|
import ProductCategoryControl from '../../../components/product-category-control';
|
||||||
import { IconReviewsByCategory } from '../../../components/icons';
|
import { IconReviewsByCategory } from '../../../components/icons';
|
||||||
import { getSharedReviewContentControls, getSharedReviewListControls } from '../edit.js';
|
import { getSharedReviewContentControls, getSharedReviewListControls } from '../edit.js';
|
||||||
import { getBlockClassName } from '../utils.js';
|
import { getBlockClassName, getOrderArgs } from '../utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to handle edit mode of "Reviews by Category".
|
* Component to handle edit mode of "Reviews by Category".
|
||||||
|
@ -167,9 +168,19 @@ const ReviewsByCategoryEditor = ( { attributes, debouncedSpeak, setAttributes }
|
||||||
return renderHiddenContentPlaceholder();
|
return renderHiddenContentPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { reviewsOnPageLoad } = attributes;
|
||||||
|
const { order, orderby } = getOrderArgs( attributes.orderby );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ getBlockClassName( 'wc-block-reviews-by-category', attributes ) }>
|
<div className={ getBlockClassName( 'wc-block-reviews-by-category', attributes ) }>
|
||||||
<EditorBlock attributes={ attributes } />
|
<EditorBlock
|
||||||
|
attributes={ attributes }
|
||||||
|
categoryIds={ categoryIds }
|
||||||
|
delayFunction={ ( callback ) => debounce( callback, 400 ) }
|
||||||
|
orderby={ orderby }
|
||||||
|
order={ order }
|
||||||
|
reviewsToDisplay={ reviewsOnPageLoad }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,17 +4,16 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { Disabled, Placeholder } from '@wordpress/components';
|
import { Disabled, Placeholder } from '@wordpress/components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { getOrderArgs, getReviews } from '../utils';
|
|
||||||
import LoadMoreButton from '../../../base/components/load-more-button';
|
import LoadMoreButton from '../../../base/components/load-more-button';
|
||||||
import ReviewList from '../../../base/components/review-list';
|
import ReviewList from '../../../base/components/review-list';
|
||||||
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
||||||
import withComponentId from '../../../base/hocs/with-component-id';
|
import withComponentId from '../../../base/hocs/with-component-id';
|
||||||
|
import withReviews from '../../../base/hocs/with-reviews';
|
||||||
import { IconReviewsByCategory } from '../../../components/icons';
|
import { IconReviewsByCategory } from '../../../components/icons';
|
||||||
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
||||||
|
|
||||||
|
@ -22,54 +21,6 @@ import { ENABLE_REVIEW_RATING } from '../../../constants';
|
||||||
* Block rendered in the editor.
|
* Block rendered in the editor.
|
||||||
*/
|
*/
|
||||||
class EditorBlock extends Component {
|
class EditorBlock extends Component {
|
||||||
constructor() {
|
|
||||||
super( ...arguments );
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
reviews: [],
|
|
||||||
totalReviews: 0,
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.renderNoReviews = this.renderNoReviews.bind( this );
|
|
||||||
this.debouncedLoadFirstReviews = debounce( this.loadFirstReviews.bind( this ), 400 );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.loadFirstReviews();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate( prevProps ) {
|
|
||||||
if (
|
|
||||||
prevProps.attributes.orderby !== this.props.attributes.orderby ||
|
|
||||||
prevProps.attributes.categoryIds !== this.props.attributes.categoryIds ||
|
|
||||||
prevProps.attributes.reviewsOnPageLoad !== this.props.attributes.reviewsOnPageLoad
|
|
||||||
) {
|
|
||||||
this.debouncedLoadFirstReviews();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultArgs() {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { order, orderby } = getOrderArgs( attributes.orderby );
|
|
||||||
const { categoryIds, reviewsOnPageLoad } = attributes;
|
|
||||||
|
|
||||||
return {
|
|
||||||
order,
|
|
||||||
orderby,
|
|
||||||
per_page: reviewsOnPageLoad,
|
|
||||||
category_id: categoryIds.join( ',' ),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFirstReviews() {
|
|
||||||
getReviews( this.getDefaultArgs() ).then( ( { reviews, totalReviews } ) => {
|
|
||||||
this.setState( { reviews, totalReviews, isLoading: false } );
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [], isLoading: false } );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
renderNoReviews() {
|
renderNoReviews() {
|
||||||
return (
|
return (
|
||||||
<Placeholder
|
<Placeholder
|
||||||
|
@ -83,8 +34,7 @@ class EditorBlock extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { attributes, componentId } = this.props;
|
const { attributes, componentId, isLoading, reviews, totalReviews } = this.props;
|
||||||
const { reviews, totalReviews, isLoading } = this.state;
|
|
||||||
|
|
||||||
if ( 0 === reviews.length && ! isLoading ) {
|
if ( 0 === reviews.length && ! isLoading ) {
|
||||||
return this.renderNoReviews();
|
return this.renderNoReviews();
|
||||||
|
@ -123,6 +73,9 @@ EditorBlock.propTypes = {
|
||||||
* From withComponentId.
|
* From withComponentId.
|
||||||
*/
|
*/
|
||||||
componentId: PropTypes.number,
|
componentId: PropTypes.number,
|
||||||
|
// from withReviews
|
||||||
|
reviews: PropTypes.array,
|
||||||
|
totalReviews: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withComponentId( EditorBlock );
|
export default withComponentId( withReviews( EditorBlock ) );
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
|
||||||
import { Component, Fragment } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { speak } from '@wordpress/a11y';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { getOrderArgs, getReviews } from '../utils';
|
|
||||||
import LoadMoreButton from '../../../base/components/load-more-button';
|
|
||||||
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
|
||||||
import ReviewList from '../../../base/components/review-list';
|
|
||||||
import withComponentId from '../../../base/hocs/with-component-id';
|
|
||||||
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block rendered in the frontend.
|
|
||||||
*/
|
|
||||||
class FrontendBlock extends Component {
|
|
||||||
constructor() {
|
|
||||||
super( ...arguments );
|
|
||||||
const { attributes } = this.props;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
orderby: attributes.orderby,
|
|
||||||
reviews: [],
|
|
||||||
totalReviews: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onChangeOrderby = this.onChangeOrderby.bind( this );
|
|
||||||
this.appendReviews = this.appendReviews.bind( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.loadFirstReviews();
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultArgs() {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { order, orderby } = getOrderArgs( this.state.orderby );
|
|
||||||
const { categoryIds, reviewsOnPageLoad } = attributes;
|
|
||||||
|
|
||||||
return {
|
|
||||||
order,
|
|
||||||
orderby,
|
|
||||||
per_page: reviewsOnPageLoad,
|
|
||||||
category_id: categoryIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFirstReviews() {
|
|
||||||
getReviews( this.getDefaultArgs() ).then( ( { reviews, totalReviews } ) => {
|
|
||||||
this.setState( { reviews, totalReviews } );
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
appendReviews() {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { reviewsOnLoadMore } = attributes;
|
|
||||||
const { reviews, totalReviews } = this.state;
|
|
||||||
|
|
||||||
const reviewsToLoad = Math.min( totalReviews - reviews.length, reviewsOnLoadMore );
|
|
||||||
this.setState( { reviews: reviews.concat( Array( reviewsToLoad ).fill( {} ) ) } );
|
|
||||||
|
|
||||||
const args = {
|
|
||||||
...this.getDefaultArgs(),
|
|
||||||
offset: reviews.length,
|
|
||||||
per_page: reviewsOnLoadMore,
|
|
||||||
};
|
|
||||||
getReviews( args ).then( ( { reviews: newReviews, totalReviews: newTotalReviews } ) => {
|
|
||||||
this.setState( {
|
|
||||||
reviews: reviews.filter( ( review ) => Object.keys( review ).length ).concat( newReviews ),
|
|
||||||
totalReviews: newTotalReviews,
|
|
||||||
} );
|
|
||||||
speak(
|
|
||||||
sprintf(
|
|
||||||
_n(
|
|
||||||
'%d review loaded.',
|
|
||||||
'%d reviews loaded.',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
),
|
|
||||||
newReviews.length
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeOrderby( event ) {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { reviewsOnPageLoad } = attributes;
|
|
||||||
const { totalReviews } = this.state;
|
|
||||||
const { order, orderby } = getOrderArgs( event.target.value );
|
|
||||||
const newReviews = Math.min( totalReviews, reviewsOnPageLoad );
|
|
||||||
|
|
||||||
this.setState( {
|
|
||||||
reviews: Array( newReviews ).fill( {} ),
|
|
||||||
orderby: event.target.value,
|
|
||||||
} );
|
|
||||||
|
|
||||||
const args = {
|
|
||||||
...this.getDefaultArgs(),
|
|
||||||
order,
|
|
||||||
orderby,
|
|
||||||
per_page: reviewsOnPageLoad,
|
|
||||||
};
|
|
||||||
getReviews( args ).then( ( { reviews, totalReviews: newTotalReviews } ) => {
|
|
||||||
this.setState( { reviews, totalReviews: newTotalReviews } );
|
|
||||||
speak( __( 'Reviews order updated.', 'woo-gutenberg-products-block' ) );
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { attributes, componentId } = this.props;
|
|
||||||
const { orderby, reviews, totalReviews } = this.state;
|
|
||||||
|
|
||||||
if ( 0 === reviews.length ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{ ( attributes.showOrderby && ENABLE_REVIEW_RATING ) && (
|
|
||||||
<ReviewOrderSelect
|
|
||||||
componentId={ componentId }
|
|
||||||
onChange={ this.onChangeOrderby }
|
|
||||||
value={ orderby }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
<ReviewList
|
|
||||||
attributes={ attributes }
|
|
||||||
componentId={ componentId }
|
|
||||||
reviews={ reviews }
|
|
||||||
/>
|
|
||||||
{ ( attributes.showLoadMore && totalReviews > reviews.length ) && (
|
|
||||||
<LoadMoreButton
|
|
||||||
onClick={ this.appendReviews }
|
|
||||||
screenReaderLabel={ __( 'Load more reviews', 'woo-gutenberg-products-block' ) }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FrontendBlock.propTypes = {
|
|
||||||
/**
|
|
||||||
* The attributes for this block.
|
|
||||||
*/
|
|
||||||
attributes: PropTypes.object.isRequired,
|
|
||||||
// from withComponentId
|
|
||||||
componentId: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withComponentId( FrontendBlock );
|
|
|
@ -6,7 +6,7 @@ import { render } from 'react-dom';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import FrontendBlock from './frontend-block.js';
|
import FrontendContainerBlock from '../frontend-container-block.js';
|
||||||
|
|
||||||
const containers = document.querySelectorAll(
|
const containers = document.querySelectorAll(
|
||||||
'.wp-block-woocommerce-reviews-by-category'
|
'.wp-block-woocommerce-reviews-by-category'
|
||||||
|
@ -25,6 +25,6 @@ if ( containers.length ) {
|
||||||
showProductName: el.classList.contains( 'has-product-name' ),
|
showProductName: el.classList.contains( 'has-product-name' ),
|
||||||
};
|
};
|
||||||
|
|
||||||
render( <FrontendBlock attributes={ attributes } />, el );
|
render( <FrontendContainerBlock attributes={ attributes } />, el );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { SearchListItem } from '@woocommerce/components';
|
import { SearchListItem } from '@woocommerce/components';
|
||||||
import { Fragment } from '@wordpress/element';
|
import { Fragment } from '@wordpress/element';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -24,7 +25,7 @@ import EditorBlock from './editor-block.js';
|
||||||
import ProductControl from '../../../components/product-control';
|
import ProductControl from '../../../components/product-control';
|
||||||
import { IconReviewsByProduct } from '../../../components/icons';
|
import { IconReviewsByProduct } from '../../../components/icons';
|
||||||
import { getSharedReviewContentControls, getSharedReviewListControls } from '../edit.js';
|
import { getSharedReviewContentControls, getSharedReviewListControls } from '../edit.js';
|
||||||
import { getBlockClassName } from '../utils.js';
|
import { getBlockClassName, getOrderArgs } from '../utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to handle edit mode of "Reviews by Product".
|
* Component to handle edit mode of "Reviews by Product".
|
||||||
|
@ -160,9 +161,19 @@ const ReviewsByProductEditor = ( { attributes, debouncedSpeak, setAttributes } )
|
||||||
return renderHiddenContentPlaceholder();
|
return renderHiddenContentPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { reviewsOnPageLoad } = attributes;
|
||||||
|
const { order, orderby } = getOrderArgs( attributes.orderby );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ getBlockClassName( 'wc-block-reviews-by-product', attributes ) }>
|
<div className={ getBlockClassName( 'wc-block-reviews-by-product', attributes ) }>
|
||||||
<EditorBlock attributes={ attributes } />
|
<EditorBlock
|
||||||
|
attributes={ attributes }
|
||||||
|
delayFunction={ ( callback ) => debounce( callback, 400 ) }
|
||||||
|
orderby={ orderby }
|
||||||
|
order={ order }
|
||||||
|
productId={ productId }
|
||||||
|
reviewsToDisplay={ reviewsOnPageLoad }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,76 +1,52 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { debounce } from 'lodash';
|
import { Disabled, Placeholder } from '@wordpress/components';
|
||||||
import { Disabled } from '@wordpress/components';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { getOrderArgs, getReviews } from '../utils';
|
|
||||||
import LoadMoreButton from '../../../base/components/load-more-button';
|
import LoadMoreButton from '../../../base/components/load-more-button';
|
||||||
import ReviewList from '../../../base/components/review-list';
|
import ReviewList from '../../../base/components/review-list';
|
||||||
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
||||||
import withComponentId from '../../../base/hocs/with-component-id';
|
import withComponentId from '../../../base/hocs/with-component-id';
|
||||||
|
import withReviews from '../../../base/hocs/with-reviews';
|
||||||
|
import { IconReviewsByProduct } from '../../../components/icons';
|
||||||
import NoReviewsPlaceholder from './no-reviews-placeholder.js';
|
import NoReviewsPlaceholder from './no-reviews-placeholder.js';
|
||||||
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
||||||
|
import { escapeHTML } from '@wordpress/escape-html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block rendered in the editor.
|
* Block rendered in the editor.
|
||||||
*/
|
*/
|
||||||
class EditorBlock extends Component {
|
class EditorBlock extends Component {
|
||||||
constructor() {
|
renderNoReviews() {
|
||||||
super( ...arguments );
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
reviews: [],
|
|
||||||
totalReviews: 0,
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.debouncedLoadFirstReviews = debounce( this.loadFirstReviews.bind( this ), 400 );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.loadFirstReviews();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate( prevProps ) {
|
|
||||||
if (
|
|
||||||
prevProps.attributes.orderby !== this.props.attributes.orderby ||
|
|
||||||
prevProps.attributes.productId !== this.props.attributes.productId ||
|
|
||||||
prevProps.attributes.reviewsOnPageLoad !== this.props.attributes.reviewsOnPageLoad
|
|
||||||
) {
|
|
||||||
this.debouncedLoadFirstReviews();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultArgs() {
|
|
||||||
const { attributes } = this.props;
|
const { attributes } = this.props;
|
||||||
const { order, orderby } = getOrderArgs( attributes.orderby );
|
const { product } = attributes;
|
||||||
const { productId, reviewsOnPageLoad } = attributes;
|
return (
|
||||||
|
<Placeholder
|
||||||
return {
|
className="wc-block-reviews-by-product"
|
||||||
order,
|
icon={ <IconReviewsByProduct className="block-editor-block-icon" /> }
|
||||||
orderby,
|
label={ __( 'Reviews by Product', 'woo-gutenberg-products-block' ) }
|
||||||
per_page: reviewsOnPageLoad,
|
>
|
||||||
product_id: productId,
|
<div dangerouslySetInnerHTML={ {
|
||||||
};
|
__html: sprintf(
|
||||||
}
|
__(
|
||||||
|
"This block lists reviews for a selected product. %s doesn't have any reviews yet, but they will show up here when it does.",
|
||||||
loadFirstReviews() {
|
'woo-gutenberg-products-block'
|
||||||
getReviews( this.getDefaultArgs() ).then( ( { reviews, totalReviews } ) => {
|
),
|
||||||
this.setState( { reviews, totalReviews, isLoading: false } );
|
'<strong>' + escapeHTML( product.name ) + '</strong>'
|
||||||
} ).catch( () => {
|
),
|
||||||
this.setState( { reviews: [], isLoading: false } );
|
} } />
|
||||||
} );
|
</Placeholder>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { attributes, componentId } = this.props;
|
const { attributes, componentId, isLoading, reviews, totalReviews } = this.props;
|
||||||
const { reviews, totalReviews, isLoading } = this.state;
|
|
||||||
|
|
||||||
if ( 0 === reviews.length && ! isLoading ) {
|
if ( 0 === reviews.length && ! isLoading ) {
|
||||||
return <NoReviewsPlaceholder attributes={ attributes } />;
|
return <NoReviewsPlaceholder attributes={ attributes } />;
|
||||||
|
@ -107,6 +83,9 @@ EditorBlock.propTypes = {
|
||||||
attributes: PropTypes.object.isRequired,
|
attributes: PropTypes.object.isRequired,
|
||||||
// from withComponentId
|
// from withComponentId
|
||||||
componentId: PropTypes.number,
|
componentId: PropTypes.number,
|
||||||
|
// from withReviews
|
||||||
|
reviews: PropTypes.array,
|
||||||
|
totalReviews: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withComponentId( EditorBlock );
|
export default withComponentId( withReviews( EditorBlock ) );
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
|
||||||
import { Component, Fragment } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { speak } from '@wordpress/a11y';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { getOrderArgs, getReviews } from '../utils';
|
|
||||||
import LoadMoreButton from '../../../base/components/load-more-button';
|
|
||||||
import ReviewOrderSelect from '../../../base/components/review-order-select';
|
|
||||||
import ReviewList from '../../../base/components/review-list';
|
|
||||||
import withComponentId from '../../../base/hocs/with-component-id';
|
|
||||||
import { ENABLE_REVIEW_RATING } from '../../../constants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block rendered in the frontend.
|
|
||||||
*/
|
|
||||||
class FrontendBlock extends Component {
|
|
||||||
constructor() {
|
|
||||||
super( ...arguments );
|
|
||||||
const { attributes } = this.props;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
orderby: attributes.orderby,
|
|
||||||
reviews: [],
|
|
||||||
totalReviews: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onChangeOrderby = this.onChangeOrderby.bind( this );
|
|
||||||
this.appendReviews = this.appendReviews.bind( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.loadFirstReviews();
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultArgs() {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { order, orderby } = getOrderArgs( this.state.orderby );
|
|
||||||
const { productId, reviewsOnPageLoad } = attributes;
|
|
||||||
|
|
||||||
return {
|
|
||||||
order,
|
|
||||||
orderby,
|
|
||||||
per_page: reviewsOnPageLoad,
|
|
||||||
product_id: productId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFirstReviews() {
|
|
||||||
getReviews( this.getDefaultArgs() ).then( ( { reviews, totalReviews } ) => {
|
|
||||||
this.setState( { reviews, totalReviews } );
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
appendReviews() {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { reviewsOnLoadMore } = attributes;
|
|
||||||
const { reviews, totalReviews } = this.state;
|
|
||||||
|
|
||||||
const reviewsToLoad = Math.min( totalReviews - reviews.length, reviewsOnLoadMore );
|
|
||||||
this.setState( { reviews: reviews.concat( Array( reviewsToLoad ).fill( {} ) ) } );
|
|
||||||
|
|
||||||
const args = {
|
|
||||||
...this.getDefaultArgs(),
|
|
||||||
offset: reviews.length,
|
|
||||||
per_page: reviewsOnLoadMore,
|
|
||||||
};
|
|
||||||
getReviews( args ).then( ( { reviews: newReviews, totalReviews: newTotalReviews } ) => {
|
|
||||||
this.setState( {
|
|
||||||
reviews: reviews.filter( ( review ) => Object.keys( review ).length ).concat( newReviews ),
|
|
||||||
totalReviews: newTotalReviews,
|
|
||||||
} );
|
|
||||||
speak(
|
|
||||||
sprintf(
|
|
||||||
_n(
|
|
||||||
'%d review loaded.',
|
|
||||||
'%d reviews loaded.',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
),
|
|
||||||
newReviews.length
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeOrderby( event ) {
|
|
||||||
const { attributes } = this.props;
|
|
||||||
const { reviewsOnPageLoad } = attributes;
|
|
||||||
const { totalReviews } = this.state;
|
|
||||||
const { order, orderby } = getOrderArgs( event.target.value );
|
|
||||||
const newReviews = Math.min( totalReviews, reviewsOnPageLoad );
|
|
||||||
|
|
||||||
this.setState( {
|
|
||||||
reviews: Array( newReviews ).fill( {} ),
|
|
||||||
orderby: event.target.value,
|
|
||||||
} );
|
|
||||||
|
|
||||||
const args = {
|
|
||||||
...this.getDefaultArgs(),
|
|
||||||
order,
|
|
||||||
orderby,
|
|
||||||
per_page: reviewsOnPageLoad,
|
|
||||||
};
|
|
||||||
getReviews( args ).then( ( { reviews, totalReviews: newTotalReviews } ) => {
|
|
||||||
this.setState( { reviews, totalReviews: newTotalReviews } );
|
|
||||||
speak( __( 'Reviews order updated.', 'woo-gutenberg-products-block' ) );
|
|
||||||
} ).catch( () => {
|
|
||||||
this.setState( { reviews: [] } );
|
|
||||||
speak(
|
|
||||||
__( 'There was an error loading the reviews.', 'woo-gutenberg-products-block' )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { attributes, componentId } = this.props;
|
|
||||||
const { orderby, reviews, totalReviews } = this.state;
|
|
||||||
|
|
||||||
if ( 0 === reviews.length ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{ ( attributes.showOrderby && ENABLE_REVIEW_RATING ) && (
|
|
||||||
<ReviewOrderSelect
|
|
||||||
componentId={ componentId }
|
|
||||||
onChange={ this.onChangeOrderby }
|
|
||||||
value={ orderby }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
<ReviewList
|
|
||||||
attributes={ attributes }
|
|
||||||
componentId={ componentId }
|
|
||||||
reviews={ reviews }
|
|
||||||
/>
|
|
||||||
{ ( attributes.showLoadMore && totalReviews > reviews.length ) && (
|
|
||||||
<LoadMoreButton
|
|
||||||
onClick={ this.appendReviews }
|
|
||||||
screenReaderLabel={ __( 'Load more reviews', 'woo-gutenberg-products-block' ) }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FrontendBlock.propTypes = {
|
|
||||||
/**
|
|
||||||
* The attributes for this block.
|
|
||||||
*/
|
|
||||||
attributes: PropTypes.object.isRequired,
|
|
||||||
// from withComponentId
|
|
||||||
componentId: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withComponentId( FrontendBlock );
|
|
|
@ -6,7 +6,7 @@ import { render } from 'react-dom';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import FrontendBlock from './frontend-block.js';
|
import FrontendContainerBlock from '../frontend-container-block.js';
|
||||||
|
|
||||||
const containers = document.querySelectorAll(
|
const containers = document.querySelectorAll(
|
||||||
'.wp-block-woocommerce-reviews-by-product'
|
'.wp-block-woocommerce-reviews-by-product'
|
||||||
|
@ -24,6 +24,6 @@ if ( containers.length ) {
|
||||||
showReviewContent: el.classList.contains( 'has-content' ),
|
showReviewContent: el.classList.contains( 'has-content' ),
|
||||||
};
|
};
|
||||||
|
|
||||||
render( <FrontendBlock attributes={ attributes } />, el );
|
render( <FrontendContainerBlock attributes={ attributes } />, el );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
"@wordpress/editor": "9.5.0",
|
"@wordpress/editor": "9.5.0",
|
||||||
"@wordpress/element": "2.6.0",
|
"@wordpress/element": "2.6.0",
|
||||||
"@wordpress/i18n": "3.6.0",
|
"@wordpress/i18n": "3.6.0",
|
||||||
|
"@wordpress/is-shallow-equal": "^1.5.0",
|
||||||
"@wordpress/jest-preset-default": "4.3.0",
|
"@wordpress/jest-preset-default": "4.3.0",
|
||||||
"@wordpress/rich-text": "3.5.0",
|
"@wordpress/rich-text": "3.5.0",
|
||||||
"@wordpress/scripts": "3.4.0",
|
"@wordpress/scripts": "3.4.0",
|
||||||
|
|
Loading…
Reference in New Issue