Skip to content

Commit

Permalink
Merge e76b0ae into 6c4fb89
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbridge committed May 7, 2021
2 parents 6c4fb89 + e76b0ae commit 710d2bf
Show file tree
Hide file tree
Showing 42 changed files with 642 additions and 1,043 deletions.
32 changes: 30 additions & 2 deletions libraries/commerce/favorites/action-creators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
ERROR_FAVORITES,
IDLE_SYNC_FAVORITES,
REQUEST_FLUSH_FAVORITES_BUFFER,
REQUEST_FAVORITES_IDS,
ERROR_FAVORITES_IDS,
RECEIVE_FAVORITES_IDS,
} from '../constants';

/**
Expand Down Expand Up @@ -62,7 +65,7 @@ export const errorFavorites = (productId, error) => ({

/**
* Request add favorites action. This action just updates the redux store.
* @param {string} productId Product identifier.
* @param {string|string[]} productId Product identifier.
* @returns {Object}
*/
export const requestAddFavorites = productId => ({
Expand Down Expand Up @@ -94,7 +97,7 @@ export const errorAddFavorites = (productId, error) => ({

/**
* Request remove favorites action. This action just updates the redux store.
* @param {string} productId Product identifier.
* @param {string|string[]} productId Product identifier.
* @returns {Object}
*/
export const requestRemoveFavorites = productId => ({
Expand Down Expand Up @@ -169,3 +172,28 @@ export const receiveFavorites = (products, requestTimestamp) => ({
export const requestFavorites = () => ({
type: REQUEST_FAVORITES,
});

/**
* @param {string[]} productIds .
* @returns {Object}
*/
export const receiveFavoriteIDs = productIds => ({
type: RECEIVE_FAVORITES_IDS,
productIds,
});

/**
* @returns {Object}
*/
export const requestFavoriteIds = () => ({
type: REQUEST_FAVORITES_IDS,
});

/**
* @param {Error} error .
* @returns {Object}
*/
export const errorFetchFavoriteIds = error => ({
type: ERROR_FAVORITES_IDS,
error,
});
5 changes: 3 additions & 2 deletions libraries/commerce/favorites/actions/addFavorites.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { mutable } from '@shopgate/pwa-common/helpers/redux';
import PipelineRequest from '@shopgate/pwa-core/classes/PipelineRequest';
import { SHOPGATE_USER_ADD_FAVORITES } from '../constants/Pipelines';
import { successAddFavorites, errorAddFavorites } from '../action-creators';

/**
* Adds a single product to the favorite list using the `addFavorites` pipeline.
* @param {string} productId Id of the product to be added.
* @param {string|string[]} productId Id of the product to be added.
* @returns {Function} A redux thunk.
*/
function addFavorites(productId) {
Expand All @@ -26,4 +27,4 @@ function addFavorites(productId) {
};
}

export default addFavorites;
export default mutable(addFavorites);
32 changes: 32 additions & 0 deletions libraries/commerce/favorites/actions/fetchFavoriteIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import PipelineRequest from '@shopgate/pwa-core/classes/PipelineRequest';
import { EFAVORITE, EUNKNOWN } from '@shopgate/pwa-core/constants/Pipeline';
import { shouldFetchData, mutable } from '@shopgate/pwa-common/helpers/redux';
import { SHOPGATE_USER_GET_FAVORITE_IDS } from '../constants/Pipelines';
import { requestFavoriteIds, receiveFavoriteIDs, errorFetchFavoriteIds } from '../action-creators';
import { getFavoritesProducts } from '../selectors';

/**
* Fetch favorite IDs action.
* @param {boolean} ignoreCache Ignores cache when true
* @returns {Function} A redux thunk.
*/
function fetchFavoriteIds(ignoreCache = false) {
return (dispatch, getState) => {
const data = getFavoritesProducts(getState());

if (!ignoreCache && !shouldFetchData(data)) {
return Promise.resolve(data);
}

dispatch(requestFavoriteIds());

return new PipelineRequest(SHOPGATE_USER_GET_FAVORITE_IDS)
.setErrorBlacklist([EFAVORITE, EUNKNOWN])
.dispatch()
.then(({ productIds }) => dispatch(receiveFavoriteIDs(productIds)))
.catch(err => dispatch(errorFetchFavoriteIds(err)));
};
}

/** @mixes {MutableFunction} */
export default mutable(fetchFavoriteIds);
5 changes: 3 additions & 2 deletions libraries/commerce/favorites/actions/removeFavorites.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { mutable } from '@shopgate/pwa-common/helpers/redux';
import PipelineRequest from '@shopgate/pwa-core/classes/PipelineRequest';
import { SHOPGATE_USER_DELETE_FAVORITES } from '../constants/Pipelines';
import { successRemoveFavorites, errorRemoveFavorites } from '../action-creators';

/**
* Removes a single product from the favorite list using the `deleteFavorites` pipeline.
* @param {string} productId Id of the product to be deleted.
* @param {string|string[]} productId Id of the product to be deleted.
* @returns {Function} A redux thunk.
*/
function removeFavorites(productId) {
Expand All @@ -26,4 +27,4 @@ function removeFavorites(productId) {
};
}

export default removeFavorites;
export default mutable(removeFavorites);
5 changes: 5 additions & 0 deletions libraries/commerce/favorites/constants/Pipelines.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* @deprecated
*/
export const SHOPGATE_USER_GET_FAVORITES = 'shopgate.user.getFavorites';

export const SHOPGATE_USER_GET_FAVORITE_IDS = 'shopgate.user.getFavoriteIds';
export const SHOPGATE_USER_ADD_FAVORITES = 'shopgate.user.addFavorites';
export const SHOPGATE_USER_DELETE_FAVORITES = 'shopgate.user.deleteFavorites';
12 changes: 12 additions & 0 deletions libraries/commerce/favorites/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ export const FAVORITES_PATH = '/favourite_list';
export const ADD_PRODUCT_TO_FAVORITES = 'ADD_PRODUCT_TO_FAVORITES';
export const REMOVE_PRODUCT_FROM_FAVORITES = 'REMOVE_PRODUCT_FROM_FAVORITES';

/** @deprecated */
export const REQUEST_FAVORITES = 'REQUEST_FAVORITES';
/** @deprecated */
export const RECEIVE_FAVORITES = 'RECEIVE_FAVORITES';
/** @deprecated */
export const ERROR_FAVORITES = 'ERROR_FAVORITES';
/** @deprecated */
export const ERROR_FETCH_FAVORITES = 'ERROR_FETCH_FAVORITES';

export const REQUEST_ADD_FAVORITES = 'REQUEST_ADD_FAVORITES';
Expand All @@ -16,12 +20,20 @@ export const REQUEST_REMOVE_FAVORITES = 'REQUEST_REMOVE_FAVORITES';
export const SUCCESS_REMOVE_FAVORITES = 'SUCCESS_REMOVE_FAVORITES';
export const ERROR_REMOVE_FAVORITES = 'ERROR_REMOVE_FAVORITES';

export const REQUEST_FAVORITES_IDS = 'REQUEST_FAVORITES_IDS';
export const RECEIVE_FAVORITES_IDS = 'RECEIVE_FAVORITES_IDS';
export const ERROR_FAVORITES_IDS = 'ERROR_FAVORITES_IDS';

/** @deprecated */
export const CANCEL_REQUEST_SYNC_FAVORITES = 'CANCEL_REQUEST_SYNC_FAVORITES';

/** @deprecated */
export const IDLE_SYNC_FAVORITES = 'IDLE_SYNC_FAVORITES';
/** @deprecated */
export const REQUEST_FLUSH_FAVORITES_BUFFER = 'REQUEST_FLUSH_FAVORITES_BUFFER';

// Defines a local error code which is not related to a backend call
/** @deprecated */
export const FAVORITES_LIMIT_ERROR = 'FAVORITES_LIMIT_ERROR';

export const FAVORITES_LIFETIME = 3600000; // 1 hour
Expand Down
123 changes: 59 additions & 64 deletions libraries/commerce/favorites/reducers/products.js
Original file line number Diff line number Diff line change
@@ -1,120 +1,115 @@
import { uniq } from 'lodash';
import { SUCCESS_LOGOUT } from '@shopgate/pwa-common/constants/ActionTypes';
import {
REQUEST_ADD_FAVORITES,
SUCCESS_ADD_FAVORITES,
CANCEL_REQUEST_SYNC_FAVORITES,
ADD_PRODUCT_TO_FAVORITES,
ERROR_ADD_FAVORITES,
REQUEST_REMOVE_FAVORITES,
SUCCESS_REMOVE_FAVORITES,
ERROR_REMOVE_FAVORITES,
REQUEST_FAVORITES,
ERROR_FETCH_FAVORITES,
RECEIVE_FAVORITES,
FAVORITES_LIFETIME,
RECEIVE_FAVORITES_IDS,
REMOVE_PRODUCT_FROM_FAVORITES,
REQUEST_FAVORITES_IDS,
SUCCESS_ADD_FAVORITES,
SUCCESS_REMOVE_FAVORITES,
ERROR_FAVORITES_IDS,
} from '../constants';

const defaultState = {
expires: 0,
ids: [],
originalIds: [],
ready: false,
};

/**
* Favorites reducer.
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
* @returns {Object} New state.
*/
const products = (state = {
lastChange: 0,
lastFetch: 0,
expires: 0,
ids: [],
syncCount: 0,
}, action) => {
const products = (state = defaultState, action) => {
switch (action.type) {
case REQUEST_ADD_FAVORITES:
case ADD_PRODUCT_TO_FAVORITES: {
const adds = [].concat(action.productId);
return {
...state,
ids: uniq([
...state.ids,
action.productId,
]),
lastChange: Date.now(),
syncCount: state.syncCount + 1,
ids: Array.from(new Set([...adds, ...state.ids])).filter(Boolean),
};
case REQUEST_REMOVE_FAVORITES:
}
case REMOVE_PRODUCT_FROM_FAVORITES: {
const removes = [].concat(action.productId);
return {
...state,
ids: state.ids.filter(id => id !== action.productId),
lastChange: Date.now(),
syncCount: state.syncCount + 1,
ids: state.ids.filter(id => !removes.includes(id)),
};
case CANCEL_REQUEST_SYNC_FAVORITES:
// 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
}

// Add succeeded to original storage
case SUCCESS_ADD_FAVORITES: {
const added = [].concat(action.productId);
return {
...state,
syncCount: state.syncCount - action.count,
// ids must remain unchanged, because it's managed by all other actions
originalIds: Array.from(new Set([...added, ...state.originalIds])),
};
case SUCCESS_ADD_FAVORITES:
case SUCCESS_REMOVE_FAVORITES:
}

// Remove succeeded from original storage
case SUCCESS_REMOVE_FAVORITES: {
const removed = [].concat(action.productId);
return {
...state,
lastChange: Date.now(),
syncCount: state.syncCount - 1,
originalIds: state.originalIds.filter(id => !removed.includes(id))
,
};
case ERROR_ADD_FAVORITES:
}

case ERROR_ADD_FAVORITES: {
// Clean up by removing the previously added product id
const added = [].concat(action.productId);
return {
...state,
ids: state.ids.filter(id => id !== action.productId),
lastChange: Date.now(),
syncCount: state.syncCount - 1,
ids: state.ids.filter(id => !added.includes(id)),
};
}

case ERROR_REMOVE_FAVORITES:
// Clean up by adding the previously removed product id back in
return {
...state,
ids: uniq([
...[].concat(action.productId),
...state.ids,
action.productId,
]),
lastChange: Date.now(),
syncCount: state.syncCount - 1,
};
case REQUEST_FAVORITES:

case REQUEST_FAVORITES_IDS:
return {
...state,
isFetching: true,
ids: state.ids,
expires: 0,
// No `ready` prop here! It should be undefined on first request and stay true later!
// `syncCount` stays untouched because this is not considered to be a sync.
};
case RECEIVE_FAVORITES:
/**
* 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.
*/

case RECEIVE_FAVORITES_IDS:
return {
...state,
...(state.ready && (state.syncCount > 0 || state.lastChange > action.requestTimestamp))
? { isFetching: false }
: {
isFetching: false,
expires: Date.now() + FAVORITES_LIFETIME,
ids: action.products.map(product => product.id),
ready: true,
// `syncCount` stays untouched because this is not considered to be a sync.
},
isFetching: false,
expires: Date.now() + FAVORITES_LIFETIME,
ids: action.productIds,
originalIds: action.productIds,
ready: true,
};
case ERROR_FETCH_FAVORITES:

case ERROR_FAVORITES_IDS:
return {
...state,
isFetching: false,
ids: state.ids,
expires: 0,
ready: true,
// `syncCount` stays untouched because this is not considered to be a sync.
ready: false,
};
case SUCCESS_LOGOUT:
return defaultState;

default:
return state;
}
Expand Down

0 comments on commit 710d2bf

Please sign in to comment.