@shopgate/pwa-common-commerce
Version:
Commerce library for the Shopgate Connect PWA.
130 lines (127 loc) • 4.25 kB
JavaScript
import "core-js/modules/es.array.reduce.js";
import uniq from 'lodash/uniq';
import pickBy from 'lodash/pickBy';
import { ENOTFOUND } from '@shopgate/pwa-core';
import { SELECT_GLOBAL_LOCATION } from '@shopgate/engage/core';
import { PRODUCT_LIFETIME, REQUEST_PRODUCTS, RECEIVE_PRODUCTS, ERROR_PRODUCTS, EXPIRE_PRODUCT_BY_ID, EXPIRE_PRODUCTS_BY_HASH, ERROR_PRODUCT, EXPIRE_PRODUCT_DATA } from "../constants";
/**
* Stores a collection of products by the related hash of the request parameters.
* @param {Object} [state={}] The current state.
* @param {Object} action The current redux action.
* @return {Object} The new state.
*/
export default function resultsByHash(state = {}, action = {}) {
switch (action.type) {
case REQUEST_PRODUCTS:
return {
...state,
[action.hash]: {
...state[action.hash],
isFetching: true,
expires: 0
}
};
case RECEIVE_PRODUCTS:
{
if (!action.hash) {
return state;
}
const {
products
} = state[action.hash];
const nextProducts = action.products || [];
/**
* If there are no previous products and no incoming products
* its set to empty array, otherwise it will be an array of the previous and the
* new products. Duplicates are removed.
*/
const stateProducts = products || nextProducts.length ? uniq([].concat(products || [], nextProducts.map(product => product.id))) : [];
return {
...state,
[action.hash]: {
...state[action.hash],
products: stateProducts,
totalResultCount: typeof action.totalResultCount !== 'undefined' ? action.totalResultCount : null,
isFetching: false,
expires: action.cached ? Date.now() + (action.cachedTime || PRODUCT_LIFETIME) : 0
}
};
}
/* Remove not found product from hash results */
case ERROR_PRODUCT:
if (action.errorCode === ENOTFOUND) {
return Object.keys(state).reduce((accumulator, hash) => {
if (accumulator[hash].products && accumulator[hash].products.includes(action.productId)) {
accumulator[hash] = {
...accumulator[hash],
products: accumulator[hash].products.filter(pId => pId !== action.productId)
};
}
return accumulator;
}, {
...state
});
}
return state;
case EXPIRE_PRODUCTS_BY_HASH:
{
return {
...state,
[action.hash]: {
...state[action.hash],
products: [],
expires: 0,
totalResultCount: null
}
};
}
case EXPIRE_PRODUCT_BY_ID:
{
const productIds = [].concat(action.productId);
return Object.keys(state).reduce((accumulator, hash) => {
if (accumulator[hash].products && productIds.some(id => accumulator[hash].products.includes(id))) {
accumulator[hash] = {
...accumulator[hash],
expires: 0,
...(action.complete && {
products: accumulator[hash].products.filter(id => !productIds.includes(id))
})
};
}
return accumulator;
}, {
...state
});
}
// Mark all product data as expired
case EXPIRE_PRODUCT_DATA:
{
if (Array.isArray(action.scopes) && action.scopes.includes('price')) {
return Object.keys(state).reduce((accumulator, hash) => {
accumulator[hash] = {
...accumulator[hash],
expires: 0
};
return accumulator;
}, {
...state
});
}
return state;
}
case ERROR_PRODUCTS:
return {
...state,
[action.hash]: {
...state[action.hash],
isFetching: false
}
};
case SELECT_GLOBAL_LOCATION:
// Remove all hashes that are not bound to a location
// because stock information may change per location
return pickBy(state, (value, key) => !JSON.parse(key).locationCodes);
default:
return state;
}
}