From 162d0b88a886e62af7d30e570682a5695691761c Mon Sep 17 00:00:00 2001 From: Jp Date: Thu, 2 Nov 2023 11:51:22 -0400 Subject: [PATCH 1/4] feat: context.js updates w/ graphQL --- lib/context.js | 833 +++++++++++++++++++++++++++++++------------------ 1 file changed, 527 insertions(+), 306 deletions(-) diff --git a/lib/context.js b/lib/context.js index 7fb8ef1..69fa8c5 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1,54 +1,148 @@ -import React, { createContext, useContext, useEffect, useState } from 'react' -import { Base64 } from 'base64-string' +import { Base64 } from 'base64-string'; +import { createContext, useContext, useState } from 'react'; // get our API clients (shopify + sanity) -import { getSanityClient } from '@lib/sanity' -import shopify from '@lib/shopify' +import { getSanityClient } from '@lib/sanity'; +import shopify from '@lib/shopify'; // get our global image GROQ -import { queries } from '@data' +import { queries } from '@data'; // Set our initial context states const initialContext = { - isPageTransition: false, - meganav: { - isOpen: false, - activeID: null, - }, - productCounts: [], - shopifyClient: shopify, - isLoading: true, - isAdding: false, - isUpdating: false, - isCartOpen: false, - checkout: { - id: null, - lineItems: [], - }, -} + isPageTransition: false, + meganav: { + isOpen: false, + activeID: null, + }, + productCounts: [], + shopifyClient: shopify, + isLoading: true, + isAdding: false, + isUpdating: false, + isCartOpen: false, + checkout: { + id: null, + lineItems: [], + }, + cart: {}, +}; // Set context const SiteContext = createContext({ - context: initialContext, - setContext: () => null, -}) + context: initialContext, + setContext: () => null, +}); -// Build a new checkout -const createNewCheckout = (context) => { - return context.shopifyClient?.checkout.create({ - presentmentCurrencyCode: 'USD', - }) +// set Shopify variables +const shopifyCartID = 'shopify_checkout_id'; +const shopifyVariantGID = 'gid://shopify/ProductVariant/'; + +// Set ShopifyGraphQL as a function so we can reuse it +async function shopifyGraphQL(query, variables) { + try { + const res = await fetch( + `https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_ID}.myshopify.com/api/2023-10/graphql.json`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Shopify-Storefront-Access-Token': + process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_API_TOKEN, + }, + body: JSON.stringify({ + query: query, + variables: variables ?? null, + }), + }, + ); + + const data = await res.json(); + return data; + } catch (error) { + console.error(error); + return ''; + } } +// defining what the query returns for the product so that we can easily reuse it +const product = `product { + title + handle + images(first: 5) { + edges { + node { + id + originalSrc + altText + } + } + } + }`; + +// Build a new checkout +const createNewCart = async (context) => { + // GraphQL query to create a cart + const query = `mutation { + cartCreate(input: {lines: []}) { + cart { + id + checkoutUrl + cost { + checkoutChargeAmount { + amount + } + } + } + } +}`; + const queryResponse = await shopifyGraphQL(query); + const cart = queryResponse.data.cartCreate; + + // Update our global store states + setCartState(cart); + + return; +}; // Get Shopify checkout cart -const fetchCheckout = (context, id) => { - return context.shopifyClient?.checkout.fetch(id) -} +const fetchCart = async (context, id) => { + // GraphQL query to fetch a cart + const query = `{ + cart(id: "${id}") { + id + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${product} + } + } + quantity + } + } + } + cost { + checkoutChargeAmount { + amount + } + } + } +}`; + const queryResponse = await shopifyGraphQL(query); + const cart = queryResponse.data; + // Update our global store states + setCartState(cart); + return; +}; // get associated variant from Sanity const fetchVariant = async (id) => { - const variant = await getSanityClient().fetch( - ` + const variant = await getSanityClient().fetch( + ` *[_type == "productVariant" && variantID == ${id}][0]{ "product": *[_type == "product" && productID == ^.productID][0]{ title, @@ -71,166 +165,170 @@ const fetchVariant = async (id) => { value } } - ` - ) + `, + ); - return variant -} - -// set Shopify variables -const shopifyCheckoutID = 'shopify_checkout_id' -const shopifyVariantGID = 'gid://shopify/ProductVariant/' + return variant; +}; // set our checkout states -const setCheckoutState = async (checkout, setContext, openCart) => { - if (!checkout) return null - - if (typeof window !== `undefined`) { - localStorage.setItem(shopifyCheckoutID, checkout.id) - } - - // get real lineItems data from Sanity - const lineItems = await Promise.all( - checkout.lineItems.map(async (item) => { - const variantID = item.variant.id.split(shopifyVariantGID)[1] - const variant = await fetchVariant(variantID) - - return { ...variant, quantity: item.quantity, lineID: item.id } - }) - ) - - // update state - setContext((prevState) => { - return { - ...prevState, - isAdding: false, - isLoading: false, - isUpdating: false, - isCartOpen: openCart ? true : prevState.isCartOpen, - checkout: { - id: checkout.id, - lineItems: lineItems, - subTotal: checkout.lineItemsSubtotalPrice, - webUrl: checkout.webUrl, - }, - } - }) -} +const setCartState = async (cart, checkout, setContext, openCart) => { + if (!checkout && !cart) return null; + + if (typeof window !== `undefined`) { + localStorage.setItem(shopifyCartID, cart.id); + } + + // get real lineItems data from Sanity + const lineItems = cart?.lines + ? await Promise.all( + cart.lines.edges.map(async (edge) => { + const variantID = edge.node.merchandise.id.replace( + shopifyVariantGID, + '', + ); + const variant = await fetchVariant(variantID); + + return { + ...variant, + quantity: edge.node.quantity, + lineID: edge.node.id, + }; + }), + ) + : []; + + // update state + setContext((prevState) => { + return { + ...prevState, + isAdding: false, + isLoading: false, + isUpdating: false, + isCartOpen: openCart ? true : prevState.isCartOpen, + checkout: { + id: checkout.id, + webUrl: checkout.webUrl, + }, + cart: cart, + }; + }); +}; /* ------------------------------ */ /* Our Context Wrapper /* ------------------------------ */ const SiteContextProvider = ({ data, children }) => { - const { productCounts } = data - - const [context, setContext] = useState({ - ...initialContext, - ...{ productCounts }, - }) - - const [initContext, setInitContext] = useState(false) - - useEffect(() => { - // Shopify checkout not build yet - if (initContext === false) { - const initializeCheckout = async () => { - const existingCheckoutID = - typeof window !== 'undefined' - ? localStorage.getItem(shopifyCheckoutID) - : false - - // existing Shopify checkout ID found - if (existingCheckoutID) { - try { - // fetch checkout from Shopify - const existingCheckout = await fetchCheckout( - context, - existingCheckoutID - ) - - // Check if there are invalid items - if ( - existingCheckout.lineItems.some((lineItem) => !lineItem.variant) - ) { - throw new Error( - 'Invalid item in checkout. This variant was probably deleted from Shopify.' - ) - } - - // Make sure this cart hasn’t already been purchased. - if (!existingCheckout.completedAt) { - setCheckoutState(existingCheckout, setContext) - return - } - } catch (e) { - localStorage.setItem(shopifyCheckoutID, null) - } - } - - // Otherwise, create a new checkout! - const newCheckout = await createNewCheckout(context) - setCheckoutState(newCheckout, setContext) - } - - // Initialize the store context - initializeCheckout() - setInitContext(true) - } - }, [initContext, context, setContext, context.shopifyClient?.checkout]) - - return ( - - {children} - - ) -} + const { productCounts } = data; + + const [context, setContext] = useState({ + ...initialContext, + ...{ productCounts }, + }); + + const [initContext, setInitContext] = useState(false); + + /* Is the error here? + useEffect(() => { + // Shopify checkout not build yet + if (initContext === false) { + const initializeCheckout = async () => { + const existingCartID = + typeof window !== 'undefined' + ? localStorage.getItem(shopifyCartID) + : false; + + // existing Shopify checkout ID found + if (existingCartID) { + try { + // fetch checkout from Shopify + const existingCart = await fetchCart(context, existingCartID); + + // /* Disabling for now + // Check if there are invalid items + if ( + existingCheckout.lineItems.some((lineItem) => !lineItem.variant) + ) { + throw new Error( + 'Invalid item in checkout. This variant was probably deleted from Shopify.', + ); + } + + // Make sure this cart hasn’t already been purchased. + if (!existingCheckout.completedAt) { + setCartState(newCart, existingCheckout, setContext); + return; + } + // *close comment + } catch (e) { + localStorage.setItem(shopifyCartID, null); + } + } + + // Otherwise, create a new checkout! + const newCart = await createNewCart(context); + }; + + // Initialize the store context + initializeCheckout(); + setInitContext(true); + } + }, [initContext, context, setContext, context.shopifyClient?.checkout]); // Needs reviewing + */ + + return ( + + {children} + + ); +}; // Access our global store states function useSiteContext() { - const { context } = useContext(SiteContext) - return context + const { context } = useContext(SiteContext); + return context; } // Toggle page transition state function useTogglePageTransition() { - const { - context: { isPageTransition }, - setContext, - } = useContext(SiteContext) - - async function togglePageTransition(state) { - setContext((prevState) => { - return { ...prevState, isPageTransition: state } - }) - } - return togglePageTransition + const { + context: { isPageTransition }, + setContext, + } = useContext(SiteContext); + + async function togglePageTransition(state) { + setContext((prevState) => { + return { ...prevState, isPageTransition: state }; + }); + } + return togglePageTransition; } // Toggle Mega Navigation states function useToggleMegaNav() { - const { - context: { meganav }, - setContext, - } = useContext(SiteContext) - - async function toggleMegaNav(state, id = null) { - setContext((prevState) => { - return { - ...prevState, - meganav: { - isOpen: state === 'toggle' ? !meganav.isOpen : state, - activeID: state === 'toggle' && meganav.isOpen ? null : id, - }, - } - }) - } - return toggleMegaNav + const { + context: { meganav }, + setContext, + } = useContext(SiteContext); + + async function toggleMegaNav(state, id = null) { + setContext((prevState) => { + return { + ...prevState, + meganav: { + isOpen: state === 'toggle' ? !meganav.isOpen : state, + activeID: state === 'toggle' && meganav.isOpen ? null : id, + }, + }; + }); + } + return toggleMegaNav; } /* ------------------------------ */ @@ -239,182 +337,305 @@ function useToggleMegaNav() { // Access our cart item count function useCartCount() { - const { - context: { checkout }, - } = useContext(SiteContext) + const { + context: { cart }, + } = useContext(SiteContext); - let count = 0 + let count = 0; - if (checkout.lineItems) { - count = checkout.lineItems.reduce((total, item) => item.quantity + total, 0) - } + if (cart.lines) { + count = cart.lines.edges.forEach((edge) => { + count += edge.node.quantity; + }); + } - return count + return count; } // Access our cart totals function useCartTotals() { - const { - context: { checkout }, - } = useContext(SiteContext) - - const subTotal = checkout.subTotal ? checkout.subTotal.amount * 100 : false - return { - subTotal, - } + const { + context: { cart }, + } = useContext(SiteContext); + + // Our GraphQL queries always return this property: + /* + The estimated amount, before taxes and discounts, for the customer to pay at checkout. + The checkout charge amount doesn't include any deferred payments that'll be paid at a later date. + If the cart has no deferred payments, then the checkout charge amount is equivalent to subtotalAmount. + */ + // We might want to use a different key in order to get discount included + // https://shopify.dev/docs/api/storefront/2023-10/objects/CartCost + const subTotal = cart?.cost?.checkoutChargeAmount?.amount + ? cart.cost.checkoutChargeAmount.amount + : false; + return { + subTotal, + }; } // Access our cart items function useCartItems() { - const { - context: { checkout }, - } = useContext(SiteContext) + const { + context: { cart }, + } = useContext(SiteContext); - return checkout.lineItems + return cart?.lines?.edges; } // Add an item to the checkout cart function useAddItem() { - const { - context: { checkout, shopifyClient }, - setContext, - } = useContext(SiteContext) - - async function addItem(variantID, quantity, attributes) { - // Bail if no ID or quantity given - if (!variantID || !quantity) return - - // Otherwise, start adding the product - setContext((prevState) => { - return { ...prevState, isAdding: true, isUpdating: true } - }) - - // build encoded variantID - const enc = new Base64() - const variant = enc.urlEncode(`${shopifyVariantGID}${variantID}`) - - // Build the cart line item - const newItem = { - variantId: variant, - quantity: quantity, - customAttributes: attributes, + const { + context: { cart }, + setContext, + } = useContext(SiteContext); + + async function addItem(variantID, quantity, attributes) { + // Bail if no ID or quantity given + if (!variantID || !quantity) return; + + // Otherwise, start adding the product + setContext((prevState) => { + return { ...prevState, isAdding: true, isUpdating: true }; + }); + + // build encoded variantID + const enc = new Base64(); + const variant = enc.urlEncode(`${shopifyVariantGID}${variantID}`); + + // GraphQL query to add items to the cart + const query = `mutation { + cartLinesAdd( + cartId: "${cart.id}" + lines: [ + { + merchandiseId: "${variant}", + quantity: ${quantity} + } + ] + ) { + cart { + id + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${product} + } + } + quantity + } + } + } + cost { + checkoutChargeAmount { + amount + } + } + } + userErrors { + field + message } - - // Add it to the Shopify checkout cart - const newCheckout = await shopifyClient.checkout.addLineItems( - checkout.id, - newItem - ) - - // Update our global store states - setCheckoutState(newCheckout, setContext, true) } +}`; + const queryResponse = await shopifyGraphQL(query); + const newCart = queryResponse.data.cartLinesAdd; + console.log(newCart); + const newCheckout = { webUrl: newCart.checkoutUrl }; + + // Update our global store states + setCartState(newCart, newCheckout, setContext, true); + } - return addItem + return addItem; } // Update item in cart function useUpdateItem() { - const { - context: { checkout, shopifyClient }, - setContext, - } = useContext(SiteContext) - - async function updateItem(itemID, quantity) { - // Bail if no ID or quantity given - if (!itemID || !quantity) return - - // Otherwise, start adding the product - setContext((prevState) => { - return { ...prevState, isUpdating: true } - }) - - const newCheckout = await shopifyClient.checkout.updateLineItems( - checkout.id, - [{ id: itemID, quantity: quantity }] - ) - - setCheckoutState(newCheckout, setContext) + const { + context: { cart }, + setContext, + } = useContext(SiteContext); + + async function updateItem(itemID, quantity) { + // Bail if no ID or quantity given + if (!itemID || !quantity) return; + + // Otherwise, start adding the product + setContext((prevState) => { + return { ...prevState, isUpdating: true }; + }); + + // GraphQL query to update items in the cart + const query = `mutation { + cartLinesUpdate( + cartId: "${cart.id}" + lines: [ + { + id: "${itemID}", + quantity: ${quantity} + }, + ] + ) { + cart { + id + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${product} + } + } + quantity + } + } + } + cost { + checkoutChargeAmount { + amount + } + } + } + userErrors { + field + message + } } - return updateItem +}`; + const queryResponse = await shopifyGraphQL(query); + const newCart = queryResponse.data.cartLinesUpdate; + console.log(newCart); + const newCheckout = { webUrl: newCart.checkoutUrl }; + + // Update our global store states + setCartState(newCart, newCheckout, setContext, true); + } + return updateItem; } // Remove item from cart function useRemoveItem() { - const { - context: { checkout, shopifyClient }, - setContext, - } = useContext(SiteContext) - - async function removeItem(itemID) { - // Bail if no ID given - if (!itemID) return - - // Otherwise, start removing the product - setContext((prevState) => { - return { ...prevState, isUpdating: true } - }) - - const newCheckout = await shopifyClient.checkout.removeLineItems( - checkout.id, - [itemID] - ) - - setCheckoutState(newCheckout, setContext) + const { + context: { cart }, + setContext, + } = useContext(SiteContext); + + async function removeItem(itemID) { + // Bail if no ID given + if (!itemID) return; + + // Otherwise, start removing the product + setContext((prevState) => { + return { ...prevState, isUpdating: true }; + }); + + // GraphQL query to remove items from the cart + const query = `mutation { + cartLinesRemove( + cartId: "${cart.id}" + lineIds: [ + "${itemID}" + ] + ) { + cart { + id + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${product} + } + } + quantity + } + } + } + cost { + checkoutChargeAmount { + amount + } + } + } + userErrors { + field + message + } } - return removeItem +}`; + const queryResponse = await shopifyGraphQL(query); + const newCart = queryResponse.data.cartLinesRemove; + console.log(newCart); + const newCheckout = { webUrl: newCart.checkoutUrl }; + + setCartState(newCart, newCheckout, setContext); + } + return removeItem; } // Build our Checkout URL function useCheckout() { - const { - context: { checkout }, - } = useContext(SiteContext) + const { + context: { checkout }, + } = useContext(SiteContext); - return checkout.webUrl + return checkout.webUrl; } // Toggle cart state function useToggleCart() { - const { - context: { isCartOpen }, - setContext, - } = useContext(SiteContext) - - async function toggleCart() { - setContext((prevState) => { - return { ...prevState, isCartOpen: !isCartOpen } - }) - } - return toggleCart + const { + context: { isCartOpen }, + setContext, + } = useContext(SiteContext); + + async function toggleCart() { + setContext((prevState) => { + return { ...prevState, isCartOpen: !isCartOpen }; + }); + } + return toggleCart; } // Reference a collection product count function useProductCount() { - const { - context: { productCounts }, - } = useContext(SiteContext) + const { + context: { productCounts }, + } = useContext(SiteContext); - function productCount(collection) { - const collectionItem = productCounts.find((c) => c.slug === collection) - return collectionItem.count - } + function productCount(collection) { + const collectionItem = productCounts.find((c) => c.slug === collection); + return collectionItem.count; + } - return productCount + return productCount; } export { - SiteContextProvider, - useSiteContext, - useTogglePageTransition, - useToggleMegaNav, - useCartCount, - useCartTotals, - useCartItems, - useAddItem, - useUpdateItem, - useRemoveItem, - useCheckout, - useToggleCart, - useProductCount, -} + SiteContextProvider, + useAddItem, + useCartCount, + useCartItems, + useCartTotals, + useCheckout, + useProductCount, + useRemoveItem, + useSiteContext, + useToggleCart, + useToggleMegaNav, + useTogglePageTransition, + useUpdateItem +}; + From dac7fc6e6f0cb38c1e66b8db865f9090e8f17b5e Mon Sep 17 00:00:00 2001 From: jpvalery Date: Thu, 2 Nov 2023 13:32:10 -0400 Subject: [PATCH 2/4] fix: switch to HULL original Prettier config --- lib/context.js | 592 ++++++++++++++++++++++++------------------------- 1 file changed, 296 insertions(+), 296 deletions(-) diff --git a/lib/context.js b/lib/context.js index 69fa8c5..5f30e32 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1,68 +1,68 @@ -import { Base64 } from 'base64-string'; -import { createContext, useContext, useState } from 'react'; +import { Base64 } from 'base64-string' +import { createContext, useContext, useState } from 'react' // get our API clients (shopify + sanity) -import { getSanityClient } from '@lib/sanity'; -import shopify from '@lib/shopify'; +import { getSanityClient } from '@lib/sanity' +import shopify from '@lib/shopify' // get our global image GROQ -import { queries } from '@data'; +import { queries } from '@data' // Set our initial context states const initialContext = { - isPageTransition: false, - meganav: { - isOpen: false, - activeID: null, - }, - productCounts: [], - shopifyClient: shopify, - isLoading: true, - isAdding: false, - isUpdating: false, - isCartOpen: false, - checkout: { - id: null, - lineItems: [], - }, - cart: {}, -}; + isPageTransition: false, + meganav: { + isOpen: false, + activeID: null, + }, + productCounts: [], + shopifyClient: shopify, + isLoading: true, + isAdding: false, + isUpdating: false, + isCartOpen: false, + checkout: { + id: null, + lineItems: [], + }, + cart: {}, +} // Set context const SiteContext = createContext({ - context: initialContext, - setContext: () => null, -}); + context: initialContext, + setContext: () => null, +}) // set Shopify variables -const shopifyCartID = 'shopify_checkout_id'; -const shopifyVariantGID = 'gid://shopify/ProductVariant/'; +const shopifyCartID = 'shopify_checkout_id' +const shopifyVariantGID = 'gid://shopify/ProductVariant/' // Set ShopifyGraphQL as a function so we can reuse it async function shopifyGraphQL(query, variables) { - try { - const res = await fetch( - `https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_ID}.myshopify.com/api/2023-10/graphql.json`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Shopify-Storefront-Access-Token': - process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_API_TOKEN, - }, - body: JSON.stringify({ - query: query, - variables: variables ?? null, - }), - }, - ); - - const data = await res.json(); - return data; - } catch (error) { - console.error(error); - return ''; - } + try { + const res = await fetch( + `https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_ID}.myshopify.com/api/2023-10/graphql.json`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Shopify-Storefront-Access-Token': + process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_API_TOKEN, + }, + body: JSON.stringify({ + query: query, + variables: variables ?? null, + }), + } + ) + + const data = await res.json() + return data + } catch (error) { + console.error(error) + return '' + } } // defining what the query returns for the product so that we can easily reuse it const product = `product { @@ -77,12 +77,12 @@ const product = `product { } } } - }`; + }` // Build a new checkout const createNewCart = async (context) => { - // GraphQL query to create a cart - const query = `mutation { + // GraphQL query to create a cart + const query = `mutation { cartCreate(input: {lines: []}) { cart { id @@ -94,20 +94,20 @@ const createNewCart = async (context) => { } } } -}`; - const queryResponse = await shopifyGraphQL(query); - const cart = queryResponse.data.cartCreate; +}` + const queryResponse = await shopifyGraphQL(query) + const cart = queryResponse.data.cartCreate - // Update our global store states - setCartState(cart); + // Update our global store states + setCartState(cart) - return; -}; + return +} // Get Shopify checkout cart const fetchCart = async (context, id) => { - // GraphQL query to fetch a cart - const query = `{ + // GraphQL query to fetch a cart + const query = `{ cart(id: "${id}") { id checkoutUrl @@ -131,18 +131,18 @@ const fetchCart = async (context, id) => { } } } -}`; - const queryResponse = await shopifyGraphQL(query); - const cart = queryResponse.data; - // Update our global store states - setCartState(cart); - return; -}; +}` + const queryResponse = await shopifyGraphQL(query) + const cart = queryResponse.data + // Update our global store states + setCartState(cart) + return +} // get associated variant from Sanity const fetchVariant = async (id) => { - const variant = await getSanityClient().fetch( - ` + const variant = await getSanityClient().fetch( + ` *[_type == "productVariant" && variantID == ${id}][0]{ "product": *[_type == "product" && productID == ^.productID][0]{ title, @@ -165,71 +165,71 @@ const fetchVariant = async (id) => { value } } - `, - ); + ` + ) - return variant; -}; + return variant +} // set our checkout states const setCartState = async (cart, checkout, setContext, openCart) => { - if (!checkout && !cart) return null; - - if (typeof window !== `undefined`) { - localStorage.setItem(shopifyCartID, cart.id); - } - - // get real lineItems data from Sanity - const lineItems = cart?.lines - ? await Promise.all( - cart.lines.edges.map(async (edge) => { - const variantID = edge.node.merchandise.id.replace( - shopifyVariantGID, - '', - ); - const variant = await fetchVariant(variantID); - - return { - ...variant, - quantity: edge.node.quantity, - lineID: edge.node.id, - }; - }), - ) - : []; - - // update state - setContext((prevState) => { - return { - ...prevState, - isAdding: false, - isLoading: false, - isUpdating: false, - isCartOpen: openCart ? true : prevState.isCartOpen, - checkout: { - id: checkout.id, - webUrl: checkout.webUrl, - }, - cart: cart, - }; - }); -}; + if (!checkout && !cart) return null + + if (typeof window !== `undefined`) { + localStorage.setItem(shopifyCartID, cart.id) + } + + // get real lineItems data from Sanity + const lineItems = cart?.lines + ? await Promise.all( + cart.lines.edges.map(async (edge) => { + const variantID = edge.node.merchandise.id.replace( + shopifyVariantGID, + '' + ) + const variant = await fetchVariant(variantID) + + return { + ...variant, + quantity: edge.node.quantity, + lineID: edge.node.id, + } + }) + ) + : [] + + // update state + setContext((prevState) => { + return { + ...prevState, + isAdding: false, + isLoading: false, + isUpdating: false, + isCartOpen: openCart ? true : prevState.isCartOpen, + checkout: { + id: checkout.id, + webUrl: checkout.webUrl, + }, + cart: cart, + } + }) +} /* ------------------------------ */ /* Our Context Wrapper /* ------------------------------ */ const SiteContextProvider = ({ data, children }) => { - const { productCounts } = data; + const { productCounts } = data - const [context, setContext] = useState({ - ...initialContext, - ...{ productCounts }, - }); + const [context, setContext] = useState({ + ...initialContext, + ...{ productCounts }, + }) - const [initContext, setInitContext] = useState(false); + const [initContext, setInitContext] = useState(false) - /* Is the error here? + /* Is the error here? useEffect(() => { // Shopify checkout not build yet if (initContext === false) { @@ -277,58 +277,58 @@ const SiteContextProvider = ({ data, children }) => { }, [initContext, context, setContext, context.shopifyClient?.checkout]); // Needs reviewing */ - return ( - - {children} - - ); -}; + return ( + + {children} + + ) +} // Access our global store states function useSiteContext() { - const { context } = useContext(SiteContext); - return context; + const { context } = useContext(SiteContext) + return context } // Toggle page transition state function useTogglePageTransition() { - const { - context: { isPageTransition }, - setContext, - } = useContext(SiteContext); - - async function togglePageTransition(state) { - setContext((prevState) => { - return { ...prevState, isPageTransition: state }; - }); - } - return togglePageTransition; + const { + context: { isPageTransition }, + setContext, + } = useContext(SiteContext) + + async function togglePageTransition(state) { + setContext((prevState) => { + return { ...prevState, isPageTransition: state } + }) + } + return togglePageTransition } // Toggle Mega Navigation states function useToggleMegaNav() { - const { - context: { meganav }, - setContext, - } = useContext(SiteContext); - - async function toggleMegaNav(state, id = null) { - setContext((prevState) => { - return { - ...prevState, - meganav: { - isOpen: state === 'toggle' ? !meganav.isOpen : state, - activeID: state === 'toggle' && meganav.isOpen ? null : id, - }, - }; - }); - } - return toggleMegaNav; + const { + context: { meganav }, + setContext, + } = useContext(SiteContext) + + async function toggleMegaNav(state, id = null) { + setContext((prevState) => { + return { + ...prevState, + meganav: { + isOpen: state === 'toggle' ? !meganav.isOpen : state, + activeID: state === 'toggle' && meganav.isOpen ? null : id, + }, + } + }) + } + return toggleMegaNav } /* ------------------------------ */ @@ -337,74 +337,74 @@ function useToggleMegaNav() { // Access our cart item count function useCartCount() { - const { - context: { cart }, - } = useContext(SiteContext); + const { + context: { cart }, + } = useContext(SiteContext) - let count = 0; + let count = 0 - if (cart.lines) { - count = cart.lines.edges.forEach((edge) => { - count += edge.node.quantity; - }); - } + if (cart.lines) { + count = cart.lines.edges.forEach((edge) => { + count += edge.node.quantity + }) + } - return count; + return count } // Access our cart totals function useCartTotals() { - const { - context: { cart }, - } = useContext(SiteContext); + const { + context: { cart }, + } = useContext(SiteContext) - // Our GraphQL queries always return this property: - /* + // Our GraphQL queries always return this property: + /* The estimated amount, before taxes and discounts, for the customer to pay at checkout. The checkout charge amount doesn't include any deferred payments that'll be paid at a later date. If the cart has no deferred payments, then the checkout charge amount is equivalent to subtotalAmount. */ - // We might want to use a different key in order to get discount included - // https://shopify.dev/docs/api/storefront/2023-10/objects/CartCost - const subTotal = cart?.cost?.checkoutChargeAmount?.amount - ? cart.cost.checkoutChargeAmount.amount - : false; - return { - subTotal, - }; + // We might want to use a different key in order to get discount included + // https://shopify.dev/docs/api/storefront/2023-10/objects/CartCost + const subTotal = cart?.cost?.checkoutChargeAmount?.amount + ? cart.cost.checkoutChargeAmount.amount + : false + return { + subTotal, + } } // Access our cart items function useCartItems() { - const { - context: { cart }, - } = useContext(SiteContext); + const { + context: { cart }, + } = useContext(SiteContext) - return cart?.lines?.edges; + return cart?.lines?.edges } // Add an item to the checkout cart function useAddItem() { - const { - context: { cart }, - setContext, - } = useContext(SiteContext); - - async function addItem(variantID, quantity, attributes) { - // Bail if no ID or quantity given - if (!variantID || !quantity) return; - - // Otherwise, start adding the product - setContext((prevState) => { - return { ...prevState, isAdding: true, isUpdating: true }; - }); - - // build encoded variantID - const enc = new Base64(); - const variant = enc.urlEncode(`${shopifyVariantGID}${variantID}`); - - // GraphQL query to add items to the cart - const query = `mutation { + const { + context: { cart }, + setContext, + } = useContext(SiteContext) + + async function addItem(variantID, quantity, attributes) { + // Bail if no ID or quantity given + if (!variantID || !quantity) return + + // Otherwise, start adding the product + setContext((prevState) => { + return { ...prevState, isAdding: true, isUpdating: true } + }) + + // build encoded variantID + const enc = new Base64() + const variant = enc.urlEncode(`${shopifyVariantGID}${variantID}`) + + // GraphQL query to add items to the cart + const query = `mutation { cartLinesAdd( cartId: "${cart.id}" lines: [ @@ -442,37 +442,37 @@ function useAddItem() { message } } -}`; - const queryResponse = await shopifyGraphQL(query); - const newCart = queryResponse.data.cartLinesAdd; - console.log(newCart); - const newCheckout = { webUrl: newCart.checkoutUrl }; - - // Update our global store states - setCartState(newCart, newCheckout, setContext, true); - } +}` + const queryResponse = await shopifyGraphQL(query) + const newCart = queryResponse.data.cartLinesAdd + console.log(newCart) + const newCheckout = { webUrl: newCart.checkoutUrl } + + // Update our global store states + setCartState(newCart, newCheckout, setContext, true) + } - return addItem; + return addItem } // Update item in cart function useUpdateItem() { - const { - context: { cart }, - setContext, - } = useContext(SiteContext); - - async function updateItem(itemID, quantity) { - // Bail if no ID or quantity given - if (!itemID || !quantity) return; - - // Otherwise, start adding the product - setContext((prevState) => { - return { ...prevState, isUpdating: true }; - }); - - // GraphQL query to update items in the cart - const query = `mutation { + const { + context: { cart }, + setContext, + } = useContext(SiteContext) + + async function updateItem(itemID, quantity) { + // Bail if no ID or quantity given + if (!itemID || !quantity) return + + // Otherwise, start adding the product + setContext((prevState) => { + return { ...prevState, isUpdating: true } + }) + + // GraphQL query to update items in the cart + const query = `mutation { cartLinesUpdate( cartId: "${cart.id}" lines: [ @@ -510,36 +510,36 @@ function useUpdateItem() { message } } -}`; - const queryResponse = await shopifyGraphQL(query); - const newCart = queryResponse.data.cartLinesUpdate; - console.log(newCart); - const newCheckout = { webUrl: newCart.checkoutUrl }; - - // Update our global store states - setCartState(newCart, newCheckout, setContext, true); - } - return updateItem; +}` + const queryResponse = await shopifyGraphQL(query) + const newCart = queryResponse.data.cartLinesUpdate + console.log(newCart) + const newCheckout = { webUrl: newCart.checkoutUrl } + + // Update our global store states + setCartState(newCart, newCheckout, setContext, true) + } + return updateItem } // Remove item from cart function useRemoveItem() { - const { - context: { cart }, - setContext, - } = useContext(SiteContext); - - async function removeItem(itemID) { - // Bail if no ID given - if (!itemID) return; - - // Otherwise, start removing the product - setContext((prevState) => { - return { ...prevState, isUpdating: true }; - }); - - // GraphQL query to remove items from the cart - const query = `mutation { + const { + context: { cart }, + setContext, + } = useContext(SiteContext) + + async function removeItem(itemID) { + // Bail if no ID given + if (!itemID) return + + // Otherwise, start removing the product + setContext((prevState) => { + return { ...prevState, isUpdating: true } + }) + + // GraphQL query to remove items from the cart + const query = `mutation { cartLinesRemove( cartId: "${cart.id}" lineIds: [ @@ -574,53 +574,53 @@ function useRemoveItem() { message } } -}`; - const queryResponse = await shopifyGraphQL(query); - const newCart = queryResponse.data.cartLinesRemove; - console.log(newCart); - const newCheckout = { webUrl: newCart.checkoutUrl }; - - setCartState(newCart, newCheckout, setContext); - } - return removeItem; +}` + const queryResponse = await shopifyGraphQL(query) + const newCart = queryResponse.data.cartLinesRemove + console.log(newCart) + const newCheckout = { webUrl: newCart.checkoutUrl } + + setCartState(newCart, newCheckout, setContext) + } + return removeItem } // Build our Checkout URL function useCheckout() { - const { - context: { checkout }, - } = useContext(SiteContext); + const { + context: { checkout }, + } = useContext(SiteContext) - return checkout.webUrl; + return checkout.webUrl } // Toggle cart state function useToggleCart() { - const { - context: { isCartOpen }, - setContext, - } = useContext(SiteContext); - - async function toggleCart() { - setContext((prevState) => { - return { ...prevState, isCartOpen: !isCartOpen }; - }); - } - return toggleCart; + const { + context: { isCartOpen }, + setContext, + } = useContext(SiteContext) + + async function toggleCart() { + setContext((prevState) => { + return { ...prevState, isCartOpen: !isCartOpen } + }) + } + return toggleCart } // Reference a collection product count function useProductCount() { - const { - context: { productCounts }, - } = useContext(SiteContext); + const { + context: { productCounts }, + } = useContext(SiteContext) - function productCount(collection) { - const collectionItem = productCounts.find((c) => c.slug === collection); - return collectionItem.count; - } + function productCount(collection) { + const collectionItem = productCounts.find((c) => c.slug === collection) + return collectionItem.count + } - return productCount; + return productCount } export { @@ -637,5 +637,5 @@ export { useToggleMegaNav, useTogglePageTransition, useUpdateItem -}; +} From 1425889d24b653a75d0bacfc91dc5cb887fb6537 Mon Sep 17 00:00:00 2001 From: jpvalery Date: Thu, 2 Nov 2023 22:27:54 -0400 Subject: [PATCH 3/4] feat: graphql refactor (fixes #125) --- components/cart-item.js | 59 +++-- components/cart.js | 32 ++- components/header.js | 22 +- lib/context.js | 530 ++++++++++++++++++++++++---------------- 4 files changed, 389 insertions(+), 254 deletions(-) diff --git a/components/cart-item.js b/components/cart-item.js index 140792a..f2a3545 100644 --- a/components/cart-item.js +++ b/components/cart-item.js @@ -1,11 +1,8 @@ -import React from 'react' import Link from 'next/link' -import { hasObject } from '@lib/helpers' -import { useUpdateItem, useRemoveItem, useToggleCart } from '@lib/context' +import { useRemoveItem, useToggleCart, useUpdateItem } from '@lib/context' -import Photo from '@components/photo' import { ProductCounter, ProductPrice } from '@components/product' function CartItem({ item }) { @@ -14,24 +11,28 @@ function CartItem({ item }) { const toggleCart = useToggleCart() const changeQuantity = (quantity) => { - updateItem(item.lineID, quantity) + updateItem(item.id, quantity) } - const defaultPhoto = item.photos.cart?.find((set) => !set.forOption) - const variantPhoto = item.photos.cart?.find((set) => { - const option = set.forOption - ? { - name: set.forOption.split(':')[0], - value: set.forOption.split(':')[1], - } - : {} - return option.value && hasObject(item.options, option) - }) + /* + const defaultPhoto = item.photos.cart?.find((set) => !set.forOption); + const variantPhoto = item.photos.cart?.find((set) => { + const option = set.forOption + ? { + name: set.forOption.split(':')[0], + value: set.forOption.split(':')[1], + } + : {}; + return option.value && hasObject(item.options, option); + }); - const photos = variantPhoto ? variantPhoto : defaultPhoto + const photos = variantPhoto ? variantPhoto : defaultPhoto; + */ + const photos = item.merchandise.product.images.edges[0].node.originalSrc return (
+ {/* {photos && ( - )} + )} + */} + {photos && }
@@ -70,11 +80,8 @@ function CartItem({ item }) { className="is-small is-inverted" />
-
diff --git a/components/cart.js b/components/cart.js index f623b79..93e400a 100644 --- a/components/cart.js +++ b/components/cart.js @@ -1,16 +1,19 @@ -import React, { useState, useEffect } from 'react' +import cx from 'classnames' import FocusTrap from 'focus-trap-react' import { m } from 'framer-motion' -import cx from 'classnames' +import { useEffect, useState } from 'react' import { centsToPrice } from '@lib/helpers' import { - useSiteContext, - useCartTotals, useCartCount, useCartItems, + useCartTotals, useCheckout, + useCheckoutCount, + useCheckoutItems, + useCheckoutTotals, + useSiteContext, useToggleCart, } from '@lib/context' @@ -23,8 +26,11 @@ const Cart = ({ data }) => { const { isCartOpen, isUpdating } = useSiteContext() const { subTotal } = useCartTotals() + const { subTotalCheckout } = useCheckoutTotals() + const checkouttCount = useCheckoutCount() const cartCount = useCartCount() - const lineItems = useCartItems() + const lineItems = useCheckoutItems() + const cartLineItems = useCartItems() const checkoutURL = useCheckout() const toggleCart = useToggleCart() @@ -52,7 +58,7 @@ const Cart = ({ data }) => { const buildCheckoutLink = shop.storeURL ? checkoutURL.replace( /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+)/g, - shop.storeURL + shop.storeURL, ) : checkoutURL setCheckoutLink(buildCheckoutLink) @@ -87,26 +93,26 @@ const Cart = ({ data }) => {
- Your Cart {cartCount} + Votre panier {cartCount}
- {lineItems?.length ? ( - + {cartLineItems?.length ? ( + ) : ( )}
- {lineItems?.length > 0 && ( + {cartLineItems?.length > 0 && (
Subtotal - ${centsToPrice(subTotal)} + {centsToPrice(subTotal * 100)}$
{ return (
{items.map((item) => { - return + return })}
) diff --git a/components/header.js b/components/header.js index 69222e0..ae61984 100644 --- a/components/header.js +++ b/components/header.js @@ -1,25 +1,26 @@ -import React, { useState, useRef, useEffect } from 'react' -import { m } from 'framer-motion' -import FocusTrap from 'focus-trap-react' -import { useInView } from 'react-cool-inview' import { useRect } from '@reach/rect' -import { useRouter } from 'next/router' -import Link from 'next/link' import cx from 'classnames' +import FocusTrap from 'focus-trap-react' +import { m } from 'framer-motion' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useEffect, useRef, useState } from 'react' +import { useInView } from 'react-cool-inview' import { isBrowser } from '@lib/helpers' import { + useCartCount, + useCheckoutCount, useSiteContext, - useToggleMegaNav, useToggleCart, - useCartCount, + useToggleMegaNav, } from '@lib/context' -import PromoBar from '@components/promo-bar' +import Icon from '@components/icon' import Menu from '@components/menu' import MegaNavigation from '@components/menu-mega-nav' -import Icon from '@components/icon' +import PromoBar from '@components/promo-bar' const Header = ({ data = {}, isTransparent, onSetup = () => {} }) => { // expand our header data @@ -216,6 +217,7 @@ const Header = ({ data = {}, isTransparent, onSetup = () => {} }) => { const CartToggle = () => { const toggleCart = useToggleCart() + const checkoutCount = useCheckoutCount() const cartCount = useCartCount() return ( diff --git a/lib/context.js b/lib/context.js index 5f30e32..bee7c4d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1,5 +1,5 @@ import { Base64 } from 'base64-string' -import { createContext, useContext, useState } from 'react' +import { createContext, useContext, useEffect, useState } from 'react' // get our API clients (shopify + sanity) import { getSanityClient } from '@lib/sanity' @@ -35,7 +35,8 @@ const SiteContext = createContext({ }) // set Shopify variables -const shopifyCartID = 'shopify_checkout_id' +const shopifyCheckoutID = 'shopify_checkout_id' +const shopifyCartID = 'shopify_cart_id' const shopifyVariantGID = 'gid://shopify/ProductVariant/' // Set ShopifyGraphQL as a function so we can reuse it @@ -54,7 +55,7 @@ async function shopifyGraphQL(query, variables) { query: query, variables: variables ?? null, }), - } + }, ) const data = await res.json() @@ -65,7 +66,7 @@ async function shopifyGraphQL(query, variables) { } } // defining what the query returns for the product so that we can easily reuse it -const product = `product { +const graphProduct = `product { title handle images(first: 5) { @@ -79,7 +80,51 @@ const product = `product { } }` +const graphCart = `cart { + id + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${graphProduct} + } + } + quantity + cost { + amountPerQuantity { + amount + } + compareAtAmountPerQuantity { + amount + } + subtotalAmount { + amount + } + totalAmount { + amount + } + } + } + } + } + cost { + checkoutChargeAmount { + amount + } + } + }` + // Build a new checkout +const createNewCheckout = (context) => { + return context.shopifyClient?.checkout.create({ + presentmentCurrencyCode: 'CAD', + }) +} + const createNewCart = async (context) => { // GraphQL query to create a cart const query = `mutation { @@ -96,47 +141,60 @@ const createNewCart = async (context) => { } }` const queryResponse = await shopifyGraphQL(query) - const cart = queryResponse.data.cartCreate - - // Update our global store states - setCartState(cart) - - return + const cart = queryResponse.data.cartCreate.cart + // NEED TO ADD CART TO CONTEXT BUT HOW—seems that L322 is doing the trick + return cart } // Get Shopify checkout cart -const fetchCart = async (context, id) => { +const fetchCheckout = (context, id) => { + return context.shopifyClient?.checkout.fetch(id) +} + +const fetchCart = async (id) => { // GraphQL query to fetch a cart const query = `{ cart(id: "${id}") { id - checkoutUrl - lines(first: 250) { - edges { - node { - id - merchandise { - ... on ProductVariant { - id - ${product} + checkoutUrl + lines(first: 250) { + edges { + node { + id + merchandise { + ... on ProductVariant { + id + ${graphProduct} + } + } + quantity + cost { + amountPerQuantity { + amount + } + compareAtAmountPerQuantity { + amount + } + subtotalAmount { + amount } + totalAmount { + amount + } + } } - quantity } } - } - cost { - checkoutChargeAmount { - amount + cost { + checkoutChargeAmount { + amount + } } - } } }` const queryResponse = await shopifyGraphQL(query) - const cart = queryResponse.data - // Update our global store states - setCartState(cart) - return + const cart = queryResponse.data.cart + return cart } // get associated variant from Sanity @@ -165,38 +223,29 @@ const fetchVariant = async (id) => { value } } - ` + `, ) return variant } // set our checkout states -const setCartState = async (cart, checkout, setContext, openCart) => { - if (!checkout && !cart) return null +const setCheckoutState = async (checkout, setContext, openCart) => { + if (!checkout) return null if (typeof window !== `undefined`) { - localStorage.setItem(shopifyCartID, cart.id) + localStorage.setItem(shopifyCheckoutID, checkout.id) } // get real lineItems data from Sanity - const lineItems = cart?.lines - ? await Promise.all( - cart.lines.edges.map(async (edge) => { - const variantID = edge.node.merchandise.id.replace( - shopifyVariantGID, - '' - ) - const variant = await fetchVariant(variantID) - - return { - ...variant, - quantity: edge.node.quantity, - lineID: edge.node.id, - } - }) - ) - : [] + const lineItems = await Promise.all( + checkout.lineItems.map(async (item) => { + const variantID = item.variant.id.split(shopifyVariantGID)[1] + const variant = await fetchVariant(variantID) + + return { ...variant, quantity: item.quantity, lineID: item.id } + }), + ) // update state setContext((prevState) => { @@ -208,8 +257,43 @@ const setCartState = async (cart, checkout, setContext, openCart) => { isCartOpen: openCart ? true : prevState.isCartOpen, checkout: { id: checkout.id, + lineItems: lineItems, + subTotal: checkout.lineItemsSubtotalPrice, webUrl: checkout.webUrl, }, + } + }) +} + +// set our cart states +const setCartState = async (cart, setContext) => { + if (!cart) return null + + if (typeof window !== `undefined`) { + localStorage.setItem(shopifyCartID, cart.id) + localStorage.setItem(cart, JSON.stringify(cart)) + } + + /* + // get real lineItems data from Sanity + const lineItems = await Promise.all( + cart.lines.edges.map(async (edge) => { + const variantID = edge.node.merchandise.id.replace(shopifyVariantGID, ''); + const variant = await fetchVariant(variantID); + + return { ...variant, quantity: item.quantity, lineID: item.id }; + }), + ); + */ + + // update state + setContext((prevState) => { + return { + ...prevState, + isAdding: false, + isLoading: false, + isUpdating: false, + isCartOpen: true, cart: cart, } }) @@ -229,53 +313,84 @@ const SiteContextProvider = ({ data, children }) => { const [initContext, setInitContext] = useState(false) - /* Is the error here? - useEffect(() => { - // Shopify checkout not build yet - if (initContext === false) { - const initializeCheckout = async () => { - const existingCartID = - typeof window !== 'undefined' - ? localStorage.getItem(shopifyCartID) - : false; - - // existing Shopify checkout ID found - if (existingCartID) { - try { - // fetch checkout from Shopify - const existingCart = await fetchCart(context, existingCartID); - - // /* Disabling for now - // Check if there are invalid items - if ( - existingCheckout.lineItems.some((lineItem) => !lineItem.variant) - ) { - throw new Error( - 'Invalid item in checkout. This variant was probably deleted from Shopify.', - ); - } - - // Make sure this cart hasn’t already been purchased. - if (!existingCheckout.completedAt) { - setCartState(newCart, existingCheckout, setContext); - return; - } - // *close comment - } catch (e) { - localStorage.setItem(shopifyCartID, null); - } - } - - // Otherwise, create a new checkout! - const newCart = await createNewCart(context); - }; - - // Initialize the store context - initializeCheckout(); - setInitContext(true); - } - }, [initContext, context, setContext, context.shopifyClient?.checkout]); // Needs reviewing - */ + useEffect(() => { + // Shopify checkout not build yet + if (initContext === false) { + const initializeCheckout = async () => { + const existingCheckoutID = + typeof window !== 'undefined' + ? localStorage.getItem(shopifyCheckoutID) + : false + + // existing Shopify checkout ID found + if (existingCheckoutID) { + try { + // fetch checkout from Shopify + const existingCheckout = await fetchCheckout( + context, + existingCheckoutID, + ) + + // Check if there are invalid items + if ( + existingCheckout.lineItems.some((lineItem) => !lineItem.variant) + ) { + throw new Error( + 'Invalid item in checkout. This variant was probably deleted from Shopify.', + ) + } + + // Make sure this cart hasn’t already been purchased. + if (!existingCheckout.completedAt) { + setCheckoutState(existingCheckout, setContext) + return + } + } catch (e) { + localStorage.setItem(shopifyCheckoutID, null) + } + } + + // Otherwise, create a new checkout! + const newCheckout = await createNewCheckout(context) + setCheckoutState(newCheckout, setContext) + } + + const initializeCart = async () => { + const existingCartID = localStorage.getItem(shopifyCartID) + //('The existing cart id'); + //console.log(existingCartID); + + // existing Shopify checkout ID found + if (existingCartID != 'null') { + console.log('SiteContextProvider: Fetching our existing cart') + try { + // fetch checkout from Shopify + const existingCart = await fetchCart(existingCartID) + //console.log(existingCart); + setCartState(existingCart, setContext) + } catch (e) { + console.log( + `Couldn't fetch the existing cart... Creating a new one...`, + ) + const newCart = await createNewCart(context) + //console.log(newCart); + setCartState(newCart, setContext) + } + } else { + // Otherwise, create a new checkout! + console.log('SiteContextProvider: Creating a new cart') + const newCart = await createNewCart(context) + //console.log(newCart); + setCartState(newCart, setContext) + } + } + + // Initialize the store context + initializeCheckout() + initializeCart() + setInitContext(true) + } + }, [initContext, context, setContext, context.shopifyClient?.checkout]) return ( item.quantity + total, 0) + } + + return count +} + function useCartCount() { const { context: { cart }, } = useContext(SiteContext) let count = 0 - - if (cart.lines) { - count = cart.lines.edges.forEach((edge) => { - count += edge.node.quantity - }) + if (cart?.lines) { + for (let i = 0; i < cart.lines.edges.length; i++) { + count += cart.lines.edges[i].node.quantity + } } return count } // Access our cart totals +function useCheckoutTotals() { + const { + context: { checkout }, + } = useContext(SiteContext) + + const subTotal = checkout.subTotal ? checkout.subTotal.amount * 100 : false + return { + subTotal, + } +} + function useCartTotals() { const { context: { cart }, @@ -374,6 +513,15 @@ function useCartTotals() { } } +// Access our cart items +function useCheckoutItems() { + const { + context: { checkout }, + } = useContext(SiteContext) + + return checkout.lineItems +} + // Access our cart items function useCartItems() { const { @@ -386,7 +534,7 @@ function useCartItems() { // Add an item to the checkout cart function useAddItem() { const { - context: { cart }, + context: { cart, checkout, shopifyClient }, setContext, } = useContext(SiteContext) @@ -403,6 +551,25 @@ function useAddItem() { const enc = new Base64() const variant = enc.urlEncode(`${shopifyVariantGID}${variantID}`) + // Build the cart line item + const newItem = { + variantId: variant, + quantity: quantity, + customAttributes: attributes, + } + + /* + // Add it to the Shopify checkout cart + const newCheckout = await shopifyClient.checkout.addLineItems( + checkout.id, + newItem, + ); + */ + + // We check if the context is providing a cart.id otherwise we rely on the localStorage we set up. + // Definitely ugly but I can't figure out how to update the context for cart + cart.id == undefined ? localStorage.getItem(shopifyCartID) : cart.id + // GraphQL query to add items to the cart const query = `mutation { cartLinesAdd( @@ -414,29 +581,7 @@ function useAddItem() { } ] ) { - cart { - id - checkoutUrl - lines(first: 250) { - edges { - node { - id - merchandise { - ... on ProductVariant { - id - ${product} - } - } - quantity - } - } - } - cost { - checkoutChargeAmount { - amount - } - } - } + ${graphCart} userErrors { field message @@ -444,12 +589,10 @@ function useAddItem() { } }` const queryResponse = await shopifyGraphQL(query) - const newCart = queryResponse.data.cartLinesAdd - console.log(newCart) - const newCheckout = { webUrl: newCart.checkoutUrl } + const newCart = queryResponse.data.cartLinesAdd.cart - // Update our global store states - setCartState(newCart, newCheckout, setContext, true) + //setCheckoutState(newCheckout, setContext, true); + setCartState(newCart, setContext, true) } return addItem @@ -458,7 +601,7 @@ function useAddItem() { // Update item in cart function useUpdateItem() { const { - context: { cart }, + context: { cart, checkout, shopifyClient }, setContext, } = useContext(SiteContext) @@ -471,6 +614,17 @@ function useUpdateItem() { return { ...prevState, isUpdating: true } }) + /* + const newCheckout = await shopifyClient.checkout.updateLineItems( + checkout.id, + [{ id: itemID, quantity: quantity }], + ); + */ + + // We check if the context is providing a cart.id otherwise we rely on the localStorage we set up. + // Definitely ugly but I can't figure out how to update the context for cart + cart.id == undefined ? localStorage.getItem(shopifyCartID) : cart.id + // GraphQL query to update items in the cart const query = `mutation { cartLinesUpdate( @@ -482,29 +636,7 @@ function useUpdateItem() { }, ] ) { - cart { - id - checkoutUrl - lines(first: 250) { - edges { - node { - id - merchandise { - ... on ProductVariant { - id - ${product} - } - } - quantity - } - } - } - cost { - checkoutChargeAmount { - amount - } - } - } + ${graphCart} userErrors { field message @@ -512,12 +644,10 @@ function useUpdateItem() { } }` const queryResponse = await shopifyGraphQL(query) - const newCart = queryResponse.data.cartLinesUpdate - console.log(newCart) - const newCheckout = { webUrl: newCart.checkoutUrl } + const newCart = queryResponse.data.cartLinesUpdate.cart - // Update our global store states - setCartState(newCart, newCheckout, setContext, true) + //setCheckoutState(newCheckout, setContext); + setCartState(newCart, setContext) } return updateItem } @@ -525,7 +655,7 @@ function useUpdateItem() { // Remove item from cart function useRemoveItem() { const { - context: { cart }, + context: { cart, heckout, shopifyClient }, setContext, } = useContext(SiteContext) @@ -538,6 +668,17 @@ function useRemoveItem() { return { ...prevState, isUpdating: true } }) + /* + const newCheckout = await shopifyClient.checkout.removeLineItems( + checkout.id, + [itemID], + ); + */ + + // We check if the context is providing a cart.id otherwise we rely on the localStorage we set up. + // Definitely ugly but I can't figure out how to update the context for cart + cart.id == undefined ? localStorage.getItem(shopifyCartID) : cart.id + // GraphQL query to remove items from the cart const query = `mutation { cartLinesRemove( @@ -546,29 +687,7 @@ function useRemoveItem() { "${itemID}" ] ) { - cart { - id - checkoutUrl - lines(first: 250) { - edges { - node { - id - merchandise { - ... on ProductVariant { - id - ${product} - } - } - quantity - } - } - } - cost { - checkoutChargeAmount { - amount - } - } - } + ${graphCart} userErrors { field message @@ -576,11 +695,10 @@ function useRemoveItem() { } }` const queryResponse = await shopifyGraphQL(query) - const newCart = queryResponse.data.cartLinesRemove - console.log(newCart) - const newCheckout = { webUrl: newCart.checkoutUrl } + const newCart = queryResponse.data.cartLinesRemove.cart - setCartState(newCart, newCheckout, setContext) + //setCheckoutState(newCheckout, setContext); + setCartState(newCart, setContext) } return removeItem } @@ -588,10 +706,10 @@ function useRemoveItem() { // Build our Checkout URL function useCheckout() { const { - context: { checkout }, + context: { cart, checkout }, } = useContext(SiteContext) - return checkout.webUrl + return cart.checkoutUrl } // Toggle cart state @@ -624,18 +742,20 @@ function useProductCount() { } export { - SiteContextProvider, - useAddItem, - useCartCount, - useCartItems, - useCartTotals, - useCheckout, - useProductCount, - useRemoveItem, - useSiteContext, - useToggleCart, - useToggleMegaNav, - useTogglePageTransition, - useUpdateItem + SiteContextProvider, + useAddItem, + useCartCount, + useCartItems, + useCartTotals, + useCheckout, + useCheckoutCount, + useCheckoutItems, + useCheckoutTotals, + useProductCount, + useRemoveItem, + useSiteContext, + useToggleCart, + useToggleMegaNav, + useTogglePageTransition, + useUpdateItem, } - From 8b34d271496a3627304b2d54fe33d2b7d07c51c7 Mon Sep 17 00:00:00 2001 From: jpvalery Date: Fri, 3 Nov 2023 09:57:31 -0400 Subject: [PATCH 4/4] fix: openCart bug on setCartState --- lib/context.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index bee7c4d..567f501 100644 --- a/lib/context.js +++ b/lib/context.js @@ -266,7 +266,7 @@ const setCheckoutState = async (checkout, setContext, openCart) => { } // set our cart states -const setCartState = async (cart, setContext) => { +const setCartState = async (cart, setContext, openCart) => { if (!cart) return null if (typeof window !== `undefined`) { @@ -293,7 +293,7 @@ const setCartState = async (cart, setContext) => { isAdding: false, isLoading: false, isUpdating: false, - isCartOpen: true, + isCartOpen: openCart ? true : prevState.isCartOpen, cart: cart, } }) @@ -757,5 +757,6 @@ export { useToggleCart, useToggleMegaNav, useTogglePageTransition, - useUpdateItem, + useUpdateItem } +