Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Introduce Atomic Add to Cart Block #2705

Merged
merged 22 commits into from
Jun 17, 2020
Merged

Introduce Atomic Add to Cart Block #2705

merged 22 commits into from
Jun 17, 2020

Conversation

mikejolley
Copy link
Member

@mikejolley mikejolley commented Jun 12, 2020

Adds an experimental/in-progress Add To Cart Atomic Block. This is a wrapper for the add to cart form button, qty picker, and any form elements that other product types require.

I've logged some inline todo's for context related follow ups (these are to support inner form elements and extensibility). There are also follow ups for:

So whilst the basic functionality is testable, this is not a final product :)

Closes #2676

How to test

  1. Create a fresh single product block
  2. You should see the cart form! Test toggling the display of form elements in the inspector
  3. Repeat this for each product type; simple, variation, variable, grouped
  4. External product type should have no option for form elements
  5. Test the button and form on the frontend for a simple product. The button should display, and qty picker should influance what is added to the cart.

@mikejolley mikejolley added the block-type: product elements Issues related to Product Element blocks. label Jun 12, 2020
@mikejolley mikejolley self-assigned this Jun 12, 2020
@github-actions
Copy link
Contributor

Add Notices to Single Product Block to catch add to cart ...

Add Notices to Single Product Block to catch add to cart errors


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/92a9f28c8a01a73427f9b9d8ecf9f49e684c3c65/assets/js/atomic/blocks/product/add-to-cart/shared/context.js#L52-L63

🚀 This comment was generated by the automations bot based on a todo comment in 92a9f28 in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

Update cart and checkout to use button component instead ...

Update cart and checkout to use button component instead of cart-checkout/button.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/92a9f28c8a01a73427f9b9d8ecf9f49e684c3c65/assets/js/base/components/button/index.js#L16-L27

🚀 This comment was generated by the automations bot based on a todo comment in 92a9f28 in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

github-actions bot commented Jun 12, 2020

Size Change: +7.71 kB (0%)

Total Size: 1.58 MB

