diff --git a/gatsby-browser.js b/gatsby-browser.js index 5f3c26c5..e0293060 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,2 +1,5 @@ import '@fontsource/montserrat'; import '@fontsource/montserrat/700.css'; +import CombinedProvider from './src/context/CombinedProvider'; + +export const wrapRootElement = CombinedProvider; diff --git a/gatsby-config.js b/gatsby-config.js index f92fa373..aeee0132 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -18,6 +18,13 @@ module.exports = { environment: process.env.CONTENTFUL_ENV, }, }, + { + resolve: 'gatsby-source-shopify', + options: { + storeUrl: process.env.SHOPIFY_STORE_NAME, + password: process.env.SHOPIFY_ADMIN_ACCESS_TOKEN, + }, + }, 'gatsby-plugin-postcss', 'gatsby-plugin-sass', 'gatsby-plugin-image', diff --git a/gatsby-node.js b/gatsby-node.js index 8c9b3f46..ee971672 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,4 +1,5 @@ const fetch = require('node-fetch'); +const path = require('path'); // Create source nodes exports.sourceNodes = async ({ @@ -93,6 +94,26 @@ exports.createPages = async ({ actions, graphql }) => { } } } + allShopifyProduct { + edges { + node { + title + handle + variants { + shopifyId + image { + src + } + } + priceRangeV2 { + maxVariantPrice { + amount + } + } + description + } + } + } } `); @@ -117,6 +138,7 @@ exports.createPages = async ({ actions, graphql }) => { data.allContentfulHomePage.edges.forEach((edge) => { const { slug } = edge.node; + console.log('here is the slug', slug); actions.createPage({ path: slug, context: { @@ -169,4 +191,16 @@ exports.createPages = async ({ actions, graphql }) => { component: require.resolve('./src/templates/pokemon.js'), }); }); + + // Iterate over all products and create a new page using a template + // The product "handle" is generated automatically by Shopify + data.allShopifyProduct.edges.forEach(({ node }) => { + actions.createPage({ + path: `/products/${node.handle}`, + component: path.resolve(`./src/templates/product.js`), + context: { + product: node, + }, + }); + }); }; diff --git a/gatsby-ssr.js b/gatsby-ssr.js new file mode 100644 index 00000000..4ee60d6a --- /dev/null +++ b/gatsby-ssr.js @@ -0,0 +1,2 @@ +import CombinedProvider from './src/context/CombinedProvider'; +export const wrapRootElement = CombinedProvider; diff --git a/package.json b/package.json index 24ddb867..6500644d 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,10 @@ "gatsby-plugin-sharp": "^4.9.1", "gatsby-source-contentful": "^7.7.2", "gatsby-source-filesystem": "^4.9.1", + "gatsby-source-shopify": "^7.13.0", "gatsby-transformer-sharp": "^4.9.0", "husky": "^8.0.1", + "isomorphic-fetch": "^3.0.0", "node-fetch": "^2.6.7", "postcss": "^8.4.12", "prop-types": "^15.8.1", @@ -53,6 +55,8 @@ "react-dom": "^17.0.1", "react-helmet": "^6.1.0", "sass": "^1.49.9", + "shopify-buy": "^2.17.1", + "styled-components": "^5.3.6", "tailwindcss": "^3.0.23", "yup": "^0.32.11" }, diff --git a/src/components/PrimaryButton.js b/src/components/PrimaryButton.js new file mode 100644 index 00000000..f34c431c --- /dev/null +++ b/src/components/PrimaryButton.js @@ -0,0 +1,42 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import styled from 'styled-components'; + +const PrimaryButton = ({ text, onClick, disabled = false }) => { + return ( + + {text} + + ); +}; + +export default PrimaryButton; + +const ButtonWrapper = styled.button` + background: #014c40; + border: none; + border-radius: 30px; + height: 40px; + width: fit-content; + display: flex; + justify-content: center; + align-items: center; + padding: 0 20px; + cursor: pointer; + :hover { + transform: scale(1.2); + transition: 0.2s; + } + :disabled { + background: rgba(1, 76, 64, 0.5); + transform: none; + cursor: not-allowed; + } +`; + +const Title = styled.p` + margin: 0; + font-weight: 600; + font-size: 12px; + color: #ffffff; +`; diff --git a/src/components/ProductCard.js b/src/components/ProductCard.js new file mode 100644 index 00000000..52a6670f --- /dev/null +++ b/src/components/ProductCard.js @@ -0,0 +1,100 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import styled from 'styled-components'; +import { navigate } from 'gatsby-link'; +import useStore from '../context/StoreContext'; + +const ProductCard = ({ product }) => { + const { addVariantToCart } = useStore(); + return ( + + addVariantToCart(product, 1)}> +

