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 <nadir.seghir@gmail.com> * No need to camel case previewdata * Use isValidElement * Implement EditorContext * Use select if no post is given Co-authored-by: Seghir Nadir <nadir.seghir@gmail.com>
This commit is contained in:
parent
c20f1bd7bf
commit
128ac6d63d
|
@ -13,13 +13,17 @@ import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
import AddressForm from '../address-form';
|
import AddressForm from '../address-form';
|
||||||
|
|
||||||
const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
const ShippingCalculatorAddress = ( {
|
||||||
|
address: initialAddress,
|
||||||
|
onUpdate,
|
||||||
|
addressFields,
|
||||||
|
} ) => {
|
||||||
const [ address, setAddress ] = useState( initialAddress );
|
const [ address, setAddress ] = useState( initialAddress );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="wc-block-shipping-calculator-address">
|
<form className="wc-block-shipping-calculator-address">
|
||||||
<AddressForm
|
<AddressForm
|
||||||
fields={ [ 'country', 'state', 'city', 'postcode' ] }
|
fields={ addressFields }
|
||||||
onChange={ setAddress }
|
onChange={ setAddress }
|
||||||
values={ address }
|
values={ address }
|
||||||
/>
|
/>
|
||||||
|
@ -39,13 +43,9 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
ShippingCalculatorAddress.propTypes = {
|
ShippingCalculatorAddress.propTypes = {
|
||||||
address: PropTypes.shape( {
|
address: PropTypes.object.isRequired,
|
||||||
city: PropTypes.string,
|
|
||||||
state: PropTypes.string,
|
|
||||||
postcode: PropTypes.string,
|
|
||||||
country: PropTypes.string,
|
|
||||||
} ),
|
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
|
addressFields: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ShippingCalculatorAddress;
|
export default ShippingCalculatorAddress;
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import { useState } from '@wordpress/element';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -11,44 +9,24 @@ import { useState } from '@wordpress/element';
|
||||||
import ShippingCalculatorAddress from './address';
|
import ShippingCalculatorAddress from './address';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
const ShippingCalculator = ( { address, setAddress } ) => {
|
const ShippingCalculator = ( { onUpdate, address, addressFields } ) => {
|
||||||
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState(
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="wc-block-cart__change-address">
|
<div className="wc-block-cart__shipping-calculator">
|
||||||
(
|
<ShippingCalculatorAddress
|
||||||
<button
|
address={ address }
|
||||||
className="wc-block-cart__change-address-button"
|
addressFields={ addressFields }
|
||||||
onClick={ () => {
|
onUpdate={ ( newAddress ) => {
|
||||||
setIsShippingCalculatorOpen( ! isShippingCalculatorOpen );
|
onUpdate( newAddress );
|
||||||
} }
|
} }
|
||||||
>
|
/>
|
||||||
{ __( 'change address', 'woo-gutenberg-products-block' ) }
|
</div>
|
||||||
</button>
|
|
||||||
)
|
|
||||||
{ isShippingCalculatorOpen && (
|
|
||||||
<ShippingCalculatorAddress
|
|
||||||
address={ address }
|
|
||||||
onUpdate={ ( newAddress ) => {
|
|
||||||
setAddress( newAddress );
|
|
||||||
setIsShippingCalculatorOpen( false );
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ShippingCalculator.propTypes = {
|
ShippingCalculator.propTypes = {
|
||||||
address: PropTypes.shape( {
|
onUpdate: PropTypes.func.isRequired,
|
||||||
city: PropTypes.string,
|
address: PropTypes.object.isRequired,
|
||||||
state: PropTypes.string,
|
addressFields: PropTypes.array.isRequired,
|
||||||
postcode: PropTypes.string,
|
|
||||||
country: PropTypes.string,
|
|
||||||
} ),
|
|
||||||
setAddress: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ShippingCalculator;
|
export default ShippingCalculator;
|
||||||
|
|
|
@ -26,8 +26,14 @@
|
||||||
white-space: pre;
|
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 {
|
.wc-block-shipping-rates-control__no-results {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resets when it's inside a panel.
|
// Resets when it's inside a panel.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { isValidElement } from '@wordpress/element';
|
||||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
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 ) }
|
className={ classnames( 'wc-block-totals-table-item', className ) }
|
||||||
>
|
>
|
||||||
<span className="wc-block-totals-table-item__label">{ label }</span>
|
<span className="wc-block-totals-table-item__label">{ label }</span>
|
||||||
<FormattedMonetaryAmount
|
{ isValidElement( value ) ? (
|
||||||
className="wc-block-totals-table-item__value"
|
<div className="wc-block-totals-table-item__value">
|
||||||
currency={ currency }
|
{ value }
|
||||||
displayType="text"
|
</div>
|
||||||
value={ value }
|
) : (
|
||||||
/>
|
<FormattedMonetaryAmount
|
||||||
<span className="wc-block-totals-table-item__description">
|
className="wc-block-totals-table-item__value"
|
||||||
|
currency={ currency }
|
||||||
|
displayType="text"
|
||||||
|
value={ value }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
<div className="wc-block-totals-table-item__description">
|
||||||
{ description }
|
{ description }
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
TotalsItem.propTypes = {
|
TotalsItem.propTypes = {
|
||||||
currency: PropTypes.object.isRequired,
|
currency: PropTypes.object,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
value: PropTypes.number.isRequired,
|
value: PropTypes.oneOfType( [ PropTypes.number, PropTypes.node ] ),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
description: PropTypes.node,
|
description: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
@ -2,69 +2,157 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import {
|
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
|
||||||
DISPLAY_CART_PRICES_INCLUDING_TAX,
|
|
||||||
SHIPPING_ENABLED,
|
|
||||||
} from '@woocommerce/block-settings';
|
|
||||||
import ShippingCalculator from '@woocommerce/base-components/shipping-calculator';
|
import ShippingCalculator from '@woocommerce/base-components/shipping-calculator';
|
||||||
import ShippingLocation from '@woocommerce/base-components/shipping-location';
|
import ShippingLocation from '@woocommerce/base-components/shipping-location';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState } from '@wordpress/element';
|
||||||
|
import { useShippingRates } from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import TotalsItem from '../totals-item';
|
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 = ( {
|
const TotalsShippingItem = ( {
|
||||||
currency,
|
currency,
|
||||||
shippingAddress,
|
|
||||||
updateShippingAddress,
|
|
||||||
values,
|
values,
|
||||||
|
showCalculator = true,
|
||||||
|
showRatesWithoutAddress = false,
|
||||||
} ) => {
|
} ) => {
|
||||||
if ( ! SHIPPING_ENABLED ) {
|
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState(
|
||||||
return null;
|
false
|
||||||
}
|
);
|
||||||
|
const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ];
|
||||||
const {
|
const {
|
||||||
total_shipping: totalShipping,
|
shippingRates,
|
||||||
total_shipping_tax: totalShippingTax,
|
shippingAddress,
|
||||||
} = values;
|
shippingRatesLoading,
|
||||||
const shippingValue = parseInt( totalShipping, 10 );
|
hasShippingAddress,
|
||||||
const shippingTaxValue = parseInt( totalShippingTax, 10 );
|
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 (
|
||||||
|
<TotalsItem
|
||||||
|
label={ __( 'Shipping', 'woo-gutenberg-products-block' ) }
|
||||||
|
value={
|
||||||
|
showCalculator ? (
|
||||||
|
<button
|
||||||
|
className="wc-block-cart__change-address-button"
|
||||||
|
onClick={ () => {
|
||||||
|
setIsShippingCalculatorOpen(
|
||||||
|
! isShippingCalculatorOpen
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
{ __(
|
||||||
|
'Calculate',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<em>
|
||||||
|
{ __(
|
||||||
|
'Calculated during checkout',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
</em>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
{ showCalculator && isShippingCalculatorOpen && (
|
||||||
|
<ShippingCalculator
|
||||||
|
onUpdate={ ( newAddress ) => {
|
||||||
|
setShippingAddress( newAddress );
|
||||||
|
setIsShippingCalculatorOpen( false );
|
||||||
|
} }
|
||||||
|
address={ shippingAddress }
|
||||||
|
addressFields={ defaultAddressFields }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TotalsItem
|
<>
|
||||||
currency={ currency }
|
<TotalsItem
|
||||||
description={
|
label={ __( 'Shipping', 'woo-gutenberg-products-block' ) }
|
||||||
<>
|
value={ totalShippingValue ? totalShippingValue : '' }
|
||||||
{ shippingAddress && (
|
description={
|
||||||
<ShippingLocation address={ shippingAddress } />
|
<>
|
||||||
) }
|
<ShippingLocation address={ shippingAddress } />{ ' ' }
|
||||||
{ updateShippingAddress && shippingAddress && (
|
{ showCalculator && (
|
||||||
<ShippingCalculator
|
<button
|
||||||
address={ shippingAddress }
|
className="wc-block-cart__change-address-button"
|
||||||
setAddress={ updateShippingAddress }
|
onClick={ () => {
|
||||||
/>
|
setIsShippingCalculatorOpen(
|
||||||
) }
|
! isShippingCalculatorOpen
|
||||||
</>
|
);
|
||||||
}
|
} }
|
||||||
label={ __( 'Shipping', 'woo-gutenberg-products-block' ) }
|
>
|
||||||
value={
|
{ __(
|
||||||
DISPLAY_CART_PRICES_INCLUDING_TAX
|
'(change address)',
|
||||||
? shippingValue + shippingTaxValue
|
'woo-gutenberg-products-block'
|
||||||
: shippingValue
|
) }
|
||||||
}
|
</button>
|
||||||
/>
|
) }
|
||||||
|
{ showCalculator && isShippingCalculatorOpen && (
|
||||||
|
<ShippingCalculator
|
||||||
|
onUpdate={ ( newAddress ) => {
|
||||||
|
setShippingAddress( newAddress );
|
||||||
|
setIsShippingCalculatorOpen( false );
|
||||||
|
} }
|
||||||
|
address={ shippingAddress }
|
||||||
|
addressFields={ defaultAddressFields }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
currency={ currency }
|
||||||
|
/>
|
||||||
|
{ showingRates && (
|
||||||
|
<fieldset className="wc-block-cart__shipping-options-fieldset">
|
||||||
|
<legend className="screen-reader-text">
|
||||||
|
{ __(
|
||||||
|
'Choose a shipping method',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
</legend>
|
||||||
|
<ShippingRateSelector
|
||||||
|
shippingRates={ shippingRates }
|
||||||
|
shippingRatesLoading={ shippingRatesLoading }
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
) }
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
TotalsShippingItem.propTypes = {
|
TotalsShippingItem.propTypes = {
|
||||||
currency: PropTypes.object.isRequired,
|
currency: PropTypes.object.isRequired,
|
||||||
shippingAddress: PropTypes.object,
|
|
||||||
updateShippingAddress: PropTypes.func,
|
|
||||||
values: PropTypes.shape( {
|
values: PropTypes.shape( {
|
||||||
total_shipping: PropTypes.string,
|
total_shipping: PropTypes.string,
|
||||||
total_shipping_tax: PropTypes.string,
|
total_shipping_tax: PropTypes.string,
|
||||||
} ).isRequired,
|
} ).isRequired,
|
||||||
|
showCalculator: PropTypes.bool,
|
||||||
|
showRatesWithoutAddress: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TotalsShippingItem;
|
export default TotalsShippingItem;
|
||||||
|
|
|
@ -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 && (
|
||||||
|
<FormattedMonetaryAmount
|
||||||
|
currency={ getCurrencyFromPriceResponse( option ) }
|
||||||
|
value={ option.price }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
{ option.price && option.delivery_time ? ' — ' : null }
|
||||||
|
{ decodeEntities( option.delivery_time ) }
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const ShippingRateSelector = ( { shippingRates, shippingRatesLoading } ) => {
|
||||||
|
return (
|
||||||
|
<fieldset className="wc-block-cart__shipping-options-fieldset">
|
||||||
|
<legend className="screen-reader-text">
|
||||||
|
{ __(
|
||||||
|
'Choose the shipping method.',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
</legend>
|
||||||
|
<ShippingRatesControl
|
||||||
|
className="wc-block-cart__shipping-options"
|
||||||
|
collapsibleWhenMultiple={ true }
|
||||||
|
noResultsMessage={ __(
|
||||||
|
'No shipping options were found.',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
renderOption={ renderShippingRatesControlOption }
|
||||||
|
shippingRates={ shippingRates }
|
||||||
|
shippingRatesLoading={ shippingRatesLoading }
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingRateSelector;
|
|
@ -14,7 +14,7 @@ const SET_BILLING_DATA = 'set_billing_data';
|
||||||
/**
|
/**
|
||||||
* Used to dispatch a status update only for the given type.
|
* Used to dispatch a status update only for the given type.
|
||||||
*
|
*
|
||||||
* @param type
|
* @param {string} type
|
||||||
*
|
*
|
||||||
* @return {Object} The action object.
|
* @return {Object} The action object.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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 (
|
||||||
|
<EditorContext.Provider value={ editorData }>
|
||||||
|
{ children }
|
||||||
|
</EditorContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -3,3 +3,4 @@ export * from './inner-block-configuration-context';
|
||||||
export * from './product-layout-context';
|
export * from './product-layout-context';
|
||||||
export * from './query-state-context';
|
export * from './query-state-context';
|
||||||
export * from './store-notices-context';
|
export * from './store-notices-context';
|
||||||
|
export * from './editor';
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
|
import { useEditorContext } from '@woocommerce/base-context';
|
||||||
|
import { previewCart } from '@woocommerce/resource-previews';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constant
|
* @constant
|
||||||
|
@ -12,6 +14,7 @@ import { useSelect } from '@wordpress/data';
|
||||||
*/
|
*/
|
||||||
const defaultCartData = {
|
const defaultCartData = {
|
||||||
cartCoupons: [],
|
cartCoupons: [],
|
||||||
|
shippingRates: [],
|
||||||
cartItems: [],
|
cartItems: [],
|
||||||
cartItemsCount: 0,
|
cartItemsCount: 0,
|
||||||
cartItemsWeight: 0,
|
cartItemsWeight: 0,
|
||||||
|
@ -19,7 +22,6 @@ const defaultCartData = {
|
||||||
cartTotals: {},
|
cartTotals: {},
|
||||||
cartIsLoading: true,
|
cartIsLoading: true,
|
||||||
cartErrors: [],
|
cartErrors: [],
|
||||||
shippingRates: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +37,7 @@ const defaultCartData = {
|
||||||
* @return {StoreCart} Object containing cart data.
|
* @return {StoreCart} Object containing cart data.
|
||||||
*/
|
*/
|
||||||
export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
|
const { isEditor } = useEditorContext();
|
||||||
const { shouldSelect } = options;
|
const { shouldSelect } = options;
|
||||||
|
|
||||||
const results = useSelect(
|
const results = useSelect(
|
||||||
|
@ -42,6 +45,21 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
if ( ! shouldSelect ) {
|
if ( ! shouldSelect ) {
|
||||||
return null;
|
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 store = select( storeKey );
|
||||||
const cartData = store.getCartData();
|
const cartData = store.getCartData();
|
||||||
const cartErrors = store.getCartErrors();
|
const cartErrors = store.getCartErrors();
|
||||||
|
@ -49,7 +67,6 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
const cartIsLoading = ! store.hasFinishedResolution(
|
const cartIsLoading = ! store.hasFinishedResolution(
|
||||||
'getCartData'
|
'getCartData'
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cartCoupons: cartData.coupons,
|
cartCoupons: cartData.coupons,
|
||||||
shippingRates: cartData.shippingRates,
|
shippingRates: cartData.shippingRates,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { pluckAddress } from '../../utils';
|
||||||
* - {Function} setShippingAddress An function that optimistically
|
* - {Function} setShippingAddress An function that optimistically
|
||||||
* update shipping address and dispatches async rate fetching.
|
* update shipping address and dispatches async rate fetching.
|
||||||
* - {Object} shippingAddress An object containing shipping address.
|
* - {Object} shippingAddress An object containing shipping address.
|
||||||
|
* - {Object} shippingAddress True when address data exists.
|
||||||
*/
|
*/
|
||||||
export const useShippingRates = ( addressFieldsKeys ) => {
|
export const useShippingRates = ( addressFieldsKeys ) => {
|
||||||
const { cartErrors, shippingRates } = useStoreCart();
|
const { cartErrors, shippingRates } = useStoreCart();
|
||||||
|
@ -59,11 +60,13 @@ export const useShippingRates = ( addressFieldsKeys ) => {
|
||||||
updateShippingAddress( debouncedShippingAddress );
|
updateShippingAddress( debouncedShippingAddress );
|
||||||
}
|
}
|
||||||
}, [ debouncedShippingAddress ] );
|
}, [ debouncedShippingAddress ] );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shippingRates,
|
shippingRates,
|
||||||
shippingAddress,
|
shippingAddress,
|
||||||
setShippingAddress,
|
setShippingAddress,
|
||||||
shippingRatesLoading,
|
shippingRatesLoading,
|
||||||
shippingRatesErrors: cartErrors,
|
shippingRatesErrors: cartErrors,
|
||||||
|
hasShippingAddress: !! shippingAddress.country,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
@ -8,12 +8,9 @@ import { Disabled, PanelBody, ToggleControl } from '@wordpress/components';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withFeedbackPrompt } from '@woocommerce/block-hocs';
|
import { withFeedbackPrompt } from '@woocommerce/block-hocs';
|
||||||
import ViewSwitcher from '@woocommerce/block-components/view-switcher';
|
import ViewSwitcher from '@woocommerce/block-components/view-switcher';
|
||||||
import {
|
|
||||||
previewCart,
|
|
||||||
previewShippingRates,
|
|
||||||
} from '@woocommerce/resource-previews';
|
|
||||||
import { SHIPPING_ENABLED } from '@woocommerce/block-settings';
|
import { SHIPPING_ENABLED } from '@woocommerce/block-settings';
|
||||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||||
|
import { EditorProvider } from '@woocommerce/base-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -30,10 +27,7 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
|
||||||
const BlockSettings = () => (
|
const BlockSettings = () => (
|
||||||
<InspectorControls>
|
<InspectorControls>
|
||||||
<PanelBody
|
<PanelBody
|
||||||
title={ __(
|
title={ __( 'Shipping rates', 'woo-gutenberg-products-block' ) }
|
||||||
'Shipping calculations',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
) }
|
|
||||||
>
|
>
|
||||||
<ToggleControl
|
<ToggleControl
|
||||||
label={ __(
|
label={ __(
|
||||||
|
@ -41,7 +35,7 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
|
||||||
'woo-gutenberg-products-block'
|
'woo-gutenberg-products-block'
|
||||||
) }
|
) }
|
||||||
help={ __(
|
help={ __(
|
||||||
'Allow customers to estimate shipping.',
|
'Allow customers to estimate shipping by entering their address.',
|
||||||
'woo-gutenberg-products-block'
|
'woo-gutenberg-products-block'
|
||||||
) }
|
) }
|
||||||
checked={ isShippingCalculatorEnabled }
|
checked={ isShippingCalculatorEnabled }
|
||||||
|
@ -51,24 +45,22 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
|
||||||
} )
|
} )
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{ isShippingCalculatorEnabled && (
|
<ToggleControl
|
||||||
<ToggleControl
|
label={ __(
|
||||||
label={ __(
|
'Hide shipping costs until an address is entered',
|
||||||
'Hide shipping costs',
|
'woo-gutenberg-products-block'
|
||||||
'woo-gutenberg-products-block'
|
) }
|
||||||
) }
|
help={ __(
|
||||||
help={ __(
|
'If checked, shipping rates will be hidden until the customer uses the shipping calculator or enters their address during checkout.',
|
||||||
'Hide shipping costs until an address is entered.',
|
'woo-gutenberg-products-block'
|
||||||
'woo-gutenberg-products-block'
|
) }
|
||||||
) }
|
checked={ isShippingCostHidden }
|
||||||
checked={ isShippingCostHidden }
|
onChange={ () =>
|
||||||
onChange={ () =>
|
setAttributes( {
|
||||||
setAttributes( {
|
isShippingCostHidden: ! isShippingCostHidden,
|
||||||
isShippingCostHidden: ! isShippingCostHidden,
|
} )
|
||||||
} )
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
) }
|
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
</InspectorControls>
|
</InspectorControls>
|
||||||
);
|
);
|
||||||
|
@ -112,19 +104,16 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
|
||||||
) }
|
) }
|
||||||
>
|
>
|
||||||
<Disabled>
|
<Disabled>
|
||||||
<FullCart
|
<EditorProvider>
|
||||||
cartItems={ previewCart.items }
|
<FullCart
|
||||||
cartTotals={ previewCart.totals }
|
isShippingCostHidden={
|
||||||
isShippingCostHidden={
|
isShippingCostHidden
|
||||||
isShippingCostHidden
|
}
|
||||||
}
|
isShippingCalculatorEnabled={
|
||||||
isShippingCalculatorEnabled={
|
isShippingCalculatorEnabled
|
||||||
isShippingCalculatorEnabled
|
}
|
||||||
}
|
/>
|
||||||
shippingRates={
|
</EditorProvider>
|
||||||
previewShippingRates
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Disabled>
|
</Disabled>
|
||||||
</BlockErrorBoundary>
|
</BlockErrorBoundary>
|
||||||
</>
|
</>
|
||||||
|
@ -159,7 +148,7 @@ CartEditor.propTypes = {
|
||||||
|
|
||||||
export default withFeedbackPrompt(
|
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'
|
'woo-gutenberg-products-block'
|
||||||
)
|
)
|
||||||
)( CartEditor );
|
)( CartEditor );
|
||||||
|
|
|
@ -17,53 +17,69 @@ import { __experimentalCreateInterpolateElement } from 'wordpress-element';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import FullCart from './full-cart';
|
import FullCart from './full-cart';
|
||||||
|
import blockAttributes from './attributes';
|
||||||
import renderFrontend from '../../../utils/render-frontend.js';
|
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 = ( {
|
const Block = ( { emptyCart, attributes } ) => {
|
||||||
emptyCart,
|
const { cartItems, cartIsLoading } = useStoreCart();
|
||||||
isShippingCalculatorEnabled,
|
|
||||||
isShippingCostHidden,
|
|
||||||
} ) => {
|
|
||||||
const {
|
|
||||||
cartItems,
|
|
||||||
cartTotals,
|
|
||||||
cartIsLoading,
|
|
||||||
cartCoupons,
|
|
||||||
shippingRates,
|
|
||||||
} = useStoreCart();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoreNoticesProvider context="wc/cart">
|
<>
|
||||||
{ ! cartIsLoading && ! cartItems.length ? (
|
{ ! cartIsLoading && cartItems.length === 0 ? (
|
||||||
<RawHTML>{ emptyCart }</RawHTML>
|
<RawHTML>{ emptyCart }</RawHTML>
|
||||||
) : (
|
) : (
|
||||||
<LoadingMask showSpinner={ true } isLoading={ cartIsLoading }>
|
<LoadingMask showSpinner={ true } isLoading={ cartIsLoading }>
|
||||||
<FullCart
|
<FullCart
|
||||||
cartItems={ cartItems }
|
|
||||||
cartTotals={ cartTotals }
|
|
||||||
cartCoupons={ cartCoupons }
|
|
||||||
isShippingCalculatorEnabled={
|
isShippingCalculatorEnabled={
|
||||||
isShippingCalculatorEnabled
|
attributes.isShippingCalculatorEnabled
|
||||||
}
|
}
|
||||||
isShippingCostHidden={ isShippingCostHidden }
|
isShippingCostHidden={ attributes.isShippingCostHidden }
|
||||||
isLoading={ cartIsLoading }
|
|
||||||
shippingRates={ shippingRates }
|
|
||||||
/>
|
/>
|
||||||
</LoadingMask>
|
</LoadingMask>
|
||||||
) }
|
) }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper component to supply API data and show empty cart view as needed.
|
||||||
|
*
|
||||||
|
* @param {*} props
|
||||||
|
*/
|
||||||
|
const CartFrontend = ( props ) => {
|
||||||
|
return (
|
||||||
|
<StoreNoticesProvider context="wc/cart">
|
||||||
|
<Block { ...props } />
|
||||||
</StoreNoticesProvider>
|
</StoreNoticesProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProps = ( el ) => ( {
|
const getProps = ( el ) => {
|
||||||
emptyCart: el.innerHTML,
|
const attributes = {};
|
||||||
isShippingCalculatorEnabled:
|
|
||||||
el.dataset.isShippingCalculatorEnabled === 'true',
|
Object.keys( blockAttributes ).forEach( ( key ) => {
|
||||||
isShippingCostHidden: el.dataset.isShippingCostHidden === 'true',
|
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 = () => {
|
const getErrorBoundaryProps = () => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -42,9 +42,6 @@ const getMaximumQuantity = ( backOrdersAllowed, lowStockAmount ) => {
|
||||||
* Cart line item table row component.
|
* Cart line item table row component.
|
||||||
*/
|
*/
|
||||||
const CartLineItemRow = ( { lineItem } ) => {
|
const CartLineItemRow = ( { lineItem } ) => {
|
||||||
/**
|
|
||||||
* @type {CartItem}
|
|
||||||
*/
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
summary,
|
summary,
|
||||||
|
@ -142,32 +139,7 @@ const CartLineItemRow = ( { lineItem } ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
CartLineItemRow.propTypes = {
|
CartLineItemRow.propTypes = {
|
||||||
lineItem: PropTypes.shape( {
|
lineItem: PropTypes.object.isRequired,
|
||||||
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,
|
|
||||||
} ),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CartLineItemRow;
|
export default CartLineItemRow;
|
||||||
|
|
|
@ -10,7 +10,51 @@ import PropTypes from 'prop-types';
|
||||||
import CartLineItemRow from './cart-line-item-row';
|
import CartLineItemRow from './cart-line-item-row';
|
||||||
|
|
||||||
const placeholderRows = [ ...Array( 3 ) ].map( ( _x, i ) => (
|
const placeholderRows = [ ...Array( 3 ) ].map( ( _x, i ) => (
|
||||||
<CartLineItemRow key={ i } />
|
<CartLineItemRow
|
||||||
|
key={ i }
|
||||||
|
lineItem={ {
|
||||||
|
key: '1',
|
||||||
|
id: 1,
|
||||||
|
quantity: 2,
|
||||||
|
name: '',
|
||||||
|
summary: '',
|
||||||
|
short_description: '',
|
||||||
|
description: '',
|
||||||
|
sku: '',
|
||||||
|
low_stock_remaining: null,
|
||||||
|
backorders_allowed: false,
|
||||||
|
sold_individually: false,
|
||||||
|
permalink: '',
|
||||||
|
images: [],
|
||||||
|
variation: [],
|
||||||
|
prices: {
|
||||||
|
currency_code: 'USD',
|
||||||
|
currency_minor_unit: 2,
|
||||||
|
currency_symbol: '$',
|
||||||
|
currency_prefix: '$',
|
||||||
|
currency_suffix: '',
|
||||||
|
currency_decimal_separator: '.',
|
||||||
|
currency_thousand_separator: ',',
|
||||||
|
price: '0',
|
||||||
|
regular_price: '0',
|
||||||
|
sale_price: '0',
|
||||||
|
price_range: null,
|
||||||
|
},
|
||||||
|
totals: {
|
||||||
|
currency_code: 'USD',
|
||||||
|
currency_minor_unit: 2,
|
||||||
|
currency_symbol: '$',
|
||||||
|
currency_prefix: '$',
|
||||||
|
currency_suffix: '',
|
||||||
|
currency_decimal_separator: '.',
|
||||||
|
currency_thousand_separator: ',',
|
||||||
|
line_subtotal: '0',
|
||||||
|
line_subtotal_tax: '0',
|
||||||
|
line_total: '0',
|
||||||
|
line_total_tax: '0',
|
||||||
|
},
|
||||||
|
} }
|
||||||
|
/>
|
||||||
) );
|
) );
|
||||||
|
|
||||||
const CartLineItemsTable = ( { lineItems = [], isLoading = false } ) => {
|
const CartLineItemsTable = ( { lineItems = [], isLoading = false } ) => {
|
||||||
|
|
|
@ -13,17 +13,14 @@ import {
|
||||||
TotalsShippingItem,
|
TotalsShippingItem,
|
||||||
TotalsTaxesItem,
|
TotalsTaxesItem,
|
||||||
} from '@woocommerce/base-components/totals';
|
} from '@woocommerce/base-components/totals';
|
||||||
import ShippingRatesControl from '@woocommerce/base-components/shipping-rates-control';
|
|
||||||
import {
|
import {
|
||||||
COUPONS_ENABLED,
|
COUPONS_ENABLED,
|
||||||
SHIPPING_ENABLED,
|
|
||||||
DISPLAY_CART_PRICES_INCLUDING_TAX,
|
DISPLAY_CART_PRICES_INCLUDING_TAX,
|
||||||
|
SHIPPING_ENABLED,
|
||||||
} from '@woocommerce/block-settings';
|
} from '@woocommerce/block-settings';
|
||||||
import { getCurrencyFromPriceResponse } from '@woocommerce/base-utils';
|
import { getCurrencyFromPriceResponse } from '@woocommerce/base-utils';
|
||||||
import { Card, CardBody } from 'wordpress-components';
|
import { Card, CardBody } from 'wordpress-components';
|
||||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
import { useStoreCartCoupons, useStoreCart } from '@woocommerce/base-hooks';
|
||||||
import { decodeEntities } from '@wordpress/html-entities';
|
|
||||||
import { useStoreCartCoupons, useShippingRates } from '@woocommerce/base-hooks';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
|
@ -41,85 +38,30 @@ import CartLineItemsTable from './cart-line-items-table';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
import './editor.scss';
|
import './editor.scss';
|
||||||
|
|
||||||
const renderShippingRatesControlOption = ( option ) => ( {
|
|
||||||
label: decodeEntities( option.name ),
|
|
||||||
value: option.rate_id,
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
{ option.price && (
|
|
||||||
<FormattedMonetaryAmount
|
|
||||||
currency={ getCurrencyFromPriceResponse( option ) }
|
|
||||||
value={ option.price }
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
{ option.price && option.delivery_time ? ' — ' : null }
|
|
||||||
{ decodeEntities( option.delivery_time ) }
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
} );
|
|
||||||
|
|
||||||
const ShippingCalculatorOptions = ( {
|
|
||||||
shippingRates,
|
|
||||||
shippingRatesLoading,
|
|
||||||
} ) => {
|
|
||||||
return (
|
|
||||||
<fieldset className="wc-block-cart__shipping-options-fieldset">
|
|
||||||
<legend className="screen-reader-text">
|
|
||||||
{ __(
|
|
||||||
'Choose the shipping method.',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
) }
|
|
||||||
</legend>
|
|
||||||
<ShippingRatesControl
|
|
||||||
className="wc-block-cart__shipping-options"
|
|
||||||
collapsibleWhenMultiple={ true }
|
|
||||||
noResultsMessage={ __(
|
|
||||||
'No shipping options were found.',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
) }
|
|
||||||
renderOption={ renderShippingRatesControlOption }
|
|
||||||
shippingRates={ shippingRates }
|
|
||||||
shippingRatesLoading={ shippingRatesLoading }
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders the Cart block when user has something in cart aka "full".
|
* Component that renders the Cart block when user has something in cart aka "full".
|
||||||
*/
|
*/
|
||||||
const Cart = ( {
|
const Cart = ( { isShippingCalculatorEnabled, isShippingCostHidden } ) => {
|
||||||
cartItems = [],
|
const { cartItems, cartTotals, cartIsLoading, cartErrors } = useStoreCart();
|
||||||
cartTotals = {},
|
|
||||||
cartCoupons = [],
|
|
||||||
isShippingCalculatorEnabled,
|
|
||||||
isShippingCostHidden,
|
|
||||||
shippingRates,
|
|
||||||
isLoading = false,
|
|
||||||
} ) => {
|
|
||||||
const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ];
|
|
||||||
const {
|
|
||||||
shippingAddress,
|
|
||||||
setShippingAddress,
|
|
||||||
shippingRatesLoading,
|
|
||||||
} = useShippingRates( defaultAddressFields );
|
|
||||||
const {
|
const {
|
||||||
applyCoupon,
|
applyCoupon,
|
||||||
removeCoupon,
|
removeCoupon,
|
||||||
isApplyingCoupon,
|
isApplyingCoupon,
|
||||||
isRemovingCoupon,
|
isRemovingCoupon,
|
||||||
|
cartCoupons,
|
||||||
|
cartCouponsErrors,
|
||||||
} = useStoreCartCoupons();
|
} = useStoreCartCoupons();
|
||||||
|
|
||||||
const showShippingCosts = Boolean(
|
const errors = [ ...cartErrors, ...cartCouponsErrors ];
|
||||||
SHIPPING_ENABLED &&
|
if ( errors.length > 0 ) {
|
||||||
isShippingCalculatorEnabled &&
|
throw new Error( errors[ 0 ].message );
|
||||||
( ! isShippingCostHidden || shippingAddress?.country )
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||||
|
|
||||||
const cartClassName = classnames( 'wc-block-cart', {
|
const cartClassName = classnames( 'wc-block-cart', {
|
||||||
'wc-block-cart--is-loading': isLoading,
|
'wc-block-cart--is-loading': cartIsLoading,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -128,7 +70,7 @@ const Cart = ( {
|
||||||
<CartLineItemsTitle itemCount={ cartItems.length } />
|
<CartLineItemsTitle itemCount={ cartItems.length } />
|
||||||
<CartLineItemsTable
|
<CartLineItemsTable
|
||||||
lineItems={ cartItems }
|
lineItems={ cartItems }
|
||||||
isLoading={ isLoading }
|
isLoading={ cartIsLoading }
|
||||||
/>
|
/>
|
||||||
</Main>
|
</Main>
|
||||||
<Sidebar className="wc-block-cart__sidebar">
|
<Sidebar className="wc-block-cart__sidebar">
|
||||||
|
@ -155,30 +97,16 @@ const Cart = ( {
|
||||||
removeCoupon={ removeCoupon }
|
removeCoupon={ removeCoupon }
|
||||||
values={ cartTotals }
|
values={ cartTotals }
|
||||||
/>
|
/>
|
||||||
{ isShippingCalculatorEnabled && (
|
{ SHIPPING_ENABLED && (
|
||||||
<TotalsShippingItem
|
<TotalsShippingItem
|
||||||
currency={ totalsCurrency }
|
showCalculator={ isShippingCalculatorEnabled }
|
||||||
shippingAddress={ shippingAddress }
|
showRatesWithoutAddress={
|
||||||
updateShippingAddress={ setShippingAddress }
|
! isShippingCostHidden
|
||||||
|
}
|
||||||
values={ cartTotals }
|
values={ cartTotals }
|
||||||
|
currency={ totalsCurrency }
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
{ showShippingCosts && (
|
|
||||||
<fieldset className="wc-block-cart__shipping-options-fieldset">
|
|
||||||
<legend className="screen-reader-text">
|
|
||||||
{ __(
|
|
||||||
'Choose the shipping method.',
|
|
||||||
'woo-gutenberg-products-block'
|
|
||||||
) }
|
|
||||||
</legend>
|
|
||||||
<ShippingCalculatorOptions
|
|
||||||
shippingRates={ shippingRates }
|
|
||||||
shippingRatesLoading={
|
|
||||||
shippingRatesLoading
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
) }
|
|
||||||
{ ! DISPLAY_CART_PRICES_INCLUDING_TAX && (
|
{ ! DISPLAY_CART_PRICES_INCLUDING_TAX && (
|
||||||
<TotalsTaxesItem
|
<TotalsTaxesItem
|
||||||
currency={ totalsCurrency }
|
currency={ totalsCurrency }
|
||||||
|
@ -188,7 +116,6 @@ const Cart = ( {
|
||||||
{ COUPONS_ENABLED && (
|
{ COUPONS_ENABLED && (
|
||||||
<TotalsCouponCodeInput
|
<TotalsCouponCodeInput
|
||||||
onSubmit={ applyCoupon }
|
onSubmit={ applyCoupon }
|
||||||
initialOpen={ true }
|
|
||||||
isLoading={ isApplyingCoupon }
|
isLoading={ isApplyingCoupon }
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
|
@ -205,26 +132,8 @@ const Cart = ( {
|
||||||
};
|
};
|
||||||
|
|
||||||
Cart.propTypes = {
|
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,
|
isShippingCalculatorEnabled: PropTypes.bool,
|
||||||
isShippingCostHidden: 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;
|
export default Cart;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
.wc-block-cart__item-count {
|
.wc-block-cart__item-count {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
.wc-block-cart__change-address {
|
.wc-block-cart__shipping-calculator {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.wc-block-cart__change-address-button {
|
.wc-block-cart__change-address-button {
|
||||||
|
|
|
@ -2,14 +2,11 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { InnerBlocks } from '@wordpress/block-editor';
|
import { InnerBlocks } from '@wordpress/block-editor';
|
||||||
import { registerBlockType } from '@wordpress/blocks';
|
import { registerBlockType } from '@wordpress/blocks';
|
||||||
import { Icon, cart } from '@woocommerce/icons';
|
import { Icon, cart } from '@woocommerce/icons';
|
||||||
import {
|
import { kebabCase } from 'lodash';
|
||||||
IS_SHIPPING_CALCULATOR_ENABLED,
|
import classnames from 'classnames';
|
||||||
IS_SHIPPING_COST_HIDDEN,
|
|
||||||
} from '@woocommerce/block-settings';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -17,6 +14,7 @@ import {
|
||||||
import edit from './edit';
|
import edit from './edit';
|
||||||
import { example } from './example';
|
import { example } from './example';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
import blockAttributes from './attributes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register and run the Cart block.
|
* Register and run the Cart block.
|
||||||
|
@ -36,35 +34,27 @@ const settings = {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
},
|
},
|
||||||
example,
|
example,
|
||||||
attributes: {
|
attributes: blockAttributes,
|
||||||
isShippingCalculatorEnabled: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: IS_SHIPPING_CALCULATOR_ENABLED,
|
|
||||||
},
|
|
||||||
isShippingCostHidden: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: IS_SHIPPING_COST_HIDDEN,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
edit,
|
edit,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the props to post content.
|
* Save the props to post content.
|
||||||
*/
|
*/
|
||||||
save( { attributes } ) {
|
save( { attributes } ) {
|
||||||
const {
|
const data = {};
|
||||||
className,
|
|
||||||
isShippingCalculatorEnabled,
|
Object.keys( blockAttributes ).forEach( ( key ) => {
|
||||||
isShippingCostHidden,
|
if (
|
||||||
} = attributes;
|
blockAttributes[ key ].save !== false &&
|
||||||
const data = {
|
typeof attributes[ key ] !== 'undefined'
|
||||||
'data-is-shipping-calculator-enabled': isShippingCalculatorEnabled,
|
) {
|
||||||
'data-is-shipping-cost-hidden': isShippingCostHidden,
|
data[ 'data-' + kebabCase( key ) ] = attributes[ key ];
|
||||||
};
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ classNames( 'is-loading', className ) }
|
className={ classnames( 'is-loading', attributes.className ) }
|
||||||
{ ...data }
|
{ ...data }
|
||||||
>
|
>
|
||||||
<InnerBlocks.Content />
|
<InnerBlocks.Content />
|
||||||
|
|
|
@ -232,7 +232,7 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
|
||||||
|
|
||||||
export default withFeedbackPrompt(
|
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'
|
'woo-gutenberg-products-block'
|
||||||
)
|
)
|
||||||
)( CheckoutEditor );
|
)( CheckoutEditor );
|
||||||
|
|
|
@ -28,10 +28,10 @@ const withFeedbackPrompt = ( content ) =>
|
||||||
createHigherOrderComponent( ( BlockEdit ) => {
|
createHigherOrderComponent( ( BlockEdit ) => {
|
||||||
return ( props ) => (
|
return ( props ) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<BlockEdit { ...props } />
|
|
||||||
<InspectorControls>
|
<InspectorControls>
|
||||||
<FeedbackPrompt text={ content } />
|
<FeedbackPrompt text={ content } />
|
||||||
</InspectorControls>
|
</InspectorControls>
|
||||||
|
<BlockEdit { ...props } />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}, 'withFeedbackPrompt' );
|
}, 'withFeedbackPrompt' );
|
||||||
|
|
|
@ -7,11 +7,14 @@ import { __ } from '@wordpress/i18n';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import productPicture from './product-image';
|
import productPicture from './product-image';
|
||||||
|
import { previewShippingRates } from './shipping-rates';
|
||||||
|
|
||||||
// Sample data for cart block.
|
// Sample data for cart block.
|
||||||
// This closely resembles the data returned from the Store API /cart endpoint.
|
// 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
|
// https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/master/src/RestApi/StoreApi#cart-api
|
||||||
export const previewCart = {
|
export const previewCart = {
|
||||||
|
coupons: [],
|
||||||
|
shipping_rates: previewShippingRates,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
|
@ -143,9 +146,17 @@ export const previewCart = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
items_count: 4,
|
||||||
|
items_weight: 0,
|
||||||
|
needs_shipping: true,
|
||||||
totals: {
|
totals: {
|
||||||
currency: 'USD',
|
currency_code: 'USD',
|
||||||
|
currency_symbol: '$',
|
||||||
currency_minor_unit: 2,
|
currency_minor_unit: 2,
|
||||||
|
currency_decimal_separator: '.',
|
||||||
|
currency_thousand_separator: ',',
|
||||||
|
currency_prefix: '$',
|
||||||
|
currency_suffix: '',
|
||||||
total_items: '3000',
|
total_items: '3000',
|
||||||
total_items_tax: '0',
|
total_items_tax: '0',
|
||||||
total_fees: '0',
|
total_fees: '0',
|
||||||
|
@ -156,5 +167,6 @@ export const previewCart = {
|
||||||
total_shipping_tax: '0',
|
total_shipping_tax: '0',
|
||||||
total_tax: '0',
|
total_tax: '0',
|
||||||
total_price: '3000',
|
total_price: '3000',
|
||||||
|
tax_lines: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -206,4 +206,13 @@
|
||||||
* (true) or not (false).
|
* (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 {};
|
export {};
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
"@woocommerce/base-hocs(.*)$": "assets/js/base/hocs/$1",
|
"@woocommerce/base-hocs(.*)$": "assets/js/base/hocs/$1",
|
||||||
"@woocommerce/base-hooks(.*)$": "assets/js/base/hooks/$1",
|
"@woocommerce/base-hooks(.*)$": "assets/js/base/hooks/$1",
|
||||||
"@woocommerce/base-utils(.*)$": "assets/js/base/utils",
|
"@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": [
|
"setupFiles": [
|
||||||
"<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js",
|
"<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js",
|
||||||
|
|
Loading…
Reference in New Issue