UNPKG

@automattic/shopping-cart

Version:
303 lines 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getInitialShoppingCartState = exports.reducerWithQueue = exports.isStatePendingUpdateOrQueuedAction = void 0; const tslib_1 = require("tslib"); const debug_1 = tslib_1.__importDefault(require("debug")); const cart_functions_1 = require("./cart-functions"); const empty_carts_1 = require("./empty-carts"); const debug = (0, debug_1.default)('shopping-cart:shopping-cart-reducer'); const emptyResponseCart = (0, empty_carts_1.getEmptyResponseCart)(); const alwaysAllowedActions = [ 'RECEIVE_INITIAL_RESPONSE_CART', 'RECEIVE_UPDATED_RESPONSE_CART', 'FETCH_INITIAL_RESPONSE_CART', 'RAISE_ERROR', ]; const cacheStatusesForQueueing = ['fresh', 'pending', 'fresh-pending']; const cacheStatusesForIgnoringReload = [ 'invalid', 'fresh', 'pending', 'fresh-pending', ]; function shouldPlayQueuedActions(state) { return state.queuedActions.length > 0 && state.cacheStatus === 'valid'; } function isStatePendingUpdateOrQueuedAction(state) { return state.queuedActions.length > 0 || state.cacheStatus !== 'valid'; } exports.isStatePendingUpdateOrQueuedAction = isStatePendingUpdateOrQueuedAction; function shouldQueueReducerEvent(cacheStatus, action) { if (alwaysAllowedActions.includes(action.type)) { return false; } if (cacheStatusesForQueueing.includes(cacheStatus)) { return true; } return false; } function reducerWithQueue(state, action) { if (cacheStatusesForIgnoringReload.includes(state.cacheStatus) && action.type === 'CART_RELOAD') { debug('cart is pending an operation; ignoring reload action'); return state; } // If the cacheStatus is 'fresh' or 'pending', then the initial cart has not // yet loaded and so we cannot make changes to it yet. We therefore will // queue any action that comes through during that time except for // 'RECEIVE_INITIAL_RESPONSE_CART' or 'RAISE_ERROR'. if (shouldQueueReducerEvent(state.cacheStatus, action)) { debug('cart has not yet loaded; queuing requested action', action); return { ...state, queuedActions: [...state.queuedActions, action], }; } const lastState = state; state = shoppingCartReducer(state, action); if (shouldPlayQueuedActions(state)) { debug('playing queued actions', state.queuedActions); const actions = [ ...state.queuedActions, { type: 'CLEAR_QUEUED_ACTIONS' }, ]; state = actions.reduce(shoppingCartReducer, state); debug('queued actions are dispatched and queue is cleared'); } // When an action is dispatched that modifies the cart, the reducer modifies // the `responseCart` stored in state. That data is then sent off to the // server to be validated and filled-in with additional properties before // being returned to the ShoppingCartManager. Because of this, optimistic // updating of the cart is not possible so we don't want to return the raw // `responseCart` to the consumer. Instead, we keep a copy of the // `responseCart` the last time the state had a `valid` CacheStatus and pass // that to our consumers. The consumers can use `isPendingUpdate` to know // when the cart data is updating. if (state !== lastState && !isStatePendingUpdateOrQueuedAction(state)) { state = shoppingCartReducer(state, { type: 'UPDATE_LAST_VALID_CART' }); } return state; } exports.reducerWithQueue = reducerWithQueue; function shoppingCartReducer(state, action) { debug('processing requested action', action); const couponStatus = state.couponStatus; switch (action.type) { case 'UPDATE_LAST_VALID_CART': return { ...state, lastValidResponseCart: (0, cart_functions_1.convertTempResponseCartToResponseCart)(state.responseCart), }; case 'FETCH_INITIAL_RESPONSE_CART': return { ...state, cacheStatus: 'fresh-pending', loadingError: undefined, loadingErrorType: undefined, }; case 'CART_RELOAD': debug('reloading cart from server'); return { ...state, cacheStatus: 'fresh', loadingError: undefined, loadingErrorType: undefined, }; case 'CLEAR_MESSAGES': return { ...state, responseCart: { ...state.responseCart, messages: { errors: [], success: [] } }, }; case 'CLEAR_QUEUED_ACTIONS': return { ...state, queuedActions: [] }; case 'REMOVE_CART_ITEM': { const uuidToRemove = action.uuidToRemove; debug('removing item from cart with uuid', uuidToRemove); return { ...state, responseCart: (0, cart_functions_1.removeItemFromResponseCart)(state.responseCart, uuidToRemove), cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; } case 'CART_PRODUCTS_ADD': debug('adding items to cart', action.products); return { ...state, responseCart: (0, cart_functions_1.addItemsToResponseCart)(state.responseCart, action.products), cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; case 'CART_PRODUCTS_REPLACE_ALL': debug('replacing items in cart with', action.products); return { ...state, responseCart: (0, cart_functions_1.replaceAllItemsInResponseCart)(state.responseCart, action.products), cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; case 'CART_PRODUCT_REPLACE': { const uuidToReplace = action.uuidToReplace; if ((0, cart_functions_1.doesResponseCartContainProductMatching)(state.responseCart, { uuid: uuidToReplace, ...action.productPropertiesToChange, })) { debug(`variant is already in cart; not submitting again`); return state; } debug(`replacing item with uuid ${uuidToReplace} with`, action.productPropertiesToChange); return { ...state, responseCart: (0, cart_functions_1.replaceItemInResponseCart)(state.responseCart, uuidToReplace, action.productPropertiesToChange), cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; } case 'REMOVE_COUPON': if (couponStatus !== 'applied') { debug(`coupon status is '${couponStatus}'; not removing`); return state; } debug('removing coupon'); return { ...state, responseCart: (0, cart_functions_1.removeCouponFromResponseCart)(state.responseCart), couponStatus: 'fresh', cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; case 'ADD_COUPON': { const newCoupon = action.couponToAdd; if ((couponStatus === 'applied' || couponStatus === 'pending') && newCoupon === state.responseCart.coupon) { debug(`coupon status is '${couponStatus}'; not submitting again`); return state; } debug('adding coupon', newCoupon); return { ...state, responseCart: (0, cart_functions_1.addCouponToResponseCart)(state.responseCart, newCoupon), couponStatus: 'pending', cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; } case 'RECEIVE_INITIAL_RESPONSE_CART': { const response = action.initialResponseCart; return { ...state, responseCart: response, couponStatus: getUpdatedCouponStatus(couponStatus, response), cacheStatus: 'valid', loadingError: undefined, loadingErrorType: undefined, }; } case 'REQUEST_UPDATED_RESPONSE_CART': return { ...state, cacheStatus: 'pending', loadingError: undefined, loadingErrorType: undefined, }; case 'RECEIVE_UPDATED_RESPONSE_CART': { const response = action.updatedResponseCart; const newCouponStatus = getUpdatedCouponStatus(couponStatus, response); const cartKey = response.cart_key; const productSlugsInCart = response.products.map((product) => product.product_slug); if (cartKey === 'no-user') { removeItemFromLocalStorage(productSlugsInCart); } return { ...state, responseCart: response, couponStatus: newCouponStatus, cacheStatus: 'valid', loadingError: undefined, loadingErrorType: undefined, }; } case 'RAISE_ERROR': switch (action.error) { case 'GET_SERVER_CART_ERROR': return { ...state, cacheStatus: 'error', loadingError: action.message || 'Error while fetching the shopping cart. Please check your network connection and try again.', loadingErrorType: action.error, }; case 'SET_SERVER_CART_ERROR': return { ...state, cacheStatus: 'error', loadingError: action.message || 'Error while updating the shopping cart endpoint. Please check your network connection and try again.', loadingErrorType: action.error, }; default: return state; } case 'SET_LOCATION': if ((0, cart_functions_1.doesCartLocationDifferFromResponseCartLocation)(state.responseCart, action.location)) { debug('changing location on cart from', state.responseCart.tax.location, 'to', action.location); return { ...state, responseCart: (0, cart_functions_1.addLocationToResponseCart)(state.responseCart, action.location), cacheStatus: 'invalid', loadingError: undefined, loadingErrorType: undefined, }; } debug('cart location is the same; not updating'); return state; default: return state; } } function getInitialShoppingCartState() { return { responseCart: emptyResponseCart, lastValidResponseCart: emptyResponseCart, cacheStatus: 'fresh', couponStatus: 'fresh', queuedActions: [], }; } exports.getInitialShoppingCartState = getInitialShoppingCartState; function removeItemFromLocalStorage(productSlugsInCart) { let cartItemsFromLocalStorage = null; try { cartItemsFromLocalStorage = JSON.parse(window.localStorage.getItem('shoppingCart') || '[]'); } catch (err) { return; } if (!Array.isArray(cartItemsFromLocalStorage)) { return; } const newCartItems = cartItemsFromLocalStorage.filter((product) => productSlugsInCart.includes(product.product_slug)); try { window.localStorage.setItem('shoppingCart', JSON.stringify(newCartItems)); } catch (err) { return; } } function getUpdatedCouponStatus(currentCouponStatus, responseCart) { const isCouponApplied = responseCart.is_coupon_applied; if (isCouponApplied) { return 'applied'; } if (currentCouponStatus === 'pending') { return 'rejected'; } return 'fresh'; } //# sourceMappingURL=shopping-cart-reducer.js.map