Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWA-468 pdp implement the options #99

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions libraries/commerce/cart/actions/addCurrentProductToCart.js

This file was deleted.

28 changes: 20 additions & 8 deletions libraries/commerce/cart/actions/addProductsToCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,34 @@ import addProductsToCart from '../action-creators/addProductsToCart';
import successAddProductsToCart from '../action-creators/successAddProductsToCart';
import errorAddProductsToCart from '../action-creators/errorAddProductsToCart';
import setCartProductPendingCount from '../action-creators/setCartProductPendingCount';
import { getProductPendingCount } from '../selectors';
import { getProductPendingCount, getAddToCartOptions } from '../selectors';
import { getProductMetadata } from '../../product/selectors/product';
import { messagesHaveErrors } from '../helpers';

/**
* Adds a product to the cart.
* @param {Array} productData The options for the products to be added.
* @param {Object} data The pieces for the products to be added.
* @return {Function} A redux thunk.
*/
const addProductToCart = productData => (dispatch, getState) => {
const pendingProductCount = getProductPendingCount(getState());
const addProductToCart = data => (dispatch, getState) => {
const state = getState();
const pendingProductCount = getProductPendingCount(state);
const options = getAddToCartOptions(state, data);
const metadata = getProductMetadata(state, data.productId);
const products = [
{
productId: data.productId,
quantity: data.quantity,
...options && { options },
...metadata && { metadata },
},
];

dispatch(addProductsToCart(productData));
dispatch(addProductsToCart(products));
dispatch(setCartProductPendingCount(pendingProductCount + 1));

const request = new PipelineRequest(pipelines.SHOPGATE_CART_ADD_PRODUCTS);
request.setInput({ products: productData })
request.setInput({ products })
.setResponseProcessed(PROCESS_SEQUENTIAL)
.setRetries(0)
.dispatch()
Expand All @@ -34,15 +46,15 @@ const addProductToCart = productData => (dispatch, getState) => {
* but a messages array within the response payload. So by now we also have to dispatch
* the error action here.
*/
dispatch(errorAddProductsToCart(productData, messages, requestsPending));
dispatch(errorAddProductsToCart(products, messages, requestsPending));
return;
}

dispatch(successAddProductsToCart(requestsPending));
})
.catch((error) => {
const requestsPending = request.hasPendingRequests();
dispatch(errorAddProductsToCart(productData, undefined, requestsPending));
dispatch(errorAddProductsToCart(products, undefined, requestsPending));
logger.error(pipelines.SHOPGATE_CART_ADD_PRODUCTS, error);
});
};
Expand Down
22 changes: 1 addition & 21 deletions libraries/commerce/cart/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,26 +180,6 @@ export const getFlags = createSelector(
({ flags }) => flags || {}
);

/**
* Builds the data for the 'metadata' property of addProductsToCart pipeline request payload.
* @returns {Object|null} The data if it was determinable, otherwise NULL.
*/
export const getAddToCartMetadata = createSelector(
getProductMetadata,
getSelectedVariantMetadata,
(metaData, variantMetaData) => {
if (variantMetaData) {
// Use the metadata from the getProductVariants data if available.
return variantMetaData;
} else if (metaData) {
// Use the metadata from the selected product if available.
return metaData;
}

return null;
}
);

/**
* Builds the data for the 'options' property of addProductsToCart pipeline request payload.
* @param {Object} state The application state.
Expand All @@ -209,7 +189,7 @@ export const getAddToCartOptions = createSelector(
hasProductOptions,
areProductOptionsSet,
getRawProductOptions,
getCurrentProductOptions,
(state, props) => props.options,
(hasOptions, areOptionsSet, options, currentOptions) => {
// Check if options are ready to be added to a pipeline request.
if (!hasOptions || !areOptionsSet) {
Expand Down
15 changes: 0 additions & 15 deletions libraries/commerce/product/action-creators/setProductOption.js

This file was deleted.

91 changes: 50 additions & 41 deletions libraries/commerce/product/selectors/options.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { createSelector } from 'reselect';
import { validateSelectorParams } from '@shopgate/pwa-common/helpers/data';
import { getCurrentProductId, getCurrentProduct, getProductCurrency } from '../selectors/product';
import {
getProductState,
getCurrentProduct,
getProductCurrency,
} from '../selectors/product';

/**
* Retrieves the product options state.
* @param {Object} state The application state.
* @returns {Object} The product options state.
*/
const getProductOptionsState = state => state.product.optionsByProductId;

/**
* Retrieves the current options for the active product.
* @param {Object} state The application state.
* @returns {Object}
*/
export const getCurrentProductOptions = state => state.product.currentProduct.options;
const getProductOptionsState = createSelector(
getProductState,
state => state.optionsByProductId
);

