2020-03-26 13:31:09 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
useContext,
|
|
|
|
useReducer,
|
|
|
|
useEffect,
|
2020-04-06 20:36:19 +00:00
|
|
|
useMemo,
|
2020-03-26 13:31:09 +00:00
|
|
|
useRef,
|
|
|
|
} from '@wordpress/element';
|
2021-01-28 14:24:01 +00:00
|
|
|
import { useStoreCart, useSelectShippingRates } from '@woocommerce/base-hooks';
|
|
|
|
import isShallowEqual from '@wordpress/is-shallow-equal';
|
|
|
|
import { deriveSelectedShippingRates } from '@woocommerce/base-utils';
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2020-03-27 11:14:32 +00:00
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2021-01-28 14:24:01 +00:00
|
|
|
import { ERROR_TYPES, DEFAULT_SHIPPING_CONTEXT_DATA } from './constants';
|
|
|
|
import { hasInvalidShippingAddress } from './utils';
|
|
|
|
import { errorStatusReducer } from './reducers';
|
2020-03-27 11:14:32 +00:00
|
|
|
import {
|
|
|
|
EMIT_TYPES,
|
|
|
|
emitterSubscribers,
|
|
|
|
reducer as emitReducer,
|
|
|
|
emitEvent,
|
|
|
|
} from './event-emit';
|
2021-01-19 15:55:44 +00:00
|
|
|
import { useCheckoutContext } from '../checkout-state';
|
|
|
|
import { useCustomerDataContext } from '../customer';
|
2020-03-27 11:14:32 +00:00
|
|
|
|
2020-03-26 13:31:09 +00:00
|
|
|
/**
|
|
|
|
* @typedef {import('@woocommerce/type-defs/contexts').ShippingDataContext} ShippingDataContext
|
2020-09-20 23:54:08 +00:00
|
|
|
* @typedef {import('react')} React
|
2020-03-26 13:31:09 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
const { NONE, INVALID_ADDRESS, UNKNOWN } = ERROR_TYPES;
|
|
|
|
const ShippingDataContext = createContext( DEFAULT_SHIPPING_CONTEXT_DATA );
|
|
|
|
|
|
|
|
/**
|
2021-01-28 14:24:01 +00:00
|
|
|
* @return {ShippingDataContext} Returns data and functions related to shipping methods.
|
2020-03-26 13:31:09 +00:00
|
|
|
*/
|
|
|
|
export const useShippingDataContext = () => {
|
|
|
|
return useContext( ShippingDataContext );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2021-01-28 14:24:01 +00:00
|
|
|
* The shipping data provider exposes the interface for shipping in the checkout/cart.
|
2020-03-26 13:31:09 +00:00
|
|
|
*
|
2020-03-31 15:40:27 +00:00
|
|
|
* @param {Object} props Incoming props for provider
|
2020-09-20 23:54:08 +00:00
|
|
|
* @param {React.ReactElement} props.children
|
2020-03-26 13:31:09 +00:00
|
|
|
*/
|
|
|
|
export const ShippingDataProvider = ( { children } ) => {
|
|
|
|
const { dispatchActions } = useCheckoutContext();
|
2020-11-20 15:13:35 +00:00
|
|
|
const { shippingAddress, setShippingAddress } = useCustomerDataContext();
|
2020-04-08 11:20:41 +00:00
|
|
|
const {
|
|
|
|
cartNeedsShipping: needsShipping,
|
2020-11-17 11:58:38 +00:00
|
|
|
cartHasCalculatedShipping: hasCalculatedShipping,
|
2020-04-08 11:20:41 +00:00
|
|
|
shippingRates,
|
|
|
|
shippingRatesLoading,
|
2020-05-14 23:55:22 +00:00
|
|
|
cartErrors,
|
2020-04-08 11:20:41 +00:00
|
|
|
} = useStoreCart();
|
2021-01-28 14:24:01 +00:00
|
|
|
const { selectShippingRate, isSelectingRate } = useSelectShippingRates();
|
2020-03-26 13:31:09 +00:00
|
|
|
const [ shippingErrorStatus, dispatchErrorStatus ] = useReducer(
|
|
|
|
errorStatusReducer,
|
|
|
|
NONE
|
|
|
|
);
|
|
|
|
const [ observers, subscriber ] = useReducer( emitReducer, {} );
|
|
|
|
const currentObservers = useRef( observers );
|
2020-04-08 16:36:04 +00:00
|
|
|
const eventSubscribers = useMemo(
|
|
|
|
() => ( {
|
|
|
|
onShippingRateSuccess: emitterSubscribers( subscriber ).onSuccess,
|
|
|
|
onShippingRateFail: emitterSubscribers( subscriber ).onFail,
|
|
|
|
onShippingRateSelectSuccess: emitterSubscribers( subscriber )
|
|
|
|
.onSelectSuccess,
|
|
|
|
onShippingRateSelectFail: emitterSubscribers( subscriber )
|
|
|
|
.onSelectFail,
|
|
|
|
} ),
|
|
|
|
[ subscriber ]
|
|
|
|
);
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2020-03-31 15:40:27 +00:00
|
|
|
// set observers on ref so it's always current.
|
2020-03-26 13:31:09 +00:00
|
|
|
useEffect( () => {
|
|
|
|
currentObservers.current = observers;
|
|
|
|
}, [ observers ] );
|
|
|
|
|
2021-01-28 14:24:01 +00:00
|
|
|
// set selected rates on ref so it's always current.
|
|
|
|
const selectedRates = useRef( () =>
|
|
|
|
deriveSelectedShippingRates( shippingRates )
|
|
|
|
);
|
|
|
|
useEffect( () => {
|
|
|
|
const derivedSelectedRates = deriveSelectedShippingRates(
|
|
|
|
shippingRates
|
|
|
|
);
|
|
|
|
if ( ! isShallowEqual( selectedRates.current, derivedSelectedRates ) ) {
|
|
|
|
selectedRates.current = derivedSelectedRates;
|
|
|
|
}
|
|
|
|
}, [ shippingRates ] );
|
|
|
|
|
2020-03-31 15:40:27 +00:00
|
|
|
// increment/decrement checkout calculating counts when shipping is loading.
|
2020-03-26 13:31:09 +00:00
|
|
|
useEffect( () => {
|
2020-03-31 15:40:27 +00:00
|
|
|
if ( shippingRatesLoading ) {
|
2020-03-26 13:31:09 +00:00
|
|
|
dispatchActions.incrementCalculating();
|
|
|
|
} else {
|
|
|
|
dispatchActions.decrementCalculating();
|
|
|
|
}
|
2020-05-14 23:55:22 +00:00
|
|
|
}, [ shippingRatesLoading, dispatchActions ] );
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2021-01-28 14:24:01 +00:00
|
|
|
// increment/decrement checkout calculating counts when shipping rates are being selected.
|
2020-03-31 15:40:27 +00:00
|
|
|
useEffect( () => {
|
|
|
|
if ( isSelectingRate ) {
|
|
|
|
dispatchActions.incrementCalculating();
|
|
|
|
} else {
|
|
|
|
dispatchActions.decrementCalculating();
|
2020-03-26 13:31:09 +00:00
|
|
|
}
|
2020-05-14 23:55:22 +00:00
|
|
|
}, [ isSelectingRate, dispatchActions ] );
|
|
|
|
|
|
|
|
// set shipping error status if there are shipping error codes
|
|
|
|
useEffect( () => {
|
|
|
|
if (
|
|
|
|
cartErrors.length > 0 &&
|
|
|
|
hasInvalidShippingAddress( cartErrors )
|
|
|
|
) {
|
|
|
|
dispatchErrorStatus( { type: INVALID_ADDRESS } );
|
|
|
|
} else {
|
|
|
|
dispatchErrorStatus( { type: NONE } );
|
|
|
|
}
|
|
|
|
}, [ cartErrors ] );
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2020-04-06 20:36:19 +00:00
|
|
|
const currentErrorStatus = useMemo(
|
|
|
|
() => ( {
|
|
|
|
isPristine: shippingErrorStatus === NONE,
|
|
|
|
isValid: shippingErrorStatus === NONE,
|
|
|
|
hasInvalidAddress: shippingErrorStatus === INVALID_ADDRESS,
|
|
|
|
hasError:
|
|
|
|
shippingErrorStatus === UNKNOWN ||
|
|
|
|
shippingErrorStatus === INVALID_ADDRESS,
|
|
|
|
} ),
|
|
|
|
[ shippingErrorStatus ]
|
|
|
|
);
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2020-03-31 15:40:27 +00:00
|
|
|
// emit events.
|
2020-03-26 13:31:09 +00:00
|
|
|
useEffect( () => {
|
2020-05-14 23:55:22 +00:00
|
|
|
if (
|
|
|
|
! shippingRatesLoading &&
|
|
|
|
( shippingRates.length === 0 || currentErrorStatus.hasError )
|
|
|
|
) {
|
2020-03-26 13:31:09 +00:00
|
|
|
emitEvent(
|
|
|
|
currentObservers.current,
|
2020-03-31 15:40:27 +00:00
|
|
|
EMIT_TYPES.SHIPPING_RATES_FAIL,
|
2020-05-14 23:55:22 +00:00
|
|
|
{
|
|
|
|
hasInvalidAddress: currentErrorStatus.hasInvalidAddress,
|
|
|
|
hasError: currentErrorStatus.hasError,
|
|
|
|
}
|
2020-03-26 13:31:09 +00:00
|
|
|
);
|
2020-04-08 16:36:04 +00:00
|
|
|
}
|
2020-05-14 23:55:22 +00:00
|
|
|
}, [
|
|
|
|
shippingRates,
|
|
|
|
shippingRatesLoading,
|
|
|
|
currentErrorStatus.hasError,
|
|
|
|
currentErrorStatus.hasInvalidAddress,
|
|
|
|
] );
|
2020-04-08 16:36:04 +00:00
|
|
|
|
|
|
|
useEffect( () => {
|
|
|
|
if (
|
|
|
|
! shippingRatesLoading &&
|
2020-05-14 23:55:22 +00:00
|
|
|
shippingRates.length > 0 &&
|
2020-04-08 16:36:04 +00:00
|
|
|
! currentErrorStatus.hasError
|
|
|
|
) {
|
2020-03-26 13:31:09 +00:00
|
|
|
emitEvent(
|
|
|
|
currentObservers.current,
|
|
|
|
EMIT_TYPES.SHIPPING_RATES_SUCCESS,
|
2020-03-31 15:40:27 +00:00
|
|
|
shippingRates
|
2020-03-26 13:31:09 +00:00
|
|
|
);
|
|
|
|
}
|
2020-04-08 16:36:04 +00:00
|
|
|
}, [ shippingRates, shippingRatesLoading, currentErrorStatus.hasError ] );
|
2020-03-26 13:31:09 +00:00
|
|
|
|
2020-03-31 15:40:27 +00:00
|
|
|
// emit shipping rate selection events.
|
|
|
|
useEffect( () => {
|
2021-01-28 14:24:01 +00:00
|
|
|
if ( isSelectingRate ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( currentErrorStatus.hasError ) {
|
2020-03-31 15:40:27 +00:00
|
|
|
emitEvent(
|
|
|
|
currentObservers.current,
|
|
|
|
EMIT_TYPES.SHIPPING_RATE_SELECT_FAIL,
|
2020-05-14 23:55:22 +00:00
|
|
|
{
|
|
|
|
hasError: currentErrorStatus.hasError,
|
|
|
|
hasInvalidAddress: currentErrorStatus.hasInvalidAddress,
|
|
|
|
}
|
2020-03-31 15:40:27 +00:00
|
|
|
);
|
2021-01-28 14:24:01 +00:00
|
|
|
} else {
|
|
|
|
emitEvent(
|
|
|
|
currentObservers.current,
|
|
|
|
EMIT_TYPES.SHIPPING_RATE_SELECT_SUCCESS,
|
|
|
|
selectedRates.current
|
|
|
|
);
|
2020-04-08 16:36:04 +00:00
|
|
|
}
|
2020-05-14 23:55:22 +00:00
|
|
|
}, [
|
|
|
|
isSelectingRate,
|
|
|
|
currentErrorStatus.hasError,
|
|
|
|
currentErrorStatus.hasInvalidAddress,
|
|
|
|
] );
|
2020-04-08 16:36:04 +00:00
|
|
|
|
2020-03-26 13:31:09 +00:00
|
|
|
/**
|
|
|
|
* @type {ShippingDataContext}
|
|
|
|
*/
|
|
|
|
const ShippingData = {
|
2020-04-06 20:36:19 +00:00
|
|
|
shippingErrorStatus: currentErrorStatus,
|
2020-03-26 13:31:09 +00:00
|
|
|
dispatchErrorStatus,
|
|
|
|
shippingErrorTypes: ERROR_TYPES,
|
2020-03-31 15:40:27 +00:00
|
|
|
shippingRates,
|
|
|
|
shippingRatesLoading,
|
2021-01-28 14:24:01 +00:00
|
|
|
selectedRates: selectedRates.current,
|
|
|
|
setSelectedRates: selectShippingRate,
|
2020-04-08 16:36:04 +00:00
|
|
|
isSelectingRate,
|
2020-03-27 11:14:32 +00:00
|
|
|
shippingAddress,
|
2020-03-26 13:31:09 +00:00
|
|
|
setShippingAddress,
|
|
|
|
needsShipping,
|
2020-11-17 11:58:38 +00:00
|
|
|
hasCalculatedShipping,
|
2021-01-28 14:24:01 +00:00
|
|
|
...eventSubscribers,
|
2020-03-26 13:31:09 +00:00
|
|
|
};
|
2021-01-28 14:24:01 +00:00
|
|
|
|
2020-03-26 13:31:09 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ShippingDataContext.Provider value={ ShippingData }>
|
|
|
|
{ children }
|
|
|
|
</ShippingDataContext.Provider>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|