From 128ac6d63d839640824026bc7735714c9ecae67a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Mar 2020 13:41:59 +0000 Subject: [PATCH] Refactor cart shipping settings and shipping calculator (https://github.com/woocommerce/woocommerce-blocks/pull/1943) * Move cart attributes to attributes file * Stop feedback prompt jumping around; consolodate strings * Update option labels and descriptions * Match checkout save function * hasShippingRate helper * Refactor full cart/frontend views for shipping calc * Add hasShippingAddress to useShippingRates hook * Initial shipping calculator in totals row implementation * Create cart context * Update preview data to match API response * Use context provider for cart * Provide default cart item for placeholder with correct shape * Remove outdated shape validation from cartlineitemrow * Use preview data in editor context * Tidy up components * Tests/lint * Update assets/js/base/components/totals/totals-shipping-item/has-shipping-rate.js Co-Authored-By: Seghir Nadir * No need to camel case previewdata * Use isValidElement * Implement EditorContext * Use select if no post is given Co-authored-by: Seghir Nadir --- .../components/shipping-calculator/address.js | 16 +- .../components/shipping-calculator/index.js | 46 ++--- .../shipping-rates-control/style.scss | 6 + .../components/totals/totals-item/index.js | 27 +-- .../totals-shipping-item/has-shipping-rate.js | 12 ++ .../totals/totals-shipping-item/index.js | 164 ++++++++++++++---- .../shipping-rate-selector.js | 51 ++++++ .../cart-checkout/payment-methods/actions.js | 2 +- .../assets/js/base/context/editor/index.js | 55 ++++++ .../assets/js/base/context/index.js | 1 + .../js/base/hooks/cart/use-store-cart.js | 21 ++- .../base/hooks/shipping/use-shipping-rates.js | 3 + .../blocks/cart-checkout/cart/attributes.js | 20 +++ .../js/blocks/cart-checkout/cart/edit.js | 71 ++++---- .../js/blocks/cart-checkout/cart/frontend.js | 72 +++++--- .../cart/full-cart/cart-line-item-row.js | 30 +--- .../cart/full-cart/cart-line-items-table.js | 46 ++++- .../cart-checkout/cart/full-cart/index.js | 129 ++------------ .../cart-checkout/cart/full-cart/style.scss | 2 +- .../js/blocks/cart-checkout/cart/index.js | 42 ++--- .../js/blocks/cart-checkout/checkout/edit.js | 2 +- .../js/hocs/with-feedback-prompt/index.js | 2 +- .../assets/js/previews/cart.js | 14 +- .../assets/js/type-defs/contexts.js | 9 + .../tests/js/jest.config.json | 3 +- 25 files changed, 513 insertions(+), 333 deletions(-) create mode 100644 plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/has-shipping-rate.js create mode 100644 plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/shipping-rate-selector.js create mode 100644 plugins/woocommerce-blocks/assets/js/base/context/editor/index.js create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/attributes.js diff --git a/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/address.js b/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/address.js index 1a4f4b946b5..2120036f7f9 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/address.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/address.js @@ -13,13 +13,17 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; import './style.scss'; import AddressForm from '../address-form'; -const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => { +const ShippingCalculatorAddress = ( { + address: initialAddress, + onUpdate, + addressFields, +} ) => { const [ address, setAddress ] = useState( initialAddress ); return (
@@ -39,13 +43,9 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => { }; ShippingCalculatorAddress.propTypes = { - address: PropTypes.shape( { - city: PropTypes.string, - state: PropTypes.string, - postcode: PropTypes.string, - country: PropTypes.string, - } ), + address: PropTypes.object.isRequired, onUpdate: PropTypes.func.isRequired, + addressFields: PropTypes.array.isRequired, }; export default ShippingCalculatorAddress; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/index.js b/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/index.js index fa111c9da7d..9575392a25d 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/index.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/shipping-calculator/index.js @@ -2,8 +2,6 @@ * External dependencies */ import PropTypes from 'prop-types'; -import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -11,44 +9,24 @@ import { useState } from '@wordpress/element'; import ShippingCalculatorAddress from './address'; import './style.scss'; -const ShippingCalculator = ( { address, setAddress } ) => { - const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState( - false - ); - +const ShippingCalculator = ( { onUpdate, address, addressFields } ) => { return ( - - ( - - ) - { isShippingCalculatorOpen && ( - { - setAddress( newAddress ); - setIsShippingCalculatorOpen( false ); - } } - /> - ) } - + /> + ); }; ShippingCalculator.propTypes = { - address: PropTypes.shape( { - city: PropTypes.string, - state: PropTypes.string, - postcode: PropTypes.string, - country: PropTypes.string, - } ), - setAddress: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, + address: PropTypes.object.isRequired, + addressFields: PropTypes.array.isRequired, }; export default ShippingCalculator; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/shipping-rates-control/style.scss b/plugins/woocommerce-blocks/assets/js/base/components/shipping-rates-control/style.scss index 04a47f7fc4f..9624ee840c0 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/shipping-rates-control/style.scss +++ b/plugins/woocommerce-blocks/assets/js/base/components/shipping-rates-control/style.scss @@ -26,8 +26,14 @@ white-space: pre; } +.wc-block-cart__shipping-address, +.wc-block-cart__shipping-address button { + color: $core-grey-dark-400; +} + .wc-block-shipping-rates-control__no-results { margin-bottom: 0; + font-style: italic; } // Resets when it's inside a panel. diff --git a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-item/index.js b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-item/index.js index 1f5bab2f2b6..56057a48883 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-item/index.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-item/index.js @@ -3,6 +3,7 @@ */ import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { isValidElement } from '@wordpress/element'; import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount'; /** @@ -16,23 +17,29 @@ const TotalsItem = ( { className, currency, label, value, description } ) => { className={ classnames( 'wc-block-totals-table-item', className ) } > { label } - - + { isValidElement( value ) ? ( +
+ { value } +
+ ) : ( + + ) } +
{ description } - +
); }; TotalsItem.propTypes = { - currency: PropTypes.object.isRequired, + currency: PropTypes.object, label: PropTypes.string.isRequired, - value: PropTypes.number.isRequired, + value: PropTypes.oneOfType( [ PropTypes.number, PropTypes.node ] ), className: PropTypes.string, description: PropTypes.node, }; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/has-shipping-rate.js b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/has-shipping-rate.js new file mode 100644 index 00000000000..f72b99897de --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/has-shipping-rate.js @@ -0,0 +1,12 @@ +/** + * Searches an array of packages/rates to see if there are actually any rates + * available. + * + * @param {Array} shippingRatePackages An array of packages and rates. + * @return {boolean} True if a rate exists. + */ +const hasShippingRate = ( shippingRatePackages ) => { + return shippingRatePackages.some( shippingRatePackage => shippingRatePackage.shipping_rates.length ); +}; + +export default hasShippingRate; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/index.js b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/index.js index eefa82bb7fb..348f86c8664 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/index.js +++ b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/index.js @@ -2,69 +2,157 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { - DISPLAY_CART_PRICES_INCLUDING_TAX, - SHIPPING_ENABLED, -} from '@woocommerce/block-settings'; +import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings'; import ShippingCalculator from '@woocommerce/base-components/shipping-calculator'; import ShippingLocation from '@woocommerce/base-components/shipping-location'; import PropTypes from 'prop-types'; +import { useState } from '@wordpress/element'; +import { useShippingRates } from '@woocommerce/base-hooks'; /** * Internal dependencies */ import TotalsItem from '../totals-item'; +import ShippingRateSelector from './shipping-rate-selector'; +import hasShippingRate from './has-shipping-rate'; +/** + * Renders the shipping totals row, rates, and calculator if enabled. + */ const TotalsShippingItem = ( { currency, - shippingAddress, - updateShippingAddress, values, + showCalculator = true, + showRatesWithoutAddress = false, } ) => { - if ( ! SHIPPING_ENABLED ) { - return null; - } + const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState( + false + ); + const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ]; const { - total_shipping: totalShipping, - total_shipping_tax: totalShippingTax, - } = values; - const shippingValue = parseInt( totalShipping, 10 ); - const shippingTaxValue = parseInt( totalShippingTax, 10 ); + shippingRates, + shippingAddress, + shippingRatesLoading, + hasShippingAddress, + setShippingAddress, + } = useShippingRates( defaultAddressFields ); + const totalShippingValue = DISPLAY_CART_PRICES_INCLUDING_TAX + ? parseInt( values.total_shipping, 10 ) + + parseInt( values.total_shipping_tax, 10 ) + : parseInt( values.total_shipping, 10 ); + const hasRates = hasShippingRate( shippingRates ) || totalShippingValue; + const showingRates = showRatesWithoutAddress || hasShippingAddress; + + // If we have no rates, and an address is needed. + if ( ! hasRates && ! hasShippingAddress ) { + return ( + { + setIsShippingCalculatorOpen( + ! isShippingCalculatorOpen + ); + } } + > + { __( + 'Calculate', + 'woo-gutenberg-products-block' + ) } + + ) : ( + + { __( + 'Calculated during checkout', + 'woo-gutenberg-products-block' + ) } + + ) + } + description={ + <> + { showCalculator && isShippingCalculatorOpen && ( + { + setShippingAddress( newAddress ); + setIsShippingCalculatorOpen( false ); + } } + address={ shippingAddress } + addressFields={ defaultAddressFields } + /> + ) } + + } + /> + ); + } return ( - - { shippingAddress && ( - - ) } - { updateShippingAddress && shippingAddress && ( - - ) } - - } - label={ __( 'Shipping', 'woo-gutenberg-products-block' ) } - value={ - DISPLAY_CART_PRICES_INCLUDING_TAX - ? shippingValue + shippingTaxValue - : shippingValue - } - /> + <> + + { ' ' } + { showCalculator && ( + + ) } + { showCalculator && isShippingCalculatorOpen && ( + { + setShippingAddress( newAddress ); + setIsShippingCalculatorOpen( false ); + } } + address={ shippingAddress } + addressFields={ defaultAddressFields } + /> + ) } + + } + currency={ currency } + /> + { showingRates && ( +
+ + { __( + 'Choose a shipping method', + 'woo-gutenberg-products-block' + ) } + + +
+ ) } + ); }; TotalsShippingItem.propTypes = { currency: PropTypes.object.isRequired, - shippingAddress: PropTypes.object, - updateShippingAddress: PropTypes.func, values: PropTypes.shape( { total_shipping: PropTypes.string, total_shipping_tax: PropTypes.string, } ).isRequired, + showCalculator: PropTypes.bool, + showRatesWithoutAddress: PropTypes.bool, }; export default TotalsShippingItem; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/shipping-rate-selector.js b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/shipping-rate-selector.js new file mode 100644 index 00000000000..5016a1159d1 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/components/totals/totals-shipping-item/shipping-rate-selector.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount'; +import { decodeEntities } from '@wordpress/html-entities'; +import { getCurrencyFromPriceResponse } from '@woocommerce/base-utils'; +import ShippingRatesControl from '@woocommerce/base-components/shipping-rates-control'; + +const renderShippingRatesControlOption = ( option ) => ( { + label: decodeEntities( option.name ), + value: option.rate_id, + description: ( + <> + { option.price && ( + + ) } + { option.price && option.delivery_time ? ' — ' : null } + { decodeEntities( option.delivery_time ) } + + ), +} ); + +const ShippingRateSelector = ( { shippingRates, shippingRatesLoading } ) => { + return ( +
+ + { __( + 'Choose the shipping method.', + 'woo-gutenberg-products-block' + ) } + + +
+ ); +}; + +export default ShippingRateSelector; diff --git a/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/actions.js b/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/actions.js index 4c59f40e140..247b040b5b3 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/actions.js +++ b/plugins/woocommerce-blocks/assets/js/base/context/cart-checkout/payment-methods/actions.js @@ -14,7 +14,7 @@ const SET_BILLING_DATA = 'set_billing_data'; /** * Used to dispatch a status update only for the given type. * - * @param type + * @param {string} type * * @return {Object} The action object. */ diff --git a/plugins/woocommerce-blocks/assets/js/base/context/editor/index.js b/plugins/woocommerce-blocks/assets/js/base/context/editor/index.js new file mode 100644 index 00000000000..13cc8f9066e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/context/editor/index.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { createContext, useContext } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; + +/** + * @typedef {import('@woocommerce/type-defs/contexts').EditorDataContext} EditorDataContext + */ + +const EditorContext = createContext( { + isEditor: false, + currentPostId: 0, +} ); + +/** + * @return {EditorDataContext} Returns the editor data context value + */ +export const useEditorContext = () => { + return useContext( EditorContext ); +}; + +/** + * Editor provider + * + * @param {Object} props Incoming props for the provider. + * @param {*} props.children The children being wrapped. + * @param {number} props.currentPostId The post being edited. + */ +export const EditorProvider = ( { children, currentPostId = 0 } ) => { + const editingPostId = useSelect( + ( select ) => { + if ( ! currentPostId ) { + const store = select( 'core/editor' ); + return store.getCurrentPostId(); + } + return currentPostId; + }, + [ currentPostId ] + ); + + /** + * @type {EditorDataContext} + */ + const editorData = { + isEditor: true, + currentPostId: editingPostId, + }; + + return ( + + { children } + + ); +}; diff --git a/plugins/woocommerce-blocks/assets/js/base/context/index.js b/plugins/woocommerce-blocks/assets/js/base/context/index.js index a4788beffc9..45f2fe580a6 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/index.js +++ b/plugins/woocommerce-blocks/assets/js/base/context/index.js @@ -3,3 +3,4 @@ export * from './inner-block-configuration-context'; export * from './product-layout-context'; export * from './query-state-context'; export * from './store-notices-context'; +export * from './editor'; diff --git a/plugins/woocommerce-blocks/assets/js/base/hooks/cart/use-store-cart.js b/plugins/woocommerce-blocks/assets/js/base/hooks/cart/use-store-cart.js index 1c59671c576..c27286fe9a5 100644 --- a/plugins/woocommerce-blocks/assets/js/base/hooks/cart/use-store-cart.js +++ b/plugins/woocommerce-blocks/assets/js/base/hooks/cart/use-store-cart.js @@ -5,6 +5,8 @@ */ import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data'; import { useSelect } from '@wordpress/data'; +import { useEditorContext } from '@woocommerce/base-context'; +import { previewCart } from '@woocommerce/resource-previews'; /** * @constant @@ -12,6 +14,7 @@ import { useSelect } from '@wordpress/data'; */ const defaultCartData = { cartCoupons: [], + shippingRates: [], cartItems: [], cartItemsCount: 0, cartItemsWeight: 0, @@ -19,7 +22,6 @@ const defaultCartData = { cartTotals: {}, cartIsLoading: true, cartErrors: [], - shippingRates: [], }; /** @@ -35,6 +37,7 @@ const defaultCartData = { * @return {StoreCart} Object containing cart data. */ export const useStoreCart = ( options = { shouldSelect: true } ) => { + const { isEditor } = useEditorContext(); const { shouldSelect } = options; const results = useSelect( @@ -42,6 +45,21 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => { if ( ! shouldSelect ) { return null; } + + if ( isEditor ) { + return { + cartCoupons: previewCart.coupons, + shippingRates: previewCart.shipping_rates, + cartItems: previewCart.items, + cartItemsCount: previewCart.items_count, + cartItemsWeight: previewCart.items_weight, + cartNeedsShipping: previewCart.needs_shipping, + cartTotals: previewCart.totals, + cartIsLoading: false, + cartErrors: [], + }; + } + const store = select( storeKey ); const cartData = store.getCartData(); const cartErrors = store.getCartErrors(); @@ -49,7 +67,6 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => { const cartIsLoading = ! store.hasFinishedResolution( 'getCartData' ); - return { cartCoupons: cartData.coupons, shippingRates: cartData.shippingRates, diff --git a/plugins/woocommerce-blocks/assets/js/base/hooks/shipping/use-shipping-rates.js b/plugins/woocommerce-blocks/assets/js/base/hooks/shipping/use-shipping-rates.js index 9ce21cd7a3e..5b951936351 100644 --- a/plugins/woocommerce-blocks/assets/js/base/hooks/shipping/use-shipping-rates.js +++ b/plugins/woocommerce-blocks/assets/js/base/hooks/shipping/use-shipping-rates.js @@ -25,6 +25,7 @@ import { pluckAddress } from '../../utils'; * - {Function} setShippingAddress An function that optimistically * update shipping address and dispatches async rate fetching. * - {Object} shippingAddress An object containing shipping address. + * - {Object} shippingAddress True when address data exists. */ export const useShippingRates = ( addressFieldsKeys ) => { const { cartErrors, shippingRates } = useStoreCart(); @@ -59,11 +60,13 @@ export const useShippingRates = ( addressFieldsKeys ) => { updateShippingAddress( debouncedShippingAddress ); } }, [ debouncedShippingAddress ] ); + return { shippingRates, shippingAddress, setShippingAddress, shippingRatesLoading, shippingRatesErrors: cartErrors, + hasShippingAddress: !! shippingAddress.country, }; }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/attributes.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/attributes.js new file mode 100644 index 00000000000..074c5e01b5c --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/attributes.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { + IS_SHIPPING_CALCULATOR_ENABLED, + IS_SHIPPING_COST_HIDDEN, +} from '@woocommerce/block-settings'; + +const blockAttributes = { + isShippingCalculatorEnabled: { + type: 'boolean', + default: IS_SHIPPING_CALCULATOR_ENABLED, + }, + isShippingCostHidden: { + type: 'boolean', + default: IS_SHIPPING_COST_HIDDEN, + }, +}; + +export default blockAttributes; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/edit.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/edit.js index 3af15175aa0..15e42d09847 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/edit.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/edit.js @@ -8,12 +8,9 @@ import { Disabled, PanelBody, ToggleControl } from '@wordpress/components'; import PropTypes from 'prop-types'; import { withFeedbackPrompt } from '@woocommerce/block-hocs'; import ViewSwitcher from '@woocommerce/block-components/view-switcher'; -import { - previewCart, - previewShippingRates, -} from '@woocommerce/resource-previews'; import { SHIPPING_ENABLED } from '@woocommerce/block-settings'; import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; +import { EditorProvider } from '@woocommerce/base-context'; /** * Internal dependencies @@ -30,10 +27,7 @@ const CartEditor = ( { className, attributes, setAttributes } ) => { const BlockSettings = () => ( { 'woo-gutenberg-products-block' ) } help={ __( - 'Allow customers to estimate shipping.', + 'Allow customers to estimate shipping by entering their address.', 'woo-gutenberg-products-block' ) } checked={ isShippingCalculatorEnabled } @@ -51,24 +45,22 @@ const CartEditor = ( { className, attributes, setAttributes } ) => { } ) } /> - { isShippingCalculatorEnabled && ( - - setAttributes( { - isShippingCostHidden: ! isShippingCostHidden, - } ) - } - /> - ) } + ); @@ -112,19 +104,16 @@ const CartEditor = ( { className, attributes, setAttributes } ) => { ) } > - + + + @@ -159,7 +148,7 @@ CartEditor.propTypes = { export default withFeedbackPrompt( __( - 'We are currently working on improving our cart and providing merchants with tools and options to customize their cart to their stores needs.', + 'We are currently working on improving our cart and checkout blocks, providing merchants with the tools and customization options they need.', 'woo-gutenberg-products-block' ) )( CartEditor ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/frontend.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/frontend.js index adc28a72d56..16da27c9f55 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/frontend.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/frontend.js @@ -17,53 +17,69 @@ import { __experimentalCreateInterpolateElement } from 'wordpress-element'; * Internal dependencies */ import FullCart from './full-cart'; +import blockAttributes from './attributes'; import renderFrontend from '../../../utils/render-frontend.js'; /** - * Wrapper component to supply API data and show empty cart view as needed. + * Renders the frontend block within the cart provider. */ -const CartFrontend = ( { - emptyCart, - isShippingCalculatorEnabled, - isShippingCostHidden, -} ) => { - const { - cartItems, - cartTotals, - cartIsLoading, - cartCoupons, - shippingRates, - } = useStoreCart(); +const Block = ( { emptyCart, attributes } ) => { + const { cartItems, cartIsLoading } = useStoreCart(); return ( - - { ! cartIsLoading && ! cartItems.length ? ( + <> + { ! cartIsLoading && cartItems.length === 0 ? ( { emptyCart } ) : ( ) } + + ); +}; + +/** + * Wrapper component to supply API data and show empty cart view as needed. + * + * @param {*} props + */ +const CartFrontend = ( props ) => { + return ( + + ); }; -const getProps = ( el ) => ( { - emptyCart: el.innerHTML, - isShippingCalculatorEnabled: - el.dataset.isShippingCalculatorEnabled === 'true', - isShippingCostHidden: el.dataset.isShippingCostHidden === 'true', -} ); +const getProps = ( el ) => { + const attributes = {}; + + Object.keys( blockAttributes ).forEach( ( key ) => { + if ( typeof el.dataset[ key ] !== 'undefined' ) { + if ( + el.dataset[ key ] === 'true' || + el.dataset[ key ] === 'false' + ) { + attributes[ key ] = el.dataset[ key ] !== 'false'; + } else { + attributes[ key ] = el.dataset[ key ]; + } + } else { + attributes[ key ] = blockAttributes[ key ].default; + } + } ); + + return { + emptyCart: el.innerHTML, + attributes, + }; +}; const getErrorBoundaryProps = () => { return { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js index 12237a4786f..87d2c6d976a 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js @@ -42,9 +42,6 @@ const getMaximumQuantity = ( backOrdersAllowed, lowStockAmount ) => { * Cart line item table row component. */ const CartLineItemRow = ( { lineItem } ) => { - /** - * @type {CartItem} - */ const { name, summary, @@ -142,32 +139,7 @@ const CartLineItemRow = ( { lineItem } ) => { }; CartLineItemRow.propTypes = { - lineItem: PropTypes.shape( { - key: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - summary: PropTypes.string.isRequired, - images: PropTypes.array.isRequired, - low_stock_remaining: PropTypes.oneOfType( [ - PropTypes.number, - PropTypes.oneOf( [ null ] ), - ] ), - backorders_allowed: PropTypes.bool.isRequired, - sold_individually: PropTypes.bool.isRequired, - variation: PropTypes.arrayOf( - PropTypes.shape( { - attribute: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - } ) - ).isRequired, - totals: PropTypes.shape( { - line_subtotal: PropTypes.string.isRequired, - line_total: PropTypes.string.isRequired, - } ).isRequired, - prices: PropTypes.shape( { - price: PropTypes.string.isRequired, - regular_price: PropTypes.string.isRequired, - } ).isRequired, - } ), + lineItem: PropTypes.object.isRequired, }; export default CartLineItemRow; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-items-table.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-items-table.js index eb02483f51a..d4dc3732c14 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-items-table.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-items-table.js @@ -10,7 +10,51 @@ import PropTypes from 'prop-types'; import CartLineItemRow from './cart-line-item-row'; const placeholderRows = [ ...Array( 3 ) ].map( ( _x, i ) => ( - + ) ); const CartLineItemsTable = ( { lineItems = [], isLoading = false } ) => { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/index.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/index.js index f38c88d949b..bca8fbfa0ec 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/index.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/index.js @@ -13,17 +13,14 @@ import { TotalsShippingItem, TotalsTaxesItem, } from '@woocommerce/base-components/totals'; -import ShippingRatesControl from '@woocommerce/base-components/shipping-rates-control'; import { COUPONS_ENABLED, - SHIPPING_ENABLED, DISPLAY_CART_PRICES_INCLUDING_TAX, + SHIPPING_ENABLED, } from '@woocommerce/block-settings'; import { getCurrencyFromPriceResponse } from '@woocommerce/base-utils'; import { Card, CardBody } from 'wordpress-components'; -import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount'; -import { decodeEntities } from '@wordpress/html-entities'; -import { useStoreCartCoupons, useShippingRates } from '@woocommerce/base-hooks'; +import { useStoreCartCoupons, useStoreCart } from '@woocommerce/base-hooks'; import classnames from 'classnames'; import { Sidebar, @@ -41,85 +38,30 @@ import CartLineItemsTable from './cart-line-items-table'; import './style.scss'; import './editor.scss'; -const renderShippingRatesControlOption = ( option ) => ( { - label: decodeEntities( option.name ), - value: option.rate_id, - description: ( - <> - { option.price && ( - - ) } - { option.price && option.delivery_time ? ' — ' : null } - { decodeEntities( option.delivery_time ) } - - ), -} ); - -const ShippingCalculatorOptions = ( { - shippingRates, - shippingRatesLoading, -} ) => { - return ( -
- - { __( - 'Choose the shipping method.', - 'woo-gutenberg-products-block' - ) } - - -
- ); -}; - /** * Component that renders the Cart block when user has something in cart aka "full". */ -const Cart = ( { - cartItems = [], - cartTotals = {}, - cartCoupons = [], - isShippingCalculatorEnabled, - isShippingCostHidden, - shippingRates, - isLoading = false, -} ) => { - const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ]; - const { - shippingAddress, - setShippingAddress, - shippingRatesLoading, - } = useShippingRates( defaultAddressFields ); +const Cart = ( { isShippingCalculatorEnabled, isShippingCostHidden } ) => { + const { cartItems, cartTotals, cartIsLoading, cartErrors } = useStoreCart(); + const { applyCoupon, removeCoupon, isApplyingCoupon, isRemovingCoupon, + cartCoupons, + cartCouponsErrors, } = useStoreCartCoupons(); - const showShippingCosts = Boolean( - SHIPPING_ENABLED && - isShippingCalculatorEnabled && - ( ! isShippingCostHidden || shippingAddress?.country ) - ); + const errors = [ ...cartErrors, ...cartCouponsErrors ]; + if ( errors.length > 0 ) { + throw new Error( errors[ 0 ].message ); + } const totalsCurrency = getCurrencyFromPriceResponse( cartTotals ); const cartClassName = classnames( 'wc-block-cart', { - 'wc-block-cart--is-loading': isLoading, + 'wc-block-cart--is-loading': cartIsLoading, } ); return ( @@ -128,7 +70,7 @@ const Cart = ( { @@ -155,30 +97,16 @@ const Cart = ( { removeCoupon={ removeCoupon } values={ cartTotals } /> - { isShippingCalculatorEnabled && ( + { SHIPPING_ENABLED && ( ) } - { showShippingCosts && ( -
- - { __( - 'Choose the shipping method.', - 'woo-gutenberg-products-block' - ) } - - -
- ) } { ! DISPLAY_CART_PRICES_INCLUDING_TAX && ( ) } @@ -205,26 +132,8 @@ const Cart = ( { }; Cart.propTypes = { - cartItems: PropTypes.array, - cartTotals: PropTypes.shape( { - total_items: PropTypes.string, - total_items_tax: PropTypes.string, - total_fees: PropTypes.string, - total_fees_tax: PropTypes.string, - total_discount: PropTypes.string, - total_discount_tax: PropTypes.string, - total_shipping: PropTypes.string, - total_shipping_tax: PropTypes.string, - total_tax: PropTypes.string, - total_price: PropTypes.string, - } ), isShippingCalculatorEnabled: PropTypes.bool, isShippingCostHidden: PropTypes.bool, - isLoading: PropTypes.bool, - /** - * List of shipping rates to display. If defined, shipping rates will not be fetched from the API (used for the block preview). - */ - shippingRates: PropTypes.array, }; export default Cart; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/style.scss index 26df354ab7e..769369939d6 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/style.scss +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/style.scss @@ -4,7 +4,7 @@ .wc-block-cart__item-count { float: right; } - .wc-block-cart__change-address { + .wc-block-cart__shipping-calculator { white-space: nowrap; } .wc-block-cart__change-address-button { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/index.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/index.js index 6bf5784b74d..a8456e7c07c 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/index.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/index.js @@ -2,14 +2,11 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import classNames from 'classnames'; import { InnerBlocks } from '@wordpress/block-editor'; import { registerBlockType } from '@wordpress/blocks'; import { Icon, cart } from '@woocommerce/icons'; -import { - IS_SHIPPING_CALCULATOR_ENABLED, - IS_SHIPPING_COST_HIDDEN, -} from '@woocommerce/block-settings'; +import { kebabCase } from 'lodash'; +import classnames from 'classnames'; /** * Internal dependencies @@ -17,6 +14,7 @@ import { import edit from './edit'; import { example } from './example'; import './style.scss'; +import blockAttributes from './attributes'; /** * Register and run the Cart block. @@ -36,35 +34,27 @@ const settings = { multiple: false, }, example, - attributes: { - isShippingCalculatorEnabled: { - type: 'boolean', - default: IS_SHIPPING_CALCULATOR_ENABLED, - }, - isShippingCostHidden: { - type: 'boolean', - default: IS_SHIPPING_COST_HIDDEN, - }, - }, - + attributes: blockAttributes, edit, /** * Save the props to post content. */ save( { attributes } ) { - const { - className, - isShippingCalculatorEnabled, - isShippingCostHidden, - } = attributes; - const data = { - 'data-is-shipping-calculator-enabled': isShippingCalculatorEnabled, - 'data-is-shipping-cost-hidden': isShippingCostHidden, - }; + const data = {}; + + Object.keys( blockAttributes ).forEach( ( key ) => { + if ( + blockAttributes[ key ].save !== false && + typeof attributes[ key ] !== 'undefined' + ) { + data[ 'data-' + kebabCase( key ) ] = attributes[ key ]; + } + } ); + return (
diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js index fab21f233ac..b5c4df41c50 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js +++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/checkout/edit.js @@ -232,7 +232,7 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => { export default withFeedbackPrompt( __( - 'We are currently working on improving our checkout and providing merchants with tools and options to customize their checkout to their stores needs.', + 'We are currently working on improving our cart and checkout blocks, providing merchants with the tools and customization options they need.', 'woo-gutenberg-products-block' ) )( CheckoutEditor ); diff --git a/plugins/woocommerce-blocks/assets/js/hocs/with-feedback-prompt/index.js b/plugins/woocommerce-blocks/assets/js/hocs/with-feedback-prompt/index.js index bf162c8928e..0cfe389b757 100644 --- a/plugins/woocommerce-blocks/assets/js/hocs/with-feedback-prompt/index.js +++ b/plugins/woocommerce-blocks/assets/js/hocs/with-feedback-prompt/index.js @@ -28,10 +28,10 @@ const withFeedbackPrompt = ( content ) => createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => ( - + ); }, 'withFeedbackPrompt' ); diff --git a/plugins/woocommerce-blocks/assets/js/previews/cart.js b/plugins/woocommerce-blocks/assets/js/previews/cart.js index 4bb2b36fd17..babf78fa5ce 100644 --- a/plugins/woocommerce-blocks/assets/js/previews/cart.js +++ b/plugins/woocommerce-blocks/assets/js/previews/cart.js @@ -7,11 +7,14 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import productPicture from './product-image'; +import { previewShippingRates } from './shipping-rates'; // Sample data for cart block. // This closely resembles the data returned from the Store API /cart endpoint. // https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/master/src/RestApi/StoreApi#cart-api export const previewCart = { + coupons: [], + shipping_rates: previewShippingRates, items: [ { key: '1', @@ -143,9 +146,17 @@ export const previewCart = { }, }, ], + items_count: 4, + items_weight: 0, + needs_shipping: true, totals: { - currency: 'USD', + currency_code: 'USD', + currency_symbol: '$', currency_minor_unit: 2, + currency_decimal_separator: '.', + currency_thousand_separator: ',', + currency_prefix: '$', + currency_suffix: '', total_items: '3000', total_items_tax: '0', total_fees: '0', @@ -156,5 +167,6 @@ export const previewCart = { total_shipping_tax: '0', total_tax: '0', total_price: '3000', + tax_lines: [], }, }; diff --git a/plugins/woocommerce-blocks/assets/js/type-defs/contexts.js b/plugins/woocommerce-blocks/assets/js/type-defs/contexts.js index a950776ffda..90c4d51e4f2 100644 --- a/plugins/woocommerce-blocks/assets/js/type-defs/contexts.js +++ b/plugins/woocommerce-blocks/assets/js/type-defs/contexts.js @@ -206,4 +206,13 @@ * (true) or not (false). */ +/** + * @typedef {Object} EditorDataContext + * + * @property {boolean} isEditor Indicates whether in + * the editor context + * (true) or not (false). + * @property {number} currentPostId The post ID being edited. + */ + export {}; diff --git a/plugins/woocommerce-blocks/tests/js/jest.config.json b/plugins/woocommerce-blocks/tests/js/jest.config.json index b1cb8520aaf..aaae2f0116c 100644 --- a/plugins/woocommerce-blocks/tests/js/jest.config.json +++ b/plugins/woocommerce-blocks/tests/js/jest.config.json @@ -18,7 +18,8 @@ "@woocommerce/base-hocs(.*)$": "assets/js/base/hocs/$1", "@woocommerce/base-hooks(.*)$": "assets/js/base/hooks/$1", "@woocommerce/base-utils(.*)$": "assets/js/base/utils", - "@woocommerce/block-data": "assets/js/data" + "@woocommerce/block-data": "assets/js/data", + "@woocommerce/resource-previews": "assets/js/previews" }, "setupFiles": [ "/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js",