* Show backorder notice in the Cart block

* Don't render variation <div> if empty

* Create ProductBackorderNotification component

* Add product backorder notification to the checkout block

* Fix classname and comment

* Rename notification->badge and don't show low stock badge if backorder is shown

* Use ternary to dispaly backorder/low stock badge
This commit is contained in:
Albert Juhé Lluveras 2020-07-14 17:25:53 +02:00 committed by GitHub
parent be513c8875
commit d894fed1df
16 changed files with 134 additions and 52 deletions

View File

@ -4,6 +4,7 @@ export { default as FormStep } from './form-step';
export { default as OrderSummary } from './order-summary'; export { default as OrderSummary } from './order-summary';
export { default as PlaceOrderButton } from './place-order-button'; export { default as PlaceOrderButton } from './place-order-button';
export { default as Policies } from './policies'; export { default as Policies } from './policies';
export { default as ProductBackorderBadge } from './product-backorder-badge';
export { default as ProductImage } from './product-image'; export { default as ProductImage } from './product-image';
export { default as ProductLowStockBadge } from './product-low-stock-badge'; export { default as ProductLowStockBadge } from './product-low-stock-badge';
export { default as ProductSummary } from './product-summary'; export { default as ProductSummary } from './product-summary';

View File

@ -5,6 +5,7 @@ import { __, sprintf } from '@wordpress/i18n';
import { getCurrency } from '@woocommerce/base-utils'; import { getCurrency } from '@woocommerce/base-utils';
import Label from '@woocommerce/base-components/label'; import Label from '@woocommerce/base-components/label';
import { import {
ProductBackorderBadge,
ProductImage, ProductImage,
ProductLowStockBadge, ProductLowStockBadge,
ProductMetadata, ProductMetadata,
@ -18,6 +19,7 @@ const OrderSummaryItem = ( { cartItem } ) => {
const { const {
images, images,
low_stock_remaining: lowStockRemaining = null, low_stock_remaining: lowStockRemaining = null,
show_backorder_badge: showBackorderBadge = false,
name, name,
permalink, permalink,
prices, prices,
@ -60,7 +62,15 @@ const OrderSummaryItem = ( { cartItem } ) => {
value={ linePrice } value={ linePrice }
/> />
</div> </div>
<ProductLowStockBadge lowStockRemaining={ lowStockRemaining } /> { showBackorderBadge ? (
<ProductBackorderBadge />
) : (
lowStockRemaining && (
<ProductLowStockBadge
lowStockRemaining={ lowStockRemaining }
/>
)
) }
<ProductMetadata <ProductMetadata
shortDescription={ shortDescription } shortDescription={ shortDescription }
fullDescription={ fullDescription } fullDescription={ fullDescription }

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import ProductBadge from '../product-badge';
/**
* Returns a backorder badge.
*/
const ProductBackorderBadge = () => {
return (
<ProductBadge className="wc-block-components-product-backorder-badge">
{ __( 'Available on backorder', 'woo-gutenberg-products-block' ) }
</ProductBadge>
);
};
export default ProductBackorderBadge;

View File

@ -0,0 +1,29 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import './style.scss';
const ProductBadge = ( { children, className } ) => {
return (
<div
className={ classNames(
'wc-block-components-product-badge',
className
) }
>
{ children }
</div>
);
};
ProductBadge.propTypes = {
className: PropTypes.string,
};
export default ProductBadge;

View File

@ -1,4 +1,4 @@
.wc-block-components-sale-badge { .wc-block-components-product-badge {
@include font-size(smaller); @include font-size(smaller);
border-radius: 2px; border-radius: 2px;
border: 1px solid; border: 1px solid;

View File

@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import './style.scss'; import ProductBadge from '../product-badge';
/** /**
* Returns a low stock badge. * Returns a low stock badge.
@ -18,13 +18,13 @@ const ProductLowStockBadge = ( { lowStockRemaining } ) => {
} }
return ( return (
<div className="wc-block-components-product-low-stock-badge"> <ProductBadge className="wc-block-components-product-low-stock-badge">
{ sprintf( { sprintf(
/* translators: %d stock amount (number of items in stock for product) */ /* translators: %d stock amount (number of items in stock for product) */
__( '%d left in stock', 'woo-gutenberg-products-block' ), __( '%d left in stock', 'woo-gutenberg-products-block' ),
lowStockRemaining lowStockRemaining
) } ) }
</div> </ProductBadge>
); );
}; };

View File

@ -1,10 +0,0 @@
.wc-block-components-product-low-stock-badge {
@include font-size(smaller);
border-radius: 2px;
border: 1px solid;
display: inline-block;
font-weight: 600;
padding: 0 0.66em;
text-transform: uppercase;
white-space: nowrap;
}

