/** * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; import classnames from 'classnames'; import { Component, Fragment } from '@wordpress/element'; import { Button, Tooltip } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import PropTypes from 'prop-types'; import StarIcon from 'gridicons/dist/star'; import StarOutlineIcon from 'gridicons/dist/star-outline'; import interpolateComponents from '@automattic/interpolate-components'; import { EmptyContent, Link, ReviewRating, ProductImage, Section, } from '@woocommerce/components'; import { getAdminLink } from '@woocommerce/settings'; import { get, isNull } from 'lodash'; import { REVIEWS_STORE_NAME } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; /** * Internal dependencies */ import './style.scss'; import { ActivityCard, ActivityCardPlaceholder, } from '~/activity-panel/activity-card'; import CheckmarkCircleIcon from './checkmark-circle-icon'; import { CurrencyContext } from '../../../lib/currency-context'; import sanitizeHTML from '../../../lib/sanitize-html'; import { REVIEW_PAGE_LIMIT, unapprovedReviewsQuery } from './utils'; const reviewsQuery = { page: 1, per_page: REVIEW_PAGE_LIMIT, status: 'hold', _embed: 1, }; class ReviewsPanel extends Component { recordReviewEvent( eventName, eventData ) { recordEvent( `reviews_${ eventName }`, eventData || {} ); } deleteReview( reviewId ) { const { deleteReview, createNotice, updateReview, clearReviewsCache, } = this.props; if ( reviewId ) { deleteReview( reviewId ) .then( () => { clearReviewsCache(); createNotice( 'success', __( 'Review successfully deleted.', 'woocommerce-admin' ), { actions: [ { label: __( 'Undo', 'woocommerce-admin' ), onClick: () => { updateReview( reviewId, { status: 'untrash', }, { _embed: 1, } ).then( () => clearReviewsCache() ); }, }, ], } ); } ) .catch( () => { createNotice( 'error', __( 'Review could not be deleted.', 'woocommerce-admin' ) ); } ); } } updateReviewStatus( reviewId, newStatus, oldStatus ) { const { createNotice, updateReview, clearReviewsCache } = this.props; if ( reviewId ) { updateReview( reviewId, { status: newStatus } ) .then( () => { clearReviewsCache(); createNotice( 'success', __( 'Review successfully updated.', 'woocommerce-admin' ), { actions: [ { label: __( 'Undo', 'woocommerce-admin' ), onClick: () => { updateReview( reviewId, { status: oldStatus, }, { _embed: 1, } ).then( () => clearReviewsCache() ); }, }, ], } ); } ) .catch( () => { createNotice( 'error', __( 'Review could not be updated.', 'woocommerce-admin' ) ); } ); } } renderReview( review ) { const product = ( review && review._embedded && review._embedded.up && review._embedded.up[ 0 ] ) || null; if ( review.isUpdating ) { return ( ); } if ( isNull( product ) || review.status !== reviewsQuery.status ) { return null; } const title = interpolateComponents( { mixedString: sprintf( __( '{{authorLink}}%s{{/authorLink}}{{verifiedCustomerIcon/}} reviewed {{productLink}}%s{{/productLink}}', 'woocommerce-admin' ), review.reviewer, product.name ), components: { productLink: ( this.recordReviewEvent( 'product' ) } type="external" /> ), authorLink: ( this.recordReviewEvent( 'customer' ) } type="external" /> ), verifiedCustomerIcon: review.verified ? ( ) : null, }, } ); const subtitle = ( ); const productImage = get( product, [ 'images', 0 ] ) || get( product, [ 'image' ] ); const productImageClasses = classnames( 'woocommerce-review-activity-card__image-overlay__product', { 'is-placeholder': ! productImage || ! productImage.src, } ); const icon = (
); const manageReviewEvent = { date: review.date_created_gmt, status: review.status, }; const cardActions = [ , , , ]; return ( ); } renderReviews( reviews ) { const renderedReviews = reviews.map( ( review ) => this.renderReview( review, this.props ) ); if ( renderedReviews.filter( Boolean ).length === 0 ) { return <>; } return ( <> { renderedReviews } this.recordReviewEvent( 'reviews_manage' ) } className="woocommerce-layout__activity-panel-outbound-link woocommerce-layout__activity-panel-empty" type="wp-admin" > { __( 'Manage all reviews', 'woocommerce-admin' ) } ); } render() { const { isRequesting, isError, reviews } = this.props; if ( isError ) { const title = __( 'There was an error getting your reviews. Please try again.', 'woocommerce-admin' ); const actionLabel = __( 'Reload', 'woocommerce-admin' ); const actionCallback = () => { // @todo Add tracking for how often an error is displayed, and the reload action is clicked. window.location.reload(); }; return ( ); } return (
{ isRequesting || ! reviews.length ? ( ) : ( <>{ this.renderReviews( reviews ) } ) }
); } } ReviewsPanel.propTypes = { reviews: PropTypes.array.isRequired, isError: PropTypes.bool, isRequesting: PropTypes.bool, }; ReviewsPanel.defaultProps = { reviews: [], isError: false, isRequesting: false, }; ReviewsPanel.contextType = CurrencyContext; export { ReviewsPanel }; export default compose( [ withSelect( ( select, props ) => { const { hasUnapprovedReviews } = props; const { getReviews, getReviewsError, isResolving } = select( REVIEWS_STORE_NAME ); let reviews = []; let isError = false; let isRequesting = false; if ( hasUnapprovedReviews ) { reviews = getReviews( reviewsQuery ); isError = Boolean( getReviewsError( reviewsQuery ) ); isRequesting = isResolving( 'getReviews', [ reviewsQuery ] ); } return { reviews, isError, isRequesting, }; } ), withDispatch( ( dispatch, props ) => { const { deleteReview, updateReview, invalidateResolution } = dispatch( REVIEWS_STORE_NAME ); const { createNotice } = dispatch( 'core/notices' ); const clearReviewsCache = () => { invalidateResolution( 'getReviews', [ reviewsQuery ] ); if ( props.reviews && props.reviews.length < 2 ) { invalidateResolution( 'getReviewsTotalCount', [ unapprovedReviewsQuery, ] ); } }; return { deleteReview, createNotice, updateReview, clearReviewsCache, }; } ), ] )( ReviewsPanel );