forked from crowetic/commerce
Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic
This commit is contained in:
commit
4a1a1a515f
@ -4,3 +4,6 @@ BIGCOMMERCE_STORE_API_URL=
|
|||||||
BIGCOMMERCE_STORE_API_TOKEN=
|
BIGCOMMERCE_STORE_API_TOKEN=
|
||||||
BIGCOMMERCE_STORE_API_CLIENT_ID=
|
BIGCOMMERCE_STORE_API_CLIENT_ID=
|
||||||
BIGCOMMERCE_CHANNEL_ID=
|
BIGCOMMERCE_CHANNEL_ID=
|
||||||
|
|
||||||
|
SHOPIFY_STORE_DOMAIN=
|
||||||
|
SHOPIFY_STOREFRONT_ACCESS_TOKEN=
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
## Changelog
|
|
||||||
|
|
||||||
- Select Variants Working
|
|
||||||
- Click on cart item title, closes the sidebar
|
|
@ -4,11 +4,11 @@ import cn from 'classnames'
|
|||||||
import type { LineItem } from '@framework/types'
|
import type { LineItem } from '@framework/types'
|
||||||
import useCart from '@framework/cart/use-cart'
|
import useCart from '@framework/cart/use-cart'
|
||||||
import useCustomer from '@framework/customer/use-customer'
|
import useCustomer from '@framework/customer/use-customer'
|
||||||
|
import { Avatar } from '@components/common'
|
||||||
import { Heart, Bag } from '@components/icons'
|
import { Heart, Bag } from '@components/icons'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import DropdownMenu from './DropdownMenu'
|
import DropdownMenu from './DropdownMenu'
|
||||||
import s from './UserNav.module.css'
|
import s from './UserNav.module.css'
|
||||||
import { Avatar } from '@components/common'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -4,9 +4,8 @@ import { NextSeo } from 'next-seo'
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import s from './ProductView.module.css'
|
import s from './ProductView.module.css'
|
||||||
|
|
||||||
import { useUI } from '@components/ui'
|
|
||||||
import { Swatch, ProductSlider } from '@components/product'
|
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 type { Product } from '@commerce/types'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
@ -100,7 +99,6 @@ const ProductView: FC<Props> = ({ product }) => {
|
|||||||
</ProductSlider>
|
</ProductSlider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={s.sidebar}>
|
<div className={s.sidebar}>
|
||||||
<section>
|
<section>
|
||||||
{product.options?.map((opt) => (
|
{product.options?.map((opt) => (
|
||||||
|
@ -8,6 +8,7 @@ export interface State {
|
|||||||
displayToast: boolean
|
displayToast: boolean
|
||||||
modalView: string
|
modalView: string
|
||||||
toastText: string
|
toastText: string
|
||||||
|
userAvatar: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -17,6 +18,7 @@ const initialState = {
|
|||||||
modalView: 'LOGIN_VIEW',
|
modalView: 'LOGIN_VIEW',
|
||||||
displayToast: false,
|
displayToast: false,
|
||||||
toastText: '',
|
toastText: '',
|
||||||
|
userAvatar: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { FC, useState } from 'react'
|
import React, { FC, useState } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { Heart } from '@components/icons'
|
|
||||||
|
|
||||||
import { useUI } from '@components/ui'
|
import { useUI } from '@components/ui'
|
||||||
import type { Product, ProductVariant } from '@commerce/types'
|
import type { Product, ProductVariant } from '@commerce/types'
|
||||||
import useCustomer from '@framework/customer/use-customer'
|
|
||||||
import useAddItem from '@framework/wishlist/use-add-item'
|
import useAddItem from '@framework/wishlist/use-add-item'
|
||||||
|
import useCustomer from '@framework/customer/use-customer'
|
||||||
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
||||||
import useWishlist from '@framework/wishlist/use-wishlist'
|
import useWishlist from '@framework/wishlist/use-wishlist'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"features": {
|
"features": {
|
||||||
"wishlist": false
|
"wishlist": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
260
framework/shopify/README.md
Normal file
260
framework/shopify/README.md
Normal file
@ -0,0 +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://demo.vercel.store/).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
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=
|
||||||
|
```
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
```
|
1
framework/shopify/api/cart/index.ts
Normal file
1
framework/shopify/api/cart/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/catalog/index.ts
Normal file
1
framework/shopify/api/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/catalog/products.ts
Normal file
1
framework/shopify/api/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/checkout/index.ts
Normal file
1
framework/shopify/api/checkout/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/customers/index.ts
Normal file
1
framework/shopify/api/customers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/customers/login.ts
Normal file
1
framework/shopify/api/customers/login.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/customers/logout.ts
Normal file
1
framework/shopify/api/customers/logout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
1
framework/shopify/api/customers/signup.ts
Normal file
1
framework/shopify/api/customers/signup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function () {}
|
60
framework/shopify/api/index.ts
Normal file
60
framework/shopify/api/index.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type { CommerceAPIConfig } from '@commerce/api'
|
||||||
|
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||||
|
|
||||||
|
export interface ShopifyConfig extends CommerceAPIConfig {}
|
||||||
|
|
||||||
|
// TODO(bc)
|
||||||
|
const API_URL =
|
||||||
|
process.env.SHOPIFY_STORE_DOMAIN ||
|
||||||
|
process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
|
||||||
|
const API_TOKEN =
|
||||||
|
process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN ||
|
||||||
|
process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN
|
||||||
|
|
||||||
|
if (!API_URL) {
|
||||||
|
throw new Error(
|
||||||
|
`The environment variable SHOPIFY_STORE_DOMAIN is missing and it's required to access your store`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!API_TOKEN) {
|
||||||
|
throw new Error(
|
||||||
|
`The environment variable SHOPIFY_STOREFRONT_ACCESS_TOKEN is missing and it's required to access your store`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Config {
|
||||||
|
private config: ShopifyConfig
|
||||||
|
|
||||||
|
constructor(config: ShopifyConfig) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(userConfig: Partial<ShopifyConfig> = {}) {
|
||||||
|
return Object.entries(userConfig).reduce<ShopifyConfig>(
|
||||||
|
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||||
|
{ ...this.config }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(newConfig: Partial<ShopifyConfig>) {
|
||||||
|
Object.assign(this.config, newConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = new Config({
|
||||||
|
commerceUrl: API_URL,
|
||||||
|
apiToken: API_TOKEN,
|
||||||
|
// TODO
|
||||||
|
// @ts-ignore
|
||||||
|
fetch: fetchGraphqlApi,
|
||||||
|
customerCookie: 'SHOP_TOKEN',
|
||||||
|
})
|
||||||
|
|
||||||
|
export function getConfig(userConfig?: Partial<ShopifyConfig>) {
|
||||||
|
return config.getConfig(userConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setConfig(newConfig: Partial<ShopifyConfig>) {
|
||||||
|
return config.setConfig(newConfig)
|
||||||
|
}
|
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
|
51
framework/shopify/api/utils/fetch-graphql-api.ts
Normal file
51
framework/shopify/api/utils/fetch-graphql-api.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { CommerceAPIFetchOptions } from '@commerce/api'
|
||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
import { getConfig } from '../index'
|
||||||
|
|
||||||
|
export interface GraphQLFetcherResult<Data = any> {
|
||||||
|
data: Data
|
||||||
|
res: Response
|
||||||
|
}
|
||||||
|
export type GraphQLFetcher<
|
||||||
|
Data extends GraphQLFetcherResult = GraphQLFetcherResult,
|
||||||
|
Variables = any
|
||||||
|
> = (
|
||||||
|
query: string,
|
||||||
|
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||||
|
fetchOptions?: RequestInit
|
||||||
|
) => Promise<Data>
|
||||||
|
|
||||||
|
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||||
|
query: string,
|
||||||
|
{ variables } = {},
|
||||||
|
fetchOptions
|
||||||
|
) => {
|
||||||
|
const config = getConfig()
|
||||||
|
const url = `https://${config.commerceUrl}/api/2020-10/graphql.json`
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
...fetchOptions,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Shopify-Storefront-Access-Token': config.apiToken,
|
||||||
|
...fetchOptions?.headers,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await res.json()
|
||||||
|
if (json.errors) {
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: json.errors ?? [{ message: 'Failed to fetch Shopify API' }],
|
||||||
|
status: res.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: json.data, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchGraphqlApi
|
2
framework/shopify/api/wishlist/index.tsx
Normal file
2
framework/shopify/api/wishlist/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type WishlistItem = { product: any; id: number }
|
||||||
|
export default function () {}
|
13
framework/shopify/auth/use-login.tsx
Normal file
13
framework/shopify/auth/use-login.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
13
framework/shopify/auth/use-logout.tsx
Normal file
13
framework/shopify/auth/use-logout.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
13
framework/shopify/auth/use-signup.tsx
Normal file
13
framework/shopify/auth/use-signup.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
5
framework/shopify/cart/index.ts
Normal file
5
framework/shopify/cart/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { default as useCart } from './use-cart'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
// export { default as useWishlistActions } from './use-cart-actions'
|
||||||
|
// export { default as useUpdateItem } from './use-cart-actions'
|
30
framework/shopify/cart/use-add-item.tsx
Normal file
30
framework/shopify/cart/use-add-item.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { LineItemToAdd } from 'shopify-buy'
|
||||||
|
import { useCommerce } from '../index'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
productId: number
|
||||||
|
variantId: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAddItem = () => {
|
||||||
|
const { checkout, client, updateCheckout } = useCommerce()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(options: Options) {
|
||||||
|
const lineItems: LineItemToAdd[] = [
|
||||||
|
{
|
||||||
|
variantId: `${options.variantId}`,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const cart = await client?.checkout.addLineItems(checkout.id, lineItems)
|
||||||
|
updateCheckout(cart)
|
||||||
|
return cart
|
||||||
|
},
|
||||||
|
[checkout, client]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useAddItem
|
42
framework/shopify/cart/use-cart.tsx
Normal file
42
framework/shopify/cart/use-cart.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useCommerce } from '../index'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const { checkout } = useCommerce()
|
||||||
|
const { lineItems, totalPriceV2 } = checkout || {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
subTotal: totalPriceV2?.amount || 0,
|
||||||
|
total: totalPriceV2?.amount || 0,
|
||||||
|
currency: {
|
||||||
|
code: '',
|
||||||
|
},
|
||||||
|
line_items:
|
||||||
|
lineItems?.map((item) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: item.id,
|
||||||
|
name: item.title,
|
||||||
|
quantity: item.quantity,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}) || [],
|
||||||
|
items:
|
||||||
|
lineItems?.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.title,
|
||||||
|
images: [{ url: '/jacket.png' }],
|
||||||
|
url: '/',
|
||||||
|
quantity: item.quantity,
|
||||||
|
productId: item.id,
|
||||||
|
variantId: item.id,
|
||||||
|
}
|
||||||
|
}) || [],
|
||||||
|
},
|
||||||
|
isEmpty: false,
|
||||||
|
isLoading: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
17
framework/shopify/cart/use-remove-item.tsx
Normal file
17
framework/shopify/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useCommerce } from '../index'
|
||||||
|
|
||||||
|
const useRemoveItem = () => {
|
||||||
|
const { checkout, client, updateCheckout } = useCommerce()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function removeItem({ id }: { id: string }) {
|
||||||
|
const cart = await client?.checkout.removeLineItems(checkout.id, [id])
|
||||||
|
updateCheckout(cart)
|
||||||
|
return cart
|
||||||
|
},
|
||||||
|
[checkout, client]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useRemoveItem
|
24
framework/shopify/cart/use-update-item.tsx
Normal file
24
framework/shopify/cart/use-update-item.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useCommerce } from '../index'
|
||||||
|
|
||||||
|
const useUpdateItem = (item: CartItem) => {
|
||||||
|
const { checkout, client, updateCheckout } = useCommerce()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function updateItem({ quantity }: { quantity: number }) {
|
||||||
|
const lineItemsToUpdate = [{ id: item.id, quantity }]
|
||||||
|
|
||||||
|
const cart = await client?.checkout.updateLineItems(
|
||||||
|
checkout.id,
|
||||||
|
lineItemsToUpdate
|
||||||
|
)
|
||||||
|
|
||||||
|
updateCheckout(cart)
|
||||||
|
|
||||||
|
return cart
|
||||||
|
},
|
||||||
|
[checkout, client]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useUpdateItem
|
55
framework/shopify/common/get-all-pages.ts
Normal file
55
framework/shopify/common/get-all-pages.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { getConfig, ShopifyConfig } from '../api'
|
||||||
|
import { Page as PageType, PageEdge } from '../types'
|
||||||
|
|
||||||
|
export type Page = PageType
|
||||||
|
|
||||||
|
export const getAllPagesQuery = /* GraphQL */ `
|
||||||
|
query($first: Int!) {
|
||||||
|
pages(first: $first) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
handle
|
||||||
|
body
|
||||||
|
bodySummary
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
first?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
variables?: Variables
|
||||||
|
config: ShopifyConfig
|
||||||
|
preview?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
pages: Page[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllPages = async (options?: Options): Promise<ReturnType> => {
|
||||||
|
let { config, variables = { first: 250 } } = options || {}
|
||||||
|
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const { data } = await config.fetch(getAllPagesQuery, { variables })
|
||||||
|
|
||||||
|
const pages = data.pages.edges.map(({ node }: PageEdge) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
name: node.handle,
|
||||||
|
url: `${config!.locale}/${node.handle}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { pages }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllPages
|
30
framework/shopify/common/get-site-info.ts
Normal file
30
framework/shopify/common/get-site-info.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ShopifyConfig } from '../index'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
config: ShopifyConfig
|
||||||
|
preview?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSiteInfo = async (options: Options) => {
|
||||||
|
// TODO
|
||||||
|
return {
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: '',
|
||||||
|
entityId: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
brands: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
path: '',
|
||||||
|
name: '',
|
||||||
|
entityId: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSiteInfo
|
32
framework/shopify/customer/use-customer.tsx
Normal file
32
framework/shopify/customer/use-customer.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import type { SwrOptions } from '@commerce/utils/use-data'
|
||||||
|
import useCommerceCustomer from '@commerce/customer/use-customer'
|
||||||
|
|
||||||
|
const defaultOpts = {}
|
||||||
|
|
||||||
|
export type Customer = {
|
||||||
|
entityId: number
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
export type CustomerData = {}
|
||||||
|
|
||||||
|
export const fetcher: HookFetcher<Customer | null> = async () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
swrOptions?: SwrOptions<Customer | null>
|
||||||
|
) {
|
||||||
|
const useCustomer = () => {
|
||||||
|
return { data: { firstName: null, lastName: null, email: null } }
|
||||||
|
}
|
||||||
|
|
||||||
|
useCustomer.extend = extendHook
|
||||||
|
|
||||||
|
return useCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extendHook(fetcher)
|
109
framework/shopify/index.tsx
Normal file
109
framework/shopify/index.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
} from 'react'
|
||||||
|
import Client from 'shopify-buy'
|
||||||
|
import { Shop, Cart, Client as ClientType } from './types'
|
||||||
|
import {
|
||||||
|
getCheckoutIdFromStorage,
|
||||||
|
setCheckoutIdInStorage,
|
||||||
|
} from './utils/storage'
|
||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
|
||||||
|
const Commerce = createContext<CommerceContextValue | {}>({})
|
||||||
|
|
||||||
|
type CommerceProps = {
|
||||||
|
children?: ReactNode
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommerceContextValue = {
|
||||||
|
client: ClientType
|
||||||
|
shop: Shop
|
||||||
|
checkout: Cart
|
||||||
|
updateCheckout: (cart: Cart | undefined) => void
|
||||||
|
currencyCode: string
|
||||||
|
locale: string
|
||||||
|
sessionToken: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommerceProvider({
|
||||||
|
children,
|
||||||
|
locale = 'en-US',
|
||||||
|
}: CommerceProps) {
|
||||||
|
const sessionToken = 'nextjs-commerce-shopify-token'
|
||||||
|
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
const client = Client.buildClient({
|
||||||
|
storefrontAccessToken: config.apiToken,
|
||||||
|
domain: config.commerceUrl,
|
||||||
|
language: locale,
|
||||||
|
}) as ClientType
|
||||||
|
|
||||||
|
const [shop, setShop] = useState<Shop>()
|
||||||
|
const [checkout, setCheckout] = useState<Cart>()
|
||||||
|
|
||||||
|
const fetchShopify = async () => {
|
||||||
|
const shopInfo: Shop = await client.shop.fetchInfo()
|
||||||
|
let checkoutResource: Cart
|
||||||
|
|
||||||
|
const checkoutOptions = {
|
||||||
|
presentmentCurrencyCode:
|
||||||
|
/*config.currencyCode ||*/ shopInfo?.currencyCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkoutId = getCheckoutIdFromStorage(sessionToken)
|
||||||
|
|
||||||
|
// we could have a cart id stored in session storage
|
||||||
|
// user could be refreshing or navigating back and forth
|
||||||
|
if (checkoutId) {
|
||||||
|
checkoutResource = await client.checkout.fetch(checkoutId)
|
||||||
|
|
||||||
|
// could be expired order - we will create a new order
|
||||||
|
if (checkoutResource.completedAt) {
|
||||||
|
checkoutResource = await client.checkout.create(checkoutOptions)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkoutResource = await client.checkout.create(checkoutOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCheckoutIdInStorage(sessionToken, checkoutResource.id)
|
||||||
|
|
||||||
|
setShop(shopInfo)
|
||||||
|
setCheckout(checkoutResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchShopify()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const updateCheckout = (newCheckout: Cart) => {
|
||||||
|
setCheckout(newCheckout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because the config is an object, if the parent re-renders this provider
|
||||||
|
// will re-render every consumer unless we memoize the config
|
||||||
|
const cfg = useMemo(
|
||||||
|
() => ({
|
||||||
|
client,
|
||||||
|
checkout,
|
||||||
|
shop,
|
||||||
|
updateCheckout: updateCheckout,
|
||||||
|
currencyCode: /*config.currencyCode ||*/ checkout?.currencyCode,
|
||||||
|
locale,
|
||||||
|
sessionToken,
|
||||||
|
}),
|
||||||
|
[client]
|
||||||
|
)
|
||||||
|
|
||||||
|
return <Commerce.Provider value={cfg}>{children}</Commerce.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCommerce<T extends CommerceContextValue>() {
|
||||||
|
return useContext(Commerce) as T
|
||||||
|
}
|
31
framework/shopify/product/get-all-product-paths.ts
Normal file
31
framework/shopify/product/get-all-product-paths.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import Client from 'shopify-buy'
|
||||||
|
import { getConfig } from '../api'
|
||||||
|
import { Product } from '../types'
|
||||||
|
import toCommerceProducts from '../utils/to-commerce-products'
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
products: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllProductPaths = async (): Promise<ReturnType> => {
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
const client = Client.buildClient({
|
||||||
|
storefrontAccessToken: config.apiToken,
|
||||||
|
domain: config.commerceUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = (await client.product.fetchAll()) as Product[]
|
||||||
|
|
||||||
|
const products = toCommerceProducts(res)
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: products.map((product) => {
|
||||||
|
return {
|
||||||
|
node: { ...product },
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProductPaths
|
40
framework/shopify/product/get-all-products.ts
Normal file
40
framework/shopify/product/get-all-products.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import Client from 'shopify-buy'
|
||||||
|
import { ShopifyConfig } from '../api'
|
||||||
|
import { Product } from '../types'
|
||||||
|
import toCommerceProducts from '../utils/to-commerce-products'
|
||||||
|
|
||||||
|
export type ProductNode = Product
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
first?: number
|
||||||
|
field?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
variables: Variables
|
||||||
|
config: ShopifyConfig
|
||||||
|
preview?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
products: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllProducts = async (options: Options): Promise<ReturnType> => {
|
||||||
|
const { config } = options
|
||||||
|
|
||||||
|
const client = Client.buildClient({
|
||||||
|
storefrontAccessToken: config.apiToken,
|
||||||
|
domain: config.commerceUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = (await client.product.fetchAll()) as Product[]
|
||||||
|
|
||||||
|
const products = toCommerceProducts(res)
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProducts
|
37
framework/shopify/product/get-product.ts
Normal file
37
framework/shopify/product/get-product.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import Client from 'shopify-buy'
|
||||||
|
import { ShopifyConfig } from '../api'
|
||||||
|
import { Product } from '../types'
|
||||||
|
import toCommerceProducts from '../utils/to-commerce-products'
|
||||||
|
|
||||||
|
export type ProductNode = Product
|
||||||
|
|
||||||
|
type Variables = {
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
variables: Variables
|
||||||
|
config: ShopifyConfig
|
||||||
|
preview?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReturnType = {
|
||||||
|
product: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProduct = async (options: Options): Promise<ReturnType> => {
|
||||||
|
const { variables, config } = options
|
||||||
|
|
||||||
|
const client = Client.buildClient({
|
||||||
|
storefrontAccessToken: config.apiToken,
|
||||||
|
domain: config.commerceUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = (await client.product.fetchByHandle(variables.slug)) as Product
|
||||||
|
|
||||||
|
return {
|
||||||
|
product: toCommerceProducts([res])[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getProduct
|
2
framework/shopify/product/use-price.tsx
Normal file
2
framework/shopify/product/use-price.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from '@commerce/use-price'
|
||||||
|
export { default } from '@commerce/use-price'
|
41
framework/shopify/product/use-search.tsx
Normal file
41
framework/shopify/product/use-search.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import type { SwrOptions } from '@commerce/utils/use-data'
|
||||||
|
import useCommerceSearch from '@commerce/product/use-search'
|
||||||
|
import { ProductEdge } from '../types'
|
||||||
|
|
||||||
|
const defaultOpts = {}
|
||||||
|
|
||||||
|
export type SearchProductsInput = {
|
||||||
|
search?: string
|
||||||
|
categoryId?: number
|
||||||
|
brandId?: number
|
||||||
|
sort?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SearchProductsData = {
|
||||||
|
products: ProductEdge[]
|
||||||
|
found: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: HookFetcher<SearchProductsData, SearchProductsInput> = (
|
||||||
|
options,
|
||||||
|
{ search, categoryId, brandId, sort },
|
||||||
|
fetch
|
||||||
|
) => {
|
||||||
|
return { found: false, products: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
swrOptions?: SwrOptions<SearchProductsData, SearchProductsInput>
|
||||||
|
) {
|
||||||
|
const useSearch = (input: SearchProductsInput = {}) => {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
useSearch.extend = extendHook
|
||||||
|
|
||||||
|
return useSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extendHook(fetcher)
|
130
framework/shopify/types.ts
Normal file
130
framework/shopify/types.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import {
|
||||||
|
Product as BaseProduct,
|
||||||
|
ProductVariant as BaseProductVariant,
|
||||||
|
Cart as BaseCart,
|
||||||
|
CheckoutResource as BaseCheckoutResource,
|
||||||
|
AttributeInput,
|
||||||
|
Client as BaseClient,
|
||||||
|
Shop as BaseShop,
|
||||||
|
Image as BaseImage,
|
||||||
|
} from 'shopify-buy'
|
||||||
|
|
||||||
|
export type SelectedOptions = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PresentmentPrice = {
|
||||||
|
price: PriceV2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductVariant = BaseProductVariant & {
|
||||||
|
selectedOptions: Array<SelectedOptions>
|
||||||
|
presentmentPrices: Array<PresentmentPrice>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
export type ProductOptions = {
|
||||||
|
node: {
|
||||||
|
__typename: string
|
||||||
|
displayName: string
|
||||||
|
values: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
label: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
export type ProductEdge = {
|
||||||
|
node: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Product = BaseProduct & {
|
||||||
|
handle: string
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
entityId: number
|
||||||
|
descriptionHtml: string
|
||||||
|
prices: {
|
||||||
|
price: {
|
||||||
|
value: number
|
||||||
|
currencyCode: string
|
||||||
|
}
|
||||||
|
retailPrice: {
|
||||||
|
value: number
|
||||||
|
currencyCode: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
images: {
|
||||||
|
edges: [{ node: { urlOriginal: string; altText: string } }]
|
||||||
|
}
|
||||||
|
productOptions: ProductOptions
|
||||||
|
variants: Array<ProductVariant> & {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
productOptions: ProductOptions[]
|
||||||
|
entityId: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PriceV2 = {
|
||||||
|
amount: number
|
||||||
|
currencyCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Cart = BaseCart & {
|
||||||
|
webUrl?: string
|
||||||
|
currencyCode?: string
|
||||||
|
lineItemsSubtotalPrice?: PriceV2
|
||||||
|
totalPriceV2?: PriceV2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Shop = BaseShop & {
|
||||||
|
currencyCode?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Create = {
|
||||||
|
presentmentCurrencyCode?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckoutResource = BaseCheckoutResource & {
|
||||||
|
updateLineItems(
|
||||||
|
checkoutId: string | number,
|
||||||
|
lineItems: AttributeInput[]
|
||||||
|
): Promise<Cart>
|
||||||
|
|
||||||
|
create: (input: Create) => Promise<Cart>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Client = BaseClient & {
|
||||||
|
checkout: CheckoutResource
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Page = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
name: string
|
||||||
|
handle: string
|
||||||
|
body: string
|
||||||
|
bodySummary: string
|
||||||
|
url: string
|
||||||
|
sort_order: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageEdge = {
|
||||||
|
node: Page
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Image = BaseImage
|
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: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
13
framework/shopify/wishlist/use-add-item.tsx
Normal file
13
framework/shopify/wishlist/use-add-item.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function emptyHook() {
|
||||||
|
const useEmptyHook = async (options = {}) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
17
framework/shopify/wishlist/use-remove-item.tsx
Normal file
17
framework/shopify/wishlist/use-remove-item.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emptyHook(options?: Options) {
|
||||||
|
const useEmptyHook = async ({ id }: { id: string | number }) => {
|
||||||
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return useEmptyHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emptyHook
|
45
framework/shopify/wishlist/use-wishlist.tsx
Normal file
45
framework/shopify/wishlist/use-wishlist.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { HookFetcher } from '@commerce/utils/types'
|
||||||
|
import { SwrOptions } from '@commerce/utils/use-data'
|
||||||
|
import useCommerceWishlist from '@commerce/wishlist/use-wishlist'
|
||||||
|
import { Product } from '../types'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
|
const defaultOpts = {}
|
||||||
|
|
||||||
|
export type Wishlist = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
id: number
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistOptions {
|
||||||
|
includeProducts?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseWishlistInput extends UseWishlistOptions {
|
||||||
|
customerId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
|
||||||
|
) {
|
||||||
|
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
||||||
|
return { data: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
useWishlist.extend = extendHook
|
||||||
|
|
||||||
|
return useWishlist
|
||||||
|
}
|
||||||
|
|
||||||
|
export default extendHook(fetcher)
|
@ -44,6 +44,7 @@
|
|||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-merge-refs": "^1.1.0",
|
"react-merge-refs": "^1.1.0",
|
||||||
"react-ticker": "^1.2.2",
|
"react-ticker": "^1.2.2",
|
||||||
|
"shopify-buy": "^2.11.0",
|
||||||
"swr": "^0.4.0",
|
"swr": "^0.4.0",
|
||||||
"tabbable": "^5.1.5",
|
"tabbable": "^5.1.5",
|
||||||
"tailwindcss": "^2.0.2"
|
"tailwindcss": "^2.0.2"
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@lib/*": ["lib/*"],
|
"@lib/*": ["lib/*"],
|
||||||
"@assets/*": ["assets/*"],
|
|
||||||
"@config/*": ["config/*"],
|
|
||||||
"@components/*": ["components/*"],
|
|
||||||
"@utils/*": ["utils/*"],
|
"@utils/*": ["utils/*"],
|
||||||
"@commerce/*": ["framework/commerce/*"],
|
"@config/*": ["config/*"],
|
||||||
|
"@assets/*": ["assets/*"],
|
||||||
|
"@components/*": ["components/*"],
|
||||||
"@commerce": ["framework/commerce"],
|
"@commerce": ["framework/commerce"],
|
||||||
"@framework/*": ["framework/bigcommerce/*"],
|
"@commerce/*": ["framework/commerce/*"],
|
||||||
"@framework": ["framework/bigcommerce"]
|
"@framework": ["framework/bigcommerce"],
|
||||||
|
"@framework/*": ["framework/bigcommerce/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||||
|
@ -6214,6 +6214,11 @@ shell-quote@1.7.2:
|
|||||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||||
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
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:
|
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user