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"