mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 22:42:33 +00:00
Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic
This commit is contained in:
commit
ce2a410cb3
@ -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=
|
||||
BIGCOMMERCE_STORE_API_CLIENT_ID=
|
||||
SHOPIFY_STORE_DOMAIN=
|
||||
SHOPIFY_STOREFRONT_ACCESS_TOKEN=
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode"]
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
## Changelog
|
||||
|
||||
- Select Variants Working
|
||||
- Click on cart item title, closes the sidebar
|
@ -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`
|
||||
|
@ -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 }) => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<UserNav wishlist={wishlist} />
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -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<Props> = ({
|
||||
categories,
|
||||
brands,
|
||||
products = [],
|
||||
wishlist = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className={s.root}>
|
||||
@ -65,7 +62,6 @@ const HomeAllProductsGrid: FC<Props> = ({
|
||||
width: 480,
|
||||
height: 480,
|
||||
}}
|
||||
wishlist={wishlist}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
|
@ -58,11 +58,10 @@ const Layout: FC<Props> = ({
|
||||
} = useUI()
|
||||
const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
|
||||
const { locale = 'en-US' } = useRouter()
|
||||
const isWishlistEnabled = commerceFeatures?.wishlist
|
||||
return (
|
||||
<CommerceProvider locale={locale}>
|
||||
<div className={cn(s.root)}>
|
||||
<Navbar wishlist={isWishlistEnabled} />
|
||||
<Navbar />
|
||||
<main className="fit">{children}</main>
|
||||
<Footer pages={pageProps.pages} />
|
||||
|
||||
@ -73,7 +72,7 @@ const Layout: FC<Props> = ({
|
||||
</Modal>
|
||||
|
||||
<Sidebar open={displaySidebar} onClose={closeSidebar}>
|
||||
<CartSidebarView wishlist={isWishlistEnabled} />
|
||||
<CartSidebarView />
|
||||
</Sidebar>
|
||||
|
||||
<FeatureBar
|
||||
|
@ -5,7 +5,7 @@ import { Searchbar, UserNav } from '@components/common'
|
||||
import NavbarRoot from './NavbarRoot'
|
||||
import s from './Navbar.module.css'
|
||||
|
||||
const Navbar: FC<{ wishlist?: boolean }> = ({ wishlist }) => (
|
||||
const Navbar: FC = () => (
|
||||
<NavbarRoot>
|
||||
<Container>
|
||||
<div className="relative flex flex-row justify-between py-4 align-center md:py-6">
|
||||
@ -36,7 +36,7 @@ const Navbar: FC<{ wishlist?: boolean }> = ({ wishlist }) => (
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end flex-1 space-x-8">
|
||||
<UserNav wishlist={wishlist} />
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -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<Props> = ({ className, wishlist = false }) => {
|
||||
const UserNav: FC<Props> = ({ className }) => {
|
||||
const { data } = useCart()
|
||||
const { data: customer } = useCustomer()
|
||||
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()
|
||||
@ -31,7 +30,7 @@ const UserNav: FC<Props> = ({ className, wishlist = false }) => {
|
||||
<Bag />
|
||||
{itemsCount > 0 && <span className={s.bagCount}>{itemsCount}</span>}
|
||||
</li>
|
||||
{wishlist && (
|
||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||
<li className={s.item}>
|
||||
<Link href="/wishlist">
|
||||
<a onClick={closeSidebarIfPresent} aria-label="Wishlist">
|
||||
|
@ -11,7 +11,6 @@ interface Props {
|
||||
product: Product
|
||||
variant?: 'slim' | 'simple'
|
||||
imgProps?: Omit<ImageProps, 'src'>
|
||||
wishlist?: boolean
|
||||
}
|
||||
|
||||
const placeholderImg = '/product-img-placeholder.svg'
|
||||
@ -21,7 +20,6 @@ const ProductCard: FC<Props> = ({
|
||||
product,
|
||||
variant,
|
||||
imgProps,
|
||||
wishlist = false,
|
||||
...props
|
||||
}) => (
|
||||
<Link href={`/product/${product.slug}`} {...props}>
|
||||
@ -59,11 +57,11 @@ const ProductCard: FC<Props> = ({
|
||||
{product.price.currencyCode}
|
||||
</span>
|
||||
</div>
|
||||
{wishlist && (
|
||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={product.id}
|
||||
variant={product.variants[0]}
|
||||
variant={product.variants[0] as any}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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<Props> = ({ product, wishlist = false }) => {
|
||||
const ProductView: FC<Props> = ({ product }) => {
|
||||
const addItem = useAddItem()
|
||||
const { price } = usePrice({
|
||||
amount: product.price.value,
|
||||
@ -101,7 +99,6 @@ const ProductView: FC<Props> = ({ product, wishlist = false }) => {
|
||||
</ProductSlider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.sidebar}>
|
||||
<section>
|
||||
{product.options?.map((opt) => (
|
||||
@ -152,11 +149,11 @@ const ProductView: FC<Props> = ({ product, wishlist = false }) => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{wishlist && (
|
||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={product.id}
|
||||
variant={product.variants[0]!}
|
||||
variant={product.variants[0]! as any}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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 =
|
||||
|
@ -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<Props> = ({
|
||||
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) => {
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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<typeof handler>
|
||||
|
@ -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'
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"features": {
|
||||
"wishlist": false
|
||||
"wishlist": true
|
||||
}
|
||||
}
|
||||
|
37
framework/bigcommerce/next.config.js
Normal file
37
framework/bigcommerce/next.config.js
Normal file
@ -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',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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 }
|
||||
}
|
@ -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<any, any, any> = MutationHook<null, {}, {}>
|
@ -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<any, any, any> = MutationHook<null>
|
@ -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<any, any, any> = MutationHook<null>
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"features": {
|
||||
"wishlist": true
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useCommerce } from '.'
|
||||
import { useCommerce } from '..'
|
||||
|
||||
export function formatPrice({
|
||||
amount,
|
@ -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<string, boolean>
|
||||
features: Record<Features, boolean>
|
||||
}
|
||||
|
||||
export type Discount = {
|
||||
|
11
framework/commerce/utils/bootstrap.js
vendored
Normal file
11
framework/commerce/utils/bootstrap.js
vendored
Normal file
@ -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
|
||||
}
|
@ -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<string, boolean> {
|
||||
let toEnvConfigMap = new Map<string, boolean>()
|
||||
Object.keys(configMap).map((r) =>
|
||||
toEnvConfigMap.set(`${r.toUpperCase()}_ENABLED`, configMap[r])
|
||||
)
|
||||
return toEnvConfigMap
|
||||
}
|
||||
|
||||
function boostrap(): FeaturesAPI {
|
||||
const basis = {
|
||||
isEnabled: () => false,
|
||||
|
@ -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<Props> = ({ 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 (
|
||||
<Button href={checkout.webUrl} Component="a" width="100%">
|
||||
Proceed to Checkout
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## General Usage
|
||||
|
||||
### CommerceProvider
|
||||
|
||||
Provider component that creates the commerce context for children.
|
||||
|
||||
```js
|
||||
import { CommerceProvider } from '@framework'
|
||||
|
||||
const App = ({ children }) => {
|
||||
return <CommerceProvider locale={locale}>{children}</CommerceProvider>
|
||||
}
|
||||
|
||||
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 <button onClick={addToCart}>Add To Cart</button>
|
||||
}
|
||||
```
|
||||
|
||||
### useRemoveItem
|
||||
|
||||
```js
|
||||
import { useRemoveItem } from '@framework/cart'
|
||||
|
||||
const RemoveButton = ({ item }) => {
|
||||
const removeItem = useRemoveItem()
|
||||
|
||||
const handleRemove = async () => {
|
||||
await removeItem({ id: item.id })
|
||||
}
|
||||
|
||||
return <button onClick={handleRemove}>Remove</button>
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<input
|
||||
type="number"
|
||||
max={99}
|
||||
min={0}
|
||||
value={quantity}
|
||||
onChange={updateQuantity}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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,
|
||||
})
|
||||
```
|
||||
|
21
framework/shopify/api/operations/get-all-collections.ts
Normal file
21
framework/shopify/api/operations/get-all-collections.ts
Normal file
@ -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
|
27
framework/shopify/api/operations/get-page.ts
Normal file
27
framework/shopify/api/operations/get-page.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { ShopifyConfig, getConfig } from '..'
|
||||
import type { Page } from '../../types'
|
||||
|
||||
export type { Page }
|
||||
|
||||
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||
|
||||
export type PageVariables = {
|
||||
id: string
|
||||
}
|
||||
|
||||
async function getPage({
|
||||
url,
|
||||
variables,
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult> {
|
||||
config = getConfig(config)
|
||||
return {}
|
||||
}
|
||||
|
||||
export default getPage
|
@ -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'
|
||||
|
13
framework/shopify/utils/storage.ts
Normal file
13
framework/shopify/utils/storage.ts
Normal file
@ -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 + '')
|
||||
}
|
||||
}
|
60
framework/shopify/utils/to-commerce-products.ts
Normal file
60
framework/shopify/utils/to-commerce-products.ts
Normal file
@ -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: [],
|
||||
}
|
||||
})
|
||||
}
|
@ -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))
|
||||
|
@ -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",
|
||||
|
@ -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<typeof getStaticProps>) {
|
||||
return (
|
||||
<>
|
||||
@ -57,7 +51,6 @@ export default function Home({
|
||||
width: i === 0 ? 1080 : 540,
|
||||
height: i === 0 ? 1080 : 540,
|
||||
}}
|
||||
wishlist={commerceFeatures.wishlist}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
@ -71,7 +64,6 @@ export default function Home({
|
||||
width: 320,
|
||||
height: 320,
|
||||
}}
|
||||
wishlist={commerceFeatures.wishlist}
|
||||
/>
|
||||
))}
|
||||
</Marquee>
|
||||
@ -94,7 +86,6 @@ export default function Home({
|
||||
width: i === 0 ? 1080 : 540,
|
||||
height: i === 0 ? 1080 : 540,
|
||||
}}
|
||||
wishlist={commerceFeatures.wishlist}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
@ -108,7 +99,6 @@ export default function Home({
|
||||
width: 320,
|
||||
height: 320,
|
||||
}}
|
||||
wishlist={commerceFeatures.wishlist}
|
||||
/>
|
||||
))}
|
||||
</Marquee>
|
||||
|
@ -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<typeof getStaticProps>) {
|
||||
const router = useRouter()
|
||||
|
||||
return router.isFallback ? (
|
||||
<h1>Loading...</h1> // TODO (BC) Add Skeleton Views
|
||||
) : (
|
||||
<ProductView
|
||||
product={product as any}
|
||||
wishlist={commerceFeatures.wishlist}
|
||||
/>
|
||||
<ProductView product={product as any} />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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<typeof getStaticProps>) {
|
||||
const [activeFilter, setActiveFilter] = useState('')
|
||||
const [toggleFilter, setToggleFilter] = useState(false)
|
||||
@ -358,7 +352,6 @@ export default function Search({
|
||||
width: 480,
|
||||
height: 480,
|
||||
}}
|
||||
wishlist={wishlist}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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"]
|
||||
|
10
yarn.lock
10
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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user