Add multi-shop cart pages

You've created a marketplace-level cart icon that will display the total number of items in the cart. Now you want buyers to be able to see and edit what's in the cart, across all merchant shops. Buyers should be able to edit product quantities and remove products from the cart. The cart page should direct buyers towards a sale.

In this tutorial, you'll learn how to create an interactive cart page at the marketplace-level. A marketplace-level cart page is one that displays all products in the cart, across all shops. The page will include a Go to Checkout button for each product, which redirects buyers from the cart to the relevant online store for purchase.

An image of the cart page with options to edit quantities, remove items, and view a summary of charges

What you'll learn

After you've finished this tutorial, you'll know how to do the following:

  • Filter shops by domain

  • Build a main cart page with a button redirecting users to an online store's checkout

  • Add the ability to edit and remove items from the cart

Requirements

Step 1: Update the internal GraphQL API with shop filtering by domain

To persist the buyer's carts, you stored the cart ID and shop domain in local storage. Now, you need to fetch the Storefront API access token for each shop in the cart so that you can query the Storefront API to fetch the cart details.

  1. In server/graphql/resolvers.js, add a filter to the shops query that only returns shops that match the list of domains.

  2. In server/graphql/schema.js, add the domains argument to the shops query.

    You should be able to query for shops by domain in the GraphQL playground, available in your app at the /graphql path.

    An image of a sample query and response in the GraphQL playground

Step 2: Build the main cart page

The cart page will live at a /cart route.

  1. In the pages folder create a cart.js file.

  2. In pages/cart.js, add a Cart component that returns a Page and export it as the default from the file.

  3. Retrieve the carts you stored in local storage using the helper you created, and query the internal GraphQL API to get the Storefront API access token for all shops in the cart.

    You can use the domain and Storefront API access token to create an Apollo Client for each shop in the cart and store it in the component's state.

  4. Add an effect that will use the shop clients from the component's state to fetch the cart data for each shop using the Storefront API, and store the them in the component's state.

    You'll use the Storefront API's Cart object to query for the cart details, and use this information to build out the cart page. You can use the inContext directive to ensure a consistent currency for all shop carts.

    You can add a loading state if the cart details are not returned, and an empty state if the cart does not contain any shops.

  5. Create a ShopCart component that accepts shopName, lineItemEdges, formattedTotal, and checkoutUrl properties and returns a cart UI.

    You'll also add a GO TO CHECKOUT button that redirects the buyer to the cart's checkout URL in a new tab.

  6. Add a ShopSummaryLine component that accepts shopName, lineItemsInfo, totalLineItems, and formattedTotal properties and returns a summary of a shop's cart.

  7. In the Cart component, use cartsInfo with the components created above to render a cart page with a section for each shop and a summary of all shop carts.

    The cart page should now look something like the following:

    An image of the main cart page displaying items from multiple merchant's shops and the total quantity of each

Step 3: Add edit and delete functionality

In this step, you'll add the ability for buyers to edit item quantities and remove items from the cart entirely.

  1. In helpers/cartHelpers.js, create the following mutations using the Storefront API's Cart object.

    • cartLinesRemove, which allows buyers to delete line items from the cart
    • cartLinesUpdate, which buyers use to edit the quantity of a line item in the cart

    Similar to the cart query you created, you can use the inContext directive to ensure cart prices are returned in a consistent currency.

    You'll use the same cart fields for both mutations. They're the same as the fields that you used on the main cart page.

    You can use a GraphQL fragment to ensure that the same cart fields are returned for all mutations and queries. The fragment should contain the same fields that you used in the main cart page query.

  2. In pages/cart.js, update the CART_DETAILS_QUERY to use the CORE_CART_FIELDS fragment that you created.

    Since the fields in the fragment are the same as in the query that you created, you don't need to make any changes to code that uses the query response data.

  3. Update the ShopCart component to accept cartId, shopClient, shopDomain, and onCartChange properties and use them to add callbacks that will fire the mutation helpers created above when the buyer removes an item from their cart, or updates the quantity.

    You can add a Close icon and a Select dropdown that lets the buyer remove an item or edit the quantity.

  4. Update the Cart component to pass in the required properties to the ShopCart component.

    Add a callback function that updates the Cart component's state with the updated cart data for a given shop and pass it in as the onCartUpdate property.

    To prevent showing an empty shop cart if all items are removed, add a check to the cartsInfo reducer to skip over the cart if it doesn't contain any line items.

  5. Add an effect to the Cart component that gets fired when cartsInfo is updated. It should calculate the total count from cartsInfo and use it to set the global local storage count value. This ensures that the badge displayed in the header is updated when items are added or removed from the cart.

    The cart page should now look something like the following:

    An image of the cart page with options to edit quantities, remove items, and view a summary of charges

Step 4: Refresh the page after checkout

When a buyer completes the checkout for a specific shop, you want your marketplace's cart page to reflect that the checkout has been completed by removing the shop's items from the cart page and displaying a message.

  1. In helpers/cartHelpers.js, in the addToExistingCart function, add code to create a new cart if the cartLinesAdd mutation returns null.

    Receiving a null cart on the mutation indicates that the checkout is complete and a new cart will be created. This will happen if the buyer completes the checkout for the shop, and then goes back and adds another item from the same shop to their cart.

    You can also add a helper that will remove a shop's cart from local storage, so that you can remove the cart once the checkout is complete.

  2. In pages/cart.js, add an effect to the Cart component that adds an event listener to process incoming messages from the Shopify checkout.

    The event will be triggered with specific message data when the checkout is complete. Then you can force refresh the page to refetch the cart details.

    If the cart is returned as null from the Storefront API, then you'll surface a message that the checkout has already been completed for this shop and remove the cart from local storage using the helper that you created.

    When checkout is complete, the shop section on the cart page should look something like the following:

    An image of the cart page after checkout is complete

Next steps