View File

@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import './style.scss'; import ProductBadge from '../product-badge';
/** /**
* ProductSaleBadge * ProductSaleBadge
@ -25,7 +25,7 @@ const ProductSaleBadge = ( { currency, saleAmount } ) => {
return null; return null;
} }
return ( return (
<div className="wc-block-components-sale-badge"> <ProductBadge className="wc-block-components-sale-badge">
{ __experimentalCreateInterpolateElement( { __experimentalCreateInterpolateElement(
/* translators: <price/> will be replaced by the discount amount */ /* translators: <price/> will be replaced by the discount amount */
__( 'Save <price/>', 'woo-gutenberg-products-block' ), __( 'Save <price/>', 'woo-gutenberg-products-block' ),
@ -38,7 +38,7 @@ const ProductSaleBadge = ( { currency, saleAmount } ) => {
), ),
} }
) } ) }
</div> </ProductBadge>
); );
}; };

View File

@ -9,7 +9,7 @@ import classNames from 'classnames';
* Returns a formatted element containing variation details. * Returns a formatted element containing variation details.
*/ */
const ProductVariationData = ( { className, variation = [] } ) => { const ProductVariationData = ( { className, variation = [] } ) => {
if ( ! variation ) { if ( ! variation || variation.length === 0 ) {
return null; return null;
} }

View File

@ -9,6 +9,7 @@ import { getCurrency } from '@woocommerce/base-utils';
import { useStoreCartItemQuantity } from '@woocommerce/base-hooks'; import { useStoreCartItemQuantity } from '@woocommerce/base-hooks';
import { Icon, trash } from '@woocommerce/icons'; import { Icon, trash } from '@woocommerce/icons';
import { import {
ProductBackorderBadge,
ProductImage, ProductImage,
ProductLowStockBadge, ProductLowStockBadge,
ProductMetadata, ProductMetadata,
@ -45,6 +46,7 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
short_description: shortDescription = '', short_description: shortDescription = '',
description: fullDescription = '', description: fullDescription = '',
low_stock_remaining: lowStockRemaining = null, low_stock_remaining: lowStockRemaining = null,
show_backorder_badge: showBackorderBadge = false,
quantity_limit: quantityLimit = 99, quantity_limit: quantityLimit = 99,
permalink = '', permalink = '',
images = [], images = [],
@ -111,7 +113,16 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
name={ name } name={ name }
disabled={ isPendingDelete } disabled={ isPendingDelete }
/> />
<ProductLowStockBadge lowStockRemaining={ lowStockRemaining } /> { showBackorderBadge ? (
<ProductBackorderBadge />
) : (
lowStockRemaining && (
<ProductLowStockBadge
lowStockRemaining={ lowStockRemaining }
/>
)
) }
{ showBackorderBadge && <ProductBackorderBadge /> }
<ProductMetadata <ProductMetadata
shortDescription={ shortDescription } shortDescription={ shortDescription }
fullDescription={ fullDescription } fullDescription={ fullDescription }

View File

@ -35,6 +35,7 @@ export const previewCart = {
permalink: 'https://example.org', permalink: 'https://example.org',
low_stock_remaining: 2, low_stock_remaining: 2,
backorders_allowed: false, backorders_allowed: false,
show_backorder_badge: false,
sold_individually: false, sold_individually: false,
images: [ images: [
{ {
@ -103,6 +104,7 @@ export const previewCart = {
sku: 'woo-cap', sku: 'woo-cap',
permalink: 'https://example.org', permalink: 'https://example.org',
backorders_allowed: false, backorders_allowed: false,
show_backorder_badge: false,
sold_individually: false, sold_individually: false,
images: [ images: [
{ {

View File

@ -143,6 +143,10 @@
* @property {boolean} backorders_allowed True if backorders are * @property {boolean} backorders_allowed True if backorders are
* allowed past stock * allowed past stock
* availability. * availability.
* @property {boolean} show_backorder_badge Whether a notification
* should be shown about the
* product being available on
* backorder.
* @property {boolean} sold_individually If true, only one item of * @property {boolean} sold_individually If true, only one item of
* this product is allowed * this product is allowed
* for purchase in a single * for purchase in a single

View File

@ -45,80 +45,86 @@ class CartItemSchema extends ProductSchema {
*/ */
public function get_properties() { public function get_properties() {
return [ return [
'key' => [ 'key' => [
'description' => __( 'Unique identifier for the item within the cart.', 'woo-gutenberg-products-block' ), 'description' => __( 'Unique identifier for the item within the cart.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'id' => [ 'id' => [
'description' => __( 'The cart item product or variation ID.', 'woo-gutenberg-products-block' ), 'description' => __( 'The cart item product or variation ID.', 'woo-gutenberg-products-block' ),
'type' => 'integer', 'type' => 'integer',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'quantity' => [ 'quantity' => [
'description' => __( 'Quantity of this item in the cart.', 'woo-gutenberg-products-block' ), 'description' => __( 'Quantity of this item in the cart.', 'woo-gutenberg-products-block' ),
'type' => 'integer', 'type' => 'integer',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'quantity_limit' => [ 'quantity_limit' => [
'description' => __( 'The maximum quantity than can be added to the cart at once.', 'woo-gutenberg-products-block' ), 'description' => __( 'The maximum quantity than can be added to the cart at once.', 'woo-gutenberg-products-block' ),
'type' => 'integer', 'type' => 'integer',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'name' => [ 'name' => [
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ), 'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'short_description' => [ 'short_description' => [
'description' => __( 'Product short description in HTML format.', 'woo-gutenberg-products-block' ), 'description' => __( 'Product short description in HTML format.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'description' => [ 'description' => [
'description' => __( 'Product full description in HTML format.', 'woo-gutenberg-products-block' ), 'description' => __( 'Product full description in HTML format.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'sku' => [ 'sku' => [
'description' => __( 'Stock keeping unit, if applicable.', 'woo-gutenberg-products-block' ), 'description' => __( 'Stock keeping unit, if applicable.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'low_stock_remaining' => [ 'low_stock_remaining' => [
'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woo-gutenberg-products-block' ), 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woo-gutenberg-products-block' ),
'type' => [ 'integer', 'null' ], 'type' => [ 'integer', 'null' ],
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'backorders_allowed' => [ 'backorders_allowed' => [
'description' => __( 'True if backorders are allowed past stock availability.', 'woo-gutenberg-products-block' ), 'description' => __( 'True if backorders are allowed past stock availability.', 'woo-gutenberg-products-block' ),
'type' => [ 'boolean' ], 'type' => [ 'boolean' ],
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'sold_individually' => [ 'show_backorder_badge' => [
'description' => __( 'True if the product is on backorder.', 'woo-gutenberg-products-block' ),
'type' => [ 'boolean' ],
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'sold_individually' => [
'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woo-gutenberg-products-block' ), 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woo-gutenberg-products-block' ),
'type' => 'boolean', 'type' => 'boolean',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'permalink' => [ 'permalink' => [
'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ), 'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ),
'type' => 'string', 'type' => 'string',
'format' => 'uri', 'format' => 'uri',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
'readonly' => true, 'readonly' => true,
], ],
'images' => [ 'images' => [
'description' => __( 'List of images.', 'woo-gutenberg-products-block' ), 'description' => __( 'List of images.', 'woo-gutenberg-products-block' ),
'type' => 'array', 'type' => 'array',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -128,7 +134,7 @@ class CartItemSchema extends ProductSchema {
'properties' => $this->image_attachment_schema->get_properties(), 'properties' => $this->image_attachment_schema->get_properties(),
], ],
], ],
'variation' => [ 'variation' => [
'description' => __( 'Chosen attributes (for variations).', 'woo-gutenberg-products-block' ), 'description' => __( 'Chosen attributes (for variations).', 'woo-gutenberg-products-block' ),
'type' => 'array', 'type' => 'array',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -151,7 +157,7 @@ class CartItemSchema extends ProductSchema {
], ],
], ],
], ],
'prices' => [ 'prices' => [
'description' => __( 'Price data for the product in the current line item, including or excluding taxes based on the "display prices during cart and checkout" setting. Provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), 'description' => __( 'Price data for the product in the current line item, including or excluding taxes based on the "display prices during cart and checkout" setting. Provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ),
'type' => 'object', 'type' => 'object',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -232,7 +238,7 @@ class CartItemSchema extends ProductSchema {
] ]
), ),
], ],
'totals' => [ 'totals' => [
'description' => __( 'Item total amounts provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), 'description' => __( 'Item total amounts provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ),
'type' => 'object', 'type' => 'object',
'context' => [ 'view', 'edit' ], 'context' => [ 'view', 'edit' ],
@ -280,22 +286,23 @@ class CartItemSchema extends ProductSchema {
$product = $cart_item['data']; $product = $cart_item['data'];
return [ return [
'key' => $cart_item['key'], 'key' => $cart_item['key'],
'id' => $product->get_id(), 'id' => $product->get_id(),
'quantity' => wc_stock_amount( $cart_item['quantity'] ), 'quantity' => wc_stock_amount( $cart_item['quantity'] ),
'quantity_limit' => $this->get_product_quantity_limit( $product ), 'quantity_limit' => $this->get_product_quantity_limit( $product ),
'name' => $this->prepare_html_response( $product->get_title() ), 'name' => $this->prepare_html_response( $product->get_title() ),
'short_description' => $this->prepare_html_response( wc_format_content( $product->get_short_description() ) ), 'short_description' => $this->prepare_html_response( wc_format_content( $product->get_short_description() ) ),
'description' => $this->prepare_html_response( wc_format_content( $product->get_description() ) ), 'description' => $this->prepare_html_response( wc_format_content( $product->get_description() ) ),
'sku' => $this->prepare_html_response( $product->get_sku() ), 'sku' => $this->prepare_html_response( $product->get_sku() ),
'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ),
'backorders_allowed' => (bool) $product->backorders_allowed(), 'backorders_allowed' => (bool) $product->backorders_allowed(),
'sold_individually' => $product->is_sold_individually(), 'show_backorder_badge' => (bool) $product->backorders_require_notification() && $product->is_on_backorder( $cart_item['quantity'] ),
'permalink' => $product->get_permalink(), 'sold_individually' => $product->is_sold_individually(),
'images' => $this->get_images( $product ), 'permalink' => $product->get_permalink(),
'variation' => $this->format_variation_data( $cart_item['variation'], $product ), 'images' => $this->get_images( $product ),
'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ), 'variation' => $this->format_variation_data( $cart_item['variation'], $product ),
'totals' => (object) array_merge( 'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ),
'totals' => (object) array_merge(
$this->get_store_currency_response(), $this->get_store_currency_response(),
[ [
'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ), 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ),

View File

@ -35,6 +35,7 @@ curl "https://example-store.com/wp-json/wc/store/cart/items"
"sku": "woo-beanie", "sku": "woo-beanie",
"low_stock_remaining": null, "low_stock_remaining": null,
"backorders_allowed": false, "backorders_allowed": false,
"show_backorder_badge": false,
"sold_individually": false, "sold_individually": false,
"permalink": "https:\/\/local.wordpress.test\/product\/beanie\/", "permalink": "https:\/\/local.wordpress.test\/product\/beanie\/",
"images": [ "images": [
@ -106,6 +107,7 @@ curl "https://example-store.com/wp-json/wc/store/cart/items"
"sku": "wp-pennant", "sku": "wp-pennant",
"low_stock_remaining": null, "low_stock_remaining": null,
"backorders_allowed": false, "backorders_allowed": false,
"show_backorder_badge": false,
"sold_individually": false, "sold_individually": false,
"permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/", "permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/",
"images": [ "images": [
@ -199,6 +201,7 @@ curl "https://example-store.com/wp-json/wc/store/cart/items/e369853df766fa44e1ed
"sku": "wp-pennant", "sku": "wp-pennant",
"low_stock_remaining": null, "low_stock_remaining": null,
"backorders_allowed": false, "backorders_allowed": false,
"show_backorder_badge": false,
"sold_individually": false, "sold_individually": false,
"permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/", "permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/",
"images": [ "images": [

View File

@ -115,6 +115,7 @@ All endpoints under `/cart` (listed in this doc) return responses in the same fo
"sku": "woo-beanie", "sku": "woo-beanie",
"low_stock_remaining": null, "low_stock_remaining": null,
"backorders_allowed": false, "backorders_allowed": false,
"show_backorder_badge": false,
"sold_individually": false, "sold_individually": false,
"permalink": "https:\/\/local.wordpress.test\/product\/beanie\/", "permalink": "https:\/\/local.wordpress.test\/product\/beanie\/",
"images": [ "images": [
@ -174,6 +175,7 @@ All endpoints under `/cart` (listed in this doc) return responses in the same fo
"sku": "wp-pennant", "sku": "wp-pennant",
"low_stock_remaining": null, "low_stock_remaining": null,
"backorders_allowed": false, "backorders_allowed": false,
"show_backorder_badge": false,
"sold_individually": false, "sold_individually": false,
"permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/", "permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/",
"images": [ "images": [

View File

@ -229,6 +229,7 @@ class CartItems extends TestCase {
$this->assertArrayHasKey( 'variation', $data ); $this->assertArrayHasKey( 'variation', $data );
$this->assertArrayHasKey( 'low_stock_remaining', $data ); $this->assertArrayHasKey( 'low_stock_remaining', $data );
$this->assertArrayHasKey( 'backorders_allowed', $data ); $this->assertArrayHasKey( 'backorders_allowed', $data );
$this->assertArrayHasKey( 'show_backorder_badge', $data );
$this->assertArrayHasKey( 'short_description', $data ); $this->assertArrayHasKey( 'short_description', $data );
} }