UNPKG

@shopgate/pwa-common-commerce

Version:

Commerce library for the Shopgate Connect PWA.

230 lines (218 loc) • 7.25 kB
import { produce } from 'immer'; import isNumber from 'lodash/isNumber'; import isString from 'lodash/isString'; import { CANCEL_REQUEST_SYNC_FAVORITES, ERROR_ADD_FAVORITES, ERROR_FETCH_FAVORITES, ERROR_REMOVE_FAVORITES, ERROR_UPDATE_FAVORITES, FAVORITES_LIFETIME, RECEIVE_FAVORITES, RECEIVE_FAVORITES_LISTS, REQUEST_ADD_FAVORITES, REQUEST_FAVORITES, REQUEST_REMOVE_FAVORITES, REQUEST_UPDATE_FAVORITES, SUCCESS_ADD_FAVORITES, SUCCESS_ADD_FAVORITES_LIST, SUCCESS_REMOVE_FAVORITES, SUCCESS_REMOVE_FAVORITES_LIST, SUCCESS_UPDATE_FAVORITES } from "../constants"; /** * Favorites reducer. * @param {Object} state Current state. * @param {Object} action Dispatched action. * @returns {Object} New state. */ const products = (state = { byList: {} }, action = {}) => produce(state, draft => { switch (action.type) { // Handle a new favorites request. case REQUEST_FAVORITES: { const existingList = draft.byList[action.listId]; if (!existingList) { draft.byList[action.listId] = { isFetching: true, lastChange: 0, lastFetch: 0, expires: 0, items: [], syncCount: 0 }; return; } existingList.isFetching = true; existingList.expires = 0; break; } // Handle incoming favorite products. case RECEIVE_FAVORITES: { const list = draft.byList[action.listId]; const isSynching = list.syncCount > 0; const isOngoing = state.lastChange > action.requestTimestamp; /** * Note: When favorites are received, an add or remove request can be in progress. In this * case only fetching state will be updated and the received data will be discarded. * A new fetch request will be queued as soon as the sync is done, which will recover * discarded data. */ list.isFetching = false; if (list.ready && (isSynching || isOngoing)) { return; } list.expires = Date.now() + FAVORITES_LIFETIME; list.items = action.items.map(({ quantity, notes, product }) => ({ quantity, notes, productId: product.id })); list.ready = true; // `syncCount` stays untouched because this is not considered to be a sync. break; } // Handle failed fetching case ERROR_FETCH_FAVORITES: { const list = draft.byList[action.listId]; list.isFetching = false; list.expires = 0; list.ready = true; // `syncCount` stays untouched because this is not considered to be a sync. break; } // Handle adding favorite list products. case REQUEST_ADD_FAVORITES: { const list = draft.byList[action.listId]; const matchingItem = list.items.find(({ productId }) => productId === action.productId); if (matchingItem) { matchingItem.notes = typeof action.notes === 'string' ? action.notes : matchingItem.notes; matchingItem.quantity = typeof action.quantity === 'number' ? matchingItem.quantity + action.quantity : matchingItem.quantity + 1; } else { list.items.push({ notes: action.notes || '', quantity: action.quantity || 1, productId: action.productId }); } list.lastChange = Date.now(); list.syncCount += 1; break; } case REQUEST_UPDATE_FAVORITES: { const list = draft.byList[action.listId]; const matchingItem = list.items.find(({ productId }) => productId === action.productId); if (matchingItem) { if (isNumber(action.quantity)) { matchingItem.quantity = action.quantity; } if (isString(action.notes)) { matchingItem.notes = action.notes; } list.lastChange = Date.now(); list.syncCount += 1; } break; } // Handle removing favorite list products. case REQUEST_REMOVE_FAVORITES: { const list = draft.byList[action.listId]; const matchingItemIndex = list.items.findIndex(({ productId }) => productId === action.productId); if (matchingItemIndex > -1) { list.items.splice(matchingItemIndex, 1); } list.lastChange = Date.now(); list.syncCount += 1; break; } // Handle cancellation of synchronization. // Sync count needs to be updated, when an add or a remove favorites action is cancelled // This recovers from invalid sync states when a backend call is detected to be redundant case CANCEL_REQUEST_SYNC_FAVORITES: { const list = draft.byList[action.listId]; list.syncCount -= action.count; break; } // Handle success of adding favorite list products. case SUCCESS_ADD_FAVORITES: case SUCCESS_UPDATE_FAVORITES: case SUCCESS_REMOVE_FAVORITES: { const list = draft.byList[action.listId]; list.lastChange = Date.now(); list.syncCount -= 1; break; } // Handle deletion failure by adding the product back in to the list. case ERROR_REMOVE_FAVORITES: { const list = draft.byList[action.listId]; list.items.push({ productId: action.productId, quantity: action.quantity || 1, notes: action.notes || '' }); list.lastChange = Date.now(); list.syncCount -= 1; break; } // Handle adding failure by removing the product from the list. case ERROR_ADD_FAVORITES: { const list = draft.byList[action.listId]; const matchingItemIndex = list.items.findIndex(({ productId }) => productId === action.productId); if (matchingItemIndex > -1) { list.items.splice(matchingItemIndex, 1); } list.lastChange = Date.now(); list.syncCount -= 1; break; } case ERROR_UPDATE_FAVORITES: { const list = draft.byList[action.listId]; list.lastChange = Date.now(); list.syncCount -= 1; break; } // Handle cleanup after deletion of a list. case SUCCESS_REMOVE_FAVORITES_LIST: { delete draft.byList[action.listId]; break; } // Handle adding new lists. case SUCCESS_ADD_FAVORITES_LIST: { draft.byList[action.listId] = { isFetching: true, lastChange: 0, lastFetch: 0, expires: 0, items: [], syncCount: 0, ready: true }; break; } // Handle cleanup after lists are updated case RECEIVE_FAVORITES_LISTS: { const listIds = action.favoritesLists.map(({ id }) => id); Object.keys(draft.byList).forEach(id => { if (!listIds.includes(id)) { // Remove list items that don't have a list anymore delete draft.byList[id]; } }); break; } default: break; } }); export default products;