Filename Size Change
build/active-filters-frontend.js 7.22 kB +10 B (0%)
build/active-filters.js 7.95 kB +7 B (0%)
build/all-products-frontend.js 40.5 kB +1.63 kB (4%)
build/all-products.js 23.6 kB +1.89 kB (7%) 🔍
build/all-reviews.js 9.54 kB -2 B (0%)
build/attribute-filter-frontend.js 16.7 kB +3 B (0%)
build/attribute-filter.js 11.5 kB +10 B (0%)
build/blocks.js 2.92 kB +4 B (0%)
build/cart-frontend.js 63.6 kB -24 B (0%)
build/cart.js 33 kB +22 B (0%)
build/checkout-frontend.js 80.2 kB +26 B (0%)
build/checkout.js 38.5 kB +31 B (0%)
build/custom-select-control-style.js 783 B +1 B
build/featured-category.js 7.59 kB +4 B (0%)
build/featured-product.js 9.84 kB +7 B (0%)
build/handpicked-products.js 7.24 kB -2 B (0%)
build/price-filter-frontend.js 14.1 kB -2 B (0%)
build/price-filter.js 10 kB +12 B (0%)
build/product-best-sellers.js 7.31 kB +3 B (0%)
build/product-categories.js 3.22 kB -1 B
build/product-new.js 7.47 kB +1 B
build/product-on-sale.js 7.87 kB -2 B (0%)
build/product-search.js 3.43 kB -1 B
build/product-tag.js 6.39 kB -1 B
build/products-by-attribute.js 8.19 kB +3 B (0%)
build/reviews-by-category.js 11.6 kB +9 B (0%)
build/reviews-by-product.js 13.1 kB +9 B (0%)
build/reviews-frontend.js 8.93 kB -3 B (0%)
build/single-product-frontend.js 43.4 kB +1.71 kB (3%)
build/single-product.js 17.4 kB +1.81 kB (10%) ⚠️
build/snackbar-notice-style.js 778 B -1 B
build/spinner-style.js 774 B +2 B (0%)
build/style-rtl.css 17.6 kB +268 B (1%)
build/style.css 17.6 kB +270 B (1%)
ℹ️ View Unchanged
Filename Size Change
build/all-reviews-legacy.js 9.24 kB 0 B
build/block-error-boundary-legacy.js 775 B 0 B
build/block-error-boundary.js 774 B 0 B
build/blocks-legacy.js 2.92 kB 0 B
build/custom-select-control-style-legacy.js 782 B 0 B
build/editor-legacy-rtl.css 12.5 kB 0 B
build/editor-legacy.css 12.5 kB 0 B
build/editor-rtl.css 13.5 kB 0 B
build/editor.css 13.5 kB 0 B
build/featured-category-legacy.js 7.27 kB 0 B
build/featured-product-legacy.js 9.51 kB 0 B
build/handpicked-products-legacy.js 6.91 kB 0 B
build/product-best-sellers-legacy.js 6.99 kB 0 B
build/product-categories-legacy.js 3.23 kB 0 B
build/product-category-legacy.js 7.91 kB 0 B
build/product-category.js 8.25 kB 0 B
build/product-list-style-legacy.js 774 B 0 B
build/product-new-legacy.js 7.15 kB 0 B
build/product-on-sale-legacy.js 7.52 kB 0 B
build/product-search-legacy.js 3.14 kB 0 B
build/product-tag-legacy.js 6.08 kB 0 B
build/product-top-rated-legacy.js 7.12 kB 0 B
build/product-top-rated.js 7.44 kB 0 B
build/products-by-attribute-legacy.js 7.88 kB 0 B
build/reviews-by-category-legacy.js 11.2 kB 0 B
build/reviews-by-product-legacy.js 12.7 kB 0 B
build/reviews-frontend-legacy.js 8.08 kB 0 B
build/snackbar-notice-style-legacy.js 778 B 0 B
build/spinner-style-legacy.js 772 B 0 B
build/style-legacy-rtl.css 5.5 kB 0 B
build/style-legacy.css 5.5 kB 0 B
build/vendors-legacy.js 366 kB 0 B
build/vendors-style-legacy-rtl.css 1.03 kB 0 B
build/vendors-style-legacy.css 1.03 kB 0 B
build/vendors-style-legacy.js 103 B 0 B
build/vendors-style-rtl.css 1.03 kB 0 B
build/vendors-style.css 1.03 kB 0 B
build/vendors-style.js 102 B 0 B
build/vendors.js 414 kB 0 B
build/wc-blocks-data.js 7.09 kB 0 B
build/wc-blocks-middleware.js 931 B 0 B
build/wc-blocks-registry.js 1.79 kB 0 B
build/wc-payment-method-cheque.js 794 B 0 B
build/wc-payment-method-paypal.js 830 B 0 B
build/wc-payment-method-stripe.js 11.9 kB 0 B
build/wc-settings.js 2.14 kB 0 B
build/wc-shared-context.js 1.51 kB 0 B

compressed-size-action

@mikejolley mikejolley changed the base branch from master to main June 16, 2020 09:42
@github-actions
Copy link
Contributor

Update cart and checkout button component.

Update cart and checkout button component. Cart and checkout should use base/components/button instead of cart-checkout/button.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/22475ee9c1dfb843764858b0c996dec497828639/assets/js/base/components/button/index.js#L16-L27

🚀 This comment was generated by the automations bot based on a todo comment in 22475ee in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

The add to cart form may have several inner form elements...

Introduce Validation Emitter for the Add to Cart Form

The add to cart form may have several inner form elements which need to run validation andchange whether or not the form can be submitted. They may also need to show errors andvalidation notices.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/22475ee9c1dfb843764858b0c996dec497828639/assets/js/base/context/add-to-cart-form-context.js#L79-L94

🚀 This comment was generated by the automations bot based on a todo comment in 22475ee in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

