ctrl-alt-del-world/midway

View on GitHub
web/src/context/siteContext.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
{
/*
  Orginal Author of this file: https://github.com/thetrevorharmon
  Orginal File: https://github.com/thetrevorharmon/sell-things-fast/blob/master/src/context/StoreContext.js

  TYPED out by Issac: https://gist.github.com/isaac-martin

  Extended by Kevin Green for ✨
*/
}

import React, { useState, useEffect, useContext } from 'react'
import { Checkout } from 'shopify-storefront-api-typings'
import ShopifyClient from 'shopify-buy'
import cookie from 'js-cookie'

const SHOPIFY_CHECKOUT_STORAGE_KEY = 'shopify_checkout_id'

// @ts-ignore
const client = ShopifyClient.buildClient({
  storefrontAccessToken: process.env.GATSBY_SHOPIFY_STOREFRONT_TOKEN,
  domain: process.env.GATSBY_SHOPIFY_STORE
})

interface InitialStore {
  shopifyClient: ShopifyClient
  isAdding: boolean
  cartIsOpen: boolean
  navIsOpen: boolean
  page: undefined
  orders: any[]
  customerEmail: string | undefined
  customerName: string | undefined
  customerToken: string | undefined
  checkout: Checkout
}

const initialStoreState = {
  shopifyClient: client,
  isAdding: false,
  cartIsOpen: false,
  page: undefined,
  customerEmail: undefined,
  customerName: undefined,
  customerToken: undefined,
  orders: [],
  navIsOpen: false,
  checkout: {
    lineItems: []
  } as Checkout
}

const StoreContext = React.createContext({
  store: initialStoreState,
  setStore: () => null,
})

const createNewCheckout = (store: InitialStore): Checkout => {
  return store.shopifyClient.checkout.create()
}

const fetchCheckout = (store: InitialStore, id: string): Checkout => {
  return store.shopifyClient.checkout.fetch(id)
}

const setCheckoutInState = (checkout: Checkout, setStore: any) => {
  const isBrowser = typeof window !== 'undefined'
  if (isBrowser) {
    localStorage.setItem(SHOPIFY_CHECKOUT_STORAGE_KEY, checkout.id)
  }

  setStore((prevState: InitialStore) => {
    return { ...prevState, checkout }
  })
}

const initCustomer = (setStore: any) => {
  const customerEmail = cookie.get('customer_email')
  const customerToken = cookie.get('customer_token')
  const customerName = cookie.get('customer_firstName')

  if (customerEmail && customerToken && customerName) {
    setStore((prevState: InitialStore) => {
      return { ...prevState, customerEmail, customerToken, customerName }
    })
  }
}

const StoreContextProvider = ({ children }: { children: any }) => {
  const [store, setStore] = useState(initialStoreState)
  const [initStore, setInitStore] = useState(false)

  useEffect(() => {
    if (initStore === false) {
      const initializeCheckout = async () => {
        // Check for an existing cart.
        const isBrowser = typeof window !== 'undefined'
        const existingCheckoutId = isBrowser
          ? localStorage.getItem(SHOPIFY_CHECKOUT_STORAGE_KEY)
          : null

        if (existingCheckoutId) {
          try {
            const checkout = await fetchCheckout(store, existingCheckoutId)
            
            // Make sure none of the items in this cart have been deleted from Shopify.
            if (checkout.lineItems.some((lineItem) => !lineItem.variant)) {
              throw new Error(
                'Invalid line item in checkout. This variant was probably deleted from Shopify',
              )
            }
            
            // Make sure this cart hasn’t already been purchased.
            if (!checkout.completedAt) {
              setCheckoutInState(checkout, setStore)
              return
            }
          } catch (e) {
            localStorage.setItem(SHOPIFY_CHECKOUT_STORAGE_KEY, null)
          }
        }

        const newCheckout = await createNewCheckout(store)
        setCheckoutInState(newCheckout, setStore)
      }
      initCustomer(setStore)
      initializeCheckout()
      setInitStore(true)
    }
  }, [store, setStore, store.shopifyClient.checkout, initStore])

  return (
    <StoreContext.Provider
      value={{
        store,
        setStore,
      }}
    >
      {children}
    </StoreContext.Provider>
  )
}

function useStore() {
  const { store } = useContext(StoreContext)
  return store
}

