This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
1,203 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { TYPES } from './constants'; | ||
|
||
const { | ||
SET_PRISTINE, | ||
SET_PROCESSING, | ||
SET_REDIRECT_URL, | ||
SET_COMPLETE, | ||
SET_HAS_ERROR, | ||
SET_NO_ERROR, | ||
INCREMENT_CALCULATING, | ||
DECREMENT_CALCULATING, | ||
} = TYPES; | ||
|
||
export const actions = { | ||
setPristine: () => ( { | ||
type: SET_PRISTINE, | ||
} ), | ||
setProcessing: () => ( { | ||
type: SET_PROCESSING, | ||
} ), | ||
setRedirectUrl: ( url ) => ( { | ||
type: SET_REDIRECT_URL, | ||
url, | ||
} ), | ||
setComplete: () => ( { | ||
type: SET_COMPLETE, | ||
} ), | ||
setHasError: () => ( { | ||
type: SET_HAS_ERROR, | ||
} ), | ||
clearError: () => ( { | ||
type: SET_NO_ERROR, | ||
} ), | ||
incrementCalculating: () => ( { | ||
type: INCREMENT_CALCULATING, | ||
} ), | ||
decrementCalculating: () => ( { | ||
type: DECREMENT_CALCULATING, | ||
} ), | ||
}; |
31 changes: 31 additions & 0 deletions
31
assets/js/base/context/cart-checkout/checkout/constants.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* @type {import("@woocommerce/type-defs/checkout").CheckoutStatusConstants} | ||
*/ | ||
export const STATUS = { | ||
PRISTINE: 'pristine', | ||
IDLE: 'idle', | ||
CALCULATING: 'calculating', | ||
PROCESSING: 'processing', | ||
COMPLETE: 'complete', | ||
}; | ||
|
||
export const DEFAULT_STATE = { | ||
redirectUrl: '', | ||
status: STATUS.PRISTINE, | ||
// this is used by the reducer to set what status the state will | ||
// take after calculating is complete. | ||
nextStatus: STATUS.IDLE, | ||
hasError: false, | ||
calculatingCount: 0, | ||
}; | ||
|
||
export const TYPES = { | ||
SET_PRISTINE: 'set_pristine', | ||
SET_REDIRECT_URL: 'set_redirect_url', | ||
SET_COMPLETE: 'set_checkout_complete', | ||
SET_PROCESSING: 'set_checkout_is_processing', | ||
SET_HAS_ERROR: 'set_checkout_has_error', | ||
SET_NO_ERROR: 'set_checkout_no_error', | ||
INCREMENT_CALCULATING: 'increment_calculating', | ||
DECREMENT_CALCULATING: 'decrement_calculating', | ||
}; |
73 changes: 73 additions & 0 deletions
73
assets/js/base/context/cart-checkout/checkout/event-emit.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { actions, reducer, emitEvent } from '../event_emit'; | ||
|
||
const EMIT_TYPES = { | ||
CHECKOUT_COMPLETE_WITH_SUCCESS: 'checkout_complete', | ||
CHECKOUT_COMPLETE_WITH_ERROR: 'checkout_complete_error', | ||
CHECKOUT_PROCESSING: 'checkout_processing', | ||
}; | ||
|
||
/** | ||
* Receives a reducer dispatcher and returns an object with the | ||
* onCheckoutComplete callback registration function for the checkout emit | ||
* events. | ||
* | ||
* Calling the event registration function with the callback will register it | ||
* for the event emitter and will return a dispatcher for removing the | ||
* registered callback (useful for implementation in `useEffect`). | ||
* | ||
* @param {Function} dispatcher The emitter reducer dispatcher. | ||
* | ||
* @return {Object} An object with the `onCheckoutComplete` emmitter registration | ||
*/ | ||
const emitterSubscribers = ( dispatcher ) => ( { | ||
onCheckoutCompleteSuccess: ( callback ) => { | ||
const action = actions.addEventCallback( | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS, | ||
callback | ||
); | ||
dispatcher( action ); | ||
return () => { | ||
dispatcher( | ||
actions.removeEventCallback( | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS, | ||
action.id | ||
) | ||
); | ||
}; | ||
}, | ||
onCheckoutCompleteError: ( callback ) => { | ||
const action = actions.addEventCallback( | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR, | ||
callback | ||
); | ||
dispatcher( action ); | ||
return () => { | ||
dispatcher( | ||
actions.removeEventCallback( | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR, | ||
action.id | ||
) | ||
); | ||
}; | ||
}, | ||
onCheckoutProcessing: ( callback ) => { | ||
const action = actions.addEventCallback( | ||
EMIT_TYPES.CHECKOUT_PROCESSING, | ||
callback | ||
); | ||
dispatcher( action ); | ||
return () => { | ||
dispatcher( | ||
actions.removeEventCallback( | ||
EMIT_TYPES.CHECKOUT_PROCESSING, | ||
action.id | ||
) | ||
); | ||
}; | ||
}, | ||
} ); | ||
|
||
export { EMIT_TYPES, emitterSubscribers, reducer, emitEvent }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { PaymentMethodDataProvider } from '../payment-methods'; | ||
import { ShippingMethodDataProvider } from '../shipping'; | ||
import { actions } from './actions'; | ||
import { reducer } from './reducer'; | ||
import { DEFAULT_STATE, STATUS } from './constants'; | ||
import { | ||
EMIT_TYPES, | ||
emitterSubscribers, | ||
emitEvent, | ||
reducer as emitReducer, | ||
} from './event-emit'; | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import { | ||
createContext, | ||
useContext, | ||
useReducer, | ||
useRef, | ||
useMemo, | ||
useEffect, | ||
} from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* @typedef {import('@woocommerce/type-defs/checkout').CheckoutDispatchActions} CheckoutDispatchActions | ||
* @typedef {import('@woocommerce/type-defs/contexts').CheckoutDataContext} CheckoutDataContext | ||
*/ | ||
|
||
const CheckoutContext = createContext( { | ||
submitLabel: '', | ||
onSubmit: () => void null, | ||
isComplete: false, | ||
isIdle: false, | ||
isCalculating: false, | ||
isProcessing: false, | ||
hasError: false, | ||
redirectUrl: '', | ||
onCheckoutCompleteSuccess: () => void null, | ||
onCheckoutCompleteError: () => void null, | ||
onCheckoutProcessing: () => void null, | ||
dispatchActions: { | ||
resetCheckout: () => void null, | ||
setRedirectUrl: () => void null, | ||
setHasError: () => void null, | ||
clearError: () => void null, | ||
incrementCalculating: () => void null, | ||
decrementCalculating: () => void null, | ||
}, | ||
} ); | ||
|
||
/** | ||
* @return {CheckoutDataContext} Returns the checkout data context value | ||
*/ | ||
export const useCheckoutContext = () => { | ||
return useContext( CheckoutContext ); | ||
}; | ||
|
||
// create context provider for coupons | ||
|
||
// todo have a local state (reducer) for validation error objects (see checkout processing event). | ||
// this will need exposed for checkout/cart steps to read from when checkout.hasError to be able | ||
// to show what fields need addressed. | ||
|
||
// checkout and cart provider context will be different context but implement | ||
// the above (in terms of finalization.). For example cart context will redirect | ||
// checkout for finalization. Checkout context handles pinging the server for | ||
// processing checkout, validation of existing fields, and redirecting on | ||
// success. | ||
|
||
export const CheckoutProvider = ( { | ||
children, | ||
activePaymentMethod: initialActivePaymentMethod, | ||
redirectUrl, | ||
submitLabel = __( 'Place Order', 'woo-gutenberg-product-block' ), | ||
} ) => { | ||
// note, this is done intentionally so that the default state now has | ||
// the redirectUrl for when checkout is reset to PRISTINE state. | ||
DEFAULT_STATE.redirectUrl = redirectUrl; | ||
const [ checkoutState, dispatch ] = useReducer( reducer, DEFAULT_STATE ); | ||
const [ observers, subscriber ] = useReducer( emitReducer, {} ); | ||
const currentObservers = useRef( observers ); | ||
// set observers on ref so it's always current | ||
useEffect( () => { | ||
currentObservers.current = observers; | ||
}, [ observers ] ); | ||
const onCheckoutCompleteSuccess = emitterSubscribers( subscriber ) | ||
.onCheckoutCompleteSuccess; | ||
const onCheckoutCompleteError = emitterSubscribers( subscriber ) | ||
.onCheckoutCompleteError; | ||
const onCheckoutProcessing = emitterSubscribers( subscriber ) | ||
.onCheckoutProcessing; | ||
|
||
/** | ||
* @type {CheckoutDispatchActions} | ||
*/ | ||
const dispatchActions = useMemo( | ||
() => ( { | ||
resetCheckout: () => void dispatch( actions.setPristine() ), | ||
setRedirectUrl: ( url ) => | ||
void dispatch( actions.setRedirectUrl( url ) ), | ||
setHasError: () => void dispatch( actions.setHasError() ), | ||
clearError: () => void dispatch( actions.clearError() ), | ||
incrementCalculating: () => | ||
void dispatch( actions.incrementCalculating() ), | ||
decrementCalculating: () => | ||
void dispatch( actions.decrementCalculating() ), | ||
} ), | ||
[] | ||
); | ||
|
||
// emit events | ||
useEffect( () => { | ||
const status = checkoutState.status; | ||
if ( status === STATUS.PROCESSING ) { | ||
const error = emitEvent( | ||
currentObservers.current, | ||
EMIT_TYPES.CHECKOUT_PROCESSING, | ||
{} | ||
); | ||
//@todo bail if error object detected (see flow). | ||
//Fire off checkoutFail event, and then reset checkout | ||
//status to idle (with hasError flag) - then return from this hook. | ||
// Finally after the event subscribers have processed, do the | ||
// checkout submit sending the order to the server for processing | ||
// and followup on errors from it. | ||
if ( error ) { | ||
dispatchActions.setHasError(); | ||
} | ||
dispatch( actions.setComplete() ); | ||
} | ||
if ( checkoutState.isComplete ) { | ||
if ( checkoutState.hasError ) { | ||
emitEvent( | ||
currentObservers.current, | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_ERROR, | ||
{} | ||
); | ||
} else { | ||
emitEvent( | ||
currentObservers.current, | ||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS, | ||
{} | ||
); | ||
} | ||
// all observers have done their thing so let's redirect (if no error) | ||
if ( ! checkoutState.hasError ) { | ||
window.location = checkoutState.redirectUrl; | ||
} | ||
} | ||
}, [ checkoutState.status, checkoutState.hasError ] ); | ||
|
||
const onSubmit = () => { | ||
dispatch( actions.setProcessing() ); | ||
}; | ||
|
||
/** | ||
* @type {CheckoutDataContext} | ||
*/ | ||
const checkoutData = { | ||
submitLabel, | ||
onSubmit, | ||
isComplete: checkoutState.status === STATUS.COMPLETE, | ||
isIdle: checkoutState.status === STATUS.IDLE, | ||
isCalculating: checkoutState.status === STATUS.CALCULATING, | ||
isProcessing: checkoutState.status === STATUS.PROCESSING, | ||
hasError: checkoutState.hasError, | ||
redirectUrl: checkoutState.redirectUrl, | ||
onCheckoutCompleteSuccess, | ||
onCheckoutCompleteError, | ||
onCheckoutProcessing, | ||
dispatchActions, | ||
}; | ||
return ( | ||
<CheckoutContext.Provider value={ checkoutData }> | ||
<PaymentMethodDataProvider | ||
activePaymentMethod={ initialActivePaymentMethod } | ||
> | ||
<ShippingMethodDataProvider> | ||
{ children } | ||
</ShippingMethodDataProvider> | ||
</PaymentMethodDataProvider> | ||
</CheckoutContext.Provider> | ||
); | ||
}; |
Oops, something went wrong.