From 52496a3506cb0672800dfe54fb3ead7f4ee24aa7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 11 Jun 2020 10:02:00 +0100 Subject: [PATCH] Stock Indicator Block (https://github.com/woocommerce/woocommerce-blocks/pull/2675) * Stock Indicator Block * Add to block map * Put behind flag * Move EditProductLink context * stock text props --- .../assets/js/atomic/blocks/index.js | 1 + .../blocks/product/category-list/edit.js | 6 +- .../js/atomic/blocks/product/sku/edit.js | 6 +- .../blocks/product/stock-indicator/block.js | 83 +++++++++++++++++++ .../blocks/product/stock-indicator/edit.js | 18 ++++ .../blocks/product/stock-indicator/index.js | 30 +++++++ .../blocks/product/stock-indicator/style.scss | 19 +++++ .../js/atomic/blocks/product/tag-list/edit.js | 6 +- .../assets/js/atomic/utils/get-block-map.js | 2 + .../js/blocks/single-product/constants.js | 1 + .../js/components/edit-product-link/index.js | 13 ++- .../assets/js/icons/index.js | 1 + .../assets/js/icons/library/barcode.js | 5 +- .../assets/js/icons/library/box.js | 18 ++++ plugins/woocommerce-blocks/src/Library.php | 1 + .../src/StoreApi/Schemas/ProductSchema.php | 9 +- 16 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/block.js create mode 100644 plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/edit.js create mode 100644 plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/index.js create mode 100644 plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/style.scss create mode 100644 plugins/woocommerce-blocks/assets/js/icons/library/box.js diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/index.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/index.js index 75ae014ca05..5065f98486f 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/index.js +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/index.js @@ -11,3 +11,4 @@ import './product/sale-badge'; import './product/sku'; import './product/category-list'; import './product/tag-list'; +import './product/stock-indicator'; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/category-list/edit.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/category-list/edit.js index 02c19b8b30b..91b94049427 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/category-list/edit.js +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/category-list/edit.js @@ -3,7 +3,6 @@ */ import { Disabled } from '@wordpress/components'; import EditProductLink from '@woocommerce/block-components/edit-product-link'; -import { useProductDataContext } from '@woocommerce/shared-context'; /** * Internal dependencies @@ -11,12 +10,9 @@ import { useProductDataContext } from '@woocommerce/shared-context'; import Block from './block'; export default ( { attributes } ) => { - const productDataContext = useProductDataContext(); - const product = productDataContext.product || {}; - return ( <> - + diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/sku/edit.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/sku/edit.js index 0cb566989ad..65ac513bb71 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/sku/edit.js +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/sku/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import EditProductLink from '@woocommerce/block-components/edit-product-link'; -import { useProductDataContext } from '@woocommerce/shared-context'; /** * Internal dependencies @@ -10,12 +9,9 @@ import { useProductDataContext } from '@woocommerce/shared-context'; import Block from './block'; export default ( { attributes } ) => { - const productDataContext = useProductDataContext(); - const product = productDataContext.product || {}; - return ( <> - + ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/block.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/block.js new file mode 100644 index 00000000000..3b104af34cc --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/block.js @@ -0,0 +1,83 @@ +/** + * External dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { + useInnerBlockLayoutContext, + useProductDataContext, +} from '@woocommerce/shared-context'; + +/** + * Internal dependencies + */ +import './style.scss'; + +/** + * Product Stock Indicator Block Component. + * + * @param {Object} props Incoming props. + * @param {string} [props.className] CSS Class name for the component. + * @param {Object} [props.product] Optional product object. Product from context will be used if + * this is not provided. + * @return {*} The component. + */ +const Block = ( { className, ...props } ) => { + const { parentClassName } = useInnerBlockLayoutContext(); + const productDataContext = useProductDataContext(); + const product = props.product || productDataContext.product || null; + + if ( ! product ) { + return null; + } + + const inStock = !! product.is_in_stock; + const lowStock = product.low_stock_remaining; + const isBackordered = product.is_on_backorder; + + return ( +
+ { lowStock + ? lowStockText( lowStock ) + : stockText( inStock, isBackordered ) } +
+ ); +}; + +const lowStockText = ( lowStock ) => { + return sprintf( + /* translators: %d stock amount (number of items in stock for product) */ + __( '%d left in stock', 'woo-gutenberg-products-block' ), + lowStock + ); +}; + +const stockText = ( inStock, isBackordered ) => { + if ( isBackordered ) { + return __( 'Available on backorder', 'woo-gutenberg-products-block' ); + } + + return inStock + ? __( 'In Stock', 'woo-gutenberg-products-block' ) + : __( 'Out of Stock', 'woo-gutenberg-products-block' ); +}; + +Block.propTypes = { + className: PropTypes.string, + product: PropTypes.object, +}; + +export default Block; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/edit.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/edit.js new file mode 100644 index 00000000000..65ac513bb71 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/edit.js @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import EditProductLink from '@woocommerce/block-components/edit-product-link'; + +/** + * Internal dependencies + */ +import Block from './block'; + +export default ( { attributes } ) => { + return ( + <> + + + + ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/index.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/index.js new file mode 100644 index 00000000000..c3934632e0e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/index.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { registerExperimentalBlockType } from '@woocommerce/block-settings'; +import { Icon, box } from '@woocommerce/icons'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared-config'; +import edit from './edit'; + +const blockConfig = { + title: __( 'Product Stock Indicator', 'woo-gutenberg-products-block' ), + description: __( + 'Display product stock status.', + 'woo-gutenberg-products-block' + ), + icon: { + src: , + foreground: '#96588a', + }, + edit, +}; + +registerExperimentalBlockType( 'woocommerce/product-stock-indicator', { + ...sharedConfig, + ...blockConfig, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/style.scss b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/style.scss new file mode 100644 index 00000000000..b45ac6e7b9b --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/stock-indicator/style.scss @@ -0,0 +1,19 @@ +.wc-block-layout { + .wc-block-components-product-stock-indicator { + margin-top: 0; + margin-bottom: em($gap-small); + display: block; + @include font-size(small); + + &--in-stock { + color: $valid-green; + } + &--out-of-stock { + color: $error-red; + } + &--low-stock, + &--available-on-backorder { + color: $notice-yellow; + } + } +} diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/tag-list/edit.js b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/tag-list/edit.js index 02c19b8b30b..91b94049427 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/tag-list/edit.js +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product/tag-list/edit.js @@ -3,7 +3,6 @@ */ import { Disabled } from '@wordpress/components'; import EditProductLink from '@woocommerce/block-components/edit-product-link'; -import { useProductDataContext } from '@woocommerce/shared-context'; /** * Internal dependencies @@ -11,12 +10,9 @@ import { useProductDataContext } from '@woocommerce/shared-context'; import Block from './block'; export default ( { attributes } ) => { - const productDataContext = useProductDataContext(); - const product = productDataContext.product || {}; - return ( <> - + diff --git a/plugins/woocommerce-blocks/assets/js/atomic/utils/get-block-map.js b/plugins/woocommerce-blocks/assets/js/atomic/utils/get-block-map.js index 82e9c1d81a7..b1a001078f2 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/utils/get-block-map.js +++ b/plugins/woocommerce-blocks/assets/js/atomic/utils/get-block-map.js @@ -16,6 +16,7 @@ import ProductTitle from '../blocks/product/title/frontend'; import ProductSku from '../blocks/product/sku/block'; import ProductCategoryList from '../blocks/product/category-list/block'; import ProductTagList from '../blocks/product/tag-list/block'; +import ProductStockIndicator from '../blocks/product/stock-indicator/block'; /** * Map blocks to components suitable for use on the frontend. @@ -33,5 +34,6 @@ export const getBlockMap = ( blockName ) => ( { 'woocommerce/product-sku': ProductSku, 'woocommerce/product-category-list': ProductCategoryList, 'woocommerce/product-tag-list': ProductTagList, + 'woocommerce/product-stock-indicator': ProductStockIndicator, ...getRegisteredInnerBlocks( blockName ), } ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/single-product/constants.js b/plugins/woocommerce-blocks/assets/js/blocks/single-product/constants.js index 36671c40796..40918fbf721 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/single-product/constants.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/single-product/constants.js @@ -35,6 +35,7 @@ export const DEFAULT_INNER_BLOCKS = [ [ 'woocommerce/product-rating' ], [ 'woocommerce/product-price' ], [ 'woocommerce/product-summary' ], + [ 'woocommerce/product-stock-indicator' ], [ 'woocommerce/product-button' ], [ 'woocommerce/product-sku' ], [ 'woocommerce/product-category-list' ], diff --git a/plugins/woocommerce-blocks/assets/js/components/edit-product-link/index.js b/plugins/woocommerce-blocks/assets/js/components/edit-product-link/index.js index f64c4342e6c..c6c598c4981 100644 --- a/plugins/woocommerce-blocks/assets/js/components/edit-product-link/index.js +++ b/plugins/woocommerce-blocks/assets/js/components/edit-product-link/index.js @@ -5,11 +5,22 @@ import { __ } from '@wordpress/i18n'; import { Icon, external } from '@woocommerce/icons'; import { ADMIN_URL } from '@woocommerce/settings'; import { InspectorControls } from '@wordpress/block-editor'; +import { useProductDataContext } from '@woocommerce/shared-context'; /** * Component to render an edit product link in the sidebar. + * + * @param {Object} props Component props. */ -const EditProductLink = ( { productId } ) => { +const EditProductLink = ( props ) => { + const productDataContext = useProductDataContext(); + const product = productDataContext.product || {}; + const productId = product.id || props.productId || 0; + + if ( ! productId ) { + return null; + } + return (
diff --git a/plugins/woocommerce-blocks/assets/js/icons/index.js b/plugins/woocommerce-blocks/assets/js/icons/index.js index 81cd3c1a791..c33c93e0169 100644 --- a/plugins/woocommerce-blocks/assets/js/icons/index.js +++ b/plugins/woocommerce-blocks/assets/js/icons/index.js @@ -5,6 +5,7 @@ export { default as arrowDownAlt2 } from './library/arrow-down-alt2'; export { default as bank } from './library/bank'; export { default as barcode } from './library/barcode'; export { default as bill } from './library/bill'; +export { default as box } from './library/box'; export { default as card } from './library/card'; export { default as cart } from './library/cart'; export { default as checkPayment } from './library/check-payment'; diff --git a/plugins/woocommerce-blocks/assets/js/icons/library/barcode.js b/plugins/woocommerce-blocks/assets/js/icons/library/barcode.js index 50f7d2b1c24..3775bb6e168 100644 --- a/plugins/woocommerce-blocks/assets/js/icons/library/barcode.js +++ b/plugins/woocommerce-blocks/assets/js/icons/library/barcode.js @@ -5,10 +5,7 @@ import { SVG } from 'wordpress-components'; const barcode = ( - + ); diff --git a/plugins/woocommerce-blocks/assets/js/icons/library/box.js b/plugins/woocommerce-blocks/assets/js/icons/library/box.js new file mode 100644 index 00000000000..334fd786264 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/icons/library/box.js @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import { SVG } from 'wordpress-components'; + +const box = ( + + + + + + +); + +export default box; diff --git a/plugins/woocommerce-blocks/src/Library.php b/plugins/woocommerce-blocks/src/Library.php index a9896dbb32d..0bf5d31f285 100644 --- a/plugins/woocommerce-blocks/src/Library.php +++ b/plugins/woocommerce-blocks/src/Library.php @@ -106,6 +106,7 @@ class Library { 'product-sku', 'product-category-list', 'product-tag-list', + 'product-stock-indicator', ]; foreach ( $atomic_blocks as $atomic_block ) { $instance = new \Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock( $atomic_block ); diff --git a/plugins/woocommerce-blocks/src/StoreApi/Schemas/ProductSchema.php b/plugins/woocommerce-blocks/src/StoreApi/Schemas/ProductSchema.php index 1685dbe53cc..c85020bfe45 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Schemas/ProductSchema.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Schemas/ProductSchema.php @@ -263,6 +263,12 @@ class ProductSchema extends AbstractSchema { 'context' => [ 'view', 'edit' ], 'readonly' => true, ], + 'is_on_backorder' => [ + 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woo-gutenberg-products-block' ), + 'type' => 'boolean', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woo-gutenberg-products-block' ), 'type' => [ 'integer', 'null' ], @@ -320,6 +326,7 @@ class ProductSchema extends AbstractSchema { 'has_options' => $product->has_options(), 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), + 'is_on_backorder' => 'onbackorder' === $product->get_stock_status(), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'add_to_cart' => (object) $this->prepare_html_response( [ @@ -365,7 +372,7 @@ class ProductSchema extends AbstractSchema { $remaining_stock = $this->get_remaining_stock( $product ); if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) { - return $remaining_stock; + return max( $remaining_stock, 0 ); } return null;