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';
|
2020-03-27 11:14:32 +00:00
|
|
|
import {
|
|
|
|
useShippingAddress,
|
|
|
|
useStoreCart,
|
2020-03-31 15:40:27 +00:00
|
|
|
useSelectShippingRate,
|
2020-03-27 11:14:32 +00:00
|
|
|
} from '@woocommerce/base-hooks';
|
2020-03-26 13:31:09 +00:00
|
|
|
import { useCheckoutContext } from '@woocommerce/base-context';
|
|
|
|
|
2020-03-27 11:14:32 +00:00
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2020-05-14 23:55:22 +00:00
|
|
|
import {
|
|
|
|
ERROR_TYPES,
|
|
|
|
DEFAULT_SHIPPING_CONTEXT_DATA,
|
|
|
|
shippingErrorCodes,
|
|
|
|
} from './constants';
|
2020-03-27 11:14:32 +00:00
|
|
|
import {
|
|
|
|
EMIT_TYPES,
|
|
|
|
emitterSubscribers,
|
|
|
|
reducer as emitReducer,
|
|
|
|
emitEvent,
|
|
|
|
} from './event-emit';
|
|
|
|
|
2020-03-26 13:31:09 +00:00
|
|
|
/**
|
|
|
|
* @typedef {import('@woocommerce/type-defs/contexts').ShippingDataContext} ShippingDataContext
|
|
|
|
*/
|
|
|
|
|
|
|
|
const { NONE, INVALID_ADDRESS, UNKNOWN } = ERROR_TYPES;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reducer for shipping status state
|
|
|
|
*
|
|
|
|
* @param {string} state The current status.
|
|
|
|
* @param {Object} action The incoming action.
|
|
|
|
*/
|
|
|
|
const errorStatusReducer = ( state, { type } ) => {
|
2020-04-08 15:46:34 +00:00
|
|
|
if ( Object.values( ERROR_TYPES ).includes( type ) ) {
|
|
|
|
return type;
|
2020-03-26 13:31:09 +00:00
|
|
|
}
|
2020-04-08 15:46:34 +00:00
|
|
|
return state;
|
2020-03-26 13:31:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const ShippingDataContext = createContext( DEFAULT_SHIPPING_CONTEXT_DATA );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {ShippingDataContext} Returns data and functions related to
|
2020-03-31 15:40:27 +00:00
|
|
|
* shipping methods.
|
2020-03-26 13:31:09 +00:00
|
|
|
*/
|
|
|
|
export const useShippingDataContext = () => {
|
|
|
|
return useContext( ShippingDataContext );
|
|
|
|
};
|
|
|
|
|
2020-05-14 23:55:22 +00:00
|
|
|
const hasInvalidShippingAddress = ( errors ) => {
|
|
|
|
return errors.some( ( error ) => {
|
|
|
|
if (
|
|
|
|
error.code &&
|
|
|
|
Object.values( shippingErrorCodes ).includes( error.code )
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2020-03-26 13:31:09 +00:00
|
|
|
/**
|
2020-03-31 15:40:27 +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-03-26 13:31:09 +00:00
|
|
|
*/
|
|
|
|
export const ShippingDataProvider = ( { children } ) => {
|
|
|
|
const { dispatchActions } = useCheckoutContext();
|
2020-04-08 11:20:41 +00:00
|
|
|
const {
|
|
|
|
cartNeedsShipping: needsShipping,
|
|
|
|
shippingRates,
|
|
|
|
shippingRatesLoading,
|
2020-05-14 23:55:22 +00:00
|
|
|
cartErrors,
|
2020-04-08 11:20:41 +00:00
|
|
|
} = useStoreCart();
|
2020-03-26 13:31:09 +00:00
|
|
|
const [ shippingErrorStatus, dispatchErrorStatus ] = useReducer(
|
|
|
|
errorStatusReducer,
|
|
|
|
NONE
|
|
|
|
);
|
|
|
|
const [ observers, subscriber ] = useReducer( emitReducer, {} );
|
2020-03-27 11:14:32 +00:00
|
|
|
const { shippingAddress, setShippingAddress } = useShippingAddress();
|
2020-03-26 13:31:09 +00:00
|
|
|
const currentObservers = useRef( observers );
|
2020-03-31 15:40:27 +00:00
|
|
|
const {
|
|
|
|
selectShippingRate: setSelectedRates,
|
|
|
|
selectedShippingRates: selectedRates,
|
|
|
|
isSelectingRate,
|
|
|
|
} = useSelectShippingRate( shippingRates );
|
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 ] );
|
|
|
|
|
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
|
|
|
|
2020-03-31 15:40:27 +00:00
|
|
|
// increment/decrement checkout calculating counts when shipping rates are
|
|
|
|
// being selected.
|
|
|
|
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( () => {
|
|
|
|
if ( ! isSelectingRate && currentErrorStatus.hasError ) {
|
|
|
|
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
|
|
|
);
|
2020-04-08 16:36:04 +00:00
|
|
|
}
|
2020-05-14 23:55:22 +00:00
|
|
|
}, [
|
|
|
|
selectedRates,
|
|
|
|
isSelectingRate,
|
|
|
|
currentErrorStatus.hasError,
|
|
|
|
currentErrorStatus.hasInvalidAddress,
|
|
|
|
] );
|
2020-04-08 16:36:04 +00:00
|
|
|
|
|
|
|
useEffect( () => {
|
|
|
|
if (
|
|
|
|
! isSelectingRate &&
|
|
|
|
selectedRates &&
|
|
|
|
! currentErrorStatus.hasError
|
|
|
|
) {
|
2020-03-31 15:40:27 +00:00
|
|
|
emitEvent(
|
|
|
|
currentObservers.current,
|
|
|
|
EMIT_TYPES.SHIPPING_RATE_SELECT_SUCCESS,
|
|
|
|
selectedRates
|
|
|
|
);
|
|
|
|
}
|
2020-04-08 16:36:04 +00:00
|
|
|
}, [ selectedRates, isSelectingRate, currentErrorStatus.hasError ] );
|
2020-03-31 15:40:27 +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,
|
|
|
|
setShippingRates: setSelectedRates,
|
|
|
|
shippingRatesLoading,
|
2020-03-26 13:31:09 +00:00
|
|
|
selectedRates,
|
|
|
|
setSelectedRates,
|
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,
|
2020-04-08 16:36:04 +00:00
|
|
|
onShippingRateSuccess: eventSubscribers.onShippingRateSuccess,
|
|
|
|
onShippingRateFail: eventSubscribers.onShippingRateFail,
|
|
|
|
onShippingRateSelectSuccess:
|
|
|
|
eventSubscribers.onShippingRateSelectSuccess,
|
|
|
|
onShippingRateSelectFail: eventSubscribers.onShippingRateSelectFail,
|
2020-03-26 13:31:09 +00:00
|
|
|
needsShipping,
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ShippingDataContext.Provider value={ ShippingData }>
|
|
|
|
{ children }
|
|
|
|
</ShippingDataContext.Provider>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|