/**
* Finds a product option item by the option id and item id.
Expand All @@ -36,55 +36,64 @@ const findProductOptionItem = (options, optionId, itemId) => (
* @returns {Object} The product options.
*/
export const getRawProductOptions = createSelector(
(state, props) => props.productId,
getProductOptionsState,
getCurrentProductId,
(productOptionsState, productId) => {
(productId, productOptionsState) => {
const productOptions = productOptionsState[productId];

if (!productOptions || productOptions.isFetching) {
return null;
}

return productOptions.options;
}
);

// TODO: This needs to be optimized!
const getOptionItems = createSelector(
options => options,
(options, values) => values,
(options, values, option) => option,
(options, values, option, selected) => selected,
(options, values, option, selected, currency) => currency,
(options, values = [], option, selected, currency) => values.map((value) => {
// Add prices to each item that are relative to the current total product price.
if (!selected) {
return {
label: value.label,
currency,
value: value.id,
price: value.unitPriceModifier,
};
}

const selectedItem = findProductOptionItem(options, option.id, selected);

return {
label: value.label,
currency,
value: value.id,
price: (value.unitPriceModifier - selectedItem.unitPriceModifier),
};
})
);

/**
* Retrieves the current products options and transforms it to the correct data structure.
* @param {Object} state The application state.
* @returns {Array} The product options.
*/
export const getProductOptions = createSelector(
getProductCurrency,
getCurrentProductOptions,
(state, props) => props.currentOptions,
getRawProductOptions,
validateSelectorParams((currency, currentOptions, options) => (
options.map((option) => {
const selected = currentOptions[option.id];

return {
id: option.id,
label: option.label,
type: option.type,
items: (option.values || []).map(({ id, label, unitPriceModifier }) => {
// Add prices to each item that are relative to the current total product price.
if (!selected) {
return {
label,
currency,
value: id,
price: unitPriceModifier,
};
}

const selectedItem = findProductOptionItem(options, option.id, selected);
return {
label,
currency,
value: id,
price: unitPriceModifier - selectedItem.unitPriceModifier,
};
}),
};
})
options.map(option => ({
id: option.id,
label: option.label,
type: option.type,
items: getOptionItems(options, option.values, option, currentOptions[option.id], currency),
}))
))
);

Expand All @@ -111,7 +120,7 @@ export const hasProductOptions = createSelector(
*/
export const areProductOptionsSet = createSelector(
getRawProductOptions,
getCurrentProductOptions,
(state, props) => props.options,
validateSelectorParams((options, currentOptions) => (
options.length === Object.keys(currentOptions).length
))
Expand Down
9 changes: 4 additions & 5 deletions libraries/commerce/product/selectors/price.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
getProductUnitPrice,
} from './product';
import {
getCurrentProductOptions,
getRawProductOptions,
hasProductOptions,
areProductOptionsSet,
Expand All @@ -21,13 +20,13 @@ import {
* @returns {number}
*/
export const getProductPriceAddition = createSelector(
getCurrentProductOptions,
(state, props) => props.options,
getRawProductOptions,
validateSelectorParams(
(currentOptions, productOptions) => {
(options, productOptions) => {
// Get all item modifiers.
const modifiers = Object.keys(currentOptions).map((optionId) => {
const itemId = currentOptions[optionId];
const modifiers = Object.keys(options).map((optionId) => {
const itemId = options[optionId];
const optionItems = productOptions.find(item => item.id === optionId).values;
const selectedItem = optionItems.find(item => item.id === itemId);

Expand Down
27 changes: 19 additions & 8 deletions libraries/commerce/product/selectors/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@ import { ITEM_PATH } from '../constants';
import { getActiveFilters } from '../../filter/selectors';
import { filterProperties } from '../helpers';

/**
* @param {Object} state The current application state.
* @return {Object}
*/
export const getProductState = state => state.product;

/**
* Selects all products from the store.
* @param {Object} state The current application state.
* @return {Object} The collection of products.
*/
export const getProducts = state => state.product.productsById;
export const getProducts = createSelector(
getProductState,
state => state.productsById
);

/**
* Retrieves the current product or variant page from the store.
Expand Down Expand Up @@ -120,14 +129,16 @@ export const getProductUnitPrice = createSelector(
* @param {Object} state The application state.
* @returns {string}
*/
export const getProductCurrency = (state) => {
const currentProduct = getCurrentProduct(state);
if (!currentProduct) {
return null;
}
export const getProductCurrency = createSelector(
getCurrentProduct,
(product) => {
if (!product) {
return null;
}

return currentProduct.price.currency;
};
return product.price.currency;
}
);

/**
* Retrieves the generated result hash for a category ID.
Expand Down