diff --git a/.env.template b/.env.template
index 73a8a6e3b..7d8400baf 100644
--- a/.env.template
+++ b/.env.template
@@ -2,4 +2,6 @@ BIGCOMMERCE_STOREFRONT_API_URL=
BIGCOMMERCE_STOREFRONT_API_TOKEN=
BIGCOMMERCE_STORE_API_URL=
BIGCOMMERCE_STORE_API_TOKEN=
-BIGCOMMERCE_STORE_API_CLIENT_ID=
\ No newline at end of file
+BIGCOMMERCE_STORE_API_CLIENT_ID=
+SHOPIFY_STORE_DOMAIN=
+SHOPIFY_STOREFRONT_ACCESS_TOKEN=
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..c83e26348
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["esbenp.prettier-vscode"]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 7d1d95638..000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,4 +0,0 @@
-## Changelog
-
-- Select Variants Working
-- Click on cart item title, closes the sidebar
diff --git a/README.md b/README.md
index ea6248c51..9d2108cfe 100644
--- a/README.md
+++ b/README.md
@@ -57,18 +57,21 @@ Main folder and its exposed functions
- getAllProducts
- `wishlist`
- useWishlist
- - addWishlistItem
- - removeWishlistItem
+ - useAddItem
+ - useRemoveItem
- `auth`
- useLogin
- useLogout
- useSignup
+- `customer`
+ - useCustomer
+ - getCustomerId
+ - getCustomerWistlist
- `cart`
- useCart
- useAddItem
- useRemoveItem
- - useCartActions
- useUpdateItem
- `config.json`
diff --git a/components/cart/CartSidebarView/CartSidebarView.tsx b/components/cart/CartSidebarView/CartSidebarView.tsx
index 5b28fde27..3ceda44fe 100644
--- a/components/cart/CartSidebarView/CartSidebarView.tsx
+++ b/components/cart/CartSidebarView/CartSidebarView.tsx
@@ -9,7 +9,7 @@ import usePrice from '@framework/product/use-price'
import CartItem from '../CartItem'
import s from './CartSidebarView.module.css'
-const CartSidebarView: FC<{ wishlist?: boolean }> = ({ wishlist }) => {
+const CartSidebarView: FC = () => {
const { closeSidebar } = useUI()
const { data, isLoading, isEmpty } = useCart()
@@ -48,7 +48,7 @@ const CartSidebarView: FC<{ wishlist?: boolean }> = ({ wishlist }) => {
-
+
diff --git a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx
index 4b838e1a4..423048f75 100644
--- a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx
+++ b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx
@@ -5,20 +5,17 @@ import { Grid } from '@components/ui'
import { ProductCard } from '@components/product'
import s from './HomeAllProductsGrid.module.css'
import { getCategoryPath, getDesignerPath } from '@lib/search'
-import wishlist from '@framework/api/wishlist'
interface Props {
categories?: any
brands?: any
products?: Product[]
- wishlist?: boolean
}
const HomeAllProductsGrid: FC = ({
categories,
brands,
products = [],
- wishlist = false,
}) => {
return (
@@ -65,7 +62,6 @@ const HomeAllProductsGrid: FC
= ({
width: 480,
height: 480,
}}
- wishlist={wishlist}
/>
))}
diff --git a/components/common/Layout/Layout.tsx b/components/common/Layout/Layout.tsx
index 2542e2b28..54749c46b 100644
--- a/components/common/Layout/Layout.tsx
+++ b/components/common/Layout/Layout.tsx
@@ -58,11 +58,10 @@ const Layout: FC = ({
} = useUI()
const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
const { locale = 'en-US' } = useRouter()
- const isWishlistEnabled = commerceFeatures?.wishlist
return (
-
+
{children}
@@ -73,7 +72,7 @@ const Layout: FC
= ({
-
+
= ({ wishlist }) => (
+const Navbar: FC = () => (
@@ -36,7 +36,7 @@ const Navbar: FC<{ wishlist?: boolean }> = ({ wishlist }) => (
-
+
diff --git a/components/common/UserNav/UserNav.tsx b/components/common/UserNav/UserNav.tsx
index 5d9d58fff..4d00970a9 100644
--- a/components/common/UserNav/UserNav.tsx
+++ b/components/common/UserNav/UserNav.tsx
@@ -4,20 +4,19 @@ import cn from 'classnames'
import type { LineItem } from '@framework/types'
import useCart from '@framework/cart/use-cart'
import useCustomer from '@framework/customer/use-customer'
+import { Avatar } from '@components/common'
import { Heart, Bag } from '@components/icons'
import { useUI } from '@components/ui/context'
import DropdownMenu from './DropdownMenu'
import s from './UserNav.module.css'
-import { Avatar } from '@components/common'
interface Props {
className?: string
- wishlist?: boolean
}
const countItem = (count: number, item: LineItem) => count + item.quantity
-const UserNav: FC = ({ className, wishlist = false }) => {
+const UserNav: FC = ({ className }) => {
const { data } = useCart()
const { data: customer } = useCustomer()
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()
@@ -31,7 +30,7 @@ const UserNav: FC = ({ className, wishlist = false }) => {
{itemsCount > 0 && {itemsCount}}
- {wishlist && (
+ {process.env.COMMERCE_WISHLIST_ENABLED && (
diff --git a/components/product/ProductCard/ProductCard.tsx b/components/product/ProductCard/ProductCard.tsx
index a9eaf8568..ade53380c 100644
--- a/components/product/ProductCard/ProductCard.tsx
+++ b/components/product/ProductCard/ProductCard.tsx
@@ -11,7 +11,6 @@ interface Props {
product: Product
variant?: 'slim' | 'simple'
imgProps?: Omit
- wishlist?: boolean
}
const placeholderImg = '/product-img-placeholder.svg'
@@ -21,7 +20,6 @@ const ProductCard: FC = ({
product,
variant,
imgProps,
- wishlist = false,
...props
}) => (
@@ -59,11 +57,11 @@ const ProductCard: FC = ({
{product.price.currencyCode}
- {wishlist && (
+ {process.env.COMMERCE_WISHLIST_ENABLED && (
)}
diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx
index c502303c4..c0fdd32d2 100644
--- a/components/product/ProductView/ProductView.tsx
+++ b/components/product/ProductView/ProductView.tsx
@@ -4,9 +4,8 @@ import { NextSeo } from 'next-seo'
import { FC, useState } from 'react'
import s from './ProductView.module.css'
-import { useUI } from '@components/ui'
import { Swatch, ProductSlider } from '@components/product'
-import { Button, Container, Text } from '@components/ui'
+import { Button, Container, Text, useUI } from '@components/ui'
import type { Product } from '@commerce/types'
import usePrice from '@framework/product/use-price'
@@ -19,10 +18,9 @@ interface Props {
className?: string
children?: any
product: Product
- wishlist?: boolean
}
-const ProductView: FC = ({ product, wishlist = false }) => {
+const ProductView: FC = ({ product }) => {
const addItem = useAddItem()
const { price } = usePrice({
amount: product.price.value,
@@ -101,7 +99,6 @@ const ProductView: FC = ({ product, wishlist = false }) => {
-
{product.options?.map((opt) => (
@@ -152,11 +149,11 @@ const ProductView: FC = ({ product, wishlist = false }) => {
- {wishlist && (
+ {process.env.COMMERCE_WISHLIST_ENABLED && (
)}
diff --git a/components/ui/context.tsx b/components/ui/context.tsx
index 013589941..13992a736 100644
--- a/components/ui/context.tsx
+++ b/components/ui/context.tsx
@@ -8,6 +8,7 @@ export interface State {
displayToast: boolean
modalView: string
toastText: string
+ userAvatar: string
}
const initialState = {
@@ -17,6 +18,7 @@ const initialState = {
modalView: 'LOGIN_VIEW',
displayToast: false,
toastText: '',
+ userAvatar: '',
}
type Action =
diff --git a/components/wishlist/WishlistButton/WishlistButton.tsx b/components/wishlist/WishlistButton/WishlistButton.tsx
index 0c4c20194..5a1f17e5c 100644
--- a/components/wishlist/WishlistButton/WishlistButton.tsx
+++ b/components/wishlist/WishlistButton/WishlistButton.tsx
@@ -1,13 +1,12 @@
import React, { FC, useState } from 'react'
import cn from 'classnames'
-import { Heart } from '@components/icons'
import { useUI } from '@components/ui'
import type { Product, ProductVariant } from '@commerce/types'
-import useCustomer from '@framework/customer/use-customer'
import useAddItem from '@framework/wishlist/use-add-item'
+import useCustomer from '@framework/customer/use-customer'
import useRemoveItem from '@framework/wishlist/use-remove-item'
-import useWishlist from '@framework/wishlist/use-add-item'
+import useWishlist from '@framework/wishlist/use-wishlist'
type Props = {
productId: Product['id']
@@ -28,7 +27,8 @@ const WishlistButton: FC = ({
const [loading, setLoading] = useState(false)
const itemInWishlist = data?.items?.find(
- (item) => item.product_id === productId && item.variant_id === variant.id
+ (item) =>
+ item.product_id === productId && (item.variant_id as any) === variant.id
)
const handleWishlistChange = async (e: any) => {
diff --git a/framework/bigcommerce/auth/use-login.tsx b/framework/bigcommerce/auth/use-login.tsx
index b66fca493..1be96a58c 100644
--- a/framework/bigcommerce/auth/use-login.tsx
+++ b/framework/bigcommerce/auth/use-login.tsx
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
-import useLogin, { UseLogin } from '@commerce/use-login'
+import useLogin, { UseLogin } from '@commerce/auth/use-login'
import type { LoginBody } from '../api/customers/login'
import useCustomer from '../customer/use-customer'
diff --git a/framework/bigcommerce/auth/use-logout.tsx b/framework/bigcommerce/auth/use-logout.tsx
index 6278a4dd1..71015a1c1 100644
--- a/framework/bigcommerce/auth/use-logout.tsx
+++ b/framework/bigcommerce/auth/use-logout.tsx
@@ -1,6 +1,6 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
-import useLogout, { UseLogout } from '@commerce/use-logout'
+import useLogout, { UseLogout } from '@commerce/auth/use-logout'
import useCustomer from '../customer/use-customer'
export default useLogout as UseLogout
diff --git a/framework/bigcommerce/auth/use-signup.tsx b/framework/bigcommerce/auth/use-signup.tsx
index 23b7ce9c6..28f7024ef 100644
--- a/framework/bigcommerce/auth/use-signup.tsx
+++ b/framework/bigcommerce/auth/use-signup.tsx
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
-import useSignup, { UseSignup } from '@commerce/use-signup'
+import useSignup, { UseSignup } from '@commerce/auth/use-signup'
import type { SignupBody } from '../api/customers/signup'
import useCustomer from '../customer/use-customer'
diff --git a/framework/bigcommerce/config.json b/framework/bigcommerce/config.json
index 17ef37e25..a0e7afc5d 100644
--- a/framework/bigcommerce/config.json
+++ b/framework/bigcommerce/config.json
@@ -1,5 +1,5 @@
{
"features": {
- "wishlist": false
+ "wishlist": true
}
}
diff --git a/framework/bigcommerce/next.config.js b/framework/bigcommerce/next.config.js
new file mode 100644
index 000000000..e732ef78a
--- /dev/null
+++ b/framework/bigcommerce/next.config.js
@@ -0,0 +1,37 @@
+module.exports = {
+ images: {
+ domains: ['cdn11.bigcommerce.com'],
+ },
+ i18n: {
+ locales: ['en-US', 'es'],
+ defaultLocale: 'en-US',
+ },
+ rewrites() {
+ return [
+ {
+ source: '/checkout',
+ destination: '/api/bigcommerce/checkout',
+ },
+ // The logout is also an action so this route is not required, but it's also another way
+ // you can allow a logout!
+ {
+ source: '/logout',
+ destination: '/api/bigcommerce/customers/logout?redirect_to=/',
+ },
+ // Rewrites for /search
+ {
+ source: '/search/designers/:name',
+ destination: '/search',
+ },
+ {
+ source: '/search/designers/:name/:category',
+ destination: '/search',
+ },
+ {
+ // This rewrite will also handle `/search/designers`
+ source: '/search/:category',
+ destination: '/search',
+ },
+ ]
+ },
+}
diff --git a/framework/bigcommerce/product/use-price.tsx b/framework/bigcommerce/product/use-price.tsx
index a79940a76..0174faf5e 100644
--- a/framework/bigcommerce/product/use-price.tsx
+++ b/framework/bigcommerce/product/use-price.tsx
@@ -1,2 +1,2 @@
-export * from '@commerce/use-price'
-export { default } from '@commerce/use-price'
+export * from '@commerce/product/use-price'
+export { default } from '@commerce/product/use-price'
diff --git a/framework/bigcommerce/wishlist/index.ts b/framework/bigcommerce/wishlist/index.ts
index 9ea28291c..241af3c7e 100644
--- a/framework/bigcommerce/wishlist/index.ts
+++ b/framework/bigcommerce/wishlist/index.ts
@@ -1,4 +1,3 @@
export { default as useAddItem } from './use-add-item'
export { default as useWishlist } from './use-wishlist'
export { default as useRemoveItem } from './use-remove-item'
-export { default as useWishlistActions } from './use-wishlist-actions'
diff --git a/framework/bigcommerce/wishlist/use-wishlist-actions.tsx b/framework/bigcommerce/wishlist/use-wishlist-actions.tsx
deleted file mode 100644
index 711d00516..000000000
--- a/framework/bigcommerce/wishlist/use-wishlist-actions.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import useAddItem from './use-add-item'
-import useRemoveItem from './use-remove-item'
-
-// This hook is probably not going to be used, but it's here
-// to show how a commerce should be structuring it
-export default function useWishlistActions() {
- const addItem = useAddItem()
- const removeItem = useRemoveItem()
-
- return { addItem, removeItem }
-}
diff --git a/framework/commerce/use-login.tsx b/framework/commerce/auth/use-login.tsx
similarity index 64%
rename from framework/commerce/use-login.tsx
rename to framework/commerce/auth/use-login.tsx
index 755e10fd9..cc4cf6a73 100644
--- a/framework/commerce/use-login.tsx
+++ b/framework/commerce/auth/use-login.tsx
@@ -1,7 +1,7 @@
-import { useHook, useMutationHook } from './utils/use-hook'
-import { mutationFetcher } from './utils/default-fetcher'
-import type { MutationHook, HookFetcherFn } from './utils/types'
-import type { Provider } from '.'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { MutationHook, HookFetcherFn } from '../utils/types'
+import type { Provider } from '..'
export type UseLogin<
H extends MutationHook = MutationHook
diff --git a/framework/commerce/use-logout.tsx b/framework/commerce/auth/use-logout.tsx
similarity index 64%
rename from framework/commerce/use-logout.tsx
rename to framework/commerce/auth/use-logout.tsx
index 0a80c318b..d0f7e3ae0 100644
--- a/framework/commerce/use-logout.tsx
+++ b/framework/commerce/auth/use-logout.tsx
@@ -1,7 +1,7 @@
-import { useHook, useMutationHook } from './utils/use-hook'
-import { mutationFetcher } from './utils/default-fetcher'
-import type { HookFetcherFn, MutationHook } from './utils/types'
-import type { Provider } from '.'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from '../utils/types'
+import type { Provider } from '..'
export type UseLogout<
H extends MutationHook = MutationHook
diff --git a/framework/commerce/use-signup.tsx b/framework/commerce/auth/use-signup.tsx
similarity index 64%
rename from framework/commerce/use-signup.tsx
rename to framework/commerce/auth/use-signup.tsx
index be3c32000..72e242209 100644
--- a/framework/commerce/use-signup.tsx
+++ b/framework/commerce/auth/use-signup.tsx
@@ -1,7 +1,7 @@
-import { useHook, useMutationHook } from './utils/use-hook'
-import { mutationFetcher } from './utils/default-fetcher'
-import type { HookFetcherFn, MutationHook } from './utils/types'
-import type { Provider } from '.'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from '../utils/types'
+import type { Provider } from '..'
export type UseSignup<
H extends MutationHook = MutationHook
diff --git a/framework/commerce/config.json b/framework/commerce/config.json
deleted file mode 100644
index a0e7afc5d..000000000
--- a/framework/commerce/config.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "features": {
- "wishlist": true
- }
-}
diff --git a/framework/commerce/use-price.tsx b/framework/commerce/product/use-price.tsx
similarity index 97%
rename from framework/commerce/use-price.tsx
rename to framework/commerce/product/use-price.tsx
index 9a3fc4b6e..9c09e3487 100644
--- a/framework/commerce/use-price.tsx
+++ b/framework/commerce/product/use-price.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react'
-import { useCommerce } from '.'
+import { useCommerce } from '..'
export function formatPrice({
amount,
diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts
index 0ae766095..57e707f35 100644
--- a/framework/commerce/types.ts
+++ b/framework/commerce/types.ts
@@ -2,8 +2,10 @@ import type { Wishlist as BCWishlist } from '@framework/api/wishlist'
import type { Customer as BCCustomer } from '@framework/api/customers'
import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products'
+export type Features = 'wishlist' | 'checkout' | string
+
export type CommerceProviderConfig = {
- features: Record
+ features: Record
}
export type Discount = {
diff --git a/framework/commerce/utils/bootstrap.js b/framework/commerce/utils/bootstrap.js
new file mode 100644
index 000000000..6a604b7dd
--- /dev/null
+++ b/framework/commerce/utils/bootstrap.js
@@ -0,0 +1,11 @@
+module.exports = ({ features }) => {
+ let output = {
+ env: {},
+ }
+ if (!!Object.keys(features).length) {
+ Object.keys(features).map(
+ (r) => (output.env[`COMMERCE_${r.toUpperCase()}_ENABLED`] = features[r])
+ )
+ }
+ return output
+}
diff --git a/framework/commerce/utils/features.ts b/framework/commerce/utils/features.ts
index d84321967..72ed0d7f5 100644
--- a/framework/commerce/utils/features.ts
+++ b/framework/commerce/utils/features.ts
@@ -1,4 +1,4 @@
-import commerceProviderConfig from '@framework/config.json'
+import commerceProviderConfig from '../config.json'
import type { CommerceProviderConfig } from '../types'
import memo from 'lodash.memoize'
@@ -14,6 +14,16 @@ function isFeatureEnabled(config: CommerceProviderConfig) {
.includes(desideredFeature)
}
+export function toEnvConfig(
+ configMap: CommerceProviderConfig['features']
+): Map {
+ let toEnvConfigMap = new Map()
+ Object.keys(configMap).map((r) =>
+ toEnvConfigMap.set(`${r.toUpperCase()}_ENABLED`, configMap[r])
+ )
+ return toEnvConfigMap
+}
+
function boostrap(): FeaturesAPI {
const basis = {
isEnabled: () => false,
diff --git a/framework/shopify/README.md b/framework/shopify/README.md
index 372e9d68f..fc6a70ce3 100644
--- a/framework/shopify/README.md
+++ b/framework/shopify/README.md
@@ -1,23 +1,260 @@
## Table of Contents
- [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)
# Shopify Storefront Data Hooks
-Collection of hooks and data fetching functions to integrate Shopify in a React application. Designed to work with [Next.js Commerce](https://commerce-theta-ashy.vercel.app).
+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/).
## Getting Started
-1. Environment variables need to be set:
+1. Install dependencies:
```
+yarn install shopify-buy
+yarn install -D @types/shopify-buy
+```
+
+3. Environment variables need to be set:
+
+```
+SHOPIFY_STORE_DOMAIN=
+SHOPIFY_STOREFRONT_ACCESS_TOKEN=
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=
```
-2. Point the framework to `shopify` by updating `tsconfig.json`:
+4. Point the framework to `shopify` by updating `tsconfig.json`:
```
"@framework/*": ["framework/shopify/*"],
"@framework": ["framework/shopify"]
```
+
+### Modifications
+
+These modifications are temporarily until contributions are made to remove them.
+
+#### Adding item to Cart
+
+```js
+// components/product/ProductView/ProductView.tsx
+const ProductView: FC = ({ product }) => {
+ const addToCart = async () => {
+ setLoading(true)
+ try {
+ await addItem({
+ productId: product.id,
+ variantId: variant ? variant.id : product.variants[0].id,
+ })
+ openSidebar()
+ setLoading(false)
+ } catch (err) {
+ setLoading(false)
+ }
+ }
+}
+```
+
+#### Proceed to Checkout
+
+```js
+// components/cart/CartSidebarView/CartSidebarView.tsx
+import { useCommerce } from '@framework'
+
+const CartSidebarView: FC = () => {
+ const { checkout } = useCommerce()
+ return (
+
+ )
+}
+```
+
+## 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.
+
+The data is fetched using the [Shopify JavaScript Buy SDK](https://github.com/Shopify/js-buy-sdk#readme). Read the [Shopify Storefront API reference](https://shopify.dev/docs/storefront-api/reference) for more information.
+
+### getProduct
+
+Get a single product by its `handle`.
+
+```js
+import getProduct from '@framework/product/get-product'
+import { getConfig } from '@framework/api'
+
+const config = getConfig()
+
+const product = await getProduct({
+ variables: { slug },
+ config,
+})
+```
+
+### getAllProducts
+
+```js
+import getAllProducts from '@framework/product/get-all-products'
+import { getConfig } from '@framework/api'
+
+const config = getConfig()
+
+const { products } = await getAllProducts({
+ variables: { first: 12 },
+ config,
+})
+```
+
+### getAllCollections
+
+```js
+import getAllCollections from '@framework/product/get-all-collections'
+import { getConfig } from '@framework/api'
+
+const config = getConfig()
+
+const collections = await getAllCollections({
+ config,
+})
+```
+
+### getAllPages
+
+```js
+import getAllPages from '@framework/common/get-all-pages'
+import { getConfig } from '@framework/api'
+
+const config = getConfig()
+
+const pages = await getAllPages({
+ variables: { first: 12 },
+ config,
+})
+```
diff --git a/framework/shopify/api/operations/get-all-collections.ts b/framework/shopify/api/operations/get-all-collections.ts
new file mode 100644
index 000000000..9cf216a91
--- /dev/null
+++ b/framework/shopify/api/operations/get-all-collections.ts
@@ -0,0 +1,21 @@
+import Client from 'shopify-buy'
+import { ShopifyConfig } from '../index'
+
+type Options = {
+ config: ShopifyConfig
+}
+
+const getAllCollections = async (options: Options) => {
+ const { config } = options
+
+ const client = Client.buildClient({
+ storefrontAccessToken: config.apiToken,
+ domain: config.commerceUrl,
+ })
+
+ const res = await client.collection.fetchAllWithProducts()
+
+ return JSON.parse(JSON.stringify(res))
+}
+
+export default getAllCollections
diff --git a/framework/shopify/api/operations/get-page.ts b/framework/shopify/api/operations/get-page.ts
new file mode 100644
index 000000000..11651e335
--- /dev/null
+++ b/framework/shopify/api/operations/get-page.ts
@@ -0,0 +1,27 @@
+import { ShopifyConfig, getConfig } from '..'
+import type { Page } from '../../types'
+
+export type { Page }
+
+export type GetPageResult = T
+
+export type PageVariables = {
+ id: string
+}
+
+async function getPage({
+ url,
+ variables,
+ config,
+ preview,
+}: {
+ url?: string
+ variables: PageVariables
+ config?: ShopifyConfig
+ preview?: boolean
+}): Promise {
+ config = getConfig(config)
+ return {}
+}
+
+export default getPage
diff --git a/framework/shopify/product/get-product.ts b/framework/shopify/product/get-product.ts
index 33f38a427..abb7834a6 100644
--- a/framework/shopify/product/get-product.ts
+++ b/framework/shopify/product/get-product.ts
@@ -1,5 +1,4 @@
import { GraphQLFetcherResult } from '@commerce/api'
-
import { getConfig, ShopifyConfig } from '../api'
import { Product } from '../schema'
import getProductQuery from '../utils/queries/get-product-query'
diff --git a/framework/shopify/utils/storage.ts b/framework/shopify/utils/storage.ts
new file mode 100644
index 000000000..d46dadb21
--- /dev/null
+++ b/framework/shopify/utils/storage.ts
@@ -0,0 +1,13 @@
+export const getCheckoutIdFromStorage = (token: string) => {
+ if (window && window.sessionStorage) {
+ return window.sessionStorage.getItem(token)
+ }
+
+ return null
+}
+
+export const setCheckoutIdInStorage = (token: string, id: string | number) => {
+ if (window && window.sessionStorage) {
+ return window.sessionStorage.setItem(token, id + '')
+ }
+}
diff --git a/framework/shopify/utils/to-commerce-products.ts b/framework/shopify/utils/to-commerce-products.ts
new file mode 100644
index 000000000..c0b411eb6
--- /dev/null
+++ b/framework/shopify/utils/to-commerce-products.ts
@@ -0,0 +1,60 @@
+import { Product, Image } from '../types'
+
+export default function toCommerceProducts(products: Product[]) {
+ return products.map((product: Product) => {
+ return {
+ id: product.id,
+ entityId: product.id,
+ name: product.title,
+ slug: product.handle,
+ title: product.title,
+ vendor: product.vendor,
+ description: product.descriptionHtml,
+ path: `/${product.handle}`,
+ price: {
+ value: +product.variants[0].price,
+ currencyCode: 'USD', // TODO
+ },
+ images: product.images.map((image: Image) => {
+ return {
+ url: image.src,
+ }
+ }),
+ variants: product.variants.map((variant) => {
+ return {
+ id: variant.id,
+ options: variant.selectedOptions.map((selectedOption) => {
+ return {
+ __typename: 'MultipleChoiceOption',
+ displayName: selectedOption.name,
+ values: [
+ {
+ node: {
+ id: variant.id,
+ label: selectedOption.value,
+ },
+ },
+ ],
+ }
+ }),
+ }
+ }),
+ productOptions: product.options.map((option) => {
+ return {
+ __typename: 'MultipleChoiceOption',
+ displayName: option.name,
+ values: option.values.map((value) => {
+ return {
+ node: {
+ entityId: 1,
+ label: value.value,
+ hexColors: [value.value],
+ },
+ }
+ }),
+ }
+ }),
+ options: [],
+ }
+ })
+}
diff --git a/next.config.js b/next.config.js
index 9ee64e00e..749fd1b0a 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,44 +1,6 @@
-module.exports = {
- images: {
- domains: ['cdn11.bigcommerce.com', 'cdn.shopify.com'],
- },
- i18n: {
- locales: ['en-US', 'es'],
- defaultLocale: 'en-US',
- },
- rewrites() {
- return [
- {
- source: '/checkout',
- destination: '/api/bigcommerce/checkout',
- },
- // The logout is also an action so this route is not required, but it's also another way
- // you can allow a logout!
- {
- source: '/logout',
- destination: '/api/bigcommerce/customers/logout?redirect_to=/',
- },
- // Rewrites for /search
- {
- source: '/search/designers/:name',
- destination: '/search',
- },
- {
- source: '/search/designers/:name/:category',
- destination: '/search',
- },
- {
- // This rewrite will also handle `/search/designers`
- source: '/search/:category',
- destination: '/search',
- },
- ]
- },
- typescript: {
- // !! WARN !!
- // Dangerously allow production builds to successfully complete even if
- // your project has type errors.
- // !! WARN !!
- ignoreBuildErrors: true,
- },
-}
+const providerConfig = require('./framework/bigcommerce/config.json')
+const providerNextConfig = require('./framework/bigcommerce/next.config')
+const bootstrap = require('./framework/commerce/utils/bootstrap')
+const d = require('deepmerge')
+
+module.exports = d(providerNextConfig, bootstrap(providerConfig))
diff --git a/package.json b/package.json
index 2d8e32772..85bd9c063 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"generate": "graphql-codegen",
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
},
+ "sideEffects": false,
"license": "MIT",
"engines": {
"node": "12.x"
@@ -45,6 +46,7 @@
"react-dom": "^17.0.1",
"react-merge-refs": "^1.1.0",
"react-ticker": "^1.2.2",
+ "shopify-buy": "^2.11.0",
"swr": "^0.4.0",
"tabbable": "^5.1.5",
"tailwindcss": "^2.0.2"
@@ -65,6 +67,7 @@
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^14.14.16",
"@types/react": "^17.0.0",
+ "deepmerge": "^4.2.2",
"graphql": "^15.4.0",
"husky": "^4.3.8",
"lint-staged": "^10.5.3",
diff --git a/pages/index.tsx b/pages/index.tsx
index acb1474be..3a84112e5 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -8,7 +8,6 @@ import { getConfig } from '@framework/api'
import getAllProducts from '@framework/product/get-all-products'
import getSiteInfo from '@framework/common/get-site-info'
import getAllPages from '@framework/common/get-all-pages'
-import Features from '@commerce/utils/features'
export async function getStaticProps({
preview,
@@ -24,7 +23,6 @@ export async function getStaticProps({
const { categories, brands } = await getSiteInfo({ config, preview })
const { pages } = await getAllPages({ config, preview })
- const isWishlistEnabled = Features.isEnabled('wishlist')
return {
props: {
@@ -32,9 +30,6 @@ export async function getStaticProps({
categories,
brands,
pages,
- commerceFeatures: {
- wishlist: isWishlistEnabled,
- },
},
revalidate: 14400,
}
@@ -44,7 +39,6 @@ export default function Home({
products,
brands,
categories,
- commerceFeatures,
}: InferGetStaticPropsType) {
return (
<>
@@ -57,7 +51,6 @@ export default function Home({
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
- wishlist={commerceFeatures.wishlist}
/>
))}
@@ -71,7 +64,6 @@ export default function Home({
width: 320,
height: 320,
}}
- wishlist={commerceFeatures.wishlist}
/>
))}
@@ -94,7 +86,6 @@ export default function Home({
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
- wishlist={commerceFeatures.wishlist}
/>
))}
@@ -108,7 +99,6 @@ export default function Home({
width: 320,
height: 320,
}}
- wishlist={commerceFeatures.wishlist}
/>
))}
diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx
index a705c001b..61420a8d9 100644
--- a/pages/product/[slug].tsx
+++ b/pages/product/[slug].tsx
@@ -11,14 +11,12 @@ import { getConfig } from '@framework/api'
import getProduct from '@framework/product/get-product'
import getAllPages from '@framework/common/get-all-pages'
import getAllProductPaths from '@framework/product/get-all-product-paths'
-import Features from '@commerce/utils/features'
export async function getStaticProps({
params,
locale,
preview,
}: GetStaticPropsContext<{ slug: string }>) {
- const isWishlistEnabled = Features.isEnabled('wishlist')
const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview })
const { product } = await getProduct({
@@ -35,9 +33,6 @@ export async function getStaticProps({
props: {
pages,
product,
- commerceFeatures: {
- wishlist: isWishlistEnabled,
- },
},
revalidate: 200,
}
@@ -62,17 +57,13 @@ export async function getStaticPaths({ locales }: GetStaticPathsContext) {
export default function Slug({
product,
- commerceFeatures,
}: InferGetStaticPropsType) {
const router = useRouter()
return router.isFallback ? (
Loading...
// TODO (BC) Add Skeleton Views
) : (
-
+
)
}
diff --git a/pages/search.tsx b/pages/search.tsx
index c9958a9f8..a05203892 100644
--- a/pages/search.tsx
+++ b/pages/search.tsx
@@ -26,14 +26,13 @@ const SORT = Object.entries({
'price-desc': 'Price: High to low',
})
-import Features from '@commerce/utils/features'
-
import {
filterQuery,
getCategoryPath,
getDesignerPath,
useSearchMeta,
} from '@lib/search'
+import { Product } from '@commerce/types'
export async function getStaticProps({
preview,
@@ -42,15 +41,11 @@ export async function getStaticProps({
const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview })
const { categories, brands } = await getSiteInfo({ config, preview })
- const isWishlistEnabled = Features.isEnabled('wishlist')
return {
props: {
pages,
categories,
brands,
- commerceFeatures: {
- wishlist: isWishlistEnabled,
- },
},
}
}
@@ -58,7 +53,6 @@ export async function getStaticProps({
export default function Search({
categories,
brands,
- commerceFeatures: { wishlist },
}: InferGetStaticPropsType) {
const [activeFilter, setActiveFilter] = useState('')
const [toggleFilter, setToggleFilter] = useState(false)
@@ -358,7 +352,6 @@ export default function Search({
width: 480,
height: 480,
}}
- wishlist={wishlist}
/>
))}
diff --git a/pages/wishlist.tsx b/pages/wishlist.tsx
index ca11152f4..ce97532b0 100644
--- a/pages/wishlist.tsx
+++ b/pages/wishlist.tsx
@@ -11,14 +11,13 @@ import { useCustomer } from '@framework/customer'
import { WishlistCard } from '@components/wishlist'
import useWishlist from '@framework/wishlist/use-wishlist'
import getAllPages from '@framework/common/get-all-pages'
-import Features from '@commerce/utils/features'
export async function getStaticProps({
preview,
locale,
}: GetStaticPropsContext) {
// Disabling page if Feature is not available
- if (Features.isEnabled('wishlist')) {
+ if (!process.env.COMMERCE_WISHLIST_ENABLED) {
return {
notFound: true,
}
diff --git a/tsconfig.json b/tsconfig.json
index 7aec4729c..ace763dbe 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,11 +16,10 @@
"jsx": "preserve",
"paths": {
"@lib/*": ["lib/*"],
- "@assets/*": ["assets/*"],
- "@config/*": ["config/*"],
- "@components/*": ["components/*"],
"@utils/*": ["utils/*"],
- "@commerce/*": ["framework/commerce/*"],
+ "@config/*": ["config/*"],
+ "@assets/*": ["assets/*"],
+ "@components/*": ["components/*"],
"@commerce": ["framework/commerce"],
"@framework/*": ["framework/shopify/*"],
"@framework": ["framework/shopify"]
diff --git a/yarn.lock b/yarn.lock
index e7cd08438..1255b9d62 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2532,6 +2532,11 @@ 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:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+ integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
defaults@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
@@ -6221,6 +6226,11 @@ shell-quote@1.7.2:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
+shopify-buy@^2.11.0:
+ version "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:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"