@automattic/shopping-cart
Version:
A library to use the WordPress.com shopping cart.
146 lines (135 loc) • 4.63 kB
text/typescript
import debugFactory from 'debug';
import { convertTempResponseCartToResponseCart } from './cart-functions';
import { getEmptyResponseCart } from './empty-carts';
import type {
ShoppingCartManager,
ShoppingCartState,
SavedActionPromise,
ShoppingCartManagerState,
ShoppingCartManagerGetState,
SubscribeCallback,
UnsubscribeFunction,
SubscriptionManager,
ActionPromises,
ShoppingCartManagerActions,
CartKey,
} from './types';
const debug = debugFactory( 'shopping-cart:managers' );
export function getShoppingCartManagerState( state: ShoppingCartState ): ShoppingCartManagerState {
const {
cacheStatus,
queuedActions,
couponStatus,
loadingErrorType,
loadingError,
lastValidResponseCart,
} = state;
const isLoading = cacheStatus === 'fresh' || cacheStatus === 'fresh-pending';
const isPendingUpdate = queuedActions.length > 0 || cacheStatus !== 'valid';
const loadingErrorForManager = cacheStatus === 'error' ? loadingError : null;
debug(
'manager isLoading',
isLoading,
'isPendingUpdate',
isPendingUpdate,
'loadingError',
loadingErrorForManager
);
return {
isLoading,
loadingError: loadingErrorForManager,
loadingErrorType,
isPendingUpdate,
couponStatus,
responseCart: lastValidResponseCart,
};
}
export function createSubscriptionManager( cartKey: CartKey | undefined ): SubscriptionManager {
let subscribedClients: SubscribeCallback[] = [];
const subscribe = ( callback: SubscribeCallback ): UnsubscribeFunction => {
debug( `adding subscriber for cartKey ${ cartKey }` );
subscribedClients.push( callback );
return () => {
debug( `removing subscriber for cartKey ${ cartKey }` );
subscribedClients = subscribedClients.filter( ( prevCallback ) => prevCallback !== callback );
};
};
const notifySubscribers = () => {
debug( `notifying ${ subscribedClients.length } subscribers for cartKey ${ cartKey }` );
subscribedClients.forEach( ( clientCallback ) => {
try {
clientCallback();
} catch ( err ) {
// eslint-disable-next-line no-console
console.error(
'An error ocurred while notifying a subscriber of a ShoppingCartManager change',
( err as Error ).message
);
throw err;
}
} );
debug( `completed notification of subscribers for cartKey ${ cartKey }` );
};
return { subscribe, notifySubscribers };
}
/**
* Create an object that manages the Promises returned by cart actions.
*
* When an action is requested, we create a Promise for that action dispatcher
* to return and store it in this object. Later when we know if the action
* resulted in a success or a failure, the resolve or reject methods are called
* on this object; those methods then resolve or reject each Promise that was
* waiting for a response.
*/
export function createActionPromisesManager(): ActionPromises {
let actionPromises: SavedActionPromise[] = [];
return {
resolve( tempResponseCart ) {
if ( actionPromises.length > 0 ) {
debug( `resolving ${ actionPromises.length } action promises` );
const responseCart = convertTempResponseCartToResponseCart( tempResponseCart );
actionPromises.forEach( ( actionPromise ) => actionPromise.resolve( responseCart ) );
actionPromises = [];
}
},
reject( error ) {
if ( actionPromises.length > 0 ) {
debug( `rejecting ${ actionPromises.length } action promises` );
actionPromises.forEach( ( actionPromise ) => actionPromise.reject( error ) );
actionPromises = [];
}
},
add( actionPromise: SavedActionPromise ) {
actionPromises.push( actionPromise );
},
};
}
const emptyCart = getEmptyResponseCart();
const noopCartAction = (): ReturnType< ShoppingCartManagerActions[ 'addProductsToCart' ] > =>
Promise.reject( 'Cart actions cannot be taken without a cart key.' );
const noopState: ShoppingCartManagerState = {
isLoading: true,
loadingError: undefined,
loadingErrorType: undefined,
isPendingUpdate: true,
couponStatus: 'fresh',
responseCart: emptyCart,
};
const noopGetState: ShoppingCartManagerGetState = () => noopState;
const noopActions: ShoppingCartManagerActions = {
addProductsToCart: noopCartAction,
removeProductFromCart: noopCartAction,
applyCoupon: noopCartAction,
removeCoupon: noopCartAction,
updateLocation: noopCartAction,
replaceProductInCart: noopCartAction,
replaceProductsInCart: noopCartAction,
reloadFromServer: () => Promise.resolve( emptyCart ),
clearMessages: () => Promise.resolve( emptyCart ),
};
export const noopManager: ShoppingCartManager = {
actions: noopActions,
getState: noopGetState,
subscribe: () => () => null,
fetchInitialCart: () => Promise.resolve( emptyCart ),
};