Refactor some of the shipping hooks/context usage (https://github.com/woocommerce/woocommerce-blocks/pull/2146)
* Counting helpers for shipping rates and packages * Use new helpers * Make shipping calculator use shipping context directly * Totals should use current address * Avoid useShippingRates * Return rates and other items from useShippingRatse in useStoreCart instead * Update tests * Merge conflict * Merge conflict
This commit is contained in:
parent
9a4fc8e2cc
commit
d73d9ca12e
|
@ -2,6 +2,7 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useShippingDataContext } from '@woocommerce/base-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -9,13 +10,18 @@ import PropTypes from 'prop-types';
|
||||||
import ShippingCalculatorAddress from './address';
|
import ShippingCalculatorAddress from './address';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
const ShippingCalculator = ( { onUpdate, address, addressFields } ) => {
|
const ShippingCalculator = ( {
|
||||||
|
onUpdate = () => {},
|
||||||
|
addressFields = [ 'country', 'state', 'city', 'postcode' ],
|
||||||
|
} ) => {
|
||||||
|
const { shippingAddress, setShippingAddress } = useShippingDataContext();
|
||||||
return (
|
return (
|
||||||
<div className="wc-block-cart__shipping-calculator">
|
<div className="wc-block-cart__shipping-calculator">
|
||||||
<ShippingCalculatorAddress
|
<ShippingCalculatorAddress
|
||||||
address={ address }
|
address={ shippingAddress }
|
||||||
addressFields={ addressFields }
|
addressFields={ addressFields }
|
||||||
onUpdate={ ( newAddress ) => {
|
onUpdate={ ( newAddress ) => {
|
||||||
|
setShippingAddress( newAddress );
|
||||||
onUpdate( newAddress );
|
onUpdate( newAddress );
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
|
@ -24,9 +30,8 @@ const ShippingCalculator = ( { onUpdate, address, addressFields } ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
ShippingCalculator.propTypes = {
|
ShippingCalculator.propTypes = {
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func,
|
||||||
address: PropTypes.object.isRequired,
|
addressFields: PropTypes.array,
|
||||||
addressFields: PropTypes.array.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ShippingCalculator;
|
export default ShippingCalculator;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '@woocommerce/base-components/cart-checkout';
|
} from '@woocommerce/base-components/cart-checkout';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import { useShippingRates } from '@woocommerce/base-hooks';
|
import { useStoreCart } from '@woocommerce/base-hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -32,14 +32,12 @@ const TotalsShippingItem = ( {
|
||||||
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState(
|
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ];
|
|
||||||
const {
|
const {
|
||||||
shippingRates,
|
shippingRates,
|
||||||
shippingAddress,
|
|
||||||
shippingRatesLoading,
|
shippingRatesLoading,
|
||||||
hasShippingAddress,
|
hasShippingAddress,
|
||||||
setShippingAddress,
|
shippingAddress,
|
||||||
} = useShippingRates();
|
} = useStoreCart();
|
||||||
const totalShippingValue = DISPLAY_CART_PRICES_INCLUDING_TAX
|
const totalShippingValue = DISPLAY_CART_PRICES_INCLUDING_TAX
|
||||||
? parseInt( values.total_shipping, 10 ) +
|
? parseInt( values.total_shipping, 10 ) +
|
||||||
parseInt( values.total_shipping_tax, 10 )
|
parseInt( values.total_shipping_tax, 10 )
|
||||||
|
@ -80,12 +78,9 @@ const TotalsShippingItem = ( {
|
||||||
<>
|
<>
|
||||||
{ showCalculator && isShippingCalculatorOpen && (
|
{ showCalculator && isShippingCalculatorOpen && (
|
||||||
<ShippingCalculator
|
<ShippingCalculator
|
||||||
onUpdate={ ( newAddress ) => {
|
onUpdate={ () => {
|
||||||
setShippingAddress( newAddress );
|
|
||||||
setIsShippingCalculatorOpen( false );
|
setIsShippingCalculatorOpen( false );
|
||||||
} }
|
} }
|
||||||
address={ shippingAddress }
|
|
||||||
addressFields={ defaultAddressFields }
|
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
</>
|
</>
|
||||||
|
@ -119,12 +114,9 @@ const TotalsShippingItem = ( {
|
||||||
) }
|
) }
|
||||||
{ showCalculator && isShippingCalculatorOpen && (
|
{ showCalculator && isShippingCalculatorOpen && (
|
||||||
<ShippingCalculator
|
<ShippingCalculator
|
||||||
onUpdate={ ( newAddress ) => {
|
onUpdate={ () => {
|
||||||
setShippingAddress( newAddress );
|
|
||||||
setIsShippingCalculatorOpen( false );
|
setIsShippingCalculatorOpen( false );
|
||||||
} }
|
} }
|
||||||
address={ shippingAddress }
|
|
||||||
addressFields={ defaultAddressFields }
|
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import {
|
import {
|
||||||
useShippingAddress,
|
useShippingAddress,
|
||||||
useShippingRates,
|
|
||||||
useStoreCart,
|
useStoreCart,
|
||||||
useSelectShippingRate,
|
useSelectShippingRate,
|
||||||
} from '@woocommerce/base-hooks';
|
} from '@woocommerce/base-hooks';
|
||||||
|
@ -65,7 +64,11 @@ export const useShippingDataContext = () => {
|
||||||
*/
|
*/
|
||||||
export const ShippingDataProvider = ( { children } ) => {
|
export const ShippingDataProvider = ( { children } ) => {
|
||||||
const { dispatchActions } = useCheckoutContext();
|
const { dispatchActions } = useCheckoutContext();
|
||||||
const { cartNeedsShipping: needsShipping } = useStoreCart();
|
const {
|
||||||
|
cartNeedsShipping: needsShipping,
|
||||||
|
shippingRates,
|
||||||
|
shippingRatesLoading,
|
||||||
|
} = useStoreCart();
|
||||||
const [ shippingErrorStatus, dispatchErrorStatus ] = useReducer(
|
const [ shippingErrorStatus, dispatchErrorStatus ] = useReducer(
|
||||||
errorStatusReducer,
|
errorStatusReducer,
|
||||||
NONE
|
NONE
|
||||||
|
@ -73,7 +76,6 @@ export const ShippingDataProvider = ( { children } ) => {
|
||||||
const [ observers, subscriber ] = useReducer( emitReducer, {} );
|
const [ observers, subscriber ] = useReducer( emitReducer, {} );
|
||||||
const { shippingAddress, setShippingAddress } = useShippingAddress();
|
const { shippingAddress, setShippingAddress } = useShippingAddress();
|
||||||
const currentObservers = useRef( observers );
|
const currentObservers = useRef( observers );
|
||||||
const { shippingRates, shippingRatesLoading } = useShippingRates();
|
|
||||||
const {
|
const {
|
||||||
selectShippingRate: setSelectedRates,
|
selectShippingRate: setSelectedRates,
|
||||||
selectedShippingRates: selectedRates,
|
selectedShippingRates: selectedRates,
|
||||||
|
|
|
@ -34,13 +34,15 @@ describe( 'useStoreCart', () => {
|
||||||
cartIsLoading: false,
|
cartIsLoading: false,
|
||||||
cartItemErrors: [],
|
cartItemErrors: [],
|
||||||
cartErrors: [],
|
cartErrors: [],
|
||||||
shippingRates: previewCart.shipping_rates,
|
|
||||||
shippingAddress: {
|
shippingAddress: {
|
||||||
country: '',
|
country: '',
|
||||||
state: '',
|
state: '',
|
||||||
city: '',
|
city: '',
|
||||||
postcode: '',
|
postcode: '',
|
||||||
},
|
},
|
||||||
|
shippingRates: previewCart.shipping_rates,
|
||||||
|
shippingRatesLoading: false,
|
||||||
|
hasShippingAddress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockCartItems = [ { key: '1', id: 1, name: 'Lorem Ipsum' } ];
|
const mockCartItems = [ { key: '1', id: 1, name: 'Lorem Ipsum' } ];
|
||||||
|
@ -67,11 +69,13 @@ describe( 'useStoreCart', () => {
|
||||||
cartItemsCount: 1,
|
cartItemsCount: 1,
|
||||||
cartItemsWeight: 10,
|
cartItemsWeight: 10,
|
||||||
cartNeedsShipping: true,
|
cartNeedsShipping: true,
|
||||||
shippingAddress: mockShippingAddress,
|
|
||||||
shippingRates: [],
|
|
||||||
cartTotals: mockCartTotals,
|
cartTotals: mockCartTotals,
|
||||||
cartIsLoading: mockCartIsLoading,
|
cartIsLoading: mockCartIsLoading,
|
||||||
cartErrors: mockCartErrors,
|
cartErrors: mockCartErrors,
|
||||||
|
shippingAddress: mockShippingAddress,
|
||||||
|
shippingRates: [],
|
||||||
|
shippingRatesLoading: false,
|
||||||
|
hasShippingAddress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWrappedComponents = ( Component ) => (
|
const getWrappedComponents = ( Component ) => (
|
||||||
|
@ -94,6 +98,7 @@ describe( 'useStoreCart', () => {
|
||||||
hasFinishedResolution: jest
|
hasFinishedResolution: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue( ! mockCartIsLoading ),
|
.mockReturnValue( ! mockCartIsLoading ),
|
||||||
|
areShippingRatesLoading: jest.fn().mockReturnValue( false ),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
registry.registerStore( storeKey, {
|
registry.registerStore( storeKey, {
|
||||||
|
|
|
@ -33,6 +33,8 @@ export const defaultCartData = {
|
||||||
country: '',
|
country: '',
|
||||||
},
|
},
|
||||||
shippingRates: [],
|
shippingRates: [],
|
||||||
|
shippingRatesLoading: false,
|
||||||
|
hasShippingAddress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,13 +70,15 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
cartTotals: previewCart.totals,
|
cartTotals: previewCart.totals,
|
||||||
cartIsLoading: false,
|
cartIsLoading: false,
|
||||||
cartErrors: [],
|
cartErrors: [],
|
||||||
shippingRates: previewCart.shipping_rates,
|
|
||||||
shippingAddress: {
|
shippingAddress: {
|
||||||
country: '',
|
country: '',
|
||||||
state: '',
|
state: '',
|
||||||
city: '',
|
city: '',
|
||||||
postcode: '',
|
postcode: '',
|
||||||
},
|
},
|
||||||
|
shippingRates: previewCart.shipping_rates,
|
||||||
|
shippingRatesLoading: false,
|
||||||
|
hasShippingAddress: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,10 +89,10 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
const cartIsLoading = ! store.hasFinishedResolution(
|
const cartIsLoading = ! store.hasFinishedResolution(
|
||||||
'getCartData'
|
'getCartData'
|
||||||
);
|
);
|
||||||
|
const shippingRatesLoading = store.areShippingRatesLoading();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cartCoupons: cartData.coupons,
|
cartCoupons: cartData.coupons,
|
||||||
shippingRates: cartData.shippingRates,
|
|
||||||
shippingAddress: cartData.shippingAddress,
|
|
||||||
cartItems: cartData.items,
|
cartItems: cartData.items,
|
||||||
cartItemsCount: cartData.itemsCount,
|
cartItemsCount: cartData.itemsCount,
|
||||||
cartItemsWeight: cartData.itemsWeight,
|
cartItemsWeight: cartData.itemsWeight,
|
||||||
|
@ -97,6 +101,10 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
|
||||||
cartTotals,
|
cartTotals,
|
||||||
cartIsLoading,
|
cartIsLoading,
|
||||||
cartErrors,
|
cartErrors,
|
||||||
|
shippingAddress: cartData.shippingAddress,
|
||||||
|
shippingRates: cartData.shippingRates,
|
||||||
|
shippingRatesLoading,
|
||||||
|
hasShippingAddress: !! cartData.shippingAddress.country,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[ shouldSelect ]
|
[ shouldSelect ]
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './use-select-shipping-rate';
|
export * from './use-select-shipping-rate';
|
||||||
export * from './use-shipping-rates';
|
|
||||||
export * from './use-shipping-address';
|
export * from './use-shipping-address';
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { useSelect } from '@wordpress/data';
|
|
||||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { useStoreCart } from '../cart/use-store-cart';
|
|
||||||
import { useShippingAddress } from '../shipping/use-shipping-address';
|
|
||||||
/**
|
|
||||||
* This is a custom hook that is wired up to the `wc/store/cart/shipping-rates` route.
|
|
||||||
* Given a a set of default fields keys, this will handle shipping form state and load
|
|
||||||
* new rates when certain fields change.
|
|
||||||
*
|
|
||||||
* @return {Object} This hook will return an object with three properties:
|
|
||||||
* - {Boolean} shippingRatesLoading A boolean indicating whether the shipping
|
|
||||||
* rates are still loading or not.
|
|
||||||
* - {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 = () => {
|
|
||||||
const { cartErrors, shippingRates } = useStoreCart();
|
|
||||||
const { shippingAddress, setShippingAddress } = useShippingAddress();
|
|
||||||
const shippingRatesLoading = useSelect(
|
|
||||||
( select ) => select( storeKey ).areShippingRatesLoading(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
shippingRates,
|
|
||||||
shippingAddress,
|
|
||||||
setShippingAddress,
|
|
||||||
shippingRatesLoading,
|
|
||||||
shippingRatesErrors: cartErrors,
|
|
||||||
hasShippingAddress: !! shippingAddress.country,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,132 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import TestRenderer, { act } from 'react-test-renderer';
|
|
||||||
import { createRegistry, RegistryProvider } from '@wordpress/data';
|
|
||||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { useShippingRates } from '../shipping';
|
|
||||||
|
|
||||||
jest.mock( '@woocommerce/block-data', () => ( {
|
|
||||||
__esModule: true,
|
|
||||||
CART_STORE_KEY: 'test/store',
|
|
||||||
} ) );
|
|
||||||
|
|
||||||
describe( 'useShippingRates', () => {
|
|
||||||
let registry, mocks, renderer;
|
|
||||||
const getProps = ( testRenderer ) => {
|
|
||||||
const {
|
|
||||||
shippingRates,
|
|
||||||
shippingAddress,
|
|
||||||
setShippingAddress,
|
|
||||||
shippingRatesLoading,
|
|
||||||
} = testRenderer.root.findByType( 'div' ).props;
|
|
||||||
return {
|
|
||||||
shippingRates,
|
|
||||||
shippingAddress,
|
|
||||||
setShippingAddress,
|
|
||||||
shippingRatesLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getWrappedComponents = ( Component ) => (
|
|
||||||
<RegistryProvider value={ registry }>
|
|
||||||
<Component />
|
|
||||||
</RegistryProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
const getTestComponent = () => () => {
|
|
||||||
const items = useShippingRates();
|
|
||||||
return <div { ...items } />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCartData = {
|
|
||||||
coupons: [],
|
|
||||||
items: [ { foo: 'bar' } ],
|
|
||||||
itemsCount: 123,
|
|
||||||
itemsWeight: 123,
|
|
||||||
needsShipping: false,
|
|
||||||
shippingAddress: {
|
|
||||||
country: '',
|
|
||||||
state: '',
|
|
||||||
city: '',
|
|
||||||
postcode: '',
|
|
||||||
},
|
|
||||||
shippingRates: [
|
|
||||||
{
|
|
||||||
shippingRates: [ { foo: 'bar' } ],
|
|
||||||
destination: {
|
|
||||||
country: '',
|
|
||||||
state: '',
|
|
||||||
city: '',
|
|
||||||
postcode: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const setUpMocks = () => {
|
|
||||||
mocks = {
|
|
||||||
selectors: {
|
|
||||||
getCartData: jest.fn().mockReturnValue( mockCartData ),
|
|
||||||
getCartErrors: jest.fn().mockReturnValue( false ),
|
|
||||||
getCartTotals: jest.fn().mockReturnValue( 123 ),
|
|
||||||
areShippingRatesLoading: jest.fn().mockReturnValue( false ),
|
|
||||||
hasFinishedResolution: jest.fn().mockReturnValue( true ),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
registry.registerStore( storeKey, {
|
|
||||||
reducer: () => ( {} ),
|
|
||||||
selectors: mocks.selectors,
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach( () => {
|
|
||||||
registry = createRegistry();
|
|
||||||
mocks = {};
|
|
||||||
renderer = null;
|
|
||||||
setUpMocks();
|
|
||||||
} );
|
|
||||||
it( 'should return expected address provided by the store', () => {
|
|
||||||
const TestComponent = getTestComponent();
|
|
||||||
act( () => {
|
|
||||||
renderer = TestRenderer.create(
|
|
||||||
getWrappedComponents( TestComponent )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
|
|
||||||
const { shippingAddress } = getProps( renderer );
|
|
||||||
expect( shippingAddress ).toStrictEqual( mockCartData.shippingAddress );
|
|
||||||
// rerender
|
|
||||||
act( () => {
|
|
||||||
renderer.update( getWrappedComponents( TestComponent ) );
|
|
||||||
} );
|
|
||||||
// re-render should result in same shippingAddress object.
|
|
||||||
const { shippingAddress: newShippingAddress } = getProps( renderer );
|
|
||||||
expect( newShippingAddress ).toStrictEqual( shippingAddress );
|
|
||||||
renderer.unmount();
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'should return expected shipping rates provided by the store', () => {
|
|
||||||
const TestComponent = getTestComponent();
|
|
||||||
act( () => {
|
|
||||||
renderer = TestRenderer.create(
|
|
||||||
getWrappedComponents( TestComponent )
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
|
|
||||||
const { shippingRates } = getProps( renderer );
|
|
||||||
expect( shippingRates ).toStrictEqual( mockCartData.shippingRates );
|
|
||||||
// rerender
|
|
||||||
act( () => {
|
|
||||||
renderer.update( getWrappedComponents( TestComponent ) );
|
|
||||||
} );
|
|
||||||
// re-render should result in same shippingAddress object.
|
|
||||||
const { shippingRates: newShippingRates } = getProps( renderer );
|
|
||||||
expect( newShippingRates ).toStrictEqual( shippingRates );
|
|
||||||
renderer.unmount();
|
|
||||||
} );
|
|
||||||
} );
|
|
|
@ -6,27 +6,32 @@
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} StoreCart
|
* @typedef {Object} StoreCart
|
||||||
*
|
*
|
||||||
* @property {Array} cartCoupons An array of coupons applied
|
* @property {Array} cartCoupons An array of coupons applied
|
||||||
* to the cart.
|
* to the cart.
|
||||||
* @property {Array} shippingRates array of selected shipping
|
* @property {Array} cartItems An array of items in the
|
||||||
* rates
|
* cart.
|
||||||
* @property {CartShippingAddress} shippingAddress Shipping address for the
|
* @property {number} cartItemsCount The number of items in the
|
||||||
* cart.
|
* cart.
|
||||||
* @property {Array} cartItems An array of items in the
|
* @property {number} cartItemsWeight The weight of all items in
|
||||||
* cart.
|
* the cart.
|
||||||
* @property {number} cartItemsCount The number of items in the
|
* @property {boolean} cartNeedsShipping True when the cart will
|
||||||
* cart.
|
* require shipping.
|
||||||
* @property {number} cartItemsWeight The weight of all items in
|
* @property {Array} cartItemErrors Item validation errors.
|
||||||
* the cart.
|
* @property {Object} cartTotals Cart and line total
|
||||||
* @property {boolean} cartNeedsShipping True when the cart will
|
* amounts.
|
||||||
* require shipping.
|
* @property {boolean} cartIsLoading True when cart data is
|
||||||
* @property {Array} cartItemErrors Item validation errors.
|
* being loaded.
|
||||||
* @property {Object} cartTotals Cart and line total
|
* @property {Array} cartErrors An array of errors thrown
|
||||||
* amounts.
|
* by the cart.
|
||||||
* @property {boolean} cartIsLoading True when cart data is
|
* @property {CartShippingAddress} shippingAddress Shipping address for the
|
||||||
* being loaded.
|
* cart.
|
||||||
* @property {Array} cartErrors An array of errors thrown
|
* @property {Array} shippingRates array of selected shipping
|
||||||
* by the cart.
|
* rates.
|
||||||
|
* @property {boolean} shippingRatesLoading Whether or not the
|
||||||
|
* shipping rates are
|
||||||
|
* being loaded.
|
||||||
|
* @property {boolean} hasShippingAddress Whether or not the cart
|
||||||
|
* has a shipping address yet.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue