From ddcd58a0efe887eaf60b324faece02d45128cf0e Mon Sep 17 00:00:00 2001 From: Andy Terranova <13182778+supernova-at@users.noreply.github.com> Date: Thu, 4 Apr 2019 17:32:25 -0400 Subject: [PATCH] Removes ability to add configurable items to cart until options are chosen (#1097) * Disables Add to Cart button until configurable products have all options chosen * Updates logic to determine if a product is configurable. - Is now based on the product __typename - No longer checks if configurable_items is an array * Updates configurable item fixture to fix broken tests --- .../queries/getProductDetail.graphql | 1 + .../__snapshots__/PWADevServer.spec.js.snap | 1 + .../MiniCart/__tests__/productOptions.spec.js | 1 + .../src/components/MiniCart/cartOptions.js | 17 +++---- .../ProductFullDetail/ProductFullDetail.js | 50 ++++++++++++------- .../src/queries/getProductDetail.graphql | 1 + .../queries/getProductDetailByName.graphql | 1 + .../__tests__/isProductConfigurable.spec.js | 21 ++++++++ .../src/util/isProductConfigurable.js | 4 ++ 9 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 packages/venia-concept/src/util/__tests__/isProductConfigurable.spec.js create mode 100644 packages/venia-concept/src/util/isProductConfigurable.js diff --git a/packages/pwa-buildpack/src/WebpackTools/__tests__/__fixtures__/queries/getProductDetail.graphql b/packages/pwa-buildpack/src/WebpackTools/__tests__/__fixtures__/queries/getProductDetail.graphql index f488d8a5b9..81bf6b04e7 100644 --- a/packages/pwa-buildpack/src/WebpackTools/__tests__/__fixtures__/queries/getProductDetail.graphql +++ b/packages/pwa-buildpack/src/WebpackTools/__tests__/__fixtures__/queries/getProductDetail.graphql @@ -1,6 +1,7 @@ query productDetail($urlKey: String, $onServer: Boolean!) { productDetail: products(filter: { url_key: { eq: $urlKey } }) { items { + __typename sku name price { diff --git a/packages/pwa-buildpack/src/WebpackTools/__tests__/__snapshots__/PWADevServer.spec.js.snap b/packages/pwa-buildpack/src/WebpackTools/__tests__/__snapshots__/PWADevServer.spec.js.snap index d173cdccf3..c2803a731e 100644 --- a/packages/pwa-buildpack/src/WebpackTools/__tests__/__snapshots__/PWADevServer.spec.js.snap +++ b/packages/pwa-buildpack/src/WebpackTools/__tests__/__snapshots__/PWADevServer.spec.js.snap @@ -17,6 +17,7 @@ Object { "query": "query productDetail($urlKey: String, $onServer: Boolean!) { productDetail: products(filter: { url_key: { eq: $urlKey } }) { items { + __typename sku name price { diff --git a/packages/venia-concept/src/components/MiniCart/__tests__/productOptions.spec.js b/packages/venia-concept/src/components/MiniCart/__tests__/productOptions.spec.js index 0d256e4ee9..2817be1400 100644 --- a/packages/venia-concept/src/components/MiniCart/__tests__/productOptions.spec.js +++ b/packages/venia-concept/src/components/MiniCart/__tests__/productOptions.spec.js @@ -17,6 +17,7 @@ const cartItem = { }; const configItem = { + __typename: 'ConfigurableProduct', configurable_options: [ { attribute_code: 'size', diff --git a/packages/venia-concept/src/components/MiniCart/cartOptions.js b/packages/venia-concept/src/components/MiniCart/cartOptions.js index d3522b6f66..6acf8ed06e 100644 --- a/packages/venia-concept/src/components/MiniCart/cartOptions.js +++ b/packages/venia-concept/src/components/MiniCart/cartOptions.js @@ -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'; @@ -36,6 +37,7 @@ class CartOptions extends Component { qty: number.isRequired }), configItem: shape({ + __typename: string, configurable_options: array }), isUpdatingItem: bool, @@ -69,21 +71,17 @@ 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); }; @@ -91,17 +89,16 @@ class CartOptions extends Component { 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) ? (
diff --git a/packages/venia-concept/src/components/ProductFullDetail/ProductFullDetail.js b/packages/venia-concept/src/components/ProductFullDetail/ProductFullDetail.js index b07a3e7548..066ba99542 100644 --- a/packages/venia-concept/src/components/ProductFullDetail/ProductFullDetail.js +++ b/packages/venia-concept/src/components/ProductFullDetail/ProductFullDetail.js @@ -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')); @@ -35,6 +36,7 @@ class ProductFullDetail extends Component { title: string }), product: shape({ + __typename: string, id: number, sku: string.isRequired, price: shape({ @@ -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; } @@ -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); } @@ -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; @@ -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 || @@ -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; @@ -212,7 +228,7 @@ class ProductFullDetail extends Component { diff --git a/packages/venia-concept/src/queries/getProductDetail.graphql b/packages/venia-concept/src/queries/getProductDetail.graphql index da40dc8df1..06af195159 100644 --- a/packages/venia-concept/src/queries/getProductDetail.graphql +++ b/packages/venia-concept/src/queries/getProductDetail.graphql @@ -1,6 +1,7 @@ query productDetail($urlKey: String, $onServer: Boolean!) { productDetail: products(filter: { url_key: { eq: $urlKey } }) { items { + __typename sku name price { diff --git a/packages/venia-concept/src/queries/getProductDetailByName.graphql b/packages/venia-concept/src/queries/getProductDetailByName.graphql index df094acf0c..9b1bd28cc1 100644 --- a/packages/venia-concept/src/queries/getProductDetailByName.graphql +++ b/packages/venia-concept/src/queries/getProductDetailByName.graphql @@ -1,6 +1,7 @@ query productDetailByName($name: String, $onServer: Boolean!) { products(filter: { name: { eq: $name } }) { items { + __typename id sku name diff --git a/packages/venia-concept/src/util/__tests__/isProductConfigurable.spec.js b/packages/venia-concept/src/util/__tests__/isProductConfigurable.spec.js new file mode 100644 index 0000000000..dda699c243 --- /dev/null +++ b/packages/venia-concept/src/util/__tests__/isProductConfigurable.spec.js @@ -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); +}); diff --git a/packages/venia-concept/src/util/isProductConfigurable.js b/packages/venia-concept/src/util/isProductConfigurable.js new file mode 100644 index 0000000000..d7c4604ec5 --- /dev/null +++ b/packages/venia-concept/src/util/isProductConfigurable.js @@ -0,0 +1,4 @@ +const isProductConfigurable = product => + product.__typename === 'ConfigurableProduct'; + +export default isProductConfigurable;