+

+
+ navigate(`${product.handle}`)}> + + + {product.title} + + {product.priceRangeV2.maxVariantPrice.amount}$ + + + +
+ ); +}; + +export default ProductCard; + +const Wrapper = styled.div` + display: grid; + justify-content: center; + align-items: center; + width: 200px; + border-radius: 20px; + + gap: 10px; + cursor: pointer; + position: relative; + box-shadow: 0px 20px 40px rgba(52, 53, 99, 0.2), + 0px 1px 3px rgba(0, 0, 0, 0.05); +`; + +const ContentWrapper = styled.div``; + +const Image = styled.img` + width: 200px; + height: 300px; + object-fit: cover; + border-radius: 20px; + margin: 0; +`; + +const TextWrapper = styled.div` + position: absolute; + bottom: 0px; + left: 0px; + border-radius: 0 0 20px 20px; + background: rgba(255, 255, 255, 0.2); + width: 200px; + padding: 10px 0; + backdrop-filter: blur(40px); +`; + +const Title = styled.p` + font-weight: 600; + text-align: center; + margin: 0; + color: #014c40; +`; + +const Price = styled.p` + font-weight: normal; + text-align: center; + margin: 0; +`; + +const AddButton = styled.div` + position: absolute; + top: 20px; + right: 20px; + background: #014c40; + padding: 10px; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + + :hover { + transform: scale(1.2); + transition: 0.2s; + } + + p { + margin: 0; + color: white; + font-weight: bold; + } +`; diff --git a/src/components/ProductRow.js b/src/components/ProductRow.js new file mode 100644 index 00000000..a5f49f85 --- /dev/null +++ b/src/components/ProductRow.js @@ -0,0 +1,56 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import styled from 'styled-components'; + +const ProductRow = ({ item }) => { + const { product, quantity } = item; + + return ( + + + {product.title} + {product.title} + + {quantity} + console.log('Remove item')}> + Remove + + + ); +}; + +export default ProductRow; + +const Wrapper = styled.div` + display: grid; + grid-template-columns: repeat(3, 330px); + gap: 20px; + align-items: center; + width: 330px; +`; + +const ProductWrapper = styled.div` + display: grid; + grid-template-columns: 80px auto; + gap: 20px; + align-items: center; + width: 330px; +`; + +const Image = styled.img` + width: 80px; + height: 80px; + object-fit: cover; + border-radius: 20px; +`; + +const Subtitle = styled.p` + font-weight: bold; + font-size: 14px; +`; + +const DeleteButton = styled.p` + color: #a61b2b; + font-size: 14px; + cursor: pointer; +`; diff --git a/src/context/CombinedProvider.js b/src/context/CombinedProvider.js new file mode 100644 index 00000000..1a146a60 --- /dev/null +++ b/src/context/CombinedProvider.js @@ -0,0 +1,10 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; + +import { StoreProvider } from './StoreContext'; + +const CombinedProvider = ({ element }) => { + return {element}; +}; + +export default CombinedProvider; diff --git a/src/context/StoreContext.js b/src/context/StoreContext.js new file mode 100644 index 00000000..6f788c19 --- /dev/null +++ b/src/context/StoreContext.js @@ -0,0 +1,189 @@ +/* eslint-disable react/prop-types */ +import React, { createContext, useState, useEffect, useContext } from 'react'; +import fetch from 'isomorphic-fetch'; +import Client from 'shopify-buy'; + +const client = Client.buildClient( + { + domain: process.env.SHOPIFY_STORE_NAME, + storefrontAccessToken: process.env.GATSBY_STOREFRONT_ACCESS_TOKEN, + }, + fetch +); + +const defaultValues = { + cart: [], + loading: false, + addVariantToCart: () => {}, + removeLineItem: () => {}, + client, + checkout: { + id: '', + lineItems: [], + webUrl: '', + }, +}; + +const StoreContext = createContext(defaultValues); + +const isBrowser = typeof window !== `undefined`; +const localStorageKey = `shopify_checkout_id`; + +export const StoreProvider = ({ children }) => { + const [cart, setCart] = useState(defaultValues.cart); + const [checkout, setCheckout] = useState(defaultValues.checkout); + const [loading, setLoading] = useState(false); + + const setCheckoutItem = (checkout) => { + if (isBrowser) { + localStorage.setItem(localStorageKey, checkout.id); + } + + setCheckout(checkout); + }; + + useEffect(() => { + const initializeCheckout = async () => { + const existingCheckoutID = isBrowser + ? localStorage.getItem(localStorageKey) + : null; + + if (existingCheckoutID && existingCheckoutID !== `null`) { + try { + const existingCheckout = await client.checkout.fetch( + existingCheckoutID + ); + if (!existingCheckout.completedAt) { + setCheckoutItem(existingCheckout); + return; + } + } catch (e) { + localStorage.setItem(localStorageKey, null); + } + } + + const newCheckout = await client.checkout.create(); + setCheckoutItem(newCheckout); + }; + + initializeCheckout(); + }, []); + + const addVariantToCart = async (product, quantity) => { + setLoading(true); + + if (checkout.id === '') { + console.error('No checkout ID assigned.'); + return; + } + + const checkoutID = checkout.id; + const variantId = product.variants[0]?.shopifyId; + const parsedQuantity = parseInt(quantity, 10); + + const lineItemsToUpdate = [ + { + variantId, + quantity: parsedQuantity, + }, + ]; + + try { + const res = await client.checkout.addLineItems( + checkoutID, + lineItemsToUpdate + ); + setCheckout(res); + + let updatedCart = []; + if (cart.length > 0) { + const itemIsInCart = cart.find( + (item) => item.product.variants[0]?.shopifyId === variantId + ); + + if (itemIsInCart) { + const newProduct = { + product: { ...itemIsInCart.product }, + quantity: itemIsInCart.quantity + parsedQuantity, + }; + const otherItems = cart.filter( + (item) => + item.product.variants[0]?.shopifyId !== variantId + ); + updatedCart = [...otherItems, newProduct]; + } else { + updatedCart = cart.concat([ + { product, quantity: parsedQuantity }, + ]); + } + } else { + updatedCart = [{ product, quantity: parsedQuantity }]; + } + setCart(updatedCart); + + setLoading(false); + alert('Item added to cart!'); + } catch (error) { + setLoading(false); + console.error(`Error in addVariantToCart: ${error}`); + } + }; + const removeLineItem = async (variantId) => { + setLoading(true); + try { + if (checkout.lineItems.length < 1) throw new Error('Cart is empty'); + let lineItemID = ''; + checkout.lineItems?.forEach((item) => { + if (item.variableValues.lineItems[0]?.variantId === variantId) { + lineItemID = item.id; + } + }); + + if (!lineItemID) { + console.log('Product not in cart'); + return; + } + + const res = await client.checkout.removeLineItems(checkout.id, [ + lineItemID, + ]); + setCheckout(res); + + const updatedCart = cart.filter( + (item) => item.product.variants[0]?.shopifyId !== variantId + ); + setCart(updatedCart); + setLoading(false); + } catch (error) { + setLoading(false); + console.error(`Error in removeLineItem: ${error}`); + } + }; + + return ( + + {children} + + ); +}; + +const useStore = () => { + const context = useContext(StoreContext); + + if (context === undefined) { + throw new Error('useStore must be used within StoreContext'); + } + + return context; +}; + +export default useStore; diff --git a/src/pages/cart.js b/src/pages/cart.js new file mode 100644 index 00000000..d26a614f --- /dev/null +++ b/src/pages/cart.js @@ -0,0 +1,61 @@ +import React from 'react'; +import styled from 'styled-components'; + +import Layout from '../components/layout'; +import ProductRow from '../components/ProductRow'; +import PrimaryButton from '../components/PrimaryButton'; + +import useStore from '../context/StoreContext'; + +const Cart = () => { + const { cart } = useStore(); + + return ( + + + + Product + Quantity + Remove Item + + + {cart.length > 0 ? ( + cart.map((item, index) => ( + + )) + ) : ( + Your cart is empty. + )} + + + console.log('Redirect to checkout page')} + /> + + + + ); +}; + +export default Cart; + +const Wrapper = styled.div` + margin: 40px; +`; + +const HeaderWrapper = styled.div` + display: grid; + grid-template-columns: repeat(3, 330px); + gap: 40px; +`; + +const Text = styled.p` + font-weight: 600; + font-size: 14px; +`; + +const ButtonWrapper = styled.div` + display: flex; + justify-content: flex-end; +`; diff --git a/src/pages/products.js b/src/pages/products.js new file mode 100644 index 00000000..85ec031b --- /dev/null +++ b/src/pages/products.js @@ -0,0 +1,44 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import { graphql } from 'gatsby'; +import ProductCard from '../components/ProductCard'; +import Layout from '../components/Layout'; + +export const query = graphql` + query MyQuery { + allShopifyProduct { + nodes { + title + handle + priceRangeV2 { + maxVariantPrice { + amount + } + } + description + variants { + shopifyId + image { + src + } + } + } + } + } +`; + +// eslint-disable-next-line react/prop-types +const Products = ({ data }) => { + const { nodes } = data.allShopifyProduct; + return ( + +
+ {nodes?.map((product, index) => ( + + ))} +
+
+ ); +}; + +export default Products; diff --git a/src/templates/product.js b/src/templates/product.js new file mode 100644 index 00000000..21ebc7c9 --- /dev/null +++ b/src/templates/product.js @@ -0,0 +1,111 @@ +/* eslint-disable react/prop-types */ +import { navigate } from 'gatsby-link'; +import React from 'react'; +import styled from 'styled-components'; + +import Layout from '../components/layout'; +import PrimaryButton from '../components/PrimaryButton'; +import useStore from '../context/StoreContext'; +import useInput from '../utilities/useInput'; + +const ProductTemplate = ({ pageContext }) => { + const { product } = pageContext; + const { addVariantToCart } = useStore(); + const bind = useInput(1); + + return ( + + navigate(-1)}>{'< '} Back + + + + {product.title} + + {product.priceRangeV2.maxVariantPrice.amount}$ + +

{product.description}

+ + + + + + + addVariantToCart(product, bind.value)} + /> +
+
+
+ ); +}; + +export default ProductTemplate; + +const BackButton = styled.p` + cursor: pointer; + color: #014c40; + margin-left: 40px; + font-size: 14px; + font-weight: 600; +`; + +const Wrapper = styled.div` + margin: 40px; + display: grid; + grid-template-columns: 400px auto; + gap: 40px; +`; + +const Image = styled.img` + width: 400px; + height: 500px; + border-radius: 30px; + object-fit: cover; +`; + +const InfoContainer = styled.div` + display: grid; + align-items: flex-start; + height: fit-content; + gap: 10px; + + p { + margin: 0; + } +`; + +const Title = styled.h1` + margin: 0; +`; + +const Subtitle = styled.p` + font-weight: bold; + max-width: 500px; +`; + +const InputForm = styled.form` + display: grid; + grid-template-columns: repeat(2, auto); + width: fit-content; + gap: 20px; + align-items: center; + gap: 10px; +`; + +const Input = styled.input` + border-radius: 20px; + border: 2px solid rgba(0, 0, 0, 0.3); + padding: 10px 20px; + max-width: 80px; + font-size: 12px; + :focus { + outline: none; + outline-color: #014c40; + } +`; diff --git a/src/utilities/useInput.js b/src/utilities/useInput.js new file mode 100644 index 00000000..c9653fec --- /dev/null +++ b/src/utilities/useInput.js @@ -0,0 +1,16 @@ +import { useState } from 'react'; + +const useInput = (initialValue) => { + const [value, setValue] = useState(initialValue); + + const handleChange = (event) => { + setValue(event.target.value); + }; + + return { + value, + onChange: handleChange, + }; +}; + +export default useInput; diff --git a/yarn.lock b/yarn.lock index 3d5a494c..de5a99b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,7 +113,7 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.18.6": +"@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== @@ -205,7 +205,7 @@ dependencies: "@babel/types" "^7.18.9" -"@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.18.6": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== @@ -1069,7 +1069,7 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.12.9", "@babel/traverse@^7.14.0", "@babel/traverse@^7.15.4", "@babel/traverse@^7.16.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1": +"@babel/traverse@^7.12.9", "@babel/traverse@^7.14.0", "@babel/traverse@^7.15.4", "@babel/traverse@^7.16.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.4.5": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== @@ -1116,6 +1116,28 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== +"@emotion/is-prop-valid@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" + integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== + dependencies: + "@emotion/memoize" "^0.8.0" + +"@emotion/memoize@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" + integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -3046,6 +3068,22 @@ babel-plugin-remove-graphql-queries@^4.24.0: "@babel/types" "^7.15.4" gatsby-core-utils "^3.24.0" +"babel-plugin-styled-components@>= 1.12.0": + version "2.0.7" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz#c81ef34b713f9da2b7d3f5550df0d1e19e798086" + integrity sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.0" + "@babel/helper-module-imports" "^7.16.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + picomatch "^2.3.0" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw== + babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: version "7.0.0-beta.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" @@ -3394,6 +3432,11 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -4030,6 +4073,11 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + css-declaration-sorter@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" @@ -4090,6 +4138,15 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@^1.1.2, css-tree@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" @@ -5992,6 +6049,19 @@ gatsby-source-filesystem@^4.24.0, gatsby-source-filesystem@^4.9.1: valid-url "^1.0.9" xstate "4.32.1" +gatsby-source-shopify@^7.13.0: + version "7.13.0" + resolved "https://registry.yarnpkg.com/gatsby-source-shopify/-/gatsby-source-shopify-7.13.0.tgz#428de18b20756be56aaafc9d7f8e8a9d51e0b706" + integrity sha512-NNfbdhVQlswCZ+7sjdCx7XV/HP1kwYSGdqr64vQkljLMT6TfWCO8sjZDp7ea42Uu4whmwK8KevHGfxvgDZTdbg== + dependencies: + "@babel/runtime" "^7.15.4" + gatsby-core-utils "^3.24.0" + gatsby-plugin-utils "^3.18.0" + gatsby-source-filesystem "^4.24.0" + node-fetch "^2.6.7" + sharp "^0.30.7" + shift-left "^0.1.5" + gatsby-telemetry@^3.24.0: version "3.24.0" resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-3.24.0.tgz#5306d8a54372eb877be92f782f32cd25fbe9cfb5" @@ -6610,7 +6680,7 @@ header-case@^2.0.4: capital-case "^1.0.4" tslib "^2.0.3" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7273,6 +7343,14 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== + dependencies: + node-fetch "^2.6.1" + whatwg-fetch "^3.4.1" + jest-worker@^26.3.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" @@ -7789,7 +7867,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0: +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8345,7 +8423,7 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== -node-fetch@2.6.7, node-fetch@^2.6.6, node-fetch@^2.6.7: +node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -8955,7 +9033,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -9298,7 +9376,7 @@ postcss-unique-selectors@^5.1.1: dependencies: postcss-selector-parser "^6.0.5" -postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -10395,6 +10473,11 @@ shallow-compare@^1.2.2: resolved "https://registry.yarnpkg.com/shallow-compare/-/shallow-compare-1.2.2.tgz#fa4794627bf455a47c4f56881d8a6132d581ffdb" integrity sha512-LUMFi+RppPlrHzbqmFnINTrazo0lPNwhcgzuAXVVcfy/mqPDrQmHAyz5bvV0gDAuRFrk804V0HpQ6u9sZ0tBeg== +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + sharp@^0.30.7: version "0.30.7" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.7.tgz#7862bda98804fdd1f0d5659c85e3324b90d94c7c" @@ -10438,6 +10521,16 @@ shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== +shift-left@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/shift-left/-/shift-left-0.1.5.tgz#4a61a9d0412b1b32f9abbd97f72466c9c74a6be1" + integrity sha512-55d8QaP1YmuL1D52fhgq8CT1tXksM/2WPZ6980RtkMbl0Cze++kuJy50GLMnXwostk/YG9hasaJmP3r+d3yUtQ== + +shopify-buy@^2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/shopify-buy/-/shopify-buy-2.17.1.tgz#9dc3bdd6fca9c11940105f59b4d8eeb5ed788a20" + integrity sha512-Hs8oMbquM5xV50PmRlTK4/hs/F9SU/KYdNCjDORbBZeuDZEnyFD8fwKqRUuu1ZbM5XymgAP8ntHSLaE0B3FY1Q== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -10949,6 +11042,22 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" +styled-components@^5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" + integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^1.1.0" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + stylehacks@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" @@ -11070,7 +11179,7 @@ sudo-prompt@^8.2.0: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e" integrity sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw== -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -12055,6 +12164,11 @@ webpack@^5.61.0: watchpack "^2.4.0" webpack-sources "^3.2.3" +whatwg-fetch@^3.4.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"