[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"next/babel\"],\n  \"plugins\": [\"inline-react-svg\"]\n}"
  },
  {
    "path": ".chec.json",
    "content": "{\n  \"npm\": \"npm\",\n  \"buildScripts\": [\"seed\", \"dev\"],\n  \"dotenv\": {\n    \"NODE_ENV\": \"development\",\n    \"NEXT_PUBLIC_CHEC_PUBLIC_API_KEY\": \"%chec_pkey%\",\n    \"CHEC_API_URL\": \"%chec_api_url%\",\n    \"CHEC_SECRET_KEY\": \"%chec_skey%\"\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# For more information about the properties used in this file,\n# please see the EditorConfig documentation:\n# http://editorconfig.org\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{yml,js,json,css,scss,feature,eslintrc}]\nindent_size = 2\n"
  },
  {
    "path": ".gitignore",
    "content": "# Build output\n.next\n\n# Environment variables\n.env\n\n# Dependency directories\nnode_modules\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Misc\n.vercel\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2021 Chec Platform LLC, All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n3) Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. \n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/chec/commercejs-examples/master/assets/logo.svg\" width=\"380\" height=\"100\" />\n</p>\n<p align=\"center\">\nA Next.js, Commerce.js, Stripe, and Vercel powered, open source storefront, cart and checkout experience.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/chec/commercejs-chopchop-demo/blob/main/LICENSE.md\">\n    <img src=\"https://img.shields.io/npm/l/@chec/commerce.js.svg\" alt=\"License\" />\n  </a>\n  <br>\n  <a href=\"https://commercejs.com\">commercejs.com</a> | <a href=\"https://twitter.com/commercejs\">@commercejs</a> | <a href=\"http://slack.commercejs.com\">Slack</a>\n  <br />\n  <br />\n  <a href=\"https://commercejs-chopchop-demo.vercel.app\">\n    <img src=\"https://cdn.chec.io/email/assets/marketing/chec-demo-btn_gray.svg\" alt=\"View demo\" />\n  </a>\n  <br />\n  <br />\n  <a href=\"https://commercejs-chopchop-demo.vercel.app\">\n    <img src=\"https://images.ctfassets.net/u77gi3ejnmxq/60D21gkBJHgH9YI3bizA3Q/c81183ac0cccb0ece6547da5021dc8b9/Group_558.png\" alt=\"View demo\" width=\"600\" />\n  </a>\n</p>\n\n## Introduction\n\nChopChop is our beautifully designed, elegantly developed demo store and starter kit that sells fine tools for thoughtful cooks. We’ve created a premium brand with a commerce experience to match. Read more about this resource on the [Commerce.js blog](https://commercejs.com/blog/chopchop-nextjs-starter-commerce/).\n\n\n## 🥞 ChopChop Stack\n\n* [Next.js](https://nextjs.org/)\n* [Commerce.js](https://commercejs.com)\n* [Tailwind CSS](https://tailwindcss.com/)\n* [Stripe](https://stripe.com)\n* [Vercel](https://vercel.com/)\n\n## Live demo\n\nCheck out https://commercejs-chopchop-demo.vercel.app to see this project in action.\n\n## Getting started\n\n### Prerequisites\n\n- IDE or code editor of your choice\n- Node (v12 or higher)\n- NPM or Yarn\n- Optional: [Chec CLI](https://github.com/chec/cli)\n\n### Use the Chec CLI\n\nYou can use the [Chec CLI](https://github.com/chec/cli) to quickly and easily install demo stores like this, and also\nto install sample data into your account. To install the Chec CLI, run `npm install -g @chec/cli` (or `yarn global add @chec/cli`).\n\n* Navigate to your projects folder: `cd ~/Projects`\n* Install the ChopChop demo store: `chec demo-store`\n  * Choose \"Chop Chop demo store (Next.js)\" from the list\n  * This will install dependencies and sample data, then start your dev server\n  * Stop the server, open `.env` and add your `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` for using Stripe, then re-run `npm run dev`\n* Open [http://localhost:3000](http://localhost:3000) and get started!\n\n### Manual installation\n\nClone the project, then get started by installing the dependencies, creating a `.env` file, and starting the dev server.\n\n```\nnpm install\ncp .env.example .env\nnpm run dev\n```\n\nOnce the server is running, open it up in your browser, start editing the code, and enjoy!\n\n### Sample data\n\nThis repository comes with some sample products and images for you to use if you want to get up and running quickly.\n\nTo install sample data, first copy `.env.example` to `.env`, then edit `.env` and fill out the\nfollowing variables:\n\n* `NEXT_PUBLIC_CHEC_PUBLIC_API_KEY`: Your Chec public/sandbox API key, available from the Chec Dashboard under\n  Developers > API keys\n* `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`: Your Stripe test publishable key, available from the Stripe dashboard\n* `CHEC_SECRET_KEY`: Your Chec secret API key, used for seeding\n* `NEXT_PUBLIC_GA_TRACKING_ID`: Set this with your Google Analytics ID if you want to enable GA.\n\nOnce this is done, save and close your file. You can now run the seeder to install sample data:\n\n```\nnpm run seed\n...\n✔ Completed seeding\nAdded:\n  3 categories\n  6 products\n  9 assets\n```\n\nAnd you're ready to go!\n\n### Deploying to Vercel (with one click)\n\nThe one-click deploy allows you to add the Vercel application to your GitHub account to clone this repository and deploy it automatically. Be sure to go to [Vercel](https://vercel.com/signup) and sign up for an account with Github, GitLab, or GitBucket before clicking the deploy button.\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/project?template=https://github.com/chec/commercejs-chopchop-demo)\n\nPlease make sure that you enter the required environment variables listed above during deployment.\n\n#### Caveats for sample data\n\nTo make your ChopChop experience even better, there are a couple of things you can do that are not included with\nthe sample data:\n\n* **Add related products:** Go into the [Chec Dashboard](https://dashboard.chec.io) and set related products for each\n  of your new products. This helps to provide upsell suggestions on your website.\n* **Set up shipping rates:** Also in the dashboard, set up some shipping zones and rates in Settings > Shipping, then\n  enable them on each of your products. This will enable the \"Shipping\" checkout screen, and allow you to charge\n  shipping for your customers as well.\n\n## Customizations and Extendability \n\n- Integrate another payment gateway, either one of our supported gateways or your own with our [manual gateway API](https://commercejs.com/docs/guides/manual-payment-integration)\n- Integrate with the Google Calendar API to automatically add ticketed items to a customer’s calendars\n- Suggest products from other sources based on items purchased, i.e. a book on knife skills if you buy the knife set\n- Add [Algolia](https://www.algolia.com/) for integrated search\n- Add additional modules to the checkout flow to handle other content types, like booking a time to pickup in-store purchases\n- Integrate with a headless CMS to make the content editable\n- Create a customers login section using our [customers endpoint](https://commercejs.com/docs/api/#customers)\n- Use webhooks to deliver SMS notifications about orders\n\n## License\n\nThis project is licensed under [BSD-3-Clause](LICENSE.md).\n\n## ⚠️ Note\n\n### This repository is no longer maintained\nHowever, we will accept issue reports and contributions for this repository. See the [contribute to the commerce community](https://commercejs.com/docs/community/contribute) page for more information on how to contribute to our open source projects. For update-to-date APIs, please check the latest version of the [API documentation](https://commercejs.com/docs/api/).\n"
  },
  {
    "path": "components/Breadcrumbs.js",
    "content": "import { useCheckoutState } from \"../context/checkout\";\n\n// TODO: Build array of crumbs dynamically from available steps\n\nfunction Breadcrumbs({ inCart }) {\n  const { currentStep, extrafields } = useCheckoutState();\n\n  if (inCart) {\n    return <span className=\"text-lg md:text-xl\">Shopping Bag</span>;\n  }\n\n  if (currentStep === \"success\") {\n    return <span className=\"text-lg md:text-xl\">Order received</span>;\n  }\n\n  return (\n    <div className=\"space-x-3\">\n      {currentStep === \"extrafields\" && (\n        <>\n          <span className=\"text-lg md:text-xl\">Shopping Bag</span>\n          <span className=\"text-lg md:text-xl\">&rarr;</span>\n          <span className=\"text-lg md:text-xl\">Booking</span>\n          <span className=\"text-lg md:text-xl opacity-50\">&rarr;</span>\n          <span className=\"text-lg md:text-xl opacity-50\">Shipping</span>\n          <span className=\"text-lg md:text-xl opacity-50\">&rarr;</span>\n          <span className=\"text-lg md:text-xl opacity-50\">Payment</span>\n        </>\n      )}\n      {currentStep === \"shipping\" && (\n        <>\n          <span className=\"text-lg md:text-xl\">Shopping Bag</span>\n\n          {extrafields.length > 0 && (\n            <>\n              <span className=\"text-lg md:text-xl\">&rarr;</span>\n              <span className=\"text-lg md:text-xl\">Booking</span>\n            </>\n          )}\n          <span className=\"text-lg md:text-xl\">&rarr;</span>\n          <span className=\"text-lg md:text-xl\">Shipping</span>\n          <span className=\"text-lg md:text-xl opacity-50\">&rarr;</span>\n          <span className=\"text-lg md:text-xl opacity-50\">Payment</span>\n        </>\n      )}\n      {currentStep === \"billing\" && (\n        <>\n          <span className=\"text-lg md:text-xl\">Shopping Bag</span>\n          {extrafields.length > 0 && (\n            <>\n              <span className=\"text-lg md:text-xl\">&rarr;</span>\n              <span className=\"text-lg md:text-xl\">Booking</span>\n            </>\n          )}\n          <span className=\"text-lg md:text-xl\">&rarr;</span>\n          <span className=\"text-lg md:text-xl\">Shipping</span>\n          <span className=\"text-lg md:text-xl\">&rarr;</span>\n          <span className=\"text-lg md:text-xl\">Payment</span>\n        </>\n      )}\n    </div>\n  );\n}\n\nexport default Breadcrumbs;\n"
  },
  {
    "path": "components/Button.js",
    "content": "import cc from \"classcat\";\n\nimport { useThemeState } from \"../context/theme\";\n\nconst buttonStyle = (theme) => {\n  switch (theme) {\n    case \"kitchen-sink-journal-chopchop-shop\":\n      return \"bg-clementine text-black\";\n    case \"walnut-cooks-tools-chopchop-shop\":\n      return \"bg-tumbleweed text-black\";\n    case \"essential-knife-set-chopchop-shop\":\n      return \"bg-hawkes-blue text-black\";\n    case \"private-cooking-class-chopchop-shop\":\n      return \"bg-asparagus text-black\";\n    case \"ceramic-dutch-oven-chopchop-shop\":\n      return \"bg-goldenrod text-black\";\n    default:\n      return \"bg-white-rock\";\n  }\n};\n\nfunction Button({ className, ...props }) {\n  const theme = useThemeState();\n\n  const buttonClass = cc([\n    \"appearance-none border-none py-0.5 px-1.5 md:px-2 text-lg md:text-xl rounded transition focus:outline-none\",\n    buttonStyle(theme),\n    className,\n  ]);\n\n  if (props.href) return <a {...props} className={buttonClass} />;\n\n  return <button {...props} className={buttonClass} />;\n}\n\nexport default Button;\n"
  },
  {
    "path": "components/Cart.js",
    "content": "import { useCartState } from \"../context/cart\";\nimport { useModalDispatch } from \"../context/modal\";\n\nimport Button from \"./Button\";\nimport CartItem from \"./CartItem\";\n\nexport default function Cart() {\n  const { line_items, subtotal, total_unique_items } = useCartState();\n  const { showCheckout } = useModalDispatch();\n\n  const isEmpty = line_items.length === 0;\n\n  return (\n    <div className=\"h-full flex flex-col justify-between\">\n      <div>\n        {line_items.map((item) => (\n          <CartItem key={item.id} {...item} />\n        ))}\n      </div>\n\n      <div className=\"flex items-center justify-between py-3 md:py-4 lg:py-5\">\n        {isEmpty ? (\n          <p>Your cart is empty.</p>\n        ) : (\n          <>\n            <div className=\"text-lg md:text-xl\">\n              Total: {subtotal?.formatted_with_symbol}, {total_unique_items}{\" \"}\n              {total_unique_items === 1 ? \"item\" : \"items\"}\n            </div>\n            <div>\n              <Button\n                className=\"appearance-none leading-none p-1 md:p-1.5 lg:px-3.5 text-lg md:text-xl\"\n                onClick={showCheckout}\n              >\n                Check Out\n              </Button>\n            </div>\n          </>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/CartItem.js",
    "content": "import React from \"react\";\nimport Image from \"next/image\";\nimport { toast } from \"react-toastify\";\n\nimport { commerce } from \"../lib/commerce\";\nimport { useCartDispatch } from \"../context/cart\";\n\nfunction CartItem({ id, media, name, quantity, line_total, selected_options }) {\n  const { setCart } = useCartDispatch();\n  const hasVariants = selected_options.length >= 1;\n\n  const handleUpdateCart = ({ cart }) => {\n    setCart(cart);\n\n    return cart;\n  };\n\n  const handleRemoveItem = () =>\n    commerce.cart\n      .remove(id)\n      .then(handleUpdateCart)\n      .then(({ subtotal }) =>\n        toast(\n          `${name} has been removed from your cart. Your new subtotal is now ${subtotal.formatted_with_symbol}`\n        )\n      );\n\n  const decrementQuantity = () => {\n    quantity > 1\n      ? commerce.cart\n          .update(id, { quantity: quantity - 1 })\n          .then(handleUpdateCart)\n          .then(({ subtotal }) =>\n            toast(\n              `1 \"${name}\" has been removed from your cart. Your new subtotal is now ${subtotal.formatted_with_symbol}`\n            )\n          )\n      : handleRemoveItem();\n  };\n\n  const incrementQuantity = () =>\n    commerce.cart\n      .update(id, { quantity: quantity + 1 })\n      .then(handleUpdateCart)\n      .then(({ subtotal }) =>\n        toast(\n          `Another \"${name}\" has been added to your cart. Your new subtotal is now ${subtotal.formatted_with_symbol}`\n        )\n      );\n\n  return (\n    <div className=\"py-3 md:py-4 lg:py-5 flex md:items-end space-x-3 md:space-x-4 lg:space-x-5 border-b border-black\">\n      <div className=\"w-24 h-24 md:w-48 md:h-48 lg:w-56 lg:h-56 rounded relative\">\n        <Image\n          src={media.source}\n          alt={name}\n          layout=\"fill\"\n          className=\"object-cover rounded-lg hover:rounded-none transition-all\"\n          loading=\"eager\"\n          priority={true}\n        />\n      </div>\n      <div className=\"flex flex-col md:flex-row md:items-end flex-grow\">\n        <div className=\"md:flex-grow\">\n          <p className=\"font-serif text-xl md:text-2xl lg:text-3xl italic leading-none\">\n            {name}\n          </p>\n          {hasVariants && (\n            <p>\n              {selected_options.map(({ option_name }, index) => (\n                <React.Fragment key={index}>\n                  {index ? `, ${option_name}` : option_name}\n                </React.Fragment>\n              ))}\n            </p>\n          )}\n        </div>\n        <div className=\"flex flex-col items-start md:items-end justify-between flex-grow\">\n          <div className=\"text-lg md:text-xl lg:text-2xl\">\n            {line_total.formatted_with_symbol}\n          </div>\n          <div className=\"w-full flex md:flex-col items-center md:items-end justify-between\">\n            <div className=\"md:pb-4 lg:pb-5 inline-flex items-center\">\n              <span className=\"pr-2\">Quantity:</span>\n              <button\n                onClick={decrementQuantity}\n                className=\"appearance-none inline-flex items-center justify-center rounded-lg border border-black w-5 h-5 text-xs text-black focus:outline-none hover:bg-black hover:text-white transition\"\n              >\n                -\n              </button>\n              <span className=\"px-2 md:text-lg\">{quantity}</span>\n              <button\n                onClick={incrementQuantity}\n                className=\"appearance-none inline-flex items-center justify-center rounded-lg border border-black w-5 h-5 text-xs text-black focus:outline-none hover:bg-black hover:text-white transition\"\n              >\n                +\n              </button>\n            </div>\n            <div>\n              <button\n                onClick={handleRemoveItem}\n                className=\"appearance-none inline-flex items-center justify-center rounded-lg border border-black text-xs text-black px-1 h-5 opacity-50 hover:opacity-100 focus:opacity-100 focus:outline-none transition\"\n              >\n                Remove\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default CartItem;\n"
  },
  {
    "path": "components/CartSummary.js",
    "content": "import { useCartState } from \"../context/cart\";\nimport { useModalDispatch } from \"../context/modal\";\n\nfunction CartSummary() {\n  const { total_unique_items } = useCartState();\n  const { openModal } = useModalDispatch();\n\n  return (\n    <button className=\"appearance-none focus:outline-none\" onClick={openModal}>\n      Shopping Bag ({total_unique_items})\n    </button>\n  );\n}\n\nexport default CartSummary;\n"
  },
  {
    "path": "components/Checkout/AddressFields.js",
    "content": "import React from \"react\";\n\nimport { FormInput, FormSelect } from \"../Form\";\n\nfunction AddressFields({ prefix = \"\", countries = {}, subdivisions = {} }) {\n  const reducer = ([code, name]) => ({\n    value: code,\n    label: name,\n  });\n\n  const formattedCountries = subdivisions\n    ? Object.entries(countries).map(reducer)\n    : [];\n\n  const formattedSubdivisions = subdivisions\n    ? Object.entries(subdivisions).map(reducer)\n    : [];\n\n  return (\n    <React.Fragment>\n      <div className=\"md:flex md:items-start md:space-x-4\">\n        <div className=\"md:w-1/2\">\n          <FormInput\n            label=\"First name\"\n            name={`${prefix}.firstname`}\n            placeholder=\"First name\"\n            required\n          />\n        </div>\n        <div className=\"md:w-1/2\">\n          <FormInput\n            label=\"Last name\"\n            name={`${prefix}.lastname`}\n            placeholder=\"Last name\"\n            required\n          />\n        </div>\n      </div>\n\n      <FormInput\n        label=\"Address\"\n        name={`${prefix}.street`}\n        placeholder=\"Address\"\n        required\n      />\n      <FormInput\n        label=\"Town / City\"\n        name={`${prefix}.town_city`}\n        placeholder=\"City\"\n        required\n      />\n\n      <div className=\"md:flex md:items-start md:space-x-4\">\n        <div className=\"md:w-1/3\">\n          <FormSelect\n            label=\"Country\"\n            name={`${prefix}.country`}\n            options={formattedCountries}\n            placeholder=\"Select country\"\n            required\n            disabled={formattedCountries.length === 0}\n          />\n        </div>\n        <div className=\"md:w-1/3\">\n          <FormSelect\n            label=\"County / State\"\n            name={`${prefix}.region`}\n            options={formattedSubdivisions}\n            placeholder=\"Select region\"\n            required\n            disabled={formattedSubdivisions.length === 0}\n          />\n        </div>\n        <div className=\"md:w-1/3\">\n          <FormInput\n            label=\"ZIP / Postcode\"\n            name={`${prefix}.postal_zip_code`}\n            placeholder=\"ZIP\"\n            required\n          />\n        </div>\n      </div>\n    </React.Fragment>\n  );\n}\n\nexport default AddressFields;\n"
  },
  {
    "path": "components/Checkout/BillingForm.js",
    "content": "import { useState, useEffect } from \"react\";\nimport { useFormContext } from \"react-hook-form\";\nimport { useDebounce } from \"use-debounce\";\nimport {\n  CardNumberElement,\n  CardExpiryElement,\n  CardCvcElement,\n} from \"@stripe/react-stripe-js\";\n\nimport { commerce } from \"../../lib/commerce\";\n\nimport { useCheckoutState, useCheckoutDispatch } from \"../../context/checkout\";\n\nimport { FormCheckbox, FormInput, FormError } from \"../Form\";\nimport AddressFields from \"./AddressFields\";\n\nconst style = {\n  base: {\n    \"::placeholder\": {\n      color: \"rgba(21,7,3,0.3)\",\n    },\n    color: \"#150703\",\n    fontSize: \"16px\",\n    fontFamily: `Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"`,\n    iconColor: \"#6B7280\",\n  },\n};\n\nfunction BillingForm() {\n  const [countries, setCountries] = useState();\n  const [subdivisions, setSubdivisions] = useState();\n  const methods = useFormContext();\n  const { collects } = useCheckoutState();\n  const { setError } = useCheckoutDispatch();\n\n  const { watch, setValue, clearErrors } = methods;\n\n  const shipping = watch(\"shipping\");\n  const [watchCountry] = useDebounce(watch(\"billing.country\"), 600);\n\n  useEffect(() => {\n    fetchCountries();\n  }, []);\n\n  useEffect(() => {\n    watchCountry && fetchSubdivisions(watchCountry);\n  }, [watchCountry]);\n\n  const fetchCountries = async () => {\n    try {\n      const { countries } = await commerce.services.localeListCountries();\n\n      setCountries(countries);\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const fetchSubdivisions = async (country) => {\n    try {\n      const { subdivisions } = await commerce.services.localeListSubdivisions(\n        country\n      );\n\n      setSubdivisions(subdivisions);\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const onStripeChange = () => {\n    clearErrors(\"stripe\");\n    setError(null);\n  };\n\n  return (\n    <div className=\"md:flex md:space-x-12 lg:space-x-24\">\n      <div className=\"md:w-1/2\">\n        <fieldset className=\"mb-3 md:mb-4\">\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3 block\">\n            Billing address\n          </legend>\n\n          {collects?.shipping_address && (\n            <FormCheckbox\n              label=\"Same as shipping address\"\n              name=\"billingIsShipping\"\n              onChange={({ target: { checked } }) =>\n                checked && setValue(\"billing\", shipping)\n              }\n            />\n          )}\n\n          <AddressFields\n            prefix=\"billing\"\n            countries={countries}\n            subdivisions={subdivisions}\n          />\n        </fieldset>\n      </div>\n\n      <div className=\"md:w-1/2\">\n        <fieldset>\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3\">\n            Payment\n          </legend>\n\n          <FormInput\n            type=\"email\"\n            label=\"Email\"\n            name=\"customer.email\"\n            placeholder=\"Receipt email\"\n            required\n            validation={{\n              pattern: {\n                value: /^\\S+@\\S+$/i,\n                message: \"You must enter a valid email\",\n              },\n            }}\n          />\n\n          <div className=\"space-y-3\">\n            <div>\n              <CardNumberElement\n                options={{ style }}\n                className=\"appearance-none bg-transparent placeholder-faded-black border border-faded-black focus:border-black focus:outline-none rounded-md w-full p-1.5\"\n                onChange={onStripeChange}\n              />\n            </div>\n\n            <div className=\"flex space-x-4\">\n              <div className=\"w-1/2\">\n                <CardExpiryElement\n                  options={{ style }}\n                  placeholder=\"Expiry\"\n                  className=\"appearance-none bg-transparent placeholder-faded-black border border-faded-black focus:border-black focus:outline-none rounded-md w-full p-1.5\"\n                  onChange={onStripeChange}\n                />\n              </div>\n              <div className=\"w-1/2\">\n                <CardCvcElement\n                  options={{ style }}\n                  className=\"appearance-none bg-transparent placeholder-faded-black border border-faded-black focus:border-black focus:outline-none rounded-md w-full p-1.5\"\n                  onChange={onStripeChange}\n                />\n              </div>\n            </div>\n          </div>\n          <FormError name=\"stripe\" />\n        </fieldset>\n      </div>\n    </div>\n  );\n}\n\nexport default BillingForm;\n"
  },
  {
    "path": "components/Checkout/Checkout.js",
    "content": "import { useState, useEffect } from \"react\";\nimport { useForm, FormProvider } from \"react-hook-form\";\nimport { useStripe, useElements } from \"@stripe/react-stripe-js\";\n\nimport { useCartDispatch } from \"../../context/cart\";\nimport { useCheckoutState, useCheckoutDispatch } from \"../../context/checkout\";\n\nimport ExtraFieldsForm from \"./ExtraFieldsForm\";\nimport ShippingForm from \"./ShippingForm\";\nimport BillingForm from \"./BillingForm\";\nimport Success from \"./Success\";\nimport CheckoutSummary from \"./CheckoutSummary\";\nimport OrderSummary from \"./OrderSummary\";\n\nimport LoadingSVG from \"../../svg/loading.svg\";\n\nfunction Checkout({ cartId }) {\n  const [order, setOrder] = useState();\n  const { reset: resetCart } = useCartDispatch();\n  const { currentStep, id, live } = useCheckoutState();\n  const {\n    generateToken,\n    setCurrentStep,\n    nextStepFrom,\n    capture,\n    setProcessing,\n    setError: setCheckoutError,\n  } = useCheckoutDispatch();\n  const methods = useForm({\n    shouldUnregister: false,\n  });\n  const { handleSubmit, setError } = methods;\n\n  const stripe = useStripe();\n  const elements = useElements();\n\n  useEffect(() => {\n    generateToken(cartId);\n  }, [cartId]);\n\n  const captureOrder = async (values) => {\n    setProcessing(true);\n\n    const {\n      customer,\n      shipping,\n      billing: { firstname, lastname, region: county_state, ...billing },\n      ...data\n    } = values;\n\n    const { error, paymentMethod } = await stripe.createPaymentMethod({\n      type: \"card\",\n      card: elements.getElement(\"cardNumber\"),\n      billing_details: {\n        name: `${billing.firstname} ${billing.lastname}`,\n        email: customer.email,\n      },\n    });\n\n    if (error) {\n      setError(\"stripe\", { type: \"manual\", message: error.message });\n      setProcessing(false);\n      return;\n    }\n\n    const checkoutPayload = {\n      ...data,\n      customer: {\n        ...customer,\n        firstname,\n        lastname,\n      },\n      ...(shipping && {\n        shipping: {\n          ...shipping,\n          name: `${shipping.firstname} ${shipping.lastname}`,\n        },\n      }),\n      billing: {\n        ...billing,\n        name: `${firstname} ${lastname}`,\n        county_state,\n      },\n    };\n\n    try {\n      const newOrder = await capture({\n        ...checkoutPayload,\n        payment: {\n          gateway: \"stripe\",\n          stripe: {\n            payment_method_id: paymentMethod.id,\n          },\n        },\n      });\n\n      handleOrderSuccess(newOrder);\n      setProcessing(false);\n    } catch (res) {\n      if (\n        res.statusCode !== 402 ||\n        res.data.error.type !== \"requires_verification\"\n      ) {\n        setCheckoutError(res.data.error.message);\n        setProcessing(false);\n        return;\n      }\n\n      const { error, paymentIntent } = await stripe.handleCardAction(\n        res.data.error.param\n      );\n\n      if (error) {\n        setError(\"stripe\", { type: \"manual\", message: error.message });\n        setProcessing(false);\n        return;\n      }\n\n      try {\n        const newOrder = await capture({\n          ...checkoutPayload,\n          payment: {\n            gateway: \"stripe\",\n            stripe: {\n              payment_intent_id: paymentIntent.id,\n            },\n          },\n        });\n\n        handleOrderSuccess(newOrder);\n        setProcessing(false);\n      } catch (err) {\n        setError(\"stripe\", { type: \"manual\", message: error.message });\n        setProcessing(false);\n      }\n    }\n  };\n\n  const handleOrderSuccess = (order) => {\n    setOrder(order);\n    setCurrentStep(\"success\");\n    resetCart();\n  };\n\n  const onSubmit = (values) => {\n    if (currentStep === \"billing\") return captureOrder(values);\n\n    return setCurrentStep(nextStepFrom(currentStep));\n  };\n\n  if (!id)\n    return (\n      <div className=\"h-full flex flex-col items-center justify-center space-y-6\">\n        <LoadingSVG className=\"w-10 md:w-16 fill-current\" />\n        <p className=\"text-black\">Preparing checkout</p>\n      </div>\n    );\n\n  return (\n    <FormProvider {...methods}>\n      <form\n        onSubmit={handleSubmit(onSubmit)}\n        className=\"h-full flex flex-col justify-between pt-6 md:pt-12\"\n      >\n        {currentStep === \"extrafields\" && <ExtraFieldsForm />}\n        {currentStep === \"shipping\" && <ShippingForm />}\n        {currentStep === \"billing\" && <BillingForm />}\n        {currentStep === \"success\" && <Success {...order} />}\n\n        {order ? <OrderSummary {...order} /> : <CheckoutSummary {...live} />}\n      </form>\n    </FormProvider>\n  );\n}\n\nexport default Checkout;\n"
  },
  {
    "path": "components/Checkout/CheckoutSummary.js",
    "content": "import cc from \"classcat\";\n\nimport { useCheckoutState } from \"../../context/checkout\";\n\nimport Button from \"../Button\";\n\nfunction CheckoutSummary({ subtotal, tax, shipping, line_items = [], total }) {\n  const { processing, error } = useCheckoutState();\n  const count = line_items.length;\n\n  return (\n    <div className=\"py-6\">\n      <div className=\"md:flex md:justify-between md:space-x-6\">\n        <div className=\"w-full md:w-1/2\">\n          <ol>\n            {subtotal && <li>Subtotal: {subtotal.formatted_with_symbol}</li>}\n            {tax && <li>Tax: {tax.amount.formatted_with_symbol}</li>}\n            {shipping && (\n              <li>Shipping: {shipping.price.formatted_with_symbol}</li>\n            )}\n            {total && (\n              <li className=\"text-lg md:text-xl py-3\">\n                Total: {total.formatted_with_symbol}, {count}{\" \"}\n                {count === 1 ? \"item\" : \"items\"}\n              </li>\n            )}\n          </ol>\n        </div>\n        <div className=\"w-full md:w-1/2 md:flex md:items-end md:justify-end\">\n          <div className=\"flex items-center space-x-3\">\n            {error && <span className=\"text-red-500 text-sm\">{error}</span>}\n            <Button\n              type=\"submit\"\n              className={cc([\n                \"appearance-none leading-none p-1 md:p-2 lg:p-3 text-lg md:text-xl\",\n                {\n                  \"opacity-75 cursor-not-allowed\": processing,\n                },\n              ])}\n              disabled={processing}\n            >\n              {processing ? \"Processing order\" : \"Continue\"}\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default CheckoutSummary;\n"
  },
  {
    "path": "components/Checkout/ExtraFieldsForm.js",
    "content": "import { useEffect } from \"react\";\nimport { useCheckoutState, useCheckoutDispatch } from \"../../context/checkout\";\n\nimport { FormInput, FormCheckbox, FormTextarea } from \"../Form\";\n\n// TODO: Update the UI to be built from the API\n// once products have extrafields that can be of\n// any type. E.g. \"date\", \"textarea\"\n\n// const fields = {\n//   BookingDate: (props) => (\n//     <>\n//       <FormInput {...props} />\n//     </>\n//   ),\n// };\n\nfunction ExtraFieldsForm() {\n  const { extrafields } = useCheckoutState();\n  const { setCurrentStep, nextStepFrom } = useCheckoutDispatch();\n\n  useEffect(() => {\n    if (extrafields.length === 0) {\n      setCurrentStep(nextStepFrom(\"extrafields\"));\n    }\n    return null;\n  }, [extrafields]);\n\n  return (\n    <div className=\"md:flex md:space-x-12 lg:space-x-24\">\n      <div className=\"md:w-1/2\">\n        <fieldset className=\"mb-3 md:mb-4\">\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3 block\">\n            Booking\n          </legend>\n\n          <FormInput name=\"bookingDate\" type=\"date\" />\n\n          <FormCheckbox\n            name=\"takeClassInBrooklyn\"\n            label=\"Take the class at our space in Brooklyn\"\n          />\n\n          <FormCheckbox name=\"takeClassOnline\" label=\"Take the class online\" />\n        </fieldset>\n      </div>\n\n      <div className=\"md:w-1/2\">\n        <fieldset className=\"mb-3 md:mb-4\">\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3 block\">\n            Lesson Plan\n          </legend>\n\n          <p className=\"text-black text-sm italic font-serif font-medium mb-3\">\n            Thanks for joining us for a lesson! Let us know what you might like\n            to learn or cook below.\n          </p>\n\n          {extrafields.map(({ id }) => {\n            const computedFieldName = `extrafields.${id}`;\n\n            return (\n              <FormTextarea\n                key={id}\n                id={computedFieldName}\n                name={computedFieldName}\n                placeholder=\"I'm interested in learning...\"\n                rows={7}\n              />\n            );\n          })}\n        </fieldset>\n      </div>\n    </div>\n  );\n}\n\nexport default ExtraFieldsForm;\n"
  },
  {
    "path": "components/Checkout/OrderSummary.js",
    "content": "import Button from \"../Button\";\n\nfunction CheckoutSummary({ has, fulfillment, order }) {\n  const { subtotal, tax, shipping, line_items, total } = order;\n\n  const count = line_items.length;\n\n  return (\n    <div className=\"py-6\">\n      <div className=\"md:flex md:justify-between md:space-x-6\">\n        <div className=\"w-full md:w-1/2\">\n          <ol>\n            <li>Subtotal: {subtotal.formatted_with_symbol}</li>\n            {tax && <li>Tax: {tax.amount.formatted_with_symbol}</li>}\n            {shipping && (\n              <li>Shipping: {shipping.price.formatted_with_symbol}</li>\n            )}\n            {total && (\n              <li className=\"text-lg md:text-xl py-3\">\n                Total: {total.formatted_with_symbol}, {count}{\" \"}\n                {count === 1 ? \"item\" : \"items\"}\n              </li>\n            )}\n          </ol>\n        </div>\n        {has.digital_fulfillment && (\n          <div className=\"w-full md:w-1/2 md:flex md:items-end md:justify-end space-y-3 md:space-y-0 md:space-x-3\">\n            {fulfillment.digital.downloads.map((download, index) => (\n              <div\n                className=\"md:flex space-y-3 md:space-y-0 md:space-x-3\"\n                key={index}\n              >\n                {download.packages.map(({ access_link, name }, index) => (\n                  <Button\n                    key={index}\n                    href={access_link}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    Download {name}\n                  </Button>\n                ))}\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport default CheckoutSummary;\n"
  },
  {
    "path": "components/Checkout/ShippingForm.js",
    "content": "import { useState, useEffect } from \"react\";\nimport { useFormContext } from \"react-hook-form\";\nimport { useDebounce } from \"use-debounce\";\n\nimport { commerce } from \"../../lib/commerce\";\n\nimport { useCheckoutState, useCheckoutDispatch } from \"../../context/checkout\";\n\nimport AddressFields from \"./AddressFields\";\nimport { FormCheckbox as FormRadio, FormError } from \"../Form\";\n\nfunction ShippingForm() {\n  const { id } = useCheckoutState();\n  const { setShippingMethod } = useCheckoutDispatch();\n  const [countries, setCountries] = useState();\n  const [subdivisions, setSubdivisions] = useState();\n  const [shippingOptions, setShippingOptions] = useState([]);\n  const methods = useFormContext();\n  const { watch, setValue } = methods;\n\n  const [watchCountry] = useDebounce(watch(\"shipping.country\"), 600);\n  const watchSubdivision = watch(\"shipping.region\");\n\n  useEffect(() => {\n    fetchCountries(id);\n  }, []);\n\n  useEffect(() => {\n    setValue(\"shipping.region\", \"\");\n\n    if (watchCountry) {\n      fetchSubdivisions(id, watchCountry);\n      fetchShippingOptions(id, watchCountry);\n    }\n  }, [watchCountry]);\n\n  useEffect(() => {\n    if (watchSubdivision) {\n      fetchShippingOptions(id, watchCountry, watchSubdivision);\n    }\n  }, [watchSubdivision]);\n\n  const fetchCountries = async (checkoutId) => {\n    try {\n      const { countries } = await commerce.services.localeListShippingCountries(\n        checkoutId\n      );\n\n      setCountries(countries);\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const fetchSubdivisions = async (checkoutId, countryCode) => {\n    try {\n      const {\n        subdivisions,\n      } = await commerce.services.localeListShippingSubdivisions(\n        checkoutId,\n        countryCode\n      );\n\n      setSubdivisions(subdivisions);\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const fetchShippingOptions = async (checkoutId, country, region) => {\n    if (!checkoutId && !country) return;\n\n    setValue(\"fulfillment.shipping_method\", null);\n\n    try {\n      const shippingOptions = await commerce.checkout.getShippingOptions(\n        checkoutId,\n        {\n          country,\n          ...(region && { region }),\n        }\n      );\n\n      setShippingOptions(shippingOptions);\n\n      if (shippingOptions.length === 1) {\n        const [shippingOption] = shippingOptions;\n\n        setValue(\"fulfillment.shipping_method\", shippingOption.id);\n        selectShippingMethod(shippingOption.id);\n      }\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const onShippingSelect = ({ target: { value } }) =>\n    selectShippingMethod(value);\n\n  const selectShippingMethod = async (optionId) => {\n    try {\n      await setShippingMethod(optionId, watchCountry, watchSubdivision);\n    } catch (err) {\n      // noop\n    }\n  };\n\n  return (\n    <div className=\"md:flex md:space-x-12 lg:space-x-24\">\n      <div className=\"md:w-1/2\">\n        <fieldset className=\"mb-3 md:mb-4\">\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3 block\">\n            Shipping address\n          </legend>\n\n          <AddressFields\n            prefix=\"shipping\"\n            countries={countries}\n            subdivisions={subdivisions}\n          />\n        </fieldset>\n      </div>\n      <div className=\"md:w-1/2\">\n        <fieldset className=\"mb-3 md:mb-4\">\n          <legend className=\"text-black font-medium text-lg md:text-xl py-3 block\">\n            Shipping\n          </legend>\n          <div>\n            {watchCountry ? (\n              <>\n                <div className=\"-space-y-1\">\n                  {shippingOptions.map(({ id, description, price }) => (\n                    <div key={id}>\n                      <FormRadio\n                        id={id}\n                        type=\"radio\"\n                        name=\"fulfillment.shipping_method\"\n                        value={id}\n                        label={`${description}: ${price.formatted_with_symbol}`}\n                        onChange={onShippingSelect}\n                        required=\"You must select a shipping option\"\n                      />\n                    </div>\n                  ))}\n                </div>\n\n                <FormError name=\"fulfillment.shipping_method\" />\n              </>\n            ) : (\n              <p className=\"text-sm text-black\">\n                Please enter your address to fetch shipping options\n              </p>\n            )}\n          </div>\n        </fieldset>\n      </div>\n    </div>\n  );\n}\n\nexport default ShippingForm;\n"
  },
  {
    "path": "components/Checkout/Success.js",
    "content": "import Image from 'next/image';\n\nfunction Success({ has }) {\n  return (\n    <div className=\"h-full lg:flex lg:items-center lg:space-x-12 lg:space-x-24\">\n      <div className=\"lg:w-1/2 \">\n        <h1 className=\"font-serif font-medium italic text-2xl md:text-4xl lg:text-5xl xl:text-6xl\">\n          Thanks!\n        </h1>\n        <p className=\"mt-3 text-lg md:text-xl font-sans\">\n          {has.digital_fulfillment\n            ? \"You’ll receive an email with your receipt, and a backup link to re-download your purchase\"\n            : \"You’ll receive an email with your receipt, and tracking information.\"}\n        </p>\n      </div>\n      <div className=\"lg:w-1/2 lg:flex lg:items-center lg:justify-center\">\n        <div className=\"bg-white shadow-thank-you transform -rotate-25 skew-y-12 mx-auto my-24 lg:mt-48 max-w-lg\">\n          <div className=\"ml-4\">\n            <Image\n              src=\"/checkout/doesntexist.svg\"\n              width={384}\n              height={126}\n              alt=\"ChopChop doesn't exist!\"\n              layout=\"responsive\"\n            />\n          </div>\n\n          <div className=\"p-4 pt-0 -mt-4 leading-tight font-sans\">\n            <p>...if it did, we'd offer you a <span className=\"font-serif italic\">100% real store credit</span>, but since it doesn't, we'd love for you to check out <a href=\"https://commercejs.com\" target=\"_blank\" rel=\"noopener nofollow\" className=\"font-serif italic underline\">commercejs.com</a> and <a href=\"https://github.com/chec/commercejs-chopchop-demo\" target=\"_blank\" rel=\"noopener nofollow\" className=\"font-serif italic underline\">the repo</a> for this store instead.</p>\n            <div className=\"mt-6 mb-1 font-serif flex justify-between items-end\">\n              <Image\n                src=\"/product-attributes/thanks.svg\"\n                width={110}\n                height={48}\n                alt=\"Thanks for visiting\"\n              />\n              <span className=\"ml-4 italic text-sm\">'Chop chop' what are you waiting for</span>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default Success;\n"
  },
  {
    "path": "components/Checkout/index.js",
    "content": "export { default } from \"./Checkout\";\n"
  },
  {
    "path": "components/Footer.js",
    "content": "import Link from \"next/link\";\n\nimport LogoSVG from \"../svg/logo.svg\";\nimport CommerceJsSVG from \"../svg/commercejs.svg\";\n\nfunction Footer() {\n  return (\n    <footer className=\"py-6 lg:py-12\">\n      <div className=\"container mx-auto px-3 md:px-4 lg:px-5 md:flex md:items-center space-y-6 md:space-y-0\">\n        <div className=\"w-full md:w-1/3\">\n          <Link href=\"/\">\n            <a title=\"Return to ChopChop\">\n              <LogoSVG className=\"w-full md:w-auto md:h-8\" />\n            </a>\n          </Link>\n        </div>\n\n        <div className=\"w-full md:w-1/3 flex items-center md:justify-center\">\n          <a\n            href=\"https://commercejs.com\"\n            title=\"Visit Commerce.js website\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"inline-flex items-center space-x-1 text-faded-black hover:text-black transition-colors\"\n          >\n            <span>Powered by</span>\n            <CommerceJsSVG className=\"h-4\" />\n          </a>\n        </div>\n\n        <div className=\"w-full md:w-1/3\">\n          <div className=\"md:text-right space-x-1\">\n            <Link href=\"/\">\n              <a className=\"text-black\">Shop</a>\n            </Link>\n            ,\n            <a\n              href=\"https://github.com/chec/commercejs-chopchop-demo\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"text-black\"\n            >\n              About\n            </a>\n            ,\n            <a\n              href=\"https://twitter.com/commercejs\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"text-black\"\n            >\n              Contact\n            </a>\n            ,\n            <Link href=\"/\">\n              <a className=\"text-black\">Legal</a>\n            </Link>\n            ,\n            <Link href=\"/\">\n              <a className=\"text-black\">Privacy</a>\n            </Link>\n            ,<span>&copy; 2021</span>\n            <p className=\"hidden md:block italic font-serif\">\n              Fine tools for thoughtful cooks\n            </p>\n          </div>\n        </div>\n      </div>\n    </footer>\n  );\n}\n\nexport default Footer;\n"
  },
  {
    "path": "components/Form/FormCheckbox.js",
    "content": "import { useFormContext } from \"react-hook-form\";\n\nfunction FormCheckbox({\n  label,\n  children,\n  name,\n  required = false,\n  validation = {},\n\n  ...props\n}) {\n  const { register } = useFormContext();\n\n  const isRequired = required\n    ? typeof required === \"boolean\"\n      ? `${label || name} is required`\n      : required\n    : false;\n\n  return (\n    <div className=\"py-1 md:py-2\">\n      <label\n        htmlFor={props.id || name}\n        className=\"flex items-center cursor-pointer w-full\"\n      >\n        <input\n          ref={register({ required: isRequired, ...validation })}\n          id={props.id || name}\n          name={name}\n          type=\"checkbox\"\n          className=\"appearance-none bg-transparent checked:bg-black border border-faded-black checked:border-black hover:border-black focus:border-black focus:checked:outline-none focus:outline-none text-black rounded w-5 h-5 cursor-pointer\"\n          {...props}\n        />\n\n        {(children || label) && (\n          <span className=\"ml-2\">{children || label}</span>\n        )}\n      </label>\n    </div>\n  );\n}\n\nexport default FormCheckbox;\n"
  },
  {
    "path": "components/Form/FormError.js",
    "content": "import cc from \"classcat\";\nimport { ErrorMessage } from \"@hookform/error-message\";\n\nfunction FormError({ className, ...props }) {\n  return (\n    <div className=\"pt-1\">\n      <ErrorMessage\n        {...props}\n        render={({ message }) => (\n          <span className={cc([\"text-red-500 text-sm\", className])} {...props}>\n            {message}\n          </span>\n        )}\n      />\n    </div>\n  );\n}\n\nexport default FormError;\n"
  },
  {
    "path": "components/Form/FormInput.js",
    "content": "import { useFormContext } from \"react-hook-form\";\n\nimport FormError from \"./FormError\";\n\nfunction FormInput({\n  label,\n  name,\n  type = \"text\",\n  required = false,\n  validation = {},\n  ...props\n}) {\n  const { register } = useFormContext();\n\n  const isRequired = required ? `${label || name} is required` : false;\n\n  return (\n    <div className=\"py-2\">\n      <input\n        ref={register({ required: isRequired, ...validation })}\n        id={name}\n        name={name}\n        type={type}\n        className=\"appearance-none bg-transparent placeholder-faded-black border border-faded-black focus:border-black focus:outline-none rounded-md w-full text-base px-1.5 py-1\"\n        {...props}\n      />\n      <FormError name={name} />\n    </div>\n  );\n}\n\nexport default FormInput;\n"
  },
  {
    "path": "components/Form/FormSelect.js",
    "content": "import { useFormContext } from \"react-hook-form\";\n\nimport Chevron from \"../../svg/chevron.svg\";\n\nimport FormError from \"./FormError\";\n\nfunction FormSelect({\n  label,\n  name,\n  options,\n  required = false,\n  validation = {},\n  placeholder,\n  ...props\n}) {\n  const { register } = useFormContext();\n\n  const isRequired = required ? `${label || name} is required` : false;\n\n  return (\n    <div className=\"py-2\">\n      <div className=\"relative overflow-hidden border border-faded-black focus:border-black focus:outline-none rounded-md w-full\">\n        <select\n          ref={register({ required: isRequired, ...validation })}\n          id={name}\n          name={name}\n          className=\"appearance-none bg-transparent w-full py-1 pr-6 pl-1.5 text-base placeholder-faded-black focus:outline-none\"\n          defaultValue=\"\"\n          {...props}\n        >\n          <option disabled value=\"\">\n            {placeholder || `Select a ${label}`}\n          </option>\n\n          {options.map(({ value, label }) => (\n            <option key={value} value={value}>\n              {label || value}\n            </option>\n          ))}\n        </select>\n\n        <div className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-black\">\n          <Chevron />\n        </div>\n      </div>\n\n      <FormError name={name} />\n    </div>\n  );\n}\n\nexport default FormSelect;\n"
  },
  {
    "path": "components/Form/FormTextarea.js",
    "content": "import { useFormContext } from \"react-hook-form\";\n\nimport FormError from \"./FormError\";\n\nfunction FormTextarea({\n  label,\n  name,\n  required = false,\n  validation = {},\n  ...props\n}) {\n  const { register } = useFormContext();\n\n  const isRequired = required ? `${label || name} is required` : false;\n\n  return (\n    <div className=\"py-2\">\n      <textarea\n        ref={register({ required: isRequired, ...validation })}\n        id={name}\n        name={name}\n        className=\"appearance-none bg-transparent placeholder-faded-black border border-faded-black focus:border-black focus:outline-none rounded-md w-full text-base px-1.5 py-1\"\n        {...props}\n      />\n      <FormError name={name} />\n    </div>\n  );\n}\n\nexport default FormTextarea;\n"
  },
  {
    "path": "components/Form/index.js",
    "content": "export { default as FormInput } from \"./FormInput\";\nexport { default as FormTextarea } from \"./FormTextarea\";\nexport { default as FormCheckbox } from \"./FormCheckbox\";\nexport { default as FormSelect } from \"./FormSelect\";\nexport { default as FormError } from \"./FormError\";\n"
  },
  {
    "path": "components/Header.js",
    "content": "import Link from \"next/link\";\n\nimport CartSummary from \"./CartSummary\";\n\nimport LogoSVG from \"../svg/logo.svg\";\n\nfunction Header() {\n  return (\n    <header className=\"md:absolute md:left-0 md:top-0 w-full z-10\">\n      <div className=\"py-3 lg:py-5 flex items-center\">\n        <Link href=\"/\">\n          <a title=\"Return to ChopChop\">Shop</a>\n        </Link>\n        <span className=\"pr-1\">,</span>\n        <CartSummary />\n      </div>\n\n      <Link href=\"/\">\n        <a title=\"Return to ChopChop\">\n          <LogoSVG className=\"w-full\" />\n        </a>\n      </Link>\n    </header>\n  );\n}\n\nexport default Header;\n"
  },
  {
    "path": "components/Layout.js",
    "content": "import Footer from \"./Footer\";\n\nfunction Layout({ children }) {\n  return (\n    <>\n      <div className=\"shadow-md\">\n        <div className=\"md:relative container mx-auto px-3\">{children}</div>\n      </div>\n      <Footer />\n    </>\n  );\n}\n\nexport default Layout;\n"
  },
  {
    "path": "components/Modal.js",
    "content": "import { useEffect } from \"react\";\nimport { useRouter } from \"next/router\";\nimport { AnimatePresence, motion } from \"framer-motion\";\n\nimport { useModalState, useModalDispatch } from \"../context/modal\";\nimport { useCheckoutDispatch } from \"../context/checkout\";\nimport { useCartState } from \"../context/cart\";\n\nimport Breadcrumbs from \"./Breadcrumbs\";\nimport Cart from \"./Cart\";\nimport Checkout from \"./Checkout\";\n\nfunction CurrentStep({ step }) {\n  const { id } = useCartState();\n\n  switch (step) {\n    case \"cart\":\n      return <Cart />;\n    case \"checkout\":\n      return <Checkout cartId={id} />;\n    default:\n      return null;\n  }\n}\n\nfunction Modal() {\n  const { open, step } = useModalState();\n  const { closeModal } = useModalDispatch();\n  const { reset: resetCheckout } = useCheckoutDispatch();\n  const router = useRouter();\n\n  useEffect(() => {\n    router.events.on(\"routeChangeStart\", closeModal);\n\n    return () => {\n      router.events.off(\"routeChangeStart\", closeModal);\n    };\n  }, []);\n\n  const closeAndResetModal = () => {\n    closeModal();\n    resetCheckout();\n  };\n\n  return (\n    <AnimatePresence>\n      {open && (\n        <motion.div\n          className=\"bg-ecru-white z-50 fixed overflow-scroll inset-0\"\n          initial={{ opacity: 0, y: -50 }}\n          animate={{\n            opacity: 1,\n            y: 0,\n          }}\n          exit={{ opacity: 0, y: -50 }}\n        >\n          <div className=\"h-full container mx-auto px-3 md:px-4 lg:px-5 flex flex-col justify-between\">\n            <div>\n              <div className=\"py-3 md:py-4 lg:py-5 flex items-center justify-between\">\n                <Breadcrumbs inCart={step === \"cart\"} />\n\n                <button\n                  className=\"appearance-none leading-none text-black p-1 -mr-1 focus:outline-none\"\n                  onClick={closeAndResetModal}\n                >\n                  Close\n                </button>\n              </div>\n            </div>\n            <CurrentStep step={step} />\n          </div>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n\nexport default Modal;\n"
  },
  {
    "path": "components/Product.js",
    "content": "import Image from \"next/image\";\nimport Link from \"next/link\";\nimport cc from \"classcat\";\n\nfunction Product({ media, name, permalink, price, className }) {\n  const imageClass = cc([\n    \"relative rounded-lg hover:rounded-none overflow-hidden w-full transition-all\",\n    className,\n  ]);\n\n  return (\n    <Link href={`/products/${permalink}`}>\n      <a className=\"group relative\">\n        {media?.source && (\n          <div className={imageClass}>\n            <Image\n              src={media.source}\n              alt={Product.name}\n              layout=\"fill\"\n              sizes=\"616px, (min-width: 768px): 352px, (min-width: 1024px): 232px, (min-width: 1280px): 288px\"\n              className=\"object-cover\"\n              priority={true}\n            />\n          </div>\n        )}\n        <div className=\"flex justify-between py-2 md:py-3 space-x-1\">\n          <span className=\"text-sm md:text-base lg:text-lg\">{name}</span>\n          <span className=\"text-sm md:text-base lg:text-lg\">\n            {price.formatted_with_symbol}\n          </span>\n        </div>\n      </a>\n    </Link>\n  );\n}\n\nexport default Product;\n"
  },
  {
    "path": "components/ProductAttributes.js",
    "content": "function ProductAttributes({ attributes = [] }) {\n  if (!attributes || attributes.length === 0) return null;\n\n  return (\n    <div className=\"py-4 hidden md:grid grid-cols-1 md:grid-cols-2 gap-4\">\n      {attributes.map((fileName) => (\n        <div\n          key={fileName}\n          className=\"w-full h-24 flex items-center justify-center\"\n        >\n          <img\n            src={`/product-attributes/${fileName}`}\n            className=\"inline-block\"\n          />\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport default ProductAttributes;\n"
  },
  {
    "path": "components/ProductGrid.js",
    "content": "import Product from \"./Product\";\n\nfunction ProductGrid({ products, ...props }) {\n  if (!products || products.length === 0) return null;\n\n  return (\n    <div className=\"w-full grid lg:grid-cols-2 gap-4 xl:gap-8\">\n      {products.map((product) => (\n        <Product key={product.id} {...product} {...props} />\n      ))}\n    </div>\n  );\n}\n\nexport default ProductGrid;\n"
  },
  {
    "path": "components/ProductImages.js",
    "content": "import Image from \"next/image\";\n\nfunction ProductImages({ images = [] }) {\n  if (!images || images.length === 0) return null;\n\n  return images.map(({ id, url, image_dimensions }) => (\n    <div key={id} className=\"md:py-3\">\n      <Image\n        key={id}\n        src={url}\n        width={image_dimensions.width}\n        height={image_dimensions.height}\n        className=\"rounded-lg hover:rounded-none transition-all\"\n        quality={100}\n        alt=\"\"\n      />\n    </div>\n  ));\n}\n\nexport default ProductImages;\n"
  },
  {
    "path": "components/ProductList.js",
    "content": "import Link from \"next/link\";\n\nfunction ProductList({ products }) {\n  if (!products || products.length === 0) return null;\n\n  return products.map(({ name, permalink }, index) => (\n    <span key={permalink}>\n      {index ? \", \" : \"\"}\n      <Link href={`/products/${permalink}`}>\n        <a className=\"text-lg md:text-xl lg:text-2xl hover:italic\">{name}</a>\n      </Link>\n    </span>\n  ));\n}\n\nexport default ProductList;\n"
  },
  {
    "path": "components/RelatedProducts.js",
    "content": "import Product from \"./Product\";\n\nfunction RelatedProducts({ products }) {\n  if (!products || products.length === 0) return null;\n\n  return (\n    <div>\n      <h3 className=\"w-1/3 md:w-full leading-tight md:leading-normal font-serif text-xl md:text-3xl\">\n        Some other things you might like\n      </h3>\n\n      <div className=\"w-full grid grid-cols-2 xl:grid-cols-4 gap-4 md:gap-8 pt-4 md:pt-8\">\n        {products.map((product) => (\n          <Product\n            key={product.id}\n            {...product}\n            className=\"h-72 md:h-96 lg:h-112\"\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nexport default RelatedProducts;\n"
  },
  {
    "path": "components/VariantPicker.js",
    "content": "import React from \"react\";\n\nimport Chevron from \"../svg/chevron.svg\";\n\nfunction VariantPicker({ variantGroups = [], defaultValues = {}, ...props }) {\n  if (!variantGroups || variantGroups.length === 0) return null;\n\n  return (\n    <div className=\"space-x-2 md:flex\">\n      {variantGroups.map(({ options, ...group }) => (\n        <div\n          key={group.id}\n          className=\"rounded border border-black relative w-32 overflow-hidden\"\n        >\n          <label htmlFor={group.id} className=\"sr-only\">\n            {group.name}:\n          </label>\n\n          <select\n            id={group.id}\n            defaultValue={defaultValues[group.id]}\n            className=\"appearance-none leading-none block w-full py-1 pr-6 pl-2\"\n            {...props}\n          >\n            {options.map((option) => (\n              <option key={option.id} value={option.id}>\n                {option.name}\n              </option>\n            ))}\n          </select>\n\n          <div className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-black\">\n            <Chevron />\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport default VariantPicker;\n"
  },
  {
    "path": "context/cart.js",
    "content": "import { createContext, useReducer, useEffect, useContext } from \"react\";\nimport { useCycle } from \"framer-motion\";\n\nimport { commerce } from \"../lib/commerce\";\n\nconst CartStateContext = createContext();\nconst CartDispatchContext = createContext();\n\nconst SET_CART = \"SET_CART\";\nconst RESET = \"RESET\";\n\nconst initialState = {\n  total_items: 0,\n  total_unique_items: 0,\n  line_items: [],\n};\n\nconst reducer = (state, action) => {\n  switch (action.type) {\n    case SET_CART:\n      return { ...state, ...action.payload };\n    case RESET:\n      return initialState;\n    default:\n      throw new Error(`Unknown action: ${action.type}`);\n  }\n};\n\nexport const CartProvider = ({ children }) => {\n  const [open, toggle] = useCycle(false, true);\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  useEffect(() => {\n    getCart();\n  }, []);\n\n  const getCart = async () => {\n    try {\n      const cart = await commerce.cart.retrieve();\n\n      dispatch({ type: SET_CART, payload: cart });\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const setCart = async (payload) => dispatch({ type: SET_CART, payload });\n\n  const showCart = () => {\n    toggle();\n    document.body.classList.add(\"overflow-hidden\");\n  };\n\n  const closeCart = () => {\n    toggle();\n    document.body.classList.remove(\"overflow-hidden\");\n  };\n\n  const reset = async () => dispatch({ type: RESET });\n\n  return (\n    <CartDispatchContext.Provider\n      value={{ setCart, showCart, closeCart, reset }}\n    >\n      <CartStateContext.Provider value={{ open, ...state }}>\n        {children}\n      </CartStateContext.Provider>\n    </CartDispatchContext.Provider>\n  );\n};\n\nexport const useCartState = () => useContext(CartStateContext);\nexport const useCartDispatch = () => useContext(CartDispatchContext);\n"
  },
  {
    "path": "context/checkout.js",
    "content": "import { createContext, useReducer, useContext } from \"react\";\n\nimport { commerce } from \"../lib/commerce\";\n\nconst CheckoutStateContext = createContext();\nconst CheckoutDispatchContext = createContext();\n\nconst SET_CURRENT_STEP = \"SET_CURRENT_STEP\";\nconst SET_CHECKOUT = \"SET_CHECKOUT\";\nconst SET_LIVE = \"SET_LIVE\";\nconst SET_PROCESSING = \"SET_PROCESSING\";\nconst SET_ERROR = \"SET_ERROR\";\nconst RESET = \"RESET\";\n\nconst initialState = {\n  currentStep: \"extrafields\",\n  processing: false,\n  error: null,\n};\n\nconst reducer = (state, action) => {\n  switch (action.type) {\n    case SET_CURRENT_STEP:\n      return {\n        ...state,\n        currentStep: action.payload,\n      };\n    case SET_CHECKOUT:\n      return {\n        ...state,\n        ...action.payload,\n      };\n    case SET_LIVE:\n      return { ...state, live: { ...state.live, ...action.payload } };\n    case SET_PROCESSING:\n      return { ...state, processing: action.payload };\n    case SET_ERROR:\n      return { ...state, error: action.payload };\n    case RESET:\n      return initialState;\n    default:\n      throw new Error(`Unknown action: ${action.type}`);\n  }\n};\n\nexport const CheckoutProvider = ({ children }) => {\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  const generateToken = async (cartId) => {\n    if (!cartId) return;\n\n    try {\n      const payload = await commerce.checkout.generateToken(cartId, {\n        type: \"cart\",\n      });\n\n      dispatch({ type: SET_CHECKOUT, payload });\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const setShippingMethod = async (shipping_option_id, country, region) => {\n    try {\n      const { live } = await commerce.checkout.checkShippingOption(state.id, {\n        shipping_option_id,\n        country,\n        ...(region && { region }),\n      });\n\n      dispatch({ type: SET_LIVE, payload: live });\n    } catch (err) {\n      // noop\n    }\n  };\n\n  const setCurrentStep = (step) =>\n    dispatch({ type: SET_CURRENT_STEP, payload: step });\n\n  const nextStepFrom = (currentStep) => {\n    switch (currentStep) {\n      case \"extrafields\":\n        return state.collects.shipping_address ? \"shipping\" : \"billing\";\n      case \"shipping\":\n      default:\n        return \"billing\";\n    }\n  };\n\n  const capture = (values) => commerce.checkout.capture(state.id, values);\n\n  const setProcessing = (payload) =>\n    dispatch({ type: SET_PROCESSING, payload });\n\n  const setError = (payload) => dispatch({ type: SET_ERROR, payload });\n\n  const reset = () => dispatch({ type: RESET });\n\n  return (\n    <CheckoutDispatchContext.Provider\n      value={{\n        generateToken,\n        setShippingMethod,\n        setCurrentStep,\n        nextStepFrom,\n        capture,\n        setProcessing,\n        setError,\n        reset,\n      }}\n    >\n      <CheckoutStateContext.Provider value={state}>\n        {children}\n      </CheckoutStateContext.Provider>\n    </CheckoutDispatchContext.Provider>\n  );\n};\n\nexport const useCheckoutState = () => useContext(CheckoutStateContext);\nexport const useCheckoutDispatch = () => useContext(CheckoutDispatchContext);\n"
  },
  {
    "path": "context/modal.js",
    "content": "import { createContext, useReducer, useContext } from \"react\";\nimport { useCycle } from \"framer-motion\";\n\nconst ModalStateContext = createContext();\nconst ModalDispatchContext = createContext();\n\nconst SHOW_CART = \"SHOW_CART\";\nconst SHOW_CHECKOUT = \"SHOW_CHECKOUT\";\n\nconst initialState = {\n  step: \"cart\",\n};\n\nconst reducer = (state, action) => {\n  switch (action.type) {\n    case SHOW_CART:\n      return { ...state, step: \"cart\" };\n    case SHOW_CHECKOUT:\n      return { ...state, step: \"checkout\" };\n    default:\n      throw new Error(`Unknown action: ${action.type}`);\n  }\n};\n\nexport const ModalProvider = ({ children }) => {\n  const [open, toggle] = useCycle(false, true);\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  const openModal = () => {\n    toggle();\n    document.body.classList.add(\"overflow-hidden\");\n  };\n\n  const closeModal = () => {\n    toggle(0);\n    document.body.classList.remove(\"overflow-hidden\");\n    dispatch({ type: \"SHOW_CART\" });\n  };\n\n  const showCart = () => dispatch({ type: \"SHOW_CART\" });\n\n  const showCheckout = () => dispatch({ type: \"SHOW_CHECKOUT\" });\n\n  return (\n    <ModalDispatchContext.Provider\n      value={{ openModal, closeModal, showCart, showCheckout }}\n    >\n      <ModalStateContext.Provider value={{ open, ...state }}>\n        {children}\n      </ModalStateContext.Provider>\n    </ModalDispatchContext.Provider>\n  );\n};\n\nexport const useModalState = () => useContext(ModalStateContext);\nexport const useModalDispatch = () => useContext(ModalDispatchContext);\n"
  },
  {
    "path": "context/theme.js",
    "content": "import * as React from \"react\";\n\nconst ThemeStateContext = React.createContext();\nconst ThemeDispatchContext = React.createContext();\n\nconst initialState = null;\n\nexport const ThemeProvider = ({ children }) => {\n  const [theme, setTheme] = React.useState(initialState);\n\n  return (\n    <ThemeDispatchContext.Provider value={setTheme}>\n      <ThemeStateContext.Provider value={theme}>\n        {children}\n      </ThemeStateContext.Provider>\n    </ThemeDispatchContext.Provider>\n  );\n};\n\nexport const useThemeState = () => React.useContext(ThemeStateContext);\nexport const useThemeDispatch = () => React.useContext(ThemeDispatchContext);\n"
  },
  {
    "path": "lib/commerce.js",
    "content": "import CommerceSDK from \"@chec/commerce.js\";\n\nconst checAPIKey = process.env.NEXT_PUBLIC_CHEC_PUBLIC_API_KEY;\nconst devEnvironment = process.env.NODE_ENV === 'development';\n\n// Commerce.js constructor options\nconst commerceConfig = {\n  axiosConfig: {\n    headers: {\n      'X-Chec-Agent': 'commerce.js/v2',\n      'Chec-Version': '2021-03-10',\n    },\n  },\n};\n\nif (devEnvironment && !checAPIKey) {\n  throw Error('Your public API key must be provided as an environment variable named `NEXT_PUBLIC_CHEC_PUBLIC_API_KEY`. Obtain your Chec public key by logging into your Chec account and navigate to Setup > Developer, or can be obtained with the Chec CLI via with the command chec whoami');\n}\n\nexport const commerce = new CommerceSDK(\n  checAPIKey,\n  devEnvironment,\n  commerceConfig,\n);\n"
  },
  {
    "path": "lib/gtag.js",
    "content": "export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID;\n\nexport const pageview = (url) => {\n  if (!GA_TRACKING_ID) return;\n\n  window.gtag(\"config\", GA_TRACKING_ID, {\n    page_path: url,\n  });\n};\n\nexport const event = ({ action, category, label, value }) => {\n  if (!GA_TRACKING_ID) return;\n\n  window.gtag(\"event\", action, {\n    event_category: category,\n    event_label: label,\n    value: value,\n  });\n};\n"
  },
  {
    "path": "next.config.js",
    "content": "module.exports = {\n  i18n: {\n    locales: ['en-US'],\n    defaultLocale: 'en-US',\n  },\n  images: {\n    domains: [\"cdn.chec.io\"],\n  },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"name\": \"chopchop\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"next\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"seed\": \"chec-seed seeds\"\n  },\n  \"dependencies\": {\n    \"@chec/commerce.js\": \"2.2.0\",\n    \"@hookform/error-message\": \"0.0.5\",\n    \"@stripe/react-stripe-js\": \"1.1.2\",\n    \"@stripe/stripe-js\": \"1.11.0\",\n    \"autoprefixer\": \"10.0.4\",\n    \"classcat\": \"4.1.0\",\n    \"framer-motion\": \"2.9.4\",\n    \"next\": \"10.0.2\",\n    \"next-google-fonts\": \"1.2.1\",\n    \"postcss\": \"8.1.14\",\n    \"react\": \"17.0.1\",\n    \"react-dom\": \"17.0.1\",\n    \"react-hook-form\": \"6.11.5\",\n    \"react-toastify\": \"6.1.0\",\n    \"use-debounce\": \"^7.0.0\"\n  },\n  \"devDependencies\": {\n    \"@chec/seeder\": \"^1.1.0\",\n    \"babel-plugin-inline-react-svg\": \"1.1.2\",\n    \"tailwindcss\": \"2.0.1\"\n  }\n}\n"
  },
  {
    "path": "pages/_app.js",
    "content": "import \"react-toastify/dist/ReactToastify.css\";\nimport \"tailwindcss/tailwind.css\";\n\nimport { useEffect } from \"react\";\nimport { AnimatePresence } from \"framer-motion\";\nimport { Elements } from \"@stripe/react-stripe-js\";\nimport { loadStripe } from \"@stripe/stripe-js\";\nimport { ToastContainer } from \"react-toastify\";\n\nimport * as gtag from \"../lib/gtag\";\n\nimport { ThemeProvider } from \"../context/theme\";\nimport { ModalProvider } from \"../context/modal\";\nimport { CartProvider } from \"../context/cart\";\nimport { CheckoutProvider } from \"../context/checkout\";\n\nimport Layout from \"../components/Layout\";\nimport Modal from \"../components/Modal\";\n\nconst stripePromise = loadStripe(\n  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY\n);\n\nconst toastOptions = {\n  position: \"bottom-center\",\n  draggable: false,\n  hideProgressBar: true,\n  className: \"w-full md:max-w-xl\",\n  toastClassName: \"bg-ecru-white rounded-lg text-black px-3 shadow-md\",\n};\n\nfunction MyApp({ Component, pageProps, router }) {\n  useEffect(() => {\n    const handleRouteChange = (url) => {\n      gtag.pageview(url);\n    };\n\n    router.events.on(\"routeChangeComplete\", handleRouteChange);\n\n    return () => {\n      router.events.off(\"routeChangeComplete\", handleRouteChange);\n    };\n  }, [router.events]);\n\n  return (\n    <>\n      <Elements\n        stripe={stripePromise}\n        options={{\n          fonts: [\n            {\n              cssSrc:\n                \"https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap\",\n            },\n          ],\n        }}\n      >\n        <ThemeProvider>\n          <ModalProvider>\n            <CartProvider>\n              <CheckoutProvider>\n                <Modal />\n                <Layout>\n                  <AnimatePresence initial={false} exitBeforeEnter>\n                    <Component {...pageProps} key={router.route} />\n                  </AnimatePresence>\n                  <ToastContainer {...toastOptions} />\n                </Layout>\n              </CheckoutProvider>\n            </CartProvider>\n          </ModalProvider>\n        </ThemeProvider>\n      </Elements>\n    </>\n  );\n}\n\nexport default MyApp;\n"
  },
  {
    "path": "pages/_document.js",
    "content": "import Document, { Html, Head, Main, NextScript } from \"next/document\";\nimport GoogleFonts from \"next-google-fonts\";\n\nimport { GA_TRACKING_ID } from \"../lib/gtag\";\n\nclass MyDocument extends Document {\n  render() {\n    return (\n      <Html>\n        <Head>\n          <meta name=\"title\" content=\"Headless Commerce example with Vercel\" key=\"title\" />\n          <meta name=\"description\" content=\"An open source headless commerce example powered by Commerce.js and Vercel. Start your headless commerce application now!\" />\n          <GoogleFonts href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap\" />\n          <GoogleFonts href=\"https://fonts.googleapis.com/css2?family=EB+Garamond:wght@500&display=swap\" />\n          {GA_TRACKING_ID && (\n            <>\n              <script\n                async\n                src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}\n              />\n              <script\n                dangerouslySetInnerHTML={{\n                  __html: `\n            window.dataLayer = window.dataLayer || [];\n            function gtag(){dataLayer.push(arguments);}\n            gtag('js', new Date());\n            gtag('config', '${GA_TRACKING_ID}', {\n              page_path: window.location.pathname,\n            });\n          `,\n                }}\n              />\n            </>\n          )}\n        </Head>\n        <body className=\"antialiased\">\n          <Main />\n          <NextScript />\n        </body>\n      </Html>\n    );\n  }\n}\n\nexport default MyDocument;\n"
  },
  {
    "path": "pages/index.js",
    "content": "import Head from \"next/head\";\nimport { motion } from \"framer-motion\";\n\nimport { commerce } from \"../lib/commerce\";\n\nimport Header from \"../components/Header\";\nimport ProductList from \"../components/ProductList\";\nimport ProductGrid from \"../components/ProductGrid\";\n\nexport async function getStaticProps() {\n  const { data } = await commerce.products.list();\n\n  const products = data.filter(({ active }) => active);\n\n  return {\n    props: {\n      products,\n    },\n    revalidate: 60,\n  };\n}\n\nfunction IndexPage({ products }) {\n  return (\n    <>\n      <Head>\n        <title>ChopChop</title>\n      </Head>\n      <div className=\"md:min-h-screen md:flex md:items-center\">\n        <div className=\"flex flex-col md:flex-row space-y-3 md:space-y-0 md:space-x-10\">\n          <div className=\"md:max-h-screen md:w-1/2 flex items-end justify-between md:sticky md:top-0\">\n            <Header />\n\n            <motion.div\n              className=\"md:py-12 hidden md:block md:sticky md:top-0\"\n              initial={{ opacity: 0, y: 50 }}\n              animate={{\n                opacity: 1,\n                y: 0,\n                transition: {\n                  delay: 0.25,\n                },\n              }}\n              exit={{ opacity: 0, y: -50 }}\n            >\n              <h1 className=\"font-serif italic text-xl md:text-3xl\">Shop:</h1>\n\n              <div className=\"pt-3\">\n                <ProductList products={products} />\n              </div>\n            </motion.div>\n          </div>\n\n          <motion.div\n            className=\"md:min-h-screen py-6 md:py-12 flex items-center md:w-1/2 md:z-40\"\n            initial={{ opacity: 0, y: 50 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: -50 }}\n          >\n            <ProductGrid\n              products={products}\n              className=\"h-112 md:h-96 xl:h-112\"\n            />\n          </motion.div>\n        </div>\n      </div>\n    </>\n  );\n}\n\nexport default IndexPage;\n"
  },
  {
    "path": "pages/products/[permalink].js",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { motion } from \"framer-motion\";\nimport { toast } from \"react-toastify\";\n\nimport { commerce } from \"../../lib/commerce\";\nimport { useCartDispatch } from \"../../context/cart\";\nimport { useThemeDispatch } from \"../../context/theme\";\nimport { useModalDispatch } from \"../../context/modal\";\n\nimport Header from \"../../components/Header\";\nimport Button from \"../../components/Button\";\nimport VariantPicker from \"../../components/VariantPicker\";\nimport ProductImages from \"../../components/ProductImages\";\nimport ProductAttributes from \"../../components/ProductAttributes\";\nimport RelatedProducts from \"../../components/RelatedProducts\";\n\nexport async function getStaticProps({ params }) {\n  const { permalink } = params;\n\n  const product = await commerce.products.retrieve(permalink, {\n    type: \"permalink\",\n  });\n\n  return {\n    props: {\n      product,\n    },\n    revalidate: 60,\n  };\n}\n\nexport async function getStaticPaths() {\n  const { data: products } = await commerce.products.list();\n\n  return {\n    paths: products.map(({ permalink }) => ({\n      params: {\n        permalink,\n      },\n    })),\n    fallback: false,\n  };\n}\n\nfunction ProductPage({ product }) {\n  const { setCart } = useCartDispatch();\n  const {\n    variant_groups: variantGroups,\n    assets,\n    meta,\n    related_products: relatedProducts,\n  } = product;\n  const images = assets.filter(({ is_image }) => is_image);\n  const setTheme = useThemeDispatch();\n  const { openModal } = useModalDispatch();\n\n  const initialVariants = React.useMemo(\n    () =>\n      variantGroups.reduce((all, { id, options }) => {\n        const [firstOption] = options;\n\n        return { ...all, [id]: firstOption.id };\n      }, {}),\n    [product.permalink]\n  );\n\n  const [selectedVariants, setSelectedVariants] = React.useState(\n    initialVariants\n  );\n\n  React.useEffect(() => {\n    setSelectedVariants(initialVariants);\n    setTheme(product.permalink);\n\n    return () => setTheme(\"default\");\n  }, [product.permalink]);\n\n  const handleVariantChange = ({ target: { id, value } }) =>\n    setSelectedVariants({\n      ...selectedVariants,\n      [id]: value,\n    });\n\n  const addToCart = () =>\n    commerce.cart\n      .add(product.id, 1, selectedVariants)\n      .then(({ cart }) => {\n        setCart(cart);\n\n        return cart;\n      })\n      .then(({ subtotal }) =>\n        toast(\n          `${product.name} is now in your cart. Your subtotal is now ${subtotal.formatted_with_symbol}. Click to view what's in your cart.`,\n          {\n            onClick: openModal,\n          }\n        )\n      )\n      .catch(() => {\n        toast.error(\"Please try again.\");\n      });\n\n  return (\n    <React.Fragment>\n      <Head>\n        <title>{product.seo.title}</title>\n        <meta name=\"description\" content={product.seo.description}></meta>\n      </Head>\n\n      <div className=\"md:hidden\">\n        <Header />\n      </div>\n\n      <div className=\"md:min-h-screen md:flex md:items-center\">\n        <div className=\"flex flex-col-reverse md:flex-row space-y-3 md:space-y-0 md:space-x-10\">\n          <div className=\"md:max-h-screen md:w-1/2 flex flex-col md:flex-row items-end justify-between md:sticky md:top-0\">\n            <div className=\"hidden md:block\">\n              <Header />\n            </div>\n            <motion.div\n              className=\"py-6 md:py-12 sticky top-0\"\n              initial={{ opacity: 0, y: 50 }}\n              animate={{\n                opacity: 1,\n                y: 0,\n                transition: {\n                  delay: 0.25,\n                },\n              }}\n              exit={{ opacity: 0, y: -50 }}\n            >\n              <h1 className=\"font-serif font-medium italic text-2xl md:text-4xl lg:text-5xl\">\n                {product.name}\n              </h1>\n\n              <div className=\"flex items-center justify-between pt-3\">\n                <div className=\"flex items-center\">\n                  <div className=\"pr-2\">\n                    <p className=\"text-lg md:text-xl lg:text-2xl font-sans\">\n                      {product.price.formatted_with_symbol}\n                    </p>\n                  </div>\n\n                  <VariantPicker\n                    variantGroups={variantGroups}\n                    defaultValues={initialVariants}\n                    onChange={handleVariantChange}\n                  />\n                </div>\n\n                <Button onClick={addToCart}>Add to Bag</Button>\n              </div>\n\n              <div\n                className=\"pt-5 md:pt-8 lg:pt-10 md:leading-relaxed lg:leading-loose lg:text-lg\"\n                dangerouslySetInnerHTML={{ __html: product.description }}\n              />\n            </motion.div>\n          </div>\n\n          <div className=\"md:min-h-screen md:py-12 flex items-center md:w-1/2 md:z-40\">\n            <motion.div\n              initial={{ opacity: 0, y: 50 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0, y: -50 }}\n            >\n              <ProductImages images={images} />\n              <ProductAttributes {...meta} />\n            </motion.div>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"py-3 md:py-4 lg:py-8\">\n        <RelatedProducts products={relatedProducts} />\n      </div>\n    </React.Fragment>\n  );\n}\n\nexport default ProductPage;\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "seeds/assets.json",
    "content": "[\n  {\n    \"link\": \"products[5].id\",\n    \"filename\": \"ChopChop_Tote.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/X1HxOcELkPfQBY22_ChopChop_Tote.jpg\"\n  },\n  {\n    \"link\": \"products[4].id\",\n    \"filename\": \"Kitchen-Sink-Journal-1.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/9yI6YD9osPkZqmXC_Kitchen-Sink-Journal-1.jpg\"\n  },\n  {\n    \"link\": \"products[3].id\",\n    \"filename\": \"italic-rqWlTD5GwKc-unsplash-1.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/italic-rqWlTD5GwKc-unsplash%201.jpg\"\n  },\n  {\n    \"link\": \"products[3].id\",\n    \"filename\": \"dutch-oven-recolor-green.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/fhEBFIGPSx4jXhEJ_dutch-oven-recolor-green.jpg\"\n  },\n  {\n    \"link\": \"products[3].id\",\n    \"filename\": \"dutch-oven-recolor-red.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/VvLWyLKtYa17nQEo_dutch-oven-recolor-red.jpg\"\n  },\n  {\n    \"link\": \"products[2].id\",\n    \"filename\": \"italic--wPo52T1z-8-unsplash-1.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/italic--wPo52T1z-8-unsplash%201.jpg\"\n  },\n  {\n    \"link\": \"products[2].id\",\n    \"filename\": \"italic-dGoB5OrHDS0-unsplash-1.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/italic-dGoB5OrHDS0-unsplash%201.jpg\"\n  },\n  {\n    \"link\": \"products[1].id\",\n    \"filename\": \"emy-kOtEYRJspm8-unsplash.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/emy-kOtEYRJspm8-unsplash.jpg\"\n  },\n  {\n    \"link\": \"products[0].id\",\n    \"filename\": \"photo-1594761077961-cadd185540a4-1.jpg\",\n    \"url\": \"https://cdn.chec.io/merchants/19303/assets/photo-1594761077961-cadd185540a4%201.jpg\"\n  }\n]\n"
  },
  {
    "path": "seeds/categories.json",
    "content": "[\n  {\n    \"slug\": \"journal\",\n    \"name\": \"Journal\"\n  },\n  {\n    \"slug\": \"cooking-class\",\n    \"name\": \"Cooking Class\"\n  },\n  {\n    \"slug\": \"cookware\",\n    \"name\": \"Cookware\"\n  }\n]\n"
  },
  {
    "path": "seeds/products.json",
    "content": "[\n  {\n    \"product\": {\n      \"name\": \"Walnut Cook's Tools\",\n      \"description\": \"Carved by our friends at Mason St. Workshop, these cook’s tools are made to last. We went back and forth for months deliberating on woods, handles and the exact tools to include before landing on the current set of five which includes a spatula, three different spoons and a large ladle.\",\n      \"price\": \"40.00\",\n      \"category_id\": \"categories[2].id\"\n    },\n    \"collect\": {\n      \"billing\": true,\n      \"fullname\": true\n    },\n    \"seo\": {\n      \"title\": \"Walnut Cook's Tools | ChopChop\",\n      \"description\": \"Carved by our friends at Mason St. Workshop, these cook’s tools are made to last. A set of five which includes a spatula, three different spoons and a large ladle.\"\n    }\n  },\n  {\n    \"product\": {\n      \"name\": \"Private Cooking Class\",\n      \"description\": \"Learn core skills or advanced techniques in our private cooking classes. Classes run two hours (online or in person at our Brooklyn storefront) and cover a range of recipes, approaches and techniques. Tell us what you want to tackle or learn a classic recipe. Just let us know in the form at checkout.\",\n      \"price\": \"120.00\",\n      \"category_id\": \"categories[1].id\"\n    },\n    \"extra_field\": [\n      {\n        \"name\": \"Lesson Plan\",\n        \"required\": false\n      }\n    ],\n    \"collect\": {\n      \"fullname\": true\n    },\n    \"seo\": {\n      \"title\": \"Private Cooking Class | ChopChop\",\n      \"description\": \"Learn core skills or advanced techniques in our private cooking classes. Classes run two hours (online or in person at our Brooklyn storefront) and cover a range of recipes, approaches and techniques.\"\n    }\n  },\n  {\n    \"product\": {\n      \"name\": \"Essential Knife Set\",\n      \"description\": \"There are a lot of knife sets out there, a lot of them are fine, but they also have a bunch of stuff you probably don’t need. We put together the essential knife set so you can snag exactly what you need to get cooking, no more no less. If you want a slightly different variation just get in touch and let us know, we’re happy to put something custom together for your needs.\",\n      \"price\": \"120.00\",\n      \"category_id\": \"categories[2].id\"\n    },\n    \"collect\": {\n      \"billing\": true,\n      \"fullname\": true\n    },\n    \"seo\": {\n      \"title\": \"Essential Knife Set | ChopChop\",\n      \"description\": \"We put together the essential knife set so you can snag exactly what you need to get cooking, no more no less.\"\n    }\n  },\n  {\n    \"product\": {\n      \"name\": \"Ceramic Dutch Oven\",\n      \"description\": \"A colorful, stovetop multi-tool that will outlive you with even the most minimal amount of care, the ceramic coated, cast iron dutch oven is the Coach duffle of stovetop cooking - gorgeous, functional and the envy of literally every penny pinching home cook alive today.\",\n      \"price\": \"250.00\",\n      \"category_id\": \"categories[2].id\"\n    },\n    \"variant\": [\n      {\n        \"name\": \"Color\",\n        \"options\": [\n          {\n            \"description\": \"Yellow\",\n            \"quantity\": \"0\",\n            \"price\": \"0.00\"\n          },\n          {\n            \"description\": \"Green\",\n            \"price\": \"0.00\"\n          },\n          {\n            \"description\": \"Red\",\n            \"price\": \"0.00\"\n          }\n        ]\n      }\n    ],\n    \"collect\": {\n      \"billing\": true,\n      \"fullname\": true\n    },\n    \"seo\": {\n      \"title\": \"Ceramic Dutch Oven | ChopChop\",\n      \"description\": \"The ceramic coated, cast iron dutch oven is the Coach duffle of stovetop cooking - gorgeous, functional and the envy of literally every penny pinching home cook alive today.\"\n    }\n  },\n  {\n    \"product\": {\n      \"name\": \"Kitchen Sink Journal\",\n      \"description\": \"Kitchen Sink Journal, our first publication, documents a year of culinary experiments by the Chop Chop team and some friends of the shop. While it includes 41 detailed recipes, in practice we use it more as a reference on how to tackle a given flavor, texture or ingredient. Hopefully you’ll find it just as useful!\",\n      \"price\": \"35.00\",\n      \"category_id\": \"categories[0].id\"\n    },\n    \"collect\": {\n      \"billing\": true,\n      \"fullname\": true\n    },\n    \"seo\": {\n      \"title\": \"Kitchen Sink Journal | ChopChop\",\n      \"description\": \"Our first publication documents a year of culinary experiments by the Chop Chop team and some friends of the shop. While it includes 41 detailed recipes, in practice we use it more as a reference on how to tackle a given flavor, texture or ingredient.\"\n    }\n  },\n  {\n    \"product\": {\n      \"name\": \"Tote bag\",\n      \"price\": \"0.00\",\n      \"active\": false\n    }\n  }\n]\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "const defaultTheme = require(\"tailwindcss/defaultTheme\");\n\nmodule.exports = {\n  purge: [\"./{components,pages}/**/*.js\"],\n  theme: {\n    extend: {\n      colors: {\n        clementine: \"#EF7300\",\n        tumbleweed: \"#D9A876\",\n        \"hawkes-blue\": \"#C7DDFD\",\n        asparagus: \"#789750\",\n        goldenrod: \"#FFCE70\",\n        black: \"#150703\",\n        \"faded-black\": \"rgba(21,7,3,0.6)\",\n        \"ecru-white\": \"#FAF8F3\",\n        \"white-rock\": \"#E8E0CF\",\n      },\n      height: {\n        112: \"28rem\",\n      },\n      rotate: {\n        '-25': '-25deg',\n      },\n      boxShadow: {\n        'thank-you': '-2.63365px 5.92572px 8.55938px rgba(0, 0, 0, 0.25)',\n      },\n    },\n    fontFamily: {\n      sans: [\"Inter\", ...defaultTheme.fontFamily.sans],\n      serif: [\"'EB Garamond'\", ...defaultTheme.fontFamily.serif],\n    },\n  },\n  variants: {\n    extend: {\n      backgroundColor: [\"checked\"],\n      borderColor: [\"checked\"],\n      borderRadius: [\"hover\"],\n      fontStyle: [\"hover\"],\n      textColor: [\"checked\"],\n    },\n  },\n};\n"
  }
]