function useCartCount() {
  const {
    store: { checkout },
  } = useContext(StoreContext)

  let count = 0
  if (checkout.lineItems) {
    count = checkout.lineItems.reduce(
      (runningTotal: number, item: any) => item.quantity + runningTotal,
      0
    )
  }

  return count
}

const setCustomerInState = () => {
  const {
    setStore
  }: { setStore: any } = useContext(StoreContext)

  async function updateCustomerInState() {
    const customerEmail = cookie.get('customer_email')
    const customerToken = cookie.get('customer_token')
    const customerName = cookie.get('customer_firstName')
    setStore((prevState: InitialStore) => {
      return { ...prevState, customerEmail, customerToken, customerName }
    })
  }

  return updateCustomerInState
}

function useCartTotals() {
  const {
    store: { checkout },
  } = useContext(StoreContext)

  const tax = checkout.totalTaxV2
    ? `${Number(checkout.totalTaxV2.amount).toFixed(2)}`
    : '-'
  const total = checkout.totalPriceV2
    ? `${Number(checkout.totalPriceV2.amount).toFixed(2)}`
    : '-'

  return {
    tax,
    total,
  }
}

function useCartItems() {
  const {
    store: { checkout },
  } = useContext(StoreContext)

  return checkout.lineItems
}

function useCustomer() {
  const {
    store: { customerEmail, customerName, customerToken }
  } = useContext(StoreContext)

  return { customerEmail, customerName, customerToken }
}

function useAddItemToCart() {
  // @ts-ignore
  const {
    store: { checkout, shopifyClient },
    setStore,
  }: { store: InitialStore, setStore: any } = useContext(StoreContext)

  async function addItemToCart(variantId: string, quantity: number, attributes?: []) {
    if (variantId === '' || !quantity) {
      console.error('Both a size and quantity are required.')
      return
    }

    setStore((prevState: InitialStore) => {
      return { ...prevState, isAdding: true }
    })

    const checkoutId = checkout.id
    const lineItemsToAdd = [{ variantId, quantity, customAttributes: attributes }]

    const newCheckout = await shopifyClient.checkout.addLineItems(
      checkoutId,
      lineItemsToAdd
    )

    setStore((prevState: InitialStore) => {
      return { ...prevState, checkout: newCheckout, cartIsOpen: true, isAdding: false }
    })
  }

  return addItemToCart
}

function useRemoveItemFromCart() {
  const {
    store: { checkout, shopifyClient },
    setStore,
  }: {
    store: InitialStore
    setStore: any
  } = useContext(StoreContext)

  async function removeItemFromCart(itemId) {
    const newCheckout = await shopifyClient.checkout.removeLineItems(checkout.id, [
      itemId,
    ])

    setStore((prevState: InitialStore) => {
      return { ...prevState, checkout: newCheckout }
    })
  }

  return removeItemFromCart
}

function useUpdateItemsFromCart() {
  const {
    store: { checkout, shopifyClient },
    setStore,
  }: {
    store: InitialStore
    setStore: any
  } = useContext(StoreContext)

  async function updateItemsFromCart(items: any) {
    items = [].concat(items)
    const newCheckout = await shopifyClient.checkout.updateLineItems(checkout.id, items)

    setStore((prevState: InitialStore) => {
      return { ...prevState, checkout: newCheckout }
    })
  }

  return updateItemsFromCart
}

function useCheckout() {
  const {
    store: { checkout },
  }: {
    store: InitialStore
  } = useContext(StoreContext)

  return () => {
    window.open(checkout.webUrl)
  }
}

function useSetPage() {
  const {
    setStore
  }: {
    setStore: any
  } = useContext(StoreContext)
  async function setPage(page: string) {
    setStore((prevState: InitialStore) => {
      return { ...prevState, page }
    })
  }
  return setPage
}


function useToggleCart() {
  const {
    store: { cartIsOpen },
    setStore
  }: {
    store: InitialStore
    setStore: any
  } = useContext(StoreContext)

  async function toggleCart() {
    setStore((prevState: InitialStore) => {
      return { ...prevState, cartIsOpen: !cartIsOpen }
    })
  }

  return toggleCart
}

export {
  client,
  StoreContextProvider,
  setCustomerInState,
  useAddItemToCart,
  useStore,
  useCustomer,
  useCartCount,
  useCartItems,
  useCartTotals,
  useSetPage,
  useRemoveItemFromCart,
  useUpdateItemsFromCart,
  useCheckout,
  useToggleCart
}