diff --git a/.env.template b/.env.template index 221e7a74e..5ebc092d3 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,6 @@ +# Available providers: bigcommerce, shopify, swell +COMMERCE_PROVIDER=swell + BIGCOMMERCE_STOREFRONT_API_URL= BIGCOMMERCE_STOREFRONT_API_TOKEN= BIGCOMMERCE_STORE_API_URL= @@ -5,8 +8,8 @@ BIGCOMMERCE_STORE_API_TOKEN= BIGCOMMERCE_STORE_API_CLIENT_ID= BIGCOMMERCE_CHANNEL_ID= -SHOPIFY_STORE_DOMAIN= -SHOPIFY_STOREFRONT_ACCESS_TOKEN= +NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= +NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= SWELL_STORE_ID= SWELL_PUBLIC_KEY= \ No newline at end of file diff --git a/README.md b/README.md index cc181b411..2eabbe6c1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Start right now at [nextjs.org/commerce](https://nextjs.org/commerce) Demo live at: [demo.vercel.store](https://demo.vercel.store/) -This project is currently under development. +- Shopify Demo: https://shopify.demo.vercel.store/ +- BigCommerce Demo: https://bigcommerce.demo.vercel.store/ ## Features @@ -21,24 +22,65 @@ This project is currently under development. - Integrations - Integrate seamlessly with the most common ecommerce platforms. - Dark Mode Support -## Work in progress - -We're using Github Projects to keep track of issues in progress and todo's. Here is our [Board](https://github.com/vercel/commerce/projects/1) - ## Integrations Next.js Commerce integrates out-of-the-box with BigCommerce and Shopify. We plan to support all major ecommerce backends. -## Goals +## Considerations -- **Next.js Commerce** should have a completely data **agnostic** UI -- **Aware of schema**: should ship with the right data schemas and types. -- All providers should return the right data types and schemas to blend correctly with Next.js Commerce. -- `@framework` will be the alias utilized in commerce and it will map to the ecommerce provider of preference- e.g BigCommerce, Shopify, Swell. All providers should expose the same standardized functions. _Note that the same applies for recipes using a CMS + an ecommerce provider._ +- `framework/commerce` contains all types, helpers and functions to be used as base to build a new **provider**. +- **Providers** live under `framework`'s root folder and they will extend Next.js Commerce types and functionality (`framework/commerce`). +- We have a **Features API** to ensure feature parity between the UI and the Provider. The UI should update accordingly and no extra code should be bundled. All extra configuration for features will live under `features` in `commerce.config.json` and if needed it can also be accessed programatically. +- Each **provider** should add its corresponding `next.config.js` and `commerce.config.json` adding specific data related to the provider. For example in case of BigCommerce, the images CDN and additional API routes. +- **Providers don't depend on anything that's specific to the application they're used in**. They only depend on `framework/commerce`, on their own framework folder and on some dependencies included in `package.json` -There is a `framework` folder in the root folder that will contain multiple ecommerce providers. +## Configuration -Additionally, we need to ensure feature parity (not all providers have e.g. wishlist) so we also have a feature API to disable/enable features in the UI. +### How to change providers + +Open `.env.local` and change the value of `COMMERCE_PROVIDER` to the provider you would like to use, then set the environment variables for that provider (use `.env.template` as the base). + +### Features + +Every provider defines the features that it supports under `framework/{provider}/commerce.config.json` + +#### How to turn Features on and off + +> NOTE: The selected provider should support the feature that you are toggling. (This means that you can't turn wishlist on if the provider doesn't support this functionality out the box) + +- Open `commerce.config.json` +- You'll see a config file like this: + ```json + { + "features": { + "wishlist": false + } + } + ``` +- Turn wishlist on by setting wishlist to true. +- Run the app and the wishlist functionality should be back on. + +### How to create a new provider + +Follow our docs for [Adding a new Commerce Provider](framework/commerce/new-provider.md). + +If you succeeded building a provider, submit a PR with a valid demo and we'll review it asap. + +## Contribute + +Our commitment to Open Source can be found [here](https://vercel.com/oss). + +1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. +2. Create a new branch `git checkout -b MY_BRANCH_NAME` +3. Install yarn: `npm install -g yarn` +4. Install the dependencies: `yarn` +5. Duplicate `.env.template` and rename it to `.env.local` +6. Add proper store values to `.env.local` +7. Run `yarn dev` to build and watch for code changes + +## Work in progress + +We're using Github Projects to keep track of issues in progress and todo's. Here is our [Board](https://github.com/vercel/commerce/projects/1) People actively working on this project: @okbel & @lfades. diff --git a/components/cart/CartItem/CartItem.tsx b/components/cart/CartItem/CartItem.tsx index cb7f8600e..e6820d32c 100644 --- a/components/cart/CartItem/CartItem.tsx +++ b/components/cart/CartItem/CartItem.tsx @@ -92,15 +92,18 @@ const CartItem = ({ })} {...rest} > -
- {item.variant.image!.altText} +
+ + closeSidebarIfPresent()} + className={s.productImage} + width={150} + height={150} + src={item.variant.image!.url} + alt={item.variant.image!.altText} + unoptimized + /> +
diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index c0fdd32d2..3b09fa39a 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -1,23 +1,20 @@ import cn from 'classnames' import Image from 'next/image' import { NextSeo } from 'next-seo' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import s from './ProductView.module.css' - import { Swatch, ProductSlider } from '@components/product' import { Button, Container, Text, useUI } from '@components/ui' - import type { Product } from '@commerce/types' import usePrice from '@framework/product/use-price' import { useAddItem } from '@framework/cart' - import { getVariant, SelectedOptions } from '../helpers' import WishlistButton from '@components/wishlist/WishlistButton' interface Props { - className?: string children?: any product: Product + className?: string } const ProductView: FC = ({ product }) => { @@ -29,12 +26,18 @@ const ProductView: FC = ({ product }) => { }) const { openSidebar } = useUI() const [loading, setLoading] = useState(false) - const [choices, setChoices] = useState({ - size: null, - color: null, - }) + const [choices, setChoices] = useState({}) + + useEffect(() => { + // Selects the default option + product.variants[0].options?.forEach((v) => { + setChoices((choices) => ({ + ...choices, + [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(), + })) + }) + }, []) - // Select the correct variant based on choices const variant = getVariant(product, choices) const addToCart = async () => { @@ -133,7 +136,7 @@ const ProductView: FC = ({ product }) => { ))}
- +
@@ -143,7 +146,6 @@ const ProductView: FC = ({ product }) => { className={s.button} onClick={addToCart} loading={loading} - disabled={!variant && product.options.length > 0} > Add to Cart diff --git a/components/product/helpers.ts b/components/product/helpers.ts index 029476c92..a0ceb7aa5 100644 --- a/components/product/helpers.ts +++ b/components/product/helpers.ts @@ -1,9 +1,5 @@ import type { Product } from '@commerce/types' - -export type SelectedOptions = { - size: string | null - color: string | null -} +export type SelectedOptions = Record export function getVariant(product: Product, opts: SelectedOptions) { const variant = product.variants.find((variant) => { diff --git a/config/seo.json b/config/seo.json index 0d0c3d37d..82520cf9b 100644 --- a/config/seo.json +++ b/config/seo.json @@ -1,12 +1,22 @@ { "title": "ACME Storefront | Powered by Next.js Commerce", "titleTemplate": "%s - ACME Storefront", - "description": "Next.js Commerce -> https://www.nextjs.org/commerce", + "description": "Next.js Commerce - https://www.nextjs.org/commerce", "openGraph": { + "title": "ACME Storefront | Powered by Next.js Commerce", + "description": "Next.js Commerce - https://www.nextjs.org/commerce", "type": "website", "locale": "en_IE", "url": "https://nextjs.org/commerce", - "site_name": "Next.js Commerce" + "site_name": "Next.js Commerce", + "images": [ + { + "url": "/card.png", + "width": 800, + "height": 600, + "alt": "Next.js Commerce" + } + ] }, "twitter": { "handle": "@nextjs", diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md deleted file mode 100644 index 437766dd8..000000000 --- a/docs/ROADMAP.md +++ /dev/null @@ -1 +0,0 @@ -# Roadmap diff --git a/framework/bigcommerce/.env.template b/framework/bigcommerce/.env.template index 43e85c046..2b91bc095 100644 --- a/framework/bigcommerce/.env.template +++ b/framework/bigcommerce/.env.template @@ -1,3 +1,5 @@ +COMMERCE_PROVIDER=bigcommerce + BIGCOMMERCE_STOREFRONT_API_URL= BIGCOMMERCE_STOREFRONT_API_TOKEN= BIGCOMMERCE_STORE_API_URL= diff --git a/framework/bigcommerce/README.md b/framework/bigcommerce/README.md index 2609b1544..7f62a5f3f 100644 --- a/framework/bigcommerce/README.md +++ b/framework/bigcommerce/README.md @@ -1,45 +1,34 @@ -# Table of Contents +# Bigcommerce Provider -- [BigCommerce Storefront Data Hooks](#bigcommerce-storefront-data-hooks) - - [Installation](#installation) - - [General Usage](#general-usage) - - [CommerceProvider](#commerceprovider) - - [useLogin hook](#uselogin-hook) - - [useLogout](#uselogout) - - [useCustomer](#usecustomer) - - [useSignup](#usesignup) - - [usePrice](#useprice) - - [Cart Hooks](#cart-hooks) - - [useCart](#usecart) - - [useAddItem](#useadditem) - - [useUpdateItem](#useupdateitem) - - [useRemoveItem](#useremoveitem) - - [Wishlist Hooks](#wishlist-hooks) - - [Product Hooks and API](#product-hooks-and-api) - - [useSearch](#usesearch) - - [getAllProducts](#getallproducts) - - [getProduct](#getproduct) - - [More](#more) +**Demo:** https://bigcommerce.demo.vercel.store/ -# BigCommerce Storefront Data Hooks +With the deploy button below you'll be able to have a [BigCommerce](https://www.bigcommerce.com/) account and a store that works with this starter: -> This project is under active development, new features and updates will be continuously added over time +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fcommerce&project-name=commerce&repo-name=commerce&demo-title=Next.js%20Commerce&demo-description=An%20all-in-one%20starter%20kit%20for%20high-performance%20e-commerce%20sites.&demo-url=https%3A%2F%2Fdemo.vercel.store&demo-image=https%3A%2F%2Fbigcommerce-demo-asset-ksvtgfvnd.vercel.app%2Fbigcommerce.png&integration-ids=oac_MuWZiE4jtmQ2ejZQaQ7ncuDT) -UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use BigCommerce as a headless e-commerce platform. The package provides: +If you already have a BigCommerce account and want to use your current store, then copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git): -- Code splitted hooks for data fetching using [SWR](https://swr.vercel.app/), and to handle common user actions -- Code splitted data fetching methods for initial data population and static generation of content -- Helpers to create the API endpoints that connect to the hooks, very well suited for Next.js applications - -## Installation - -To install: - -``` -yarn add storefront-data-hooks +```bash +cp framework/bigcommerce/.env.template .env.local ``` -After install, the first thing you do is: set your environment variables in `.env.local` +Then, set the environment variables in `.env.local` to match the ones from your store. + +## Contribute + +Our commitment to Open Source can be found [here](https://vercel.com/oss). + +If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues). + +## Troubleshoot + +
+I already own a BigCommerce store. What should I do? +
+First thing you do is: set your environment variables +
+
+.env.local ```sh BIGCOMMERCE_STOREFRONT_API_URL=<> @@ -50,331 +39,21 @@ BIGCOMMERCE_STORE_API_CLIENT_ID=<> BIGCOMMERCE_CHANNEL_ID=<> ``` -## General Usage +If your project was started with a "Deploy with Vercel" button, you can use Vercel's CLI to retrieve these credentials. -### CommerceProvider +1. Install Vercel CLI: `npm i -g vercel` +2. Link local instance with Vercel and Github accounts (creates .vercel file): `vercel link` +3. Download your environment variables: `vercel env pull .env.local` -This component is a provider pattern component that creates commerce context for it's children. It takes config values for the locale and an optional `fetcherRef` object for data fetching. +Next, you're free to customize the starter. More updates coming soon. Stay tuned. -```jsx -... -import { CommerceProvider } from '@bigcommerce/storefront-data-hooks' +
-const App = ({ locale = 'en-US', children }) => { - return ( - - {children} - - ) -} -... -``` - -### useLogin hook - -Hook for bigcommerce user login functionality, returns `login` function to handle user login. - -```jsx -... -import useLogin from '@bigcommerce/storefront-data-hooks/use-login' - -const LoginView = () => { - const login = useLogin() - - const handleLogin = async () => { - await login({ - email, - password, - }) - } - - return ( -
- {children} -
- ) -} -... -``` - -### useLogout - -Hook to logout user. - -```jsx -... -import useLogout from '@bigcommerce/storefront-data-hooks/use-logout' - -const LogoutLink = () => { - const logout = useLogout() - return ( - logout()}> - Logout - - ) -} -``` - -### useCustomer - -Hook for getting logged in customer data, and fetching customer info. - -```jsx -... -import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer' -... - -const Profile = () => { - const { data } = useCustomer() - - if (!data) { - return null - } - - return ( -
Hello, {data.firstName}
- ) -} -``` - -### useSignup - -Hook for bigcommerce user signup, returns `signup` function to handle user signups. - -```jsx -... -import useSignup from '@bigcommerce/storefront-data-hooks/use-login' - -const SignupView = () => { - const signup = useSignup() - - const handleSignup = async () => { - await signup({ - email, - firstName, - lastName, - password, - }) - } - - return ( -
- {children} -
- ) -} -... -``` - -### usePrice - -Helper hook to format price according to commerce locale, and return discount if available. - -```jsx -import usePrice from '@bigcommerce/storefront-data-hooks/use-price' -... - const { price, discount, basePrice } = usePrice( - data && { - amount: data.cart_amount, - currencyCode: data.currency.code, - } - ) -... -``` - -## Cart Hooks - -### useCart - -Returns the current cart data for use - -```jsx -... -import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart' - -const countItem = (count: number, item: LineItem) => count + item.quantity - -const CartNumber = () => { - const { data } = useCart() - const itemsCount = data?.lineItems.reduce(countItem, 0) ?? 0 - - return itemsCount > 0 ? {itemsCount} : null -} -``` - -### useAddItem - -```jsx -... -import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item' - -const AddToCartButton = ({ productId, variantId }) => { - const addItem = useAddItem() - - const addToCart = async () => { - await addItem({ - productId, - variantId, - }) - } - - return -} -... -``` - -### useUpdateItem - -```jsx -... -import useUpdateItem from '@bigcommerce/storefront-data-hooks/cart/use-update-item' - -const CartItem = ({ item }) => { - const [quantity, setQuantity] = useState(item.quantity) - const updateItem = useUpdateItem(item) - - const updateQuantity = async (e) => { - const val = e.target.value - await updateItem({ quantity: val }) - } - - return ( - - ) -} -... -``` - -### useRemoveItem - -Provided with a cartItemId, will remove an item from the cart: - -```jsx -... -import useRemoveItem from '@bigcommerce/storefront-data-hooks/cart/use-remove-item' - -const RemoveButton = ({ item }) => { - const removeItem = useRemoveItem() - - const handleRemove = async () => { - await removeItem({ id: item.id }) - } - - return -} -... -``` - -## Wishlist Hooks - -Wishlist hooks are similar to cart hooks. See the below example for how to use `useWishlist`, `useAddItem`, and `useRemoveItem`. - -```jsx -import useAddItem from '@bigcommerce/storefront-data-hooks/wishlist/use-add-item' -import useRemoveItem from '@bigcommerce/storefront-data-hooks/wishlist/use-remove-item' -import useWishlist from '@bigcommerce/storefront-data-hooks/wishlist/use-wishlist' - -const WishlistButton = ({ productId, variant }) => { - const addItem = useAddItem() - const removeItem = useRemoveItem() - const { data } = useWishlist() - const { data: customer } = useCustomer() - const itemInWishlist = data?.items?.find( - (item) => - item.product_id === productId && - item.variant_id === variant?.node.entityId - ) - - const handleWishlistChange = async (e) => { - e.preventDefault() - - if (!customer) { - return - } - - if (itemInWishlist) { - await removeItem({ id: itemInWishlist.id! }) - } else { - await addItem({ - productId, - variantId: variant?.node.entityId!, - }) - } - } - - return ( - - ) -} -``` - -## Product Hooks and API - -### useSearch - -`useSearch` handles searching the bigcommerce storefront product catalog by catalog, brand, and query string. - -```jsx -... -import useSearch from '@bigcommerce/storefront-data-hooks/products/use-search' - -const SearchPage = ({ searchString, category, brand, sortStr }) => { - const { data } = useSearch({ - search: searchString || '', - categoryId: category?.entityId, - brandId: brand?.entityId, - sort: sortStr || '', - }) - - return ( - - {data.products.map(({ node }) => ( - - ))} - - ) -} -``` - -### getAllProducts - -API function to retrieve a product list. - -```js -import { getConfig } from '@bigcommerce/storefront-data-hooks/api' -import getAllProducts from '@bigcommerce/storefront-data-hooks/api/operations/get-all-products' - -const { products } = await getAllProducts({ - variables: { field: 'featuredProducts', first: 6 }, - config, - preview, -}) -``` - -### getProduct - -API product to retrieve a single product when provided with the product -slug string. - -```js -import { getConfig } from '@bigcommerce/storefront-data-hooks/api' -import getProduct from '@bigcommerce/storefront-data-hooks/api/operations/get-product' - -const { product } = await getProduct({ - variables: { slug }, - config, - preview, -}) -``` - -## More - -Feel free to read through the source for more usage, and check the commerce vercel demo and commerce repo for usage examples: ([demo.vercel.store](https://demo.vercel.store/)) ([repo](https://github.com/vercel/commerce)) +
+BigCommerce shows a Coming Soon page and requests a Preview Code +
+After Email confirmation, Checkout should be manually enabled through BigCommerce platform. Look for "Review & test your store" section through BigCommerce's dashboard. +
+
+BigCommerce team has been notified and they plan to add more detailed about this subject. +
diff --git a/framework/commerce/README.md b/framework/commerce/README.md new file mode 100644 index 000000000..ecdebb8c0 --- /dev/null +++ b/framework/commerce/README.md @@ -0,0 +1,334 @@ +# Commerce Framework + +- [Commerce Framework](#commerce-framework) + - [Commerce Hooks](#commerce-hooks) + - [CommerceProvider](#commerceprovider) + - [Authentication Hooks](#authentication-hooks) + - [useSignup](#usesignup) + - [useLogin](#uselogin) + - [useLogout](#uselogout) + - [Customer Hooks](#customer-hooks) + - [useCustomer](#usecustomer) + - [Product Hooks](#product-hooks) + - [usePrice](#useprice) + - [useSearch](#usesearch) + - [Cart Hooks](#cart-hooks) + - [useCart](#usecart) + - [useAddItem](#useadditem) + - [useUpdateItem](#useupdateitem) + - [useRemoveItem](#useremoveitem) + - [Wishlist Hooks](#wishlist-hooks) + - [Commerce API](#commerce-api) + - [More](#more) + +The commerce framework ships multiple hooks and a Node.js API, both using an underlying headless e-commerce platform, which we call commerce providers. + +The core features are: + +- Code splitted hooks for data fetching using [SWR](https://swr.vercel.app/), and to handle common user actions +- A Node.js API for initial data population, static generation of content and for creating the API endpoints that connect to the hooks, if required. + +> 👩‍🔬 If you would like to contribute a new provider, check the docs for [Adding a new Commerce Provider](./new-provider.md). + +> 🚧 The core commerce framework is under active development, new features and updates will be continuously added over time. Breaking changes are expected while we finish the API. + +## Commerce Hooks + +A commerce hook is a [React hook](https://reactjs.org/docs/hooks-intro.html) that's connected to a commerce provider. They focus on user actions and data fetching of data that wasn't statically generated. + +Data fetching hooks use [SWR](https://swr.vercel.app/) underneath and you're welcome to use any of its [return values](https://swr.vercel.app/docs/options#return-values) and [options](https://swr.vercel.app/docs/options#options). For example, using the `useCustomer` hook: + +```jsx +const { data, isLoading, error } = useCustomer({ + swrOptions: { + revalidateOnFocus: true, + }, +}) +``` + +### CommerceProvider + +This component adds the provider config and handlers to the context of your React tree for it's children. You can optionally pass the `locale` to it: + +```jsx +import { CommerceProvider } from '@framework' + +const App = ({ locale = 'en-US', children }) => { + return {children} +} +``` + +## Authentication Hooks + +### useSignup + +Returns a _signup_ function that can be used to sign up the current visitor: + +```jsx +import useSignup from '@framework/auth/use-signup' + +const SignupView = () => { + const signup = useSignup() + + const handleSignup = async () => { + await signup({ + email, + firstName, + lastName, + password, + }) + } + + return
{children}
+} +``` + +### useLogin + +Returns a _login_ function that can be used to sign in the current visitor into an existing customer: + +```jsx +import useLogin from '@framework/auth/use-login' + +const LoginView = () => { + const login = useLogin() + const handleLogin = async () => { + await login({ + email, + password, + }) + } + + return
{children}
+} +``` + +### useLogout + +Returns a _logout_ function that signs out the current customer when called. + +```jsx +import useLogout from '@framework/auth/use-logout' + +const LogoutButton = () => { + const logout = useLogout() + return ( + + ) +} +``` + +## Customer Hooks + +### useCustomer + +Fetches and returns the data of the signed in customer: + +```jsx +import useCustomer from '@framework/customer/use-customer' + +const Profile = () => { + const { data, isLoading, error } = useCustomer() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (!data) return null + + return
Hello, {data.firstName}
+} +``` + +## Product Hooks + +### usePrice + +Helper hook to format price according to the commerce locale and currency code. It also handles discounts: + +```jsx +import useCart from '@framework/cart/use-cart' +import usePrice from '@framework/product/use-price' + +// ... +const { data } = useCart() +const { price, discount, basePrice } = usePrice( + data && { + amount: data.subtotalPrice, + currencyCode: data.currency.code, + // If `baseAmount` is used, a discount will be calculated + // baseAmount: number, + } +) +// ... +``` + +### useSearch + +Fetches and returns the products that match a set of filters: + +```jsx +import useSearch from '@framework/product/use-search' + +const SearchPage = ({ searchString, category, brand, sortStr }) => { + const { data } = useSearch({ + search: searchString || '', + categoryId: category?.entityId, + brandId: brand?.entityId, + sort: sortStr, + }) + + return ( + + {data.products.map((product) => ( + + ))} + + ) +} +``` + +## Cart Hooks + +### useCart + +Fetches and returns the data of the current cart: + +```jsx +import useCart from '@framework/cart/use-cart' + +const CartTotal = () => { + const { data, isLoading, isEmpty, error } = useCart() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (isEmpty) return

The cart is empty

+ + return

The cart total is {data.totalPrice}

+} +``` + +### useAddItem + +Returns a function that adds a new item to the cart when called, if this is the first item it will create the cart: + +```jsx +import { useAddItem } from '@framework/cart' + +const AddToCartButton = ({ productId, variantId }) => { + const addItem = useAddItem() + + const addToCart = async () => { + await addItem({ + productId, + variantId, + }) + } + + return +} +``` + +### useUpdateItem + +Returns a function that updates a current item in the cart when called, usually the quantity. + +```jsx +import { useUpdateItem } from '@framework/cart' + +const CartItemQuantity = ({ item }) => { + const [quantity, setQuantity] = useState(item.quantity) + const updateItem = useUpdateItem({ item }) + + const updateQuantity = async (e) => { + const val = e.target.value + + setQuantity(val) + await updateItem({ quantity: val }) + } + + return ( + + ) +} +``` + +If the `quantity` is lower than 1 the item will be removed from the cart. + +### useRemoveItem + +Returns a function that removes an item in the cart when called: + +```jsx +import { useRemoveItem } from '@framework/cart' + +const RemoveButton = ({ item }) => { + const removeItem = useRemoveItem() + const handleRemove = async () => { + await removeItem(item) + } + + return +} +``` + +## Wishlist Hooks + +Wishlist hooks work just like [cart hooks](#cart-hooks). Feel free to check how those work first. + +The example below shows how to use the `useWishlist`, `useAddItem` and `useRemoveItem` hooks: + +```jsx +import { useWishlist, useAddItem, useRemoveItem } from '@framework/wishlist' + +const WishlistButton = ({ productId, variant }) => { + const addItem = useAddItem() + const removeItem = useRemoveItem() + const { data, isLoading, isEmpty, error } = useWishlist() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (isEmpty) return

The wihslist is empty

+ + const { data: customer } = useCustomer() + const itemInWishlist = data?.items?.find( + (item) => item.product_id === productId && item.variant_id === variant.id + ) + + const handleWishlistChange = async (e) => { + e.preventDefault() + if (!customer) return + + if (itemInWishlist) { + await removeItem({ id: itemInWishlist.id }) + } else { + await addItem({ + productId, + variantId: variant.id, + }) + } + } + + return ( + + ) +} +``` + +## Commerce API + +While commerce hooks focus on client side data fetching and interactions, the commerce API focuses on static content generation for pages and API endpoints in a Node.js context. + +> The commerce API is currently going through a refactor in https://github.com/vercel/commerce/pull/252 - We'll update the docs once the API is released. + +## More + +Feel free to read through the source for more usage, and check the commerce vercel demo and commerce repo for usage examples: ([demo.vercel.store](https://demo.vercel.store/)) ([repo](https://github.com/vercel/commerce)) diff --git a/framework/commerce/config.js b/framework/commerce/config.js new file mode 100644 index 000000000..fa88c22de --- /dev/null +++ b/framework/commerce/config.js @@ -0,0 +1,66 @@ +/** + * This file is expected to be used in next.config.js only + */ + +const path = require('path') +const fs = require('fs') +const merge = require('deepmerge') +const prettier = require('prettier') + +const PROVIDERS = ['bigcommerce', 'shopify', 'swell'] + +function getProviderName() { + return ( + process.env.COMMERCE_PROVIDER || + (process.env.BIGCOMMERCE_STOREFRONT_API_URL + ? 'bigcommerce' + : process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN + ? 'shopify' + : null) + ) +} + +function withCommerceConfig(nextConfig = {}) { + const commerce = nextConfig.commerce || {} + const name = commerce.provider || getProviderName() + + if (!name) { + throw new Error( + `The commerce provider is missing, please add a valid provider name or its environment variables` + ) + } + if (!PROVIDERS.includes(name)) { + throw new Error( + `The commerce provider "${name}" can't be found, please use one of "${PROVIDERS.join( + ', ' + )}"` + ) + } + + const commerceNextConfig = require(path.join('../', name, 'next.config')) + const config = merge(commerceNextConfig, nextConfig) + + config.env = config.env || {} + + Object.entries(config.commerce.features).forEach(([k, v]) => { + if (v) config.env[`COMMERCE_${k.toUpperCase()}_ENABLED`] = true + }) + + // Update paths in `tsconfig.json` to point to the selected provider + if (config.commerce.updateTSConfig !== false) { + const tsconfigPath = path.join(process.cwd(), 'tsconfig.json') + const tsconfig = require(tsconfigPath) + + tsconfig.compilerOptions.paths['@framework'] = [`framework/${name}`] + tsconfig.compilerOptions.paths['@framework/*'] = [`framework/${name}/*`] + + fs.writeFileSync( + tsconfigPath, + prettier.format(JSON.stringify(tsconfig), { parser: 'json' }) + ) + } + + return config +} + +module.exports = { withCommerceConfig, getProviderName } diff --git a/framework/commerce/new-provider.md b/framework/commerce/new-provider.md new file mode 100644 index 000000000..4051c0f01 --- /dev/null +++ b/framework/commerce/new-provider.md @@ -0,0 +1,239 @@ +# Adding a new Commerce Provider + +A commerce provider is a headless e-commerce platform that integrates with the [Commerce Framework](./README.md). Right now we have the following providers: + +- BigCommerce ([framework/bigcommerce](../bigcommerce)) +- Shopify ([framework/shopify](../shopify)) + +Adding a commerce provider means adding a new folder in `framework` with a folder structure like the next one: + +- `api` + - index.ts +- `product` + - usePrice + - useSearch + - getProduct + - getAllProducts +- `wishlist` + - useWishlist + - useAddItem + - useRemoveItem +- `auth` + - useLogin + - useLogout + - useSignup +- `customer` + - useCustomer + - getCustomerId + - getCustomerWistlist +- `cart` + - useCart + - useAddItem + - useRemoveItem + - useUpdateItem +- `env.template` +- `index.ts` +- `provider.ts` +- `commerce.config.json` +- `next.config.js` +- `README.md` + +`provider.ts` exports a provider object with handlers for the [Commerce Hooks](./README.md#commerce-hooks) and `api/index.ts` exports a Node.js provider for the [Commerce API](./README.md#commerce-api) + +> **Important:** We use TypeScript for every provider and expect its usage for every new one. + +The app imports from the provider directly instead of the core commerce folder (`framework/commerce`), but all providers are interchangeable and to achieve it every provider always has to implement the core types and helpers. + +The provider folder should only depend on `framework/commerce` and dependencies in the main `package.json`. In the future we'll move the `framework` folder to a package that can be shared easily for multiple apps. + +## Adding the provider hooks + +Using BigCommerce as an example. The first thing to do is export a `CommerceProvider` component that includes a `provider` object with all the handlers that can be used for hooks: + +```tsx +import type { ReactNode } from 'react' +import { + CommerceConfig, + CommerceProvider as CoreCommerceProvider, + useCommerce as useCoreCommerce, +} from '@commerce' +import { bigcommerceProvider, BigcommerceProvider } from './provider' + +export { bigcommerceProvider } +export type { BigcommerceProvider } + +export const bigcommerceConfig: CommerceConfig = { + locale: 'en-us', + cartCookie: 'bc_cartId', +} + +export type BigcommerceConfig = Partial + +export type BigcommerceProps = { + children?: ReactNode + locale: string +} & BigcommerceConfig + +export function CommerceProvider({ children, ...config }: BigcommerceProps) { + return ( + + {children} + + ) +} + +export const useCommerce = () => useCoreCommerce() +``` + +The exported types and components extend from the core ones exported by `@commerce`, which refers to `framework/commerce`. + +The `bigcommerceProvider` object looks like this: + +```tsx +import { handler as useCart } from './cart/use-cart' +import { handler as useAddItem } from './cart/use-add-item' +import { handler as useUpdateItem } from './cart/use-update-item' +import { handler as useRemoveItem } from './cart/use-remove-item' + +import { handler as useWishlist } from './wishlist/use-wishlist' +import { handler as useWishlistAddItem } from './wishlist/use-add-item' +import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item' + +import { handler as useCustomer } from './customer/use-customer' +import { handler as useSearch } from './product/use-search' + +import { handler as useLogin } from './auth/use-login' +import { handler as useLogout } from './auth/use-logout' +import { handler as useSignup } from './auth/use-signup' + +import fetcher from './fetcher' + +export const bigcommerceProvider = { + locale: 'en-us', + cartCookie: 'bc_cartId', + fetcher, + cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, + wishlist: { + useWishlist, + useAddItem: useWishlistAddItem, + useRemoveItem: useWishlistRemoveItem, + }, + customer: { useCustomer }, + products: { useSearch }, + auth: { useLogin, useLogout, useSignup }, +} + +export type BigcommerceProvider = typeof bigcommerceProvider +``` + +The provider object, in this case `bigcommerceProvider`, has to match the `Provider` type defined in [framework/commerce](./index.ts). + +A hook handler, like `useCart`, looks like this: + +```tsx +import { useMemo } from 'react' +import { SWRHook } from '@commerce/utils/types' +import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart' +import { normalizeCart } from '../lib/normalize' +import type { Cart } from '../types' + +export default useCart as UseCart + +export const handler: SWRHook< + Cart | null, + {}, + FetchCartInput, + { isEmpty?: boolean } +> = { + fetchOptions: { + url: '/api/bigcommerce/cart', + method: 'GET', + }, + async fetcher({ input: { cartId }, options, fetch }) { + const data = cartId ? await fetch(options) : null + return data && normalizeCart(data) + }, + useHook: ({ useData }) => (input) => { + const response = useData({ + swrOptions: { revalidateOnFocus: false, ...input?.swrOptions }, + }) + + return useMemo( + () => + Object.create(response, { + isEmpty: { + get() { + return (response.data?.lineItems.length ?? 0) <= 0 + }, + enumerable: true, + }, + }), + [response] + ) + }, +} +``` + +In the case of data fetching hooks like `useCart` each handler has to implement the `SWRHook` type that's defined in the core types. For mutations it's the `MutationHook`, e.g for `useAddItem`: + +```tsx +import { useCallback } from 'react' +import type { MutationHook } from '@commerce/utils/types' +import { CommerceError } from '@commerce/utils/errors' +import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' +import { normalizeCart } from '../lib/normalize' +import type { + Cart, + BigcommerceCart, + CartItemBody, + AddCartItemBody, +} from '../types' +import useCart from './use-cart' + +export default useAddItem as UseAddItem + +export const handler: MutationHook = { + fetchOptions: { + url: '/api/bigcommerce/cart', + method: 'POST', + }, + async fetcher({ input: item, options, fetch }) { + if ( + item.quantity && + (!Number.isInteger(item.quantity) || item.quantity! < 1) + ) { + throw new CommerceError({ + message: 'The item quantity has to be a valid integer greater than 0', + }) + } + + const data = await fetch({ + ...options, + body: { item }, + }) + + return normalizeCart(data) + }, + useHook: ({ fetch }) => () => { + const { mutate } = useCart() + + return useCallback( + async function addItem(input) { + const data = await fetch({ input }) + await mutate(data, false) + return data + }, + [fetch, mutate] + ) + }, +} +``` + +## Adding the Node.js provider API + +TODO + +> The commerce API is currently going through a refactor in https://github.com/vercel/commerce/pull/252 - We'll update the docs once the API is released. diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index a398070ac..86361fd9f 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -163,6 +163,7 @@ interface Entity { export interface Product extends Entity { name: string description: string + descriptionHtml?: string slug?: string path?: string images: ProductImage[] diff --git a/framework/shopify/.env.template b/framework/shopify/.env.template index 24521c2a1..74f446835 100644 --- a/framework/shopify/.env.template +++ b/framework/shopify/.env.template @@ -1,2 +1,4 @@ -SHOPIFY_STORE_DOMAIN= -SHOPIFY_STOREFRONT_ACCESS_TOKEN= +COMMERCE_PROVIDER=shopify + +NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= +NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= diff --git a/framework/shopify/README.md b/framework/shopify/README.md index fc6a70ce3..d67111a41 100644 --- a/framework/shopify/README.md +++ b/framework/shopify/README.md @@ -1,57 +1,28 @@ -## Table of Contents +## Shopify Provider -- [Getting Started](#getting-started) - - [Modifications](#modifications) - - [Adding item to Cart](#adding-item-to-cart) - - [Proceed to Checkout](#proceed-to-checkout) -- [General Usage](#general-usage) - - [CommerceProvider](#commerceprovider) - - [useCommerce](#usecommerce) -- [Hooks](#hooks) - - [usePrice](#useprice) - - [useAddItem](#useadditem) - - [useRemoveItem](#useremoveitem) - - [useUpdateItem](#useupdateitem) -- [APIs](#apis) - - [getProduct](#getproduct) - - [getAllProducts](#getallproducts) - - [getAllCollections](#getallcollections) - - [getAllPages](#getallpages) +**Demo:** https://shopify.demo.vercel.store/ -# Shopify Storefront Data Hooks +Before getting starter, a [Shopify](https://www.shopify.com/) account and store is required before using the provider. -Collection of hooks and data fetching functions to integrate Shopify in a React application. Designed to work with [Next.js Commerce](https://demo.vercel.store/). +Next, copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git): -## Getting Started - -1. Install dependencies: - -``` -yarn install shopify-buy -yarn install -D @types/shopify-buy +```bash +cp framework/shopify/.env.template .env.local ``` -3. Environment variables need to be set: +Then, set the environment variables in `.env.local` to match the ones from your store. -``` -SHOPIFY_STORE_DOMAIN= -SHOPIFY_STOREFRONT_ACCESS_TOKEN= -NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= -NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= -``` +## Contribute -4. Point the framework to `shopify` by updating `tsconfig.json`: +Our commitment to Open Source can be found [here](https://vercel.com/oss). -``` -"@framework/*": ["framework/shopify/*"], -"@framework": ["framework/shopify"] -``` +If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues). -### Modifications +## Modifications These modifications are temporarily until contributions are made to remove them. -#### Adding item to Cart +### Adding item to Cart ```js // components/product/ProductView/ProductView.tsx @@ -72,7 +43,7 @@ const ProductView: FC = ({ product }) => { } ``` -#### Proceed to Checkout +### Proceed to Checkout ```js // components/cart/CartSidebarView/CartSidebarView.tsx @@ -88,114 +59,6 @@ const CartSidebarView: FC = () => { } ``` -## General Usage - -### CommerceProvider - -Provider component that creates the commerce context for children. - -```js -import { CommerceProvider } from '@framework' - -const App = ({ children }) => { - return {children} -} - -export default App -``` - -### useCommerce - -Returns the configs that are defined in the nearest `CommerceProvider`. Also provides access to Shopify's `checkout` and `shop`. - -```js -import { useCommerce } from 'nextjs-commerce-shopify' - -const { checkout, shop } = useCommerce() -``` - -- `checkout`: The information required to checkout items and pay ([Documentation](https://shopify.dev/docs/storefront-api/reference/checkouts/checkout)). -- `shop`: Represents a collection of the general settings and information about the shop ([Documentation](https://shopify.dev/docs/storefront-api/reference/online-store/shop/index)). - -## Hooks - -### usePrice - -Display the product variant price according to currency and locale. - -```js -import usePrice from '@framework/product/use-price' - -const { price } = usePrice({ - amount, -}) -``` - -Takes in either `amount` or `variant`: - -- `amount`: A price value for a particular item if the amount is known. -- `variant`: A shopify product variant. Price will be extracted from the variant. - -### useAddItem - -```js -import { useAddItem } from '@framework/cart' - -const AddToCartButton = ({ variantId, quantity }) => { - const addItem = useAddItem() - - const addToCart = async () => { - await addItem({ - variantId, - }) - } - - return -} -``` - -### useRemoveItem - -```js -import { useRemoveItem } from '@framework/cart' - -const RemoveButton = ({ item }) => { - const removeItem = useRemoveItem() - - const handleRemove = async () => { - await removeItem({ id: item.id }) - } - - return -} -``` - -### useUpdateItem - -```js -import { useUpdateItem } from '@framework/cart' - -const CartItem = ({ item }) => { - const [quantity, setQuantity] = useState(item.quantity) - const updateItem = useUpdateItem(item) - - const updateQuantity = async (e) => { - const val = e.target.value - await updateItem({ quantity: val }) - } - - return ( - - ) -} -``` - ## APIs Collections of APIs to fetch data from a Shopify store. diff --git a/framework/shopify/api/index.ts b/framework/shopify/api/index.ts index 4f15cae15..387ed02fc 100644 --- a/framework/shopify/api/index.ts +++ b/framework/shopify/api/index.ts @@ -5,7 +5,6 @@ import { API_TOKEN, SHOPIFY_CHECKOUT_ID_COOKIE, SHOPIFY_CUSTOMER_TOKEN_COOKIE, - SHOPIFY_COOKIE_EXPIRE, } from '../const' if (!API_URL) { @@ -48,7 +47,7 @@ const config = new Config({ commerceUrl: API_URL, apiToken: API_TOKEN!, cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, - cartCookieMaxAge: SHOPIFY_COOKIE_EXPIRE, + cartCookieMaxAge: 60 * 60 * 24 * 30, fetch: fetchGraphqlApi, customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE, }) diff --git a/framework/shopify/auth/use-login.tsx b/framework/shopify/auth/use-login.tsx index 188dd54a2..7993822cd 100644 --- a/framework/shopify/auth/use-login.tsx +++ b/framework/shopify/auth/use-login.tsx @@ -10,7 +10,7 @@ import { MutationCheckoutCreateArgs, } from '../schema' import useLogin, { UseLogin } from '@commerce/auth/use-login' -import { setCustomerToken } from '../utils' +import { setCustomerToken, throwUserErrors } from '../utils' export default useLogin as UseLogin @@ -45,13 +45,8 @@ export const handler: MutationHook = { }, }) - const errors = customerAccessTokenCreate?.customerUserErrors + throwUserErrors(customerAccessTokenCreate?.customerUserErrors) - if (errors && errors.length) { - throw new ValidationError({ - message: getErrorMessage(errors[0]), - }) - } const customerAccessToken = customerAccessTokenCreate?.customerAccessToken const accessToken = customerAccessToken?.accessToken diff --git a/framework/shopify/auth/use-signup.tsx b/framework/shopify/auth/use-signup.tsx index 7f66448d3..9ca5c682f 100644 --- a/framework/shopify/auth/use-signup.tsx +++ b/framework/shopify/auth/use-signup.tsx @@ -1,15 +1,16 @@ import { useCallback } from 'react' import type { MutationHook } from '@commerce/utils/types' -import { CommerceError } from '@commerce/utils/errors' +import { CommerceError, ValidationError } from '@commerce/utils/errors' import useSignup, { UseSignup } from '@commerce/auth/use-signup' import useCustomer from '../customer/use-customer' -import { CustomerCreateInput } from '../schema' - import { - customerCreateMutation, - customerAccessTokenCreateMutation, -} from '../utils/mutations' -import handleLogin from '../utils/handle-login' + CustomerCreateInput, + Mutation, + MutationCustomerCreateArgs, +} from '../schema' + +import { customerCreateMutation } from '../utils/mutations' +import { handleAutomaticLogin, throwUserErrors } from '../utils' export default useSignup as UseSignup @@ -33,7 +34,11 @@ export const handler: MutationHook< 'A first name, last name, email and password are required to signup', }) } - const data = await fetch({ + + const { customerCreate } = await fetch< + Mutation, + MutationCustomerCreateArgs + >({ ...options, variables: { input: { @@ -45,19 +50,10 @@ export const handler: MutationHook< }, }) - try { - const loginData = await fetch({ - query: customerAccessTokenCreateMutation, - variables: { - input: { - email, - password, - }, - }, - }) - handleLogin(loginData) - } catch (error) {} - return data + throwUserErrors(customerCreate?.customerUserErrors) + await handleAutomaticLogin(fetch, { email, password }) + + return null }, useHook: ({ fetch }) => () => { const { revalidate } = useCustomer() diff --git a/framework/shopify/cart/index.ts b/framework/shopify/cart/index.ts index 3d288b1df..f6d36b443 100644 --- a/framework/shopify/cart/index.ts +++ b/framework/shopify/cart/index.ts @@ -1,3 +1,4 @@ export { default as useCart } from './use-cart' export { default as useAddItem } from './use-add-item' +export { default as useUpdateItem } from './use-update-item' export { default as useRemoveItem } from './use-remove-item' diff --git a/framework/shopify/cart/use-add-item.tsx b/framework/shopify/cart/use-add-item.tsx index d0f891148..cce0950e9 100644 --- a/framework/shopify/cart/use-add-item.tsx +++ b/framework/shopify/cart/use-add-item.tsx @@ -1,12 +1,15 @@ +import { useCallback } from 'react' import type { MutationHook } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import useCart from './use-cart' +import { + checkoutLineItemAddMutation, + getCheckoutId, + checkoutToCart, +} from '../utils' import { Cart, CartItemBody } from '../types' -import { checkoutLineItemAddMutation, getCheckoutId } from '../utils' -import { checkoutToCart } from './utils' import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema' -import { useCallback } from 'react' export default useAddItem as UseAddItem @@ -40,8 +43,7 @@ export const handler: MutationHook = { }, }) - // TODO: Fix this Cart type here - return checkoutToCart(checkoutLineItemsAdd) as any + return checkoutToCart(checkoutLineItemsAdd) }, useHook: ({ fetch }) => () => { const { mutate } = useCart() diff --git a/framework/shopify/cart/use-cart.tsx b/framework/shopify/cart/use-cart.tsx index d154bb837..c2ea7ea49 100644 --- a/framework/shopify/cart/use-cart.tsx +++ b/framework/shopify/cart/use-cart.tsx @@ -6,7 +6,7 @@ import useCommerceCart, { import { Cart } from '../types' import { SWRHook } from '@commerce/utils/types' -import { checkoutCreate, checkoutToCart } from './utils' +import { checkoutCreate, checkoutToCart } from '../utils' import getCheckoutQuery from '../utils/queries/get-checkout-query' export default useCommerceCart as UseCart @@ -26,7 +26,7 @@ export const handler: SWRHook< const data = await fetch({ ...options, variables: { - checkoutId, + checkoutId: checkoutId, }, }) checkout = data.node @@ -36,8 +36,7 @@ export const handler: SWRHook< checkout = await checkoutCreate(fetch) } - // TODO: Fix this type - return checkoutToCart({ checkout } as any) + return checkoutToCart({ checkout }) }, useHook: ({ useData }) => (input) => { const response = useData({ diff --git a/framework/shopify/cart/use-remove-item.tsx b/framework/shopify/cart/use-remove-item.tsx index e2aef13d8..8db38eac2 100644 --- a/framework/shopify/cart/use-remove-item.tsx +++ b/framework/shopify/cart/use-remove-item.tsx @@ -1,23 +1,22 @@ import { useCallback } from 'react' - import type { MutationHookContext, HookFetcherContext, } from '@commerce/utils/types' - +import { RemoveCartItemBody } from '@commerce/types' import { ValidationError } from '@commerce/utils/errors' - import useRemoveItem, { RemoveItemInput as RemoveItemInputBase, UseRemoveItem, } from '@commerce/cart/use-remove-item' - import useCart from './use-cart' -import { checkoutLineItemRemoveMutation, getCheckoutId } from '../utils' -import { checkoutToCart } from './utils' +import { + checkoutLineItemRemoveMutation, + getCheckoutId, + checkoutToCart, +} from '../utils' import { Cart, LineItem } from '../types' import { Mutation, MutationCheckoutLineItemsRemoveArgs } from '../schema' -import { RemoveCartItemBody } from '@commerce/types' export type RemoveItemFn = T extends LineItem ? (input?: RemoveItemInput) => Promise diff --git a/framework/shopify/cart/use-update-item.tsx b/framework/shopify/cart/use-update-item.tsx index 666ce3d08..49dd6be14 100644 --- a/framework/shopify/cart/use-update-item.tsx +++ b/framework/shopify/cart/use-update-item.tsx @@ -13,7 +13,7 @@ import useUpdateItem, { import useCart from './use-cart' import { handler as removeItemHandler } from './use-remove-item' import type { Cart, LineItem, UpdateCartItemBody } from '../types' -import { checkoutToCart } from './utils' +import { checkoutToCart } from '../utils' import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils' import { Mutation, MutationCheckoutLineItemsUpdateArgs } from '../schema' diff --git a/framework/shopify/fetcher.ts b/framework/shopify/fetcher.ts index 9c4fe9a9e..a69150503 100644 --- a/framework/shopify/fetcher.ts +++ b/framework/shopify/fetcher.ts @@ -2,9 +2,14 @@ import { Fetcher } from '@commerce/utils/types' import { API_TOKEN, API_URL } from './const' import { handleFetchResponse } from './utils' -const fetcher: Fetcher = async ({ method = 'POST', variables, query }) => { +const fetcher: Fetcher = async ({ + url = API_URL, + method = 'POST', + variables, + query, +}) => { return handleFetchResponse( - await fetch(API_URL, { + await fetch(url, { method, body: JSON.stringify({ query, variables }), headers: { diff --git a/framework/shopify/index.tsx b/framework/shopify/index.tsx index c26704771..5b25d6b21 100644 --- a/framework/shopify/index.tsx +++ b/framework/shopify/index.tsx @@ -28,8 +28,7 @@ export type ShopifyProps = { export function CommerceProvider({ children, ...config }: ShopifyProps) { return ( {children} diff --git a/framework/shopify/product/get-product.ts b/framework/shopify/product/get-product.ts index 1f00288c7..1d861e1a1 100644 --- a/framework/shopify/product/get-product.ts +++ b/framework/shopify/product/get-product.ts @@ -21,11 +21,10 @@ const getProduct = async (options: { const { data }: GraphQLFetcherResult = await config.fetch(getProductQuery, { variables, }) - - const { productByHandle: product } = data + const { productByHandle } = data return { - product: product ? normalizeProduct(product) : null, + product: productByHandle ? normalizeProduct(productByHandle) : null, } } diff --git a/framework/shopify/product/use-search.tsx b/framework/shopify/product/use-search.tsx index 425df9e83..bf812af3d 100644 --- a/framework/shopify/product/use-search.tsx +++ b/framework/shopify/product/use-search.tsx @@ -48,7 +48,8 @@ export const handler: SWRHook< edges = data.node?.products?.edges ?? [] if (brandId) { edges = edges.filter( - ({ node: { vendor } }: ProductEdge) => vendor === brandId + ({ node: { vendor } }: ProductEdge) => + vendor.replace(/\s+/g, '-').toLowerCase() === brandId ) } } else { diff --git a/framework/shopify/provider.ts b/framework/shopify/provider.ts index 383822baa..00db5c1d3 100644 --- a/framework/shopify/provider.ts +++ b/framework/shopify/provider.ts @@ -1,4 +1,4 @@ -import { SHOPIFY_CHECKOUT_ID_COOKIE, STORE_DOMAIN } from './const' +import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const' import { handler as useCart } from './cart/use-cart' import { handler as useAddItem } from './cart/use-add-item' @@ -17,15 +17,11 @@ import fetcher from './fetcher' export const shopifyProvider = { locale: 'en-us', cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, - storeDomain: STORE_DOMAIN, fetcher, cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, customer: { useCustomer }, products: { useSearch }, auth: { useLogin, useLogout, useSignup }, - features: { - wishlist: false, - }, } export type ShopifyProvider = typeof shopifyProvider diff --git a/framework/shopify/types.ts b/framework/shopify/types.ts index c4e42b67d..e7bcb2476 100644 --- a/framework/shopify/types.ts +++ b/framework/shopify/types.ts @@ -7,13 +7,11 @@ export type ShopifyCheckout = { lineItems: CheckoutLineItem[] } -export interface Cart extends Core.Cart { - id: string +export type Cart = Core.Cart & { lineItems: LineItem[] } - export interface LineItem extends Core.LineItem { - options: any[] + options?: any[] } /** diff --git a/framework/shopify/utils/checkout-create.ts b/framework/shopify/utils/checkout-create.ts new file mode 100644 index 000000000..359d16315 --- /dev/null +++ b/framework/shopify/utils/checkout-create.ts @@ -0,0 +1,33 @@ +import Cookies from 'js-cookie' + +import { + SHOPIFY_CHECKOUT_ID_COOKIE, + SHOPIFY_CHECKOUT_URL_COOKIE, + SHOPIFY_COOKIE_EXPIRE, +} from '../const' + +import checkoutCreateMutation from './mutations/checkout-create' +import { CheckoutCreatePayload } from '../schema' + +export const checkoutCreate = async ( + fetch: any +): Promise => { + const data = await fetch({ + query: checkoutCreateMutation, + }) + + const checkout = data.checkoutCreate?.checkout + const checkoutId = checkout?.id + + if (checkoutId) { + const options = { + expires: SHOPIFY_COOKIE_EXPIRE, + } + Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options) + Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout.webUrl, options) + } + + return checkout +} + +export default checkoutCreate diff --git a/framework/shopify/utils/checkout-to-cart.ts b/framework/shopify/utils/checkout-to-cart.ts new file mode 100644 index 000000000..034ff11d7 --- /dev/null +++ b/framework/shopify/utils/checkout-to-cart.ts @@ -0,0 +1,48 @@ +import { Cart } from '../types' +import { CommerceError } from '@commerce/utils/errors' + +import { + CheckoutLineItemsAddPayload, + CheckoutLineItemsRemovePayload, + CheckoutLineItemsUpdatePayload, + CheckoutCreatePayload, + CheckoutUserError, + Checkout, + Maybe, +} from '../schema' + +import { normalizeCart } from './normalize' +import throwUserErrors from './throw-user-errors' + +export type CheckoutQuery = { + checkout: Checkout + checkoutUserErrors?: Array +} + +export type CheckoutPayload = + | CheckoutLineItemsAddPayload + | CheckoutLineItemsUpdatePayload + | CheckoutLineItemsRemovePayload + | CheckoutCreatePayload + | CheckoutQuery + +const checkoutToCart = (checkoutPayload?: Maybe): Cart => { + if (!checkoutPayload) { + throw new CommerceError({ + message: 'Missing checkout payload from response', + }) + } + + const checkout = checkoutPayload?.checkout + throwUserErrors(checkoutPayload?.checkoutUserErrors) + + if (!checkout) { + throw new CommerceError({ + message: 'Missing checkout object from response', + }) + } + + return normalizeCart(checkout) +} + +export default checkoutToCart diff --git a/framework/shopify/utils/get-sort-variables.ts b/framework/shopify/utils/get-sort-variables.ts index b8cdeec51..141d9a180 100644 --- a/framework/shopify/utils/get-sort-variables.ts +++ b/framework/shopify/utils/get-sort-variables.ts @@ -1,4 +1,4 @@ -const getSortVariables = (sort?: string, isCategory = false) => { +const getSortVariables = (sort?: string, isCategory: boolean = false) => { let output = {} switch (sort) { case 'price-asc': diff --git a/framework/shopify/utils/get-vendors.ts b/framework/shopify/utils/get-vendors.ts index f04483bb1..24843f177 100644 --- a/framework/shopify/utils/get-vendors.ts +++ b/framework/shopify/utils/get-vendors.ts @@ -2,13 +2,14 @@ import { ShopifyConfig } from '../api' import fetchAllProducts from '../api/utils/fetch-all-products' import getAllProductVendors from './queries/get-all-product-vendors-query' -export type BrandNode = { +export type Brand = { + entityId: string name: string path: string } export type BrandEdge = { - node: BrandNode + node: Brand } export type Brands = BrandEdge[] @@ -24,13 +25,16 @@ const getVendors = async (config: ShopifyConfig): Promise => { let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor) - return [...new Set(vendorsStrings)].map((v) => ({ - node: { - entityId: v, - name: v, - path: `brands/${v}`, - }, - })) + return [...new Set(vendorsStrings)].map((v) => { + const id = v.replace(/\s+/g, '-').toLowerCase() + return { + node: { + entityId: id, + name: v, + path: `brands/${id}`, + }, + } + }) } export default getVendors diff --git a/framework/shopify/utils/handle-account-activation.ts b/framework/shopify/utils/handle-account-activation.ts new file mode 100644 index 000000000..d11f80ba1 --- /dev/null +++ b/framework/shopify/utils/handle-account-activation.ts @@ -0,0 +1,30 @@ +import { FetcherOptions } from '@commerce/utils/types' +import throwUserErrors from './throw-user-errors' + +import { + MutationCustomerActivateArgs, + MutationCustomerActivateByUrlArgs, +} from '../schema' +import { Mutation } from '../schema' +import { customerActivateByUrlMutation } from './mutations' + +const handleAccountActivation = async ( + fetch: (options: FetcherOptions) => Promise, + input: MutationCustomerActivateByUrlArgs +) => { + try { + const { customerActivateByUrl } = await fetch< + Mutation, + MutationCustomerActivateArgs + >({ + query: customerActivateByUrlMutation, + variables: { + input, + }, + }) + + throwUserErrors(customerActivateByUrl?.customerUserErrors) + } catch (error) {} +} + +export default handleAccountActivation diff --git a/framework/shopify/utils/handle-login.ts b/framework/shopify/utils/handle-login.ts index 77b6873e3..de86fa1d2 100644 --- a/framework/shopify/utils/handle-login.ts +++ b/framework/shopify/utils/handle-login.ts @@ -1,30 +1,12 @@ -import { ValidationError } from '@commerce/utils/errors' +import { FetcherOptions } from '@commerce/utils/types' +import { CustomerAccessTokenCreateInput } from '../schema' import { setCustomerToken } from './customer-token' - -const getErrorMessage = ({ - code, - message, -}: { - code: string - message: string -}) => { - switch (code) { - case 'UNIDENTIFIED_CUSTOMER': - message = 'Cannot find an account that matches the provided credentials' - break - } - return message -} +import { customerAccessTokenCreateMutation } from './mutations' +import throwUserErrors from './throw-user-errors' const handleLogin = (data: any) => { const response = data.customerAccessTokenCreate - const errors = response?.customerUserErrors - - if (errors && errors.length) { - throw new ValidationError({ - message: getErrorMessage(errors[0]), - }) - } + throwUserErrors(response?.customerUserErrors) const customerAccessToken = response?.customerAccessToken const accessToken = customerAccessToken?.accessToken @@ -36,4 +18,19 @@ const handleLogin = (data: any) => { return customerAccessToken } +export const handleAutomaticLogin = async ( + fetch: (options: FetcherOptions) => Promise, + input: CustomerAccessTokenCreateInput +) => { + try { + const loginData = await fetch({ + query: customerAccessTokenCreateMutation, + variables: { + input, + }, + }) + handleLogin(loginData) + } catch (error) {} +} + export default handleLogin diff --git a/framework/shopify/utils/index.ts b/framework/shopify/utils/index.ts index 2d59aa506..61e5975d7 100644 --- a/framework/shopify/utils/index.ts +++ b/framework/shopify/utils/index.ts @@ -4,6 +4,11 @@ export { default as getSortVariables } from './get-sort-variables' export { default as getVendors } from './get-vendors' export { default as getCategories } from './get-categories' export { default as getCheckoutId } from './get-checkout-id' +export { default as checkoutCreate } from './checkout-create' +export { default as checkoutToCart } from './checkout-to-cart' +export { default as handleLogin, handleAutomaticLogin } from './handle-login' +export { default as handleAccountActivation } from './handle-account-activation' +export { default as throwUserErrors } from './throw-user-errors' export * from './queries' export * from './mutations' export * from './normalize' diff --git a/framework/shopify/utils/mutations/checkout-create.ts b/framework/shopify/utils/mutations/checkout-create.ts index 912e1cbd2..ffbd555c7 100644 --- a/framework/shopify/utils/mutations/checkout-create.ts +++ b/framework/shopify/utils/mutations/checkout-create.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutCreateMutation = /* GraphQL */ ` mutation { checkoutCreate(input: {}) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-add.ts b/framework/shopify/utils/mutations/checkout-line-item-add.ts index 67b9cf250..2282c4e26 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-add.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-add.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutLineItemAddMutation = /* GraphQL */ ` mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) { checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-remove.ts b/framework/shopify/utils/mutations/checkout-line-item-remove.ts index d967a5168..8dea4ce08 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-remove.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-remove.ts @@ -6,9 +6,10 @@ const checkoutLineItemRemoveMutation = /* GraphQL */ ` checkoutId: $checkoutId lineItemIds: $lineItemIds ) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-update.ts b/framework/shopify/utils/mutations/checkout-line-item-update.ts index 8edf17587..76254341e 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-update.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-update.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutLineItemUpdateMutation = /* GraphQL */ ` mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) { checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/customer-activate-by-url.ts b/framework/shopify/utils/mutations/customer-activate-by-url.ts new file mode 100644 index 000000000..345d502bd --- /dev/null +++ b/framework/shopify/utils/mutations/customer-activate-by-url.ts @@ -0,0 +1,19 @@ +const customerActivateByUrlMutation = /* GraphQL */ ` + mutation customerActivateByUrl($activationUrl: URL!, $password: String!) { + customerActivateByUrl(activationUrl: $activationUrl, password: $password) { + customer { + id + } + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + field + message + } + } + } +` +export default customerActivateByUrlMutation diff --git a/framework/shopify/utils/mutations/customer-activate.ts b/framework/shopify/utils/mutations/customer-activate.ts new file mode 100644 index 000000000..b1d057c69 --- /dev/null +++ b/framework/shopify/utils/mutations/customer-activate.ts @@ -0,0 +1,19 @@ +const customerActivateMutation = /* GraphQL */ ` + mutation customerActivate($id: ID!, $input: CustomerActivateInput!) { + customerActivate(id: $id, input: $input) { + customer { + id + } + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + field + message + } + } + } +` +export default customerActivateMutation diff --git a/framework/shopify/utils/mutations/index.ts b/framework/shopify/utils/mutations/index.ts index 3a16d7cec..165fb192d 100644 --- a/framework/shopify/utils/mutations/index.ts +++ b/framework/shopify/utils/mutations/index.ts @@ -5,3 +5,5 @@ export { default as checkoutLineItemUpdateMutation } from './checkout-line-item- export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove' export { default as customerAccessTokenCreateMutation } from './customer-access-token-create' export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete' +export { default as customerActivateMutation } from './customer-activate' +export { default as customerActivateByUrlMutation } from './customer-activate-by-url' diff --git a/framework/shopify/utils/normalize.ts b/framework/shopify/utils/normalize.ts index c9b428b37..4ebc3a1ae 100644 --- a/framework/shopify/utils/normalize.ts +++ b/framework/shopify/utils/normalize.ts @@ -33,7 +33,7 @@ const normalizeProductOption = ({ let output: any = { label: value, } - if (displayName === 'Color') { + if (displayName.match(/colou?r/gi)) { output = { ...output, hexColors: [value], @@ -54,21 +54,24 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { return edges?.map( ({ node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 }, - }) => ({ - id, - name: title, - sku: sku ?? id, - price: +priceV2.amount, - listPrice: +compareAtPriceV2?.amount, - requiresShipping: true, - options: selectedOptions.map(({ name, value }: SelectedOption) => - normalizeProductOption({ - id, - name, - values: [value], - }) - ), - }) + }) => { + return { + id, + name: title, + sku: sku ?? id, + price: +priceV2.amount, + listPrice: +compareAtPriceV2?.amount, + requiresShipping: true, + options: selectedOptions.map(({ name, value }: SelectedOption) => { + const options = normalizeProductOption({ + id, + name, + values: [value], + }) + return options + }), + } + } ) } @@ -80,6 +83,7 @@ export function normalizeProduct(productNode: ShopifyProduct): Product { images, variants, description, + descriptionHtml, handle, priceRange, options, @@ -90,13 +94,18 @@ export function normalizeProduct(productNode: ShopifyProduct): Product { id, name, vendor, - description, path: `/${handle}`, slug: handle?.replace(/^\/+|\/+$/g, ''), price: money(priceRange?.minVariantPrice), images: normalizeProductImages(images), variants: variants ? normalizeProductVariants(variants) : [], - options: options ? options.map((o) => normalizeProductOption(o)) : [], + options: options + ? options + .filter((o) => o.name !== 'Title') // By default Shopify adds a 'Title' name when there's only one option. We don't need it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 + .map((o) => normalizeProductOption(o)) + : [], + ...(description && { description }), + ...(descriptionHtml && { descriptionHtml }), ...rest, } @@ -122,7 +131,7 @@ export function normalizeCart(checkout: Checkout): Cart { } function normalizeLineItem({ - node: { id, title, variant, quantity }, + node: { id, title, variant, quantity, ...rest }, }: CheckoutLineItemEdge): LineItem { return { id, @@ -135,18 +144,22 @@ function normalizeLineItem({ sku: variant?.sku ?? '', name: variant?.title!, image: { - url: variant?.image?.originalSrc, + url: variant?.image?.originalSrc ?? '/product-img-placeholder.svg', }, requiresShipping: variant?.requiresShipping ?? false, price: variant?.priceV2?.amount, listPrice: variant?.compareAtPriceV2?.amount, }, - path: '', + path: String(variant?.product?.handle), discounts: [], - options: [ - { - value: variant?.title, - }, - ], + options: + // By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 + variant?.title == 'Default Title' + ? [] + : [ + { + value: variant?.title, + }, + ], } } diff --git a/framework/shopify/utils/queries/get-all-products-query.ts b/framework/shopify/utils/queries/get-all-products-query.ts index 5eb44c7a7..f48140d31 100644 --- a/framework/shopify/utils/queries/get-all-products-query.ts +++ b/framework/shopify/utils/queries/get-all-products-query.ts @@ -9,7 +9,6 @@ edges { title vendor handle - description priceRange { minVariantPrice { amount diff --git a/framework/shopify/utils/queries/get-checkout-query.ts b/framework/shopify/utils/queries/get-checkout-query.ts index 194e1619a..d8758e321 100644 --- a/framework/shopify/utils/queries/get-checkout-query.ts +++ b/framework/shopify/utils/queries/get-checkout-query.ts @@ -43,6 +43,9 @@ export const checkoutDetailsFragment = ` amount currencyCode } + product { + handle + } } quantity } diff --git a/framework/shopify/utils/throw-user-errors.ts b/framework/shopify/utils/throw-user-errors.ts new file mode 100644 index 000000000..5488ba282 --- /dev/null +++ b/framework/shopify/utils/throw-user-errors.ts @@ -0,0 +1,38 @@ +import { ValidationError } from '@commerce/utils/errors' + +import { + CheckoutErrorCode, + CheckoutUserError, + CustomerErrorCode, + CustomerUserError, +} from '../schema' + +export type UserErrors = Array + +export type UserErrorCode = + | CustomerErrorCode + | CheckoutErrorCode + | null + | undefined + +const getCustomMessage = (code: UserErrorCode, message: string) => { + switch (code) { + case 'UNIDENTIFIED_CUSTOMER': + message = 'Cannot find an account that matches the provided credentials' + break + } + return message +} + +export const throwUserErrors = (errors?: UserErrors) => { + if (errors && errors.length) { + throw new ValidationError({ + errors: errors.map(({ code, message }) => ({ + code: code ?? 'validation_error', + message: getCustomMessage(code, message), + })), + }) + } +} + +export default throwUserErrors diff --git a/lib/colors.ts b/lib/colors.ts index f9c0b5b0b..139cda23d 100644 --- a/lib/colors.ts +++ b/lib/colors.ts @@ -194,6 +194,7 @@ const colorMap: Record = { } export function isDark(color: string = ''): boolean { + color = color.toLowerCase() // Equation from http://24ways.org/2010/calculating-color-contrast let rgb = colorMap[color] ? hexToRgb(colorMap[color]) : hexToRgb(color) const res = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 diff --git a/next.config.js b/next.config.js index 3569eff26..1e1b1a126 100644 --- a/next.config.js +++ b/next.config.js @@ -1,8 +1,12 @@ const commerce = require('./commerce.config.json') -const withCommerceConfig = require('./framework/commerce/with-config') +const { + withCommerceConfig, + getProviderName, +} = require('./framework/commerce/config') -const isBC = commerce.provider === 'bigcommerce' -const isShopify = commerce.provider === 'shopify' +const provider = commerce.provider || getProviderName() +const isBC = provider === 'bigcommerce' +const isShopify = provider === 'shopify' const isSwell = commerce.provider === 'swell' module.exports = withCommerceConfig({ @@ -40,3 +44,6 @@ module.exports = withCommerceConfig({ ].filter((x) => x) }, }) + +// Don't delete this console log, useful to see the commerce config in Vercel deployments +console.log('next.config.js', JSON.stringify(module.exports, null, 2)) diff --git a/package.json b/package.json index b054dec66..d28fb5bb9 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lodash.debounce": "^4.0.8", "lodash.random": "^3.2.0", "lodash.throttle": "^4.1.1", - "next": "^10.0.7", + "next": "^10.0.9-canary.5", "next-seo": "^4.11.0", "next-themes": "^0.0.4", "postcss": "^8.2.6", @@ -74,9 +74,6 @@ "prettier": "^2.2.1", "typescript": "^4.0.3" }, - "resolutions": { - "webpack": "5.11.1" - }, "husky": { "hooks": { "pre-commit": "lint-staged" diff --git a/public/card.png b/public/card.png new file mode 100644 index 000000000..40fcf42d1 Binary files /dev/null and b/public/card.png differ diff --git a/tsconfig.json b/tsconfig.json index 3d1b7584d..ffb74e388 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,5 +27,5 @@ } }, "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], - "exclude": ["node_modules", "swell-js"] + "exclude": ["node_modules"] } diff --git a/yarn.lock b/yarn.lock index a0846380f..8bc30e067 100644 --- a/yarn.lock +++ b/yarn.lock @@ -876,20 +876,20 @@ dependencies: webpack-bundle-analyzer "4.3.0" -"@next/env@10.0.7": - version "10.0.7" - resolved "https://registry.yarnpkg.com/@next/env/-/env-10.0.7.tgz#7b3e87a9029ca37491e2ec25c27593f0906725f9" - integrity sha512-/vnz2SL/mk3Tei58WfRtVnvz5xHmAqcBmZL5sTBEy1CZG6OtZGNx0qAFCjtVkeJ5m1Bh4Ut+WFh/RF333wx8Sg== +"@next/env@10.0.9-canary.5": + version "10.0.9-canary.5" + resolved "https://registry.yarnpkg.com/@next/env/-/env-10.0.9-canary.5.tgz#a1e1b490e4037840349ecaea8e63a8c0d0de5361" + integrity sha512-5Sos4xVdjckm2iJwRxLkshIzUMr32lDyYMnBXRJ7PhFbITrgXUrV0d4XKDken8Yu8L11QDvDUKnpqGeBhFaL/A== -"@next/polyfill-module@10.0.7": - version "10.0.7" - resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-10.0.7.tgz#ec45ec1f28f47beed15ed67dffc907edd7143094" - integrity sha512-HxqzRpoSgmZP0kRIWwH+e0SgtAXqJ0VkYtwWcsQFED8+xF4Eqn+7Twyp4uE6hutC8gr8IFSFqH+DEYhRtg1ltQ== +"@next/polyfill-module@10.0.9-canary.5": + version "10.0.9-canary.5" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-10.0.9-canary.5.tgz#d3eb63ece1aed00cf5ac77865553bb1b3d894ad6" + integrity sha512-CbnMly7rGj6MelgzrHxxTANO9whsq7VM4mAH/3lFbO9sNK9lWFuJaMAwYtVJfYGd8ySjDFdpQsGlpW2ovzJZBA== -"@next/react-dev-overlay@10.0.7": - version "10.0.7" - resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-10.0.7.tgz#5fe777011cab75ec09ad539ee61bb95ab5a2bdeb" - integrity sha512-yq71MDHVqN2N+IqOvZDiFsMpQrBcymrdpTx1ShhAADX7cWQvW4dhcIir4BbfrS10vS1LLz/3a8uKZkGdNoJj3w== +"@next/react-dev-overlay@10.0.9-canary.5": + version "10.0.9-canary.5" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-10.0.9-canary.5.tgz#e267c609bfff6f9664164d58346298e8768f99cb" + integrity sha512-d0cFQvLe28ujAbWdv5CpJo03dpqmyNhXqUn+Dkpeybo1IEV68Arr0Me++ofIJvUxZV3isDBKHoyN6xFmgkYgSQ== dependencies: "@babel/code-frame" "7.12.11" anser "1.4.9" @@ -903,10 +903,10 @@ stacktrace-parser "0.1.10" strip-ansi "6.0.0" -"@next/react-refresh-utils@10.0.7": - version "10.0.7" - resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-10.0.7.tgz#866ce30fe2f321e011255e81ed5d55eeda05894b" - integrity sha512-d/71vtQglv6m7sh4W1O9drc2hYti7UnAdEXfBLZAS354g2S80lvCRGIhbDrMx4w0rpShoxBIZboE2++LihAESg== +"@next/react-refresh-utils@10.0.9-canary.5": + version "10.0.9-canary.5" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-10.0.9-canary.5.tgz#5ece6594ec08b48bc319d8f171d9c46a7beb04ba" + integrity sha512-47Y2GO+PezgJF4t41/VOEmIpUe66bbzbh/jO+doldwoPiLPxrSfOMpAnlJLpm5keHquBJMCIQbwDtmSpNvPkTg== "@nodelib/fs.scandir@2.1.4": version "2.1.4" @@ -1506,19 +1506,6 @@ app-module-path@^2.2.0: resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" integrity sha1-ZBqlXft9am8KgUHEucCqULbCTdU= -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -1531,19 +1518,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -array-flatten@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" - integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== - -array-includes-with-glob@^3.0.6: - version "3.0.8" - resolved "https://registry.yarnpkg.com/array-includes-with-glob/-/array-includes-with-glob-3.0.8.tgz#522a982e7913a9e6397efd3d933aa3cc61776cb2" - integrity sha512-g1XH4sJ/LMdyUSDB/9pNEC/eEO62chSTIi9I5agGHi4NI90SASER1Qe4emrTgCeaNPCAcivfi3Ba0owq6Pwo4Q== - dependencies: - "@babel/runtime" "^7.13.10" - matcher "^4.0.0" - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -2049,11 +2023,6 @@ chokidar@3.5.1, chokidar@^3.4.3: optionalDependencies: fsevents "~2.3.1" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -2256,11 +2225,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - constant-case@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.3.tgz#ac910a99caf3926ac5112f352e3af599d8c5fc0a" @@ -2487,6 +2451,13 @@ debounce@^1.2.0: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== +debug@2: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -2535,19 +2506,10 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= dedent@^0.7.0: version "0.7.0" @@ -2564,7 +2526,7 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@4.2.2, deepmerge@^4.2.2: +deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -2598,11 +2560,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -2642,11 +2599,6 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detective-amd@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-3.0.1.tgz#aca8eddb1f405821953faf4a893d9b9e0430b09e" @@ -2852,7 +2804,7 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -3033,11 +2985,6 @@ execa@^4.1.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -3236,11 +3183,6 @@ fs-capacitor@^6.1.0: resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.2.0.tgz#fa79ac6576629163cb84561995602d8999afb7f5" integrity sha512-nKcE1UduoSKX27NSZlg879LdQc94OtbOsEmKMN2MBNudXREvijRKx2GEBsTMTfws+BrbkJoEuynbGSVRSpauvw== -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -3266,20 +3208,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.1: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3307,6 +3235,13 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.1" +get-orientation@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947" + integrity sha512-/pViTfifW+gBbh/RnlFYHINvELT9Znt+SYyDKAUL6uV6By019AK/s+i9XP4jSwq7lwP38Fd8HVeTxym3+hkwmQ== + dependencies: + stream-parser "^0.3.1" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -3326,11 +3261,6 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= - glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" @@ -3495,11 +3425,6 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -4601,16 +4526,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -4642,16 +4557,11 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -4707,11 +4617,6 @@ nanoid@^3.1.16, nanoid@^3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - native-url@0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8" @@ -4742,17 +4647,17 @@ next-unused@^0.0.3: madge "^3.8.0" ts-loader "^7.0.0" -next@^10.0.7: - version "10.0.7" - resolved "https://registry.yarnpkg.com/next/-/next-10.0.7.tgz#442f8e1da7454de33b0bbcc1ce5684b923597ee6" - integrity sha512-We0utmwwfkvO12eLyUZd3tX9VLDE3FPpOaHpH3kqKdUTxJzUKt8FLBXCTm0mwsTKW5XColWG8mJvz2OLu3+3QA== +next@^10.0.9-canary.5: + version "10.0.9-canary.5" + resolved "https://registry.yarnpkg.com/next/-/next-10.0.9-canary.5.tgz#969dfb3b4646736db1144fb965d4af3e83fa6ff2" + integrity sha512-wIqEpQe9gKxwBZZHQgDGuztNlfv6/opIjf6nFYf7Iimw4SnNprPJWpEKrqF3JVxMoLawI6tafMwsYg1TQNZM7A== dependencies: "@babel/runtime" "7.12.5" "@hapi/accept" "5.0.1" - "@next/env" "10.0.7" - "@next/polyfill-module" "10.0.7" - "@next/react-dev-overlay" "10.0.7" - "@next/react-refresh-utils" "10.0.7" + "@next/env" "10.0.9-canary.5" + "@next/polyfill-module" "10.0.9-canary.5" + "@next/react-dev-overlay" "10.0.9-canary.5" + "@next/react-refresh-utils" "10.0.9-canary.5" "@opentelemetry/api" "0.14.0" ast-types "0.13.2" browserslist "4.16.1" @@ -4764,6 +4669,7 @@ next@^10.0.7: cssnano-simple "1.2.2" etag "1.8.1" find-cache-dir "3.3.1" + get-orientation "1.1.2" jest-worker "24.9.0" native-url "0.3.4" node-fetch "2.6.1" @@ -4782,9 +4688,7 @@ next@^10.0.7: styled-jsx "3.3.2" use-subscription "1.5.1" vm-browserify "1.1.2" - watchpack "2.0.0-beta.13" - optionalDependencies: - sharp "0.26.3" + watchpack "2.1.1" no-case@^3.0.3, no-case@^3.0.4: version "3.0.4" @@ -4794,18 +4698,6 @@ no-case@^3.0.3, no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^2.7.0: - version "2.19.3" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d" - integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg== - dependencies: - semver "^5.4.1" - -node-addon-api@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" - integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== - node-emoji@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" @@ -4871,11 +4763,6 @@ node-source-walk@^4.0.0, node-source-walk@^4.2.0: dependencies: "@babel/parser" "^7.0.0" -noop-logger@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" - integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= - normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4915,16 +4802,6 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -npmlog@^4.0.1, npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -5698,27 +5575,6 @@ postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.6: nanoid "^3.1.20" source-map "^0.6.1" -prebuild-install@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.0.tgz#669022bcde57c710a869e39c5ca6bf9cd207f316" - integrity sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw== - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^2.7.0" - noop-logger "^0.1.1" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - which-pm-runs "^1.0.0" - precinct@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/precinct/-/precinct-6.3.1.tgz#8ad735a8afdfc48b56ed39c9ad3bf999b6b928dc" @@ -5962,7 +5818,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.3, readable-stream@^2.3.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -5975,7 +5831,7 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -6266,7 +6122,7 @@ serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -6294,21 +6150,17 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.3.tgz#9de8577a986b22538e6e12ced1f7e8a53f9728de" - integrity sha512-NdEJ9S6AMr8Px0zgtFo1TJjMK/ROMU92MkDtYn2BBrDjIx3YfH9TUyGdzPC+I/L619GeYQc690Vbaxc5FPCCWg== +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: - array-flatten "^3.0.0" - color "^3.1.3" - detect-libc "^1.0.3" - node-addon-api "^3.0.2" - npmlog "^4.1.2" - prebuild-install "^6.0.0" - semver "^7.3.2" - simple-get "^4.0.0" - tar-fs "^2.1.1" - tunnel-agent "^0.6.0" + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shebang-command@^2.0.0: version "2.0.0" @@ -6332,7 +6184,7 @@ shopify-buy@^2.11.0: resolved "https://registry.yarnpkg.com/shopify-buy/-/shopify-buy-2.11.0.tgz#0f7cb52741395e4ae778c336f32ddf3fe67c2f35" integrity sha512-bGjS1b/VCPvCjazSstlKwgLtK1WBotWom06/12loja8yfo/cWkLuJsakBbQe1uEIDiOLhKaR0M0CAXZFheYDug== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -6342,29 +6194,6 @@ signedsource@^1.0.0: resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo= -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== - dependencies: - decompress-response "^4.2.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-get@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" - integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -6519,6 +6348,13 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-parser@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" + integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M= + dependencies: + debug "2" + streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" @@ -6548,7 +6384,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.1.1: +string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -6774,27 +6610,6 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== -tar-fs@^2.0.0, tar-fs@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - temp@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60" @@ -6949,13 +6764,6 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -7138,15 +6946,7 @@ warning@^4.0.3: dependencies: loose-envify "^1.0.0" -watchpack@2.0.0-beta.13: - version "2.0.0-beta.13" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.0-beta.13.tgz#9d9b0c094b8402139333e04eb6194643c8384f55" - integrity sha512-ZEFq2mx/k5qgQwgi6NOm+2ImICb8ngAkA/rZ6oyXZ7SgPn3pncf+nfhYTCrs3lmHwOxnPtGLTOuFLfpSMh1VMA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -watchpack@^2.0.0: +watchpack@2.1.1, watchpack@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== @@ -7250,13 +7050,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"