onChange should trigger when a form element changes, so f...

Add Event Callbacks to the Add to Cart Form

onChange should trigger when a form element changes, so for example, a variation picker could indicate that it's ready.onSuccess should trigger after a successful add to cart. This could be used to reset form elements, do a redirect, or show something to the user.onFail should trigger when adding to cart fails. Form elements might show extra notices or reset. A fallback might be to redirect to the core product page in case of incompatibilities.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/22475ee9c1dfb843764858b0c996dec497828639/assets/js/base/context/add-to-cart-form-context.js#L102-L125

🚀 This comment was generated by the automations bot based on a todo comment in 22475ee in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

If the addToCart function within useStoreAddToCart fails,...

Surface add to cart errors in the single product block

If the addToCart function within useStoreAddToCart fails, a notice should be shown on the product page.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/22475ee9c1dfb843764858b0c996dec497828639/assets/js/base/context/add-to-cart-form-context.js#L94-L112

🚀 This comment was generated by the automations bot based on a todo comment in 22475ee in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

Surface add to cart errors in the single product block.

Surface add to cart errors in the single product block.

If the addToCart function within useStoreAddToCart fails, a notice should be shown on the product page.


https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/93ed273207aa022f469d8423e4abf83433ba4777/assets/js/base/context/add-to-cart-form-context.js#L94-L112

🚀 This comment was generated by the automations bot based on a todo comment in 93ed273 in #2705. cc @mikejolley

@github-actions
Copy link
Contributor

Add Event Callbacks to the Add to Cart Form.

Add Event Callbacks to the Add to Cart Form.

  • onChange should trigger when a form element changes, so for example, a variation picker could indicate that it's ready.
  • onSuccess should trigger after a successful add to cart. This could be used to reset form elements, do a redirect, or show something to the user.
  • onFail should trigger when adding to cart fails. Form elements might show extra notices or reset. A fallback might be to redirect to the core product page in case of incompatibilities.

https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/93ed273207aa022f469d8423e4abf83433ba4777/assets/js/base/context/add-to-cart-form-context.js#L102-L125

🚀 This comment was generated by the automations bot based on a todo comment in 93ed273 in #2705. cc @mikejolley

