Skip to content

Commit

Permalink
Merge pull request #595 from shopgate/PWA-1786
Browse files Browse the repository at this point in the history
Added possibility to implement custom UI for product options
  • Loading branch information
devbucket committed Apr 2, 2019
2 parents 4be468c + 14ab340 commit 5f87d69
Show file tree
Hide file tree
Showing 43 changed files with 760 additions and 1,206 deletions.
8 changes: 4 additions & 4 deletions libraries/commerce/product/selectors/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@ export const getProductOptions = createSelector(
...option.type === OPTION_TYPE_TEXT && {
info: option.annotation,
required: !!option.required,
price: {
currency,
price: option.unitPriceModifier,
},
price: option.unitPriceModifier,
},
}))
// Move select type options on top, keep the rest
.sort((a, b) => {
if (a.type === b.type) {
return 0;
}
if (a.type === 'select') {
return -1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { connect } from 'react-redux';
import {
getBaseProductId,
getVariantId,
} from '@shopgate/pwa-common-commerce/product/selectors/product';
getProductCurrency,
} from '@shopgate/pwa-common-commerce/product';
import addProductsToCart from '@shopgate/pwa-common-commerce/cart/actions/addProductsToCart';

/**
Expand All @@ -14,11 +15,11 @@ import addProductsToCart from '@shopgate/pwa-common-commerce/cart/actions/addPro
const mapStateToProps = (state, props) => ({
baseProductId: getBaseProductId(state, props),
variantId: getVariantId(state, props),
currency: getProductCurrency(state, props),
});

/**
* @param {Function} dispatch The redux dispatch function.
* @param {Function} props The component props.
* @return {Object} The extended component props.
*/
const mapDispatchToProps = dispatch => ({
Expand Down
20 changes: 13 additions & 7 deletions themes/theme-gmd/pages/Product/components/Content/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import { ProductContext } from '../../context';
class ProductContent extends PureComponent {
static propTypes = {
baseProductId: PropTypes.string,
currency: PropTypes.string,
isVariant: PropTypes.bool,
productId: PropTypes.string,
variantId: PropTypes.string,
};

static defaultProps = {
baseProductId: null,
currency: null,
isVariant: false,
productId: null,
variantId: null,
Expand All @@ -42,7 +44,9 @@ class ProductContent extends PureComponent {
};

this.state = {
currency: props.currency,
options: {},
optionsPrices: {},
productId: props.variantId ? props.baseProductId : props.productId,
variantId: props.variantId ? props.variantId : null,
};
Expand Down Expand Up @@ -71,31 +75,37 @@ class ProductContent extends PureComponent {
this.setState({
productId,
variantId,
currency: nextProps.currency,
});
}

/**
* Stores the selected options in local state.
* @param {string} optionId The ID of the option.
* @param {string} value The option value.
* @param {number} [price=0] The option value.
*/
storeOptionSelection = (optionId, value) => {
setOption = (optionId, value, price = 0) => {
this.setState(prevState => ({
options: {
...prevState.options,
[optionId]: value,
},
optionsPrices: {
...prevState.optionsPrices,
[optionId]: !!value && price,
},
}));
};

/**
* @return {JSX}
*/
render() {
const id = this.state.variantId || this.state.productId;
const contextValue = {
...this.state,
...this.baseContextValue,
setOption: this.setOption,
};

return (
Expand All @@ -106,11 +116,7 @@ class ProductContent extends PureComponent {
<ImageSlider productId={this.state.productId} variantId={this.state.variantId} />
<Header />
<Characteristics productId={this.state.productId} variantId={this.state.variantId} />
<Options
productId={id}
storeSelection={this.storeOptionSelection}
currentOptions={this.state.options}
/>
<Options />
<Description productId={this.state.productId} variantId={this.state.variantId} />
<Properties productId={this.state.productId} variantId={this.state.variantId} />
<Reviews productId={this.state.productId} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors/price';
import { getProductPriceData } from '@shopgate/pwa-common-commerce/product';

/**
* Maps the contents of the state to the component props.
* @param {Object} state The current application state.
* @return {Object} The extended component props.
*/
const mapStateToProps = state => ({
price: getProductPrice(state),
price: getProductPriceData(state),
});

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { connect } from 'react-redux';
import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors/price';
import { hasProductOptions } from '@shopgate/pwa-common-commerce/product/selectors/options';
import { getProductPriceData } from '@shopgate/pwa-common-commerce/product';

/**
* Maps the contents of the state to the component props.
Expand All @@ -9,8 +8,7 @@ import { hasProductOptions } from '@shopgate/pwa-common-commerce/product/selecto
* @return {Object} The extended component props.
*/
const mapStateToProps = (state, props) => ({
price: getProductPrice(state, props),
showTotalPrice: hasProductOptions(state, props),
price: getProductPriceData(state, props),
});

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import pure from 'recompose/pure';
import Portal from '@shopgate/pwa-common/components/Portal';
import {
PRODUCT_PRICE,
Expand All @@ -9,43 +8,64 @@ import {
} from '@shopgate/pwa-common-commerce/product/constants/Portals';
import PlaceholderLabel from '@shopgate/pwa-ui-shared/PlaceholderLabel';
import PriceBase from '@shopgate/pwa-ui-shared/Price';
import { ProductContext } from '../../../../context';
import connect from './connector';
import styles from './style';

/**
* Calculate total price to show with additions
* @param {number} price unit amount
* @param {Object} additions price modifiers
* @returns {number}
*/
const getTotalPrice = (price, additions) => {
if (!additions) {
return price;
}
return price + Object.values(additions)
.reduce((p, val) => {
// eslint-disable-next-line no-param-reassign
p += val;
return p;
}, 0);
};

/**
* The Price component.
* @param {Object} props The component props.
* @return {JSX}
*/
const Price = ({ showTotalPrice, price }) => (
const Price = ({ price }) => (
<Fragment>
<Portal name={PRODUCT_PRICE_BEFORE} />
<Portal name={PRODUCT_PRICE}>
<PlaceholderLabel ready={(price !== null)} className={styles.placeholder}>
{(price && typeof price.unitPrice === 'number') && (
<PriceBase
className={styles.price}
currency={price.currency}
discounted={!!price.discount}
taxDisclaimer
unitPrice={!showTotalPrice ? price.unitPrice : price.totalPrice}
unitPriceMin={!showTotalPrice ? price.unitPriceMin : 0}
/>
<Portal name={PRODUCT_PRICE} props={{ price }}>
<ProductContext.Consumer>
{({ optionsPrices }) => (
<PlaceholderLabel ready={(price !== null)} className={styles.placeholder}>
{(price && typeof price.unitPrice === 'number') && (
<PriceBase
className={styles.price}
currency={price.currency}
discounted={!!price.discount}
taxDisclaimer
unitPrice={getTotalPrice(price.unitPrice, optionsPrices)}
unitPriceMin={!optionsPrices ? price.unitPriceMin : 0}
/>
)}
</PlaceholderLabel>
)}
</PlaceholderLabel>
</ProductContext.Consumer>
</Portal>
<Portal name={PRODUCT_PRICE_AFTER} />
</Fragment>
);

Price.propTypes = {
price: PropTypes.shape(),
showTotalPrice: PropTypes.bool,
};

Price.defaultProps = {
price: null,
showTotalPrice: false,
};

export default connect(pure(Price));
export default connect(Price);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors/price';
import { getProductPriceData } from '@shopgate/pwa-common-commerce/product';

/**
* Maps the contents of the state to the component props.
Expand All @@ -8,7 +8,7 @@ import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors
* @return {Object} The extended component props.
*/
const mapStateToProps = (state, props) => ({
price: getProductPrice(state, props),
price: getProductPriceData(state, props),
});

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { connect } from 'react-redux';
import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors/price';
import { getProductPriceData } from '@shopgate/pwa-common-commerce/product';

/**
* @param {Object} state The current application state.
* @param {Object} props The component props.
* @return {Object} The extended component props.
*/
const mapStateToProps = (state, props) => ({
price: getProductPrice(state, props),
price: getProductPriceData(state, props),
});

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors/price';
import { getProductPriceData } from '@shopgate/pwa-common-commerce/product';

/**
* Maps the contents of the state to the component props.
Expand All @@ -8,7 +8,7 @@ import { getProductPrice } from '@shopgate/pwa-common-commerce/product/selectors
* @return {Object} The extended component props.
*/
const mapStateToProps = (state, props) => ({
price: getProductPrice(state, props),
price: getProductPriceData(state, props),
});

/**
Expand Down
Loading

0 comments on commit 5f87d69

Please sign in to comment.