diff --git a/plugins/woocommerce-blocks/.travis.yml b/plugins/woocommerce-blocks/.travis.yml index 8ddcb192b9a..18d8fc31695 100644 --- a/plugins/woocommerce-blocks/.travis.yml +++ b/plugins/woocommerce-blocks/.travis.yml @@ -21,6 +21,8 @@ jobs: env: WP_VERSION=latest WP_MULTISITE=0 WP_CORE_DIR=/tmp/wordpress php: 7.1 script: + - composer install + - composer run-script phpcs . - npm install - npm run lint - npm run build diff --git a/plugins/woocommerce-blocks/assets/js/components/product-orderby-control/index.js b/plugins/woocommerce-blocks/assets/js/components/product-orderby-control/index.js new file mode 100644 index 00000000000..5351d27aaad --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/components/product-orderby-control/index.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { SelectControl } from '@wordpress/components'; +import PropTypes from 'prop-types'; + +/** + * A pre-configured SelectControl for product orderby settings. + */ +const ProductOrderbyControl = ( { value, setAttributes } ) => { + return ( + setAttributes( { orderby } ) } + /> + ); +}; + +ProductOrderbyControl.propTypes = { + /** + * Callback to update the order setting. + */ + setAttributes: PropTypes.func.isRequired, + /** + * The selected order setting. + */ + value: PropTypes.string.isRequired, +}; + +export default ProductOrderbyControl; diff --git a/plugins/woocommerce-blocks/assets/js/index.js b/plugins/woocommerce-blocks/assets/js/index.js index 79baf332ada..a4a9ea4a076 100644 --- a/plugins/woocommerce-blocks/assets/js/index.js +++ b/plugins/woocommerce-blocks/assets/js/index.js @@ -14,6 +14,7 @@ import getShortcode from './utils/get-shortcode'; import ProductBestSellersBlock from './product-best-sellers'; import ProductByCategoryBlock from './product-category-block'; import ProductTopRatedBlock from './product-top-rated'; +import ProductOnSaleBlock from './product-on-sale'; import sharedAttributes from './utils/shared-attributes'; const validAlignments = [ 'wide', 'full' ]; @@ -157,3 +158,46 @@ registerBlockType( 'woocommerce/product-top-rated', { ); }, } ); + +registerBlockType( 'woocommerce/product-on-sale', { + title: __( 'On Sale Products', 'woo-gutenberg-products-block' ), + icon: , + category: 'widgets', + keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ], + description: __( + 'Display a grid of on sale products.', + 'woo-gutenberg-products-block' + ), + attributes: { + ...sharedAttributes, + /** + * How to order the products: 'date', 'popularity', 'price_asc', 'price_desc' 'rating', 'title'. + */ + orderby: { + type: 'string', + default: 'date', + }, + }, + getEditWrapperProps, + /** + * Renders and manages the block. + */ + edit( props ) { + return ; + }, + /** + * Save the block content in the post content. Block content is saved as a products shortcode. + * + * @return string + */ + save( props ) { + const { + align, + } = props.attributes; /* eslint-disable-line react/prop-types */ + return ( + + { getShortcode( props, 'woocommerce/product-on-sale' ) } + + ); + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/product-best-sellers.js b/plugins/woocommerce-blocks/assets/js/product-best-sellers.js index 07b01e87223..c06da70f422 100644 --- a/plugins/woocommerce-blocks/assets/js/product-best-sellers.js +++ b/plugins/woocommerce-blocks/assets/js/product-best-sellers.js @@ -39,9 +39,7 @@ class ProductBestSellersBlock extends Component { } componentDidMount() { - if ( this.props.attributes.categories ) { - this.getProducts(); - } + this.getProducts(); } componentDidUpdate( prevProps ) { @@ -55,7 +53,10 @@ class ProductBestSellersBlock extends Component { getProducts() { apiFetch( { - path: addQueryArgs( '/wc-pb/v3/products', getQuery( this.props.attributes, this.props.name ) ), + path: addQueryArgs( + '/wc-pb/v3/products', + getQuery( this.props.attributes, this.props.name ) + ), } ) .then( ( products ) => { this.setState( { products, loaded: true } ); @@ -113,7 +114,10 @@ class ProductBestSellersBlock extends Component { const { setAttributes } = this.props; const { columns, align } = this.props.attributes; const { loaded, products } = this.state; - const classes = [ 'wc-block-products-grid', 'wc-block-best-selling-products' ]; + const classes = [ + 'wc-block-products-grid', + 'wc-block-best-selling-products', + ]; if ( columns ) { classes.push( `cols-${ columns }` ); } diff --git a/plugins/woocommerce-blocks/assets/js/product-category-block.js b/plugins/woocommerce-blocks/assets/js/product-category-block.js index b16bbae5f93..80192a6c7bb 100644 --- a/plugins/woocommerce-blocks/assets/js/product-category-block.js +++ b/plugins/woocommerce-blocks/assets/js/product-category-block.js @@ -15,7 +15,6 @@ import { PanelBody, Placeholder, RangeControl, - SelectControl, Spinner, Toolbar, withSpokenMessages, @@ -27,6 +26,7 @@ import PropTypes from 'prop-types'; */ import getQuery from './utils/get-query'; import ProductCategoryControl from './components/product-category-control'; +import ProductOrderbyControl from './components/product-orderby-control'; import ProductPreview from './components/product-preview'; /** @@ -112,56 +112,7 @@ class ProductByCategoryBlock extends Component { title={ __( 'Order By', 'woo-gutenberg-products-block' ) } initialOpen={ false } > - setAttributes( { orderby: value } ) } - /> + ); diff --git a/plugins/woocommerce-blocks/assets/js/product-on-sale.js b/plugins/woocommerce-blocks/assets/js/product-on-sale.js new file mode 100644 index 00000000000..0f627622f79 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/product-on-sale.js @@ -0,0 +1,193 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; +import apiFetch from '@wordpress/api-fetch'; +import { + BlockAlignmentToolbar, + BlockControls, + InspectorControls, +} from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import Gridicon from 'gridicons'; +import { + PanelBody, + Placeholder, + RangeControl, + Spinner, +} from '@wordpress/components'; +import PropTypes from 'prop-types'; + +/** + * Internal dependencies + */ +import getQuery from './utils/get-query'; +import ProductCategoryControl from './components/product-category-control'; +import ProductOrderbyControl from './components/product-orderby-control'; +import ProductPreview from './components/product-preview'; + +/** + * Component to handle edit mode of "On Sale Products". + */ +class ProductOnSaleBlock extends Component { + constructor() { + super( ...arguments ); + this.state = { + products: [], + loaded: false, + }; + } + + componentDidMount() { + this.getProducts(); + } + + componentDidUpdate( prevProps ) { + const hasChange = [ 'rows', 'columns', 'orderby', 'categories' ].reduce( + ( acc, key ) => { + return acc || prevProps.attributes[ key ] !== this.props.attributes[ key ]; + }, + false + ); + if ( hasChange ) { + this.getProducts(); + } + } + + getProducts() { + apiFetch( { + path: addQueryArgs( + '/wc-pb/v3/products', + getQuery( this.props.attributes, this.props.name ) + ), + } ) + .then( ( products ) => { + this.setState( { products, loaded: true } ); + } ) + .catch( () => { + this.setState( { products: [], loaded: true } ); + } ); + } + + getInspectorControls() { + const { attributes, setAttributes } = this.props; + const { columns, rows, orderby } = attributes; + + return ( + + + setAttributes( { columns: value } ) } + min={ wc_product_block_data.min_columns } + max={ wc_product_block_data.max_columns } + /> + setAttributes( { rows: value } ) } + min={ wc_product_block_data.min_rows } + max={ wc_product_block_data.max_rows } + /> + + + + + + { + const ids = value.map( ( { id } ) => id ); + setAttributes( { categories: ids } ); + } } + /> + + + ); + } + + render() { + const { setAttributes } = this.props; + const { columns, align } = this.props.attributes; + const { loaded, products } = this.state; + const classes = [ + 'wc-block-products-grid', + 'wc-block-on-sale-products', + ]; + if ( columns ) { + classes.push( `cols-${ columns }` ); + } + if ( products && ! products.length ) { + if ( ! loaded ) { + classes.push( 'is-loading' ); + } else { + classes.push( 'is-not-found' ); + } + } + + return ( + + + setAttributes( { align: nextAlign } ) } + /> + + { this.getInspectorControls() } +
+ { products.length ? ( + products.map( ( product ) => ( + + ) ) + ) : ( + } + label={ __( + 'On Sale Products', + 'woo-gutenberg-products-block' + ) } + > + { ! loaded ? ( + + ) : ( + __( 'No products found.', 'woo-gutenberg-products-block' ) + ) } + + ) } +
+
+ ); + } +} + +ProductOnSaleBlock.propTypes = { + /** + * The attributes for this block + */ + attributes: PropTypes.object.isRequired, + /** + * The register block name. + */ + name: PropTypes.string.isRequired, + /** + * A callback to update attributes + */ + setAttributes: PropTypes.func.isRequired, +}; + +export default ProductOnSaleBlock; diff --git a/plugins/woocommerce-blocks/assets/js/utils/get-query.js b/plugins/woocommerce-blocks/assets/js/utils/get-query.js index 21aa7195126..6997f5f21c3 100644 --- a/plugins/woocommerce-blocks/assets/js/utils/get-query.js +++ b/plugins/woocommerce-blocks/assets/js/utils/get-query.js @@ -33,8 +33,13 @@ export default function getQuery( attributes, name ) { case 'woocommerce/product-best-sellers': query.orderby = 'popularity'; break; +<<<<<<< HEAD case 'woocommerce/product-top-rated': query.orderby = 'rating'; +======= + case 'woocommerce/product-on-sale': + query.on_sale = 1; +>>>>>>> 75e957b33c668e2ef98500fab68ecfe8ca705ac4 break; } diff --git a/plugins/woocommerce-blocks/assets/js/utils/get-shortcode.js b/plugins/woocommerce-blocks/assets/js/utils/get-shortcode.js index 34417dac22e..948c33f4913 100644 --- a/plugins/woocommerce-blocks/assets/js/utils/get-shortcode.js +++ b/plugins/woocommerce-blocks/assets/js/utils/get-shortcode.js @@ -32,6 +32,9 @@ export default function getShortcode( { attributes }, name ) { case 'woocommerce/product-top-rated': shortcodeAtts.set( 'orderby', 'rating' ); break; + case 'woocommerce/product-on-sale': + shortcodeAtts.set( 'on_sale', '1' ); + break; } // Build the shortcode string out of the set shortcode attributes. diff --git a/plugins/woocommerce-blocks/composer.json b/plugins/woocommerce-blocks/composer.json index 96bb727313e..6efa618d6c6 100644 --- a/plugins/woocommerce-blocks/composer.json +++ b/plugins/woocommerce-blocks/composer.json @@ -14,7 +14,7 @@ }, "scripts": { "phpcs": [ - "phpcs -s -p" + "phpcs --extensions=php -s -p" ], "phpcbf": [ "phpcbf -p" diff --git a/plugins/woocommerce-blocks/package-lock.json b/plugins/woocommerce-blocks/package-lock.json index f044f1db761..4f2930b2c96 100644 --- a/plugins/woocommerce-blocks/package-lock.json +++ b/plugins/woocommerce-blocks/package-lock.json @@ -4517,9 +4517,9 @@ } }, "css-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.0.0.tgz", - "integrity": "sha512-3Fq8HJYs7ruBiDpJA/w2ZROtivA769ePuH3/vgPdOB+FQiotErJ7VJYRZq86SPRVFaccn1wEktUnaaUyf+Uslw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.0.1.tgz", + "integrity": "sha512-XIVwoIOzSFRVsafOKa060GJ/A70c0IP/C1oVPHEX4eHIFF39z0Jl7j8Kua1SUTiqWDupUnbY3/yQx9r7EUB35w==", "dev": true, "requires": { "icss-utils": "^4.0.0", diff --git a/plugins/woocommerce-blocks/package.json b/plugins/woocommerce-blocks/package.json index bed0c7b2963..d63ff7e0858 100644 --- a/plugins/woocommerce-blocks/package.json +++ b/plugins/woocommerce-blocks/package.json @@ -42,7 +42,7 @@ "clean-webpack-plugin": "1.0.0", "core-js": "2.6.0", "cross-env": "5.2.0", - "css-loader": "2.0.0", + "css-loader": "2.0.1", "eslint": "5.10.0", "eslint-config-wordpress": "2.0.0", "eslint-plugin-jest": "22.1.2", diff --git a/plugins/woocommerce-blocks/woocommerce-gutenberg-products-block.php b/plugins/woocommerce-blocks/woocommerce-gutenberg-products-block.php index 4679923a538..e715a6e681f 100644 --- a/plugins/woocommerce-blocks/woocommerce-gutenberg-products-block.php +++ b/plugins/woocommerce-blocks/woocommerce-gutenberg-products-block.php @@ -43,7 +43,13 @@ add_action( 'woocommerce_loaded', 'wgpb_initialize' ); */ function wgpb_plugins_notice() { echo '

'; - esc_html_e( 'WooCommerce Product Blocks development mode requires files to be built. From the plugin directory, run npm install to install dependencies, npm run build to build the files or npm start to build the files and watch for changes.', 'woo-gutenberg-products-block' ); + printf( + /* Translators: %1$s is the install command, %2$s is the build command, %3$s is the watch command. */ + esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the plugin directory, run %1$s to install dependencies, %2$s to build the files or %3$s to build the files and watch for changes.', 'woo-gutenberg-products-block' ), + 'npm install', + 'npm run build', + 'npm start' + ); echo '

'; }