const VariationPicker = () => {
return (
<Placeholder className="wc-block-components-product-add-to-cart-variation-picker">
This is a placeholder for the variation picker form element.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This form element will be built in a follow up.

disabled,
onClick,
} ) => {
const [ wasClicked, setWasClicked ] = useState( false );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is show we only show qty in cart after using the add to cart button.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - what's the problem with showing the amount in cart on page refresh? I guess that's a design question, but feels appropriate to me. If you've added the product to cart, the button consistently shows you this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make sure it was shown as an "Add to Cart" button on first load like the mocks, rather than a button which says "x in cart". I think design will need another pass over all the blocks when we have them all built :)


if ( ! product || ! product.categories ) {
if ( isEmpty( product ) || isEmpty( product.categories ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stops {} products from being used.

* @typedef {import('@woocommerce/type-defs/contexts').AddToCartFormContext} AddToCartFormContext
*/

const AddToCartFormContext = createContext( {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've listed events and emitters I think we'll need but I will most likely need some help (in the follow up) making these work - it should be similar to the checkout extensibility work.

*
* @param {number} quantityInCart Quantity of the item in the cart.
*/
export const useTriggerFragmentRefresh = ( quantityInCart ) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this out to a hook to make it more reusable.

@mikejolley mikejolley added this to the Behind Feature Flag milestone Jun 16, 2020
@mikejolley mikejolley marked this pull request as ready for review June 16, 2020 15:27
@mikejolley mikejolley requested a review from a team as a code owner June 16, 2020 15:27
@mikejolley mikejolley requested review from haszari and Aljullu and removed request for a team June 16, 2020 15:27
*
* @todo Update cart and checkout button component. Cart and checkout should use base/components/button instead of cart-checkout/button.
*/
const Button = ( { className, showSpinner = false, children, ...props } ) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the cart/checkout button - I logged a todo to migrate the cart/checkout to this one instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're looking at this, is the docblock description accurate? It looks like the button vs a might come from Gutenberg button. This component seems to be focused on providing various classes + markup for our styles, and providing spinner.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does indeed, however since this implements that button I think it's clearer to state that this is what will happen if used.

continue;
}

if ( $term->term_id === $default_category ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a bug whilst testing where the 'uncategorized' category was being listed. This prevents that happening.

Copy link
Member

@haszari haszari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all looking good and testing well. I tested in Chrome only with the dev build (was exploring component tree in dev tools).

I tested all product types, didn't see any problems. Also tested external product type - I think this is fully functional in this PR? Worked correctly - button shows custom text and links out to external url.

Screen Shot 2020-06-17 at 11 32 39 AM

Added a bunch of inline comments for minor stuff and to improve my understanding, none of this is blocking 👍


return (
<>
<VariationPicker />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this VariableProductForm is very similar to SimpleProductForm - is it worth combining them and adding a prop for showing variation picker? Not a biggie.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think whilst the requirements are not completely clear (some of this may change as we built in extensibility) we should keep them separate and evaluate if they should merge once the picker is merged.

/**
* Quantity Input Component.
*/
const QuantityInput = ( { disabled, min, max, value, onChange } ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this might use QuantitySelector from cart. I guess that's a design consideration, for now we want to be consistent with current add-to-cart button?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, the designs use a plain input.

.wc-block-components-product-add-to-cart-quantity {
margin: 0 1em em($gap-small) 0;
width: 5em;
padding: 0.618em;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get this padding value from a constant? (Minor!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I got this value from the button styles so they had equal height. There is an issue to handle styles in a follow up across all atomic blocks: #2706

*
* @todo Update cart and checkout button component. Cart and checkout should use base/components/button instead of cart-checkout/button.
*/
const Button = ( { className, showSpinner = false, children, ...props } ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're looking at this, is the docblock description accurate? It looks like the button vs a might come from Gutenberg button. This component seems to be focused on providing various classes + markup for our styles, and providing spinner.

component: Button,
};

export const Default = () => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see you added stories for this! I'll give them a test too :)

disabled,
onClick,
} ) => {
const [ wasClicked, setWasClicked ] = useState( false );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - what's the problem with showing the amount in cart on page refresh? I guess that's a design question, but feels appropriate to me. If you've added the product to cart, the button consistently shows you this.


Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor - should we declare our expectations for product shape? e.g. { id, type, add_to_cart: { ?url, ?text } }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do this because product can be null and contains lots more props that those. There doesn't appear to be an existing pattern for doing this for product objects.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do a union-style null or shapeOf but probably limited value doing this.

disabled={ disabled }
showSpinner={ loading }
onClick={ ( e ) => {
e.preventDefault();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is preventDefault needed here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beats me, Ill remove it.

import { useAddToCartFormContext } from '@woocommerce/base-context';

/**
* Add to Cart Form Qty + Button Block Component.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment accurate? This looks like just the CTA/button component, the quantity picker is elsewhere.

},
} = product;

if ( ( showFormElements || ! hasOptions ) && isPurchasable ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused on why this is conditional on showFormElements || ! hasOptions. I'm guessing this is appropriate, can you clarify so I understand? Maybe add a comment if you think that's useful.

showFormElements - does this component need to be aware, or could the higher-level component (Block) simply not render AddToCartButton when form elements are toggled off?

hasOptions - when a product doesn't have options, why would that force the button to render?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment.

// If we are showing form elements, OR if the product has no additional form options, we can show
// a functional direct add to cart button, provided that the product is purchasable.
// No link is required to the full form under these circumstances.

@mikejolley
Copy link
Member Author

Merging this in; it's behind a feature flag!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
block-type: product elements Issues related to Product Element blocks.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Atomic Block] Add to Cart Form
3 participants