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

Removes ability to add configurable items to cart until options are chosen #1097

Merged
merged 8 commits into from
Apr 4, 2019
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
query productDetail($urlKey: String, $onServer: Boolean!) {
productDetail: products(filter: { url_key: { eq: $urlKey } }) {
items {
__typename
sku
name
price {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Object {
"query": "query productDetail($urlKey: String, $onServer: Boolean!) {
productDetail: products(filter: { url_key: { eq: $urlKey } }) {
items {
__typename
sku
name
price {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const cartItem = {
};

const configItem = {
__typename: 'ConfigurableProduct',
configurable_options: [
{
attribute_code: 'size',
Expand Down
17 changes: 7 additions & 10 deletions packages/venia-concept/src/components/MiniCart/cartOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import defaultClasses from './cartOptions.css';
import Button from 'src/components/Button';
import Quantity from 'src/components/ProductQuantity';
import appendOptionsToPayload from 'src/util/appendOptionsToPayload';
import isProductConfigurable from 'src/util/isProductConfigurable';

// TODO: get real currencyCode for cartItem
const currencyCode = 'USD';
Expand Down Expand Up @@ -36,6 +37,7 @@ class CartOptions extends Component {
qty: number.isRequired
}),
configItem: shape({
__typename: string,
configurable_options: array
}),
isUpdatingItem: bool,
Expand Down Expand Up @@ -69,39 +71,34 @@ class CartOptions extends Component {
handleClick = async () => {
const { updateCart, cartItem, configItem } = this.props;
const { optionSelections, quantity } = this.state;
const { configurable_options } = configItem;
const isConfigurable = Array.isArray(configurable_options);
const productType = isConfigurable
? 'ConfigurableProduct'
: 'SimpleProduct';

const payload = {
item: configItem,
productType,
productType: configItem.__typename,
quantity: quantity
};

if (productType === 'ConfigurableProduct') {
if (isProductConfigurable(configItem)) {
appendOptionsToPayload(payload, optionSelections);
}

updateCart(payload, cartItem.item_id);
};

render() {
const { fallback, handleSelectionChange, props } = this;
const { classes, cartItem, configItem, isUpdatingItem } = props;
const { name, price } = cartItem;
const { configurable_options } = configItem;

const modalClass = isUpdatingItem
? classes.modal_active
: classes.modal;

const options = Array.isArray(configurable_options) ? (
const options = isProductConfigurable(configItem) ? (
<Suspense fallback={fallback}>
<section className={classes.options}>
<Options
options={configurable_options}
options={configItem.configurable_options}
onSelectionChange={handleSelectionChange}
/>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import RichText from 'src/components/RichText';
import defaultClasses from './productFullDetail.css';
import appendOptionsToPayload from 'src/util/appendOptionsToPayload';
import findMatchingVariant from 'src/util/findMatchingProductVariant';
import isProductConfigurable from 'src/util/isProductConfigurable';

const Options = React.lazy(() => import('../ProductOptions'));

Expand All @@ -35,6 +36,7 @@ class ProductFullDetail extends Component {
title: string
}),
product: shape({
__typename: string,
id: number,
sku: string.isRequired,
price: shape({
Expand Down Expand Up @@ -63,7 +65,7 @@ class ProductFullDetail extends Component {
const optionCodes = new Map(state.optionCodes);

// if this is a simple product, do nothing
if (!Array.isArray(configurable_options)) {
if (!isProductConfigurable(props.product)) {
return null;
}

Expand All @@ -87,19 +89,14 @@ class ProductFullDetail extends Component {
const { props, state } = this;
const { optionSelections, quantity, optionCodes } = state;
const { addToCart, product } = props;
const { configurable_options } = product;
const isConfigurable = Array.isArray(configurable_options);
const productType = isConfigurable
? 'ConfigurableProduct'
: 'SimpleProduct';

const payload = {
item: product,
productType,
productType: product.__typename,
quantity
};

if (productType === 'ConfigurableProduct') {
if (isProductConfigurable(product)) {
appendOptionsToPayload(payload, optionSelections, optionCodes);
}

Expand All @@ -122,7 +119,7 @@ class ProductFullDetail extends Component {
get productOptions() {
const { fallback, handleSelectionChange, props } = this;
const { configurable_options } = props.product;
const isConfigurable = Array.isArray(configurable_options);
const isConfigurable = isProductConfigurable(props.product);

if (!isConfigurable) {
return null;
Expand All @@ -142,13 +139,9 @@ class ProductFullDetail extends Component {
const { props, state } = this;
const { product } = props;
const { optionCodes, optionSelections } = state;
const {
configurable_options,
media_gallery_entries,
variants
} = product;
const { media_gallery_entries, variants } = product;

const isConfigurable = Array.isArray(configurable_options);
const isConfigurable = isProductConfigurable(product);

if (
!isConfigurable ||
Expand All @@ -170,8 +163,31 @@ class ProductFullDetail extends Component {
return item.product.media_gallery_entries;
}

get isMissingOptions() {
const { product } = this.props;

// Non-configurable products can't be missing options
if (!isProductConfigurable(product)) {
return false;
}

// Configurable products are missing options if we have fewer
// option selections than the product has options.
const { configurable_options } = product;
const numProductOptions = configurable_options.length;
const numProductSelections = this.state.optionSelections.size;

return numProductSelections < numProductOptions;
}

render() {
const { addToCart, productOptions, props, mediaGalleryEntries } = this;
const {
addToCart,
isMissingOptions,
mediaGalleryEntries,
productOptions,
props
} = this;
const { classes, isAddingItem, product } = props;
const { regularPrice } = product.price;

Expand Down Expand Up @@ -212,7 +228,7 @@ class ProductFullDetail extends Component {
<Button
priority="high"
onClick={addToCart}
disabled={isAddingItem}
disabled={isAddingItem || isMissingOptions}
>
<span>Add to Cart</span>
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
query productDetail($urlKey: String, $onServer: Boolean!) {
productDetail: products(filter: { url_key: { eq: $urlKey } }) {
items {
__typename
sku
name
price {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
query productDetailByName($name: String, $onServer: Boolean!) {
products(filter: { name: { eq: $name } }) {
items {
__typename
id
sku
name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import isProductConfigurable from '../isProductConfigurable';

test('returns true for a configurable product', () => {
const product = {
__typename: 'ConfigurableProduct'
};

const result = isProductConfigurable(product);

expect(result).toBe(true);
});

test('returns false for a non-configurable product', () => {
const product = {
__typename: 'SimpleProduct'
};

const result = isProductConfigurable(product);

expect(result).toBe(false);
});
4 changes: 4 additions & 0 deletions packages/venia-concept/src/util/isProductConfigurable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const isProductConfigurable = product =>
product.__typename === 'ConfigurableProduct';

export default isProductConfigurable;