mirror of
https://github.com/vercel/commerce.git
synced 2025-05-17 23:16:59 +00:00
Progress with LocalProvider
This commit is contained in:
parent
e752555e02
commit
9039a27f99
@ -38,6 +38,7 @@ const UserNav: FC<Props> = ({ className }) => {
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{process.env.COMMERCE_CUSTOMER_ENABLED && (
|
||||
<li className={s.item}>
|
||||
{customer ? (
|
||||
<DropdownMenu />
|
||||
@ -51,6 +52,7 @@ const UserNav: FC<Props> = ({ className }) => {
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ const fs = require('fs')
|
||||
const merge = require('deepmerge')
|
||||
const prettier = require('prettier')
|
||||
|
||||
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure']
|
||||
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure', 'local']
|
||||
|
||||
function getProviderName() {
|
||||
return (
|
||||
@ -69,6 +69,14 @@ function withCommerceConfig(nextConfig = {}) {
|
||||
}, exclude)
|
||||
}
|
||||
|
||||
if (process.env.COMMERCE_PROVIDER == 'local') {
|
||||
tsconfig.exclude = tsconfig.exclude.concat(
|
||||
'components/cart',
|
||||
'components/auth',
|
||||
'components/wishlist'
|
||||
)
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
tsconfigPath,
|
||||
prettier.format(JSON.stringify(tsconfig), { parser: 'json' })
|
||||
|
1
framework/local-old/.env.template
Normal file
1
framework/local-old/.env.template
Normal file
@ -0,0 +1 @@
|
||||
COMMERCE_PROVIDER=local
|
1
framework/local-old/README.md
Normal file
1
framework/local-old/README.md
Normal file
@ -0,0 +1 @@
|
||||
## Local Provider
|
32
framework/local-old/api/index.ts
Normal file
32
framework/local-old/api/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {
|
||||
CommerceAPI,
|
||||
CommerceAPIConfig,
|
||||
getCommerceApi as commerceApi,
|
||||
} from '@commerce/api'
|
||||
|
||||
export interface LocalConfig extends CommerceAPIConfig {}
|
||||
import * as operations from './operations'
|
||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
|
||||
const config: LocalConfig = {
|
||||
commerceUrl: '',
|
||||
apiToken: '',
|
||||
customerCookie: '',
|
||||
cartCookie: '',
|
||||
cartCookieMaxAge: 16000000,
|
||||
fetch: fetchGraphqlApi,
|
||||
}
|
||||
|
||||
export const provider = {
|
||||
config,
|
||||
operations,
|
||||
}
|
||||
|
||||
export type Provider = typeof provider
|
||||
export type LocalAPI<P extends Provider = Provider> = CommerceAPI<P>
|
||||
|
||||
export function getCommerceApi<P extends Provider>(
|
||||
customProvider: P = provider as any
|
||||
): LocalAPI<P> {
|
||||
return commerceApi(customProvider)
|
||||
}
|
67
framework/local-old/api/operations/get-all-pages.ts
Normal file
67
framework/local-old/api/operations/get-all-pages.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import {
|
||||
GetAllPagesQuery,
|
||||
GetAllPagesQueryVariables,
|
||||
PageEdge,
|
||||
} from '../../schema'
|
||||
import { normalizePages } from '../../utils'
|
||||
import type { ShopifyConfig, Provider } from '..'
|
||||
import type { GetAllPagesOperation, Page } from '../../types/page'
|
||||
import getAllPagesQuery from '../../utils/queries/get-all-pages-query'
|
||||
|
||||
export default function getAllPagesOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllPages<T extends GetAllPagesOperation>(opts?: {
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllPages<T extends GetAllPagesOperation>(
|
||||
opts: {
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllPages<T extends GetAllPagesOperation>({
|
||||
query = getAllPagesQuery,
|
||||
config,
|
||||
variables,
|
||||
}: {
|
||||
url?: string
|
||||
config?: Partial<ShopifyConfig>
|
||||
variables?: GetAllPagesQueryVariables
|
||||
preview?: boolean
|
||||
query?: string
|
||||
} = {}): Promise<T['data']> {
|
||||
const { fetch, locale, locales = ['en-US'] } = commerce.getConfig(config)
|
||||
|
||||
const { data } = await fetch<GetAllPagesQuery, GetAllPagesQueryVariables>(
|
||||
query,
|
||||
{
|
||||
variables,
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
pages: locales.reduce<Page[]>(
|
||||
(arr, locale) =>
|
||||
arr.concat(normalizePages(data.pages.edges as PageEdge[], locale)),
|
||||
[]
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return getAllPages
|
||||
}
|
27
framework/local-old/api/operations/get-all-product-paths.ts
Normal file
27
framework/local-old/api/operations/get-all-product-paths.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { LocalConfig, Provider } from '..'
|
||||
import type { OperationContext } from '@commerce/api/operations'
|
||||
import { GetAllProductPathsOperation } from '../../types/product'
|
||||
|
||||
export default function getAllProductPathsOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||
query,
|
||||
config,
|
||||
variables,
|
||||
}: {
|
||||
query?: string
|
||||
config?: LocalConfig
|
||||
variables?: T['variables']
|
||||
} = {}): Promise<T['data']> {
|
||||
return {
|
||||
products: [
|
||||
{
|
||||
path: `/hank`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return getAllProductPaths
|
||||
}
|
33
framework/local-old/api/operations/get-all-products.ts
Normal file
33
framework/local-old/api/operations/get-all-products.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Provider } from '..'
|
||||
import type { Product } from '@commerce/types/product'
|
||||
import type { OperationContext } from '@commerce/api/operations'
|
||||
|
||||
export default function getAllProductsOperation({}: OperationContext<Provider>) {
|
||||
async function getAllProducts(): Promise<{ products: Product[] }> {
|
||||
const product = {
|
||||
id: 'Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=',
|
||||
name: 'New Short Sleeve T-Shirt',
|
||||
description: '',
|
||||
vendor: 'Next.js',
|
||||
path: '/new-short-sleeve-t-shirt',
|
||||
slug: 'new-short-sleeve-t-shirt',
|
||||
price: { value: 25, currencyCode: 'USD' },
|
||||
images: [
|
||||
{
|
||||
url: '/assets/t-shirt-0.png',
|
||||
altText: null,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
options: [],
|
||||
}
|
||||
|
||||
return {
|
||||
products: [product],
|
||||
}
|
||||
}
|
||||
|
||||
return getAllProducts
|
||||
}
|
64
framework/local-old/api/operations/get-page.ts
Normal file
64
framework/local-old/api/operations/get-page.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import { normalizePage } from '../../utils'
|
||||
import type { ShopifyConfig, Provider } from '..'
|
||||
import {
|
||||
GetPageQuery,
|
||||
GetPageQueryVariables,
|
||||
Page as ShopifyPage,
|
||||
} from '../../schema'
|
||||
import { GetPageOperation } from '../../types/page'
|
||||
import getPageQuery from '../../utils/queries/get-page-query'
|
||||
|
||||
export default function getPageOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getPage<T extends GetPageOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getPage<T extends GetPageOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getPage<T extends GetPageOperation>({
|
||||
query = getPageQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables: T['variables']
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']> {
|
||||
const { fetch, locale = 'en-US' } = commerce.getConfig(config)
|
||||
|
||||
const {
|
||||
data: { node: page },
|
||||
} = await fetch<GetPageQuery, GetPageQueryVariables>(
|
||||
query,
|
||||
{
|
||||
variables,
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return page ? { page: normalizePage(page as ShopifyPage, locale) } : {}
|
||||
}
|
||||
|
||||
return getPage
|
||||
}
|
11
framework/local-old/api/operations/get-product.ts
Normal file
11
framework/local-old/api/operations/get-product.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { Product } from '@commerce/types/product'
|
||||
|
||||
export default function getProductOperation() {
|
||||
async function getProduct(): Promise<Product | {} | any> {
|
||||
return {
|
||||
product: [],
|
||||
}
|
||||
}
|
||||
|
||||
return getProduct
|
||||
}
|
62
framework/local-old/api/operations/get-site-info.ts
Normal file
62
framework/local-old/api/operations/get-site-info.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import { GetSiteInfoQueryVariables } from '../../schema'
|
||||
import type { ShopifyConfig, Provider } from '..'
|
||||
import { GetSiteInfoOperation } from '../../types/site'
|
||||
|
||||
import { getCategories, getBrands, getSiteInfoQuery } from '../../utils'
|
||||
|
||||
export default function getSiteInfoOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>(opts?: {
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>(
|
||||
opts: {
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>({
|
||||
query = getSiteInfoQuery,
|
||||
config,
|
||||
variables,
|
||||
}: {
|
||||
query?: string
|
||||
config?: Partial<ShopifyConfig>
|
||||
preview?: boolean
|
||||
variables?: GetSiteInfoQueryVariables
|
||||
} = {}): Promise<T['data']> {
|
||||
const cfg = commerce.getConfig(config)
|
||||
|
||||
const categories = await getCategories(cfg)
|
||||
const brands = await getBrands(cfg)
|
||||
/*
|
||||
const { fetch, locale } = cfg
|
||||
const { data } = await fetch<GetSiteInfoQuery, GetSiteInfoQueryVariables>(
|
||||
query,
|
||||
{ variables },
|
||||
{
|
||||
...(locale && {
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
*/
|
||||
|
||||
return {
|
||||
categories,
|
||||
brands,
|
||||
}
|
||||
}
|
||||
|
||||
return getSiteInfo
|
||||
}
|
7
framework/local-old/api/operations/index.ts
Normal file
7
framework/local-old/api/operations/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export { default as getAllPages } from './get-all-pages'
|
||||
export { default as getPage } from './get-page'
|
||||
export { default as getAllProducts } from './get-all-products'
|
||||
export { default as getAllProductPaths } from './get-all-product-paths'
|
||||
export { default as getProduct } from './get-product'
|
||||
export { default as getSiteInfo } from './get-site-info'
|
||||
export { default as login } from './login'
|
48
framework/local-old/api/operations/login.ts
Normal file
48
framework/local-old/api/operations/login.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type { OperationContext } from '@commerce/api/operations'
|
||||
import type { LoginOperation } from '../../types/login'
|
||||
import type { ShopifyConfig, Provider } from '..'
|
||||
import {
|
||||
customerAccessTokenCreateMutation,
|
||||
setCustomerToken,
|
||||
throwUserErrors,
|
||||
} from '../../utils'
|
||||
import { CustomerAccessTokenCreateMutation } from '../../schema'
|
||||
|
||||
export default function loginOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function login<T extends LoginOperation>({
|
||||
query = customerAccessTokenCreateMutation,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables: T['variables']
|
||||
res: ServerResponse
|
||||
config?: ShopifyConfig
|
||||
}): Promise<T['data']> {
|
||||
config = commerce.getConfig(config)
|
||||
|
||||
const {
|
||||
data: { customerAccessTokenCreate },
|
||||
} = await config.fetch<CustomerAccessTokenCreateMutation>(query, {
|
||||
variables,
|
||||
})
|
||||
|
||||
throwUserErrors(customerAccessTokenCreate?.customerUserErrors)
|
||||
|
||||
const customerAccessToken = customerAccessTokenCreate?.customerAccessToken
|
||||
const accessToken = customerAccessToken?.accessToken
|
||||
|
||||
if (accessToken) {
|
||||
setCustomerToken(accessToken)
|
||||
}
|
||||
|
||||
return {
|
||||
result: customerAccessToken?.accessToken,
|
||||
}
|
||||
}
|
||||
|
||||
return login
|
||||
}
|
45
framework/local-old/api/utils/fetch-graphql-api.ts
Normal file
45
framework/local-old/api/utils/fetch-graphql-api.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import type { GraphQLFetcher } from '@commerce/api'
|
||||
import fetch from './fetch'
|
||||
|
||||
import { API_URL, API_TOKEN } from '../../const'
|
||||
import { getError } from '../../utils/handle-fetch-response'
|
||||
|
||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||
query: string,
|
||||
{ variables } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
try {
|
||||
const res = await fetch(API_URL, {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Shopify-Storefront-Access-Token': API_TOKEN!,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const { data, errors, status } = await res.json()
|
||||
|
||||
if (errors) {
|
||||
throw getError(errors, status)
|
||||
}
|
||||
|
||||
return { data, res }
|
||||
} catch (err) {
|
||||
throw getError(
|
||||
[
|
||||
{
|
||||
message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`,
|
||||
},
|
||||
],
|
||||
500
|
||||
)
|
||||
}
|
||||
}
|
||||
export default fetchGraphqlApi
|
2
framework/local-old/api/utils/fetch.ts
Normal file
2
framework/local-old/api/utils/fetch.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
export default zeitFetch()
|
8
framework/local-old/commerce.config.json
Normal file
8
framework/local-old/commerce.config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"provider": "local",
|
||||
"features": {
|
||||
"cart": false,
|
||||
"customer": false,
|
||||
"wishlist": false
|
||||
}
|
||||
}
|
18
framework/local-old/example.json
Normal file
18
framework/local-old/example.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjA=",
|
||||
"name": "New Short Sleeve T-Shirt",
|
||||
"vendor": "Next.js",
|
||||
"path": "/new-short-sleeve-t-shirt",
|
||||
"slug": "new-short-sleeve-t-shirt",
|
||||
"price": { "value": 25, "currencyCode": "USD" },
|
||||
"images": [
|
||||
{
|
||||
"url": "https://cdn.shopify.com/s/files/1/0434/0285/4564/products/short-sleeve-t-shirt-0.png?v=1622902418",
|
||||
"altText": null,
|
||||
"width": 1000,
|
||||
"height": 1000
|
||||
}
|
||||
],
|
||||
"variants": [],
|
||||
"options": []
|
||||
}
|
37
framework/local-old/index.tsx
Normal file
37
framework/local-old/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
CommerceConfig,
|
||||
CommerceProvider as CoreCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from '@commerce'
|
||||
|
||||
import { localProvider } from './provider'
|
||||
import type { LocalProvider } from './provider'
|
||||
|
||||
export { localProvider }
|
||||
export type { LocalProvider }
|
||||
|
||||
export const localConfig: CommerceConfig = {
|
||||
locale: 'en-us',
|
||||
cartCookie: '',
|
||||
}
|
||||
|
||||
export function CommerceProvider({
|
||||
children,
|
||||
...config
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
locale: string
|
||||
} & Partial<CommerceConfig>) {
|
||||
return (
|
||||
<CoreCommerceProvider
|
||||
provider={localProvider}
|
||||
config={{ ...localConfig, ...config }}
|
||||
>
|
||||
{children}
|
||||
</CoreCommerceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCommerce = () => useCoreCommerce<LocalProvider>()
|
5
framework/local-old/next.config.js
Normal file
5
framework/local-old/next.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
}
|
64
framework/local-old/product/use-price.tsx
Normal file
64
framework/local-old/product/use-price.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useCommerce } from '..'
|
||||
|
||||
export function formatPrice({
|
||||
amount,
|
||||
currencyCode,
|
||||
locale,
|
||||
}: {
|
||||
amount: number
|
||||
currencyCode: string
|
||||
locale: string
|
||||
}) {
|
||||
const formatCurrency = new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: currencyCode,
|
||||
})
|
||||
|
||||
return formatCurrency.format(amount)
|
||||
}
|
||||
|
||||
export function formatVariantPrice({
|
||||
amount,
|
||||
baseAmount,
|
||||
currencyCode,
|
||||
locale,
|
||||
}: {
|
||||
baseAmount: number
|
||||
amount: number
|
||||
currencyCode: string
|
||||
locale: string
|
||||
}) {
|
||||
const hasDiscount = baseAmount > amount
|
||||
const formatDiscount = new Intl.NumberFormat(locale, { style: 'percent' })
|
||||
const discount = hasDiscount
|
||||
? formatDiscount.format((baseAmount - amount) / baseAmount)
|
||||
: null
|
||||
|
||||
const price = formatPrice({ amount, currencyCode, locale })
|
||||
const basePrice = hasDiscount
|
||||
? formatPrice({ amount: baseAmount, currencyCode, locale })
|
||||
: null
|
||||
|
||||
return { price, basePrice, discount }
|
||||
}
|
||||
|
||||
export default function usePrice(
|
||||
data?: {
|
||||
amount: number
|
||||
baseAmount?: number
|
||||
currencyCode: string
|
||||
} | null
|
||||
) {
|
||||
const { amount, baseAmount, currencyCode } = data ?? {}
|
||||
const { locale } = useCommerce()
|
||||
const value = useMemo(() => {
|
||||
if (typeof amount !== 'number' || !currencyCode) return ''
|
||||
|
||||
return baseAmount
|
||||
? formatVariantPrice({ amount, baseAmount, currencyCode, locale })
|
||||
: formatPrice({ amount, currencyCode, locale })
|
||||
}, [amount, baseAmount, currencyCode])
|
||||
|
||||
return typeof value === 'string' ? { price: value } : value
|
||||
}
|
20
framework/local-old/product/use-search.tsx
Normal file
20
framework/local-old/product/use-search.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import { SWRFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, SWRHook } from '../utils/types'
|
||||
import type { SearchProductsHook } from '../types/product'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseSearch<
|
||||
H extends SWRHook<SearchProductsHook<any>> = SWRHook<SearchProductsHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<SearchProductsHook> = SWRFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.products?.useSearch!
|
||||
|
||||
const useSearch: UseSearch = (input) => {
|
||||
const hook = useHook(fn)
|
||||
return useSWRHook({ fetcher, ...hook })(input)
|
||||
}
|
||||
|
||||
export default useSearch
|
11
framework/local-old/provider.ts
Normal file
11
framework/local-old/provider.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export const localProvider = {
|
||||
locale: 'en-us',
|
||||
cartCookie: '',
|
||||
fetcher: () => {},
|
||||
cart: {},
|
||||
customer: {},
|
||||
products: {},
|
||||
auth: {},
|
||||
}
|
||||
|
||||
export type LocalProvider = typeof localProvider
|
5586
framework/local-old/schema.d.ts
vendored
Normal file
5586
framework/local-old/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9702
framework/local-old/schema.graphql
Normal file
9702
framework/local-old/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
1
framework/local/.env.template
Normal file
1
framework/local/.env.template
Normal file
@ -0,0 +1 @@
|
||||
COMMERCE_PROVIDER=local
|
33
framework/local/README.md
Normal file
33
framework/local/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Vendure Storefront Data Hooks
|
||||
|
||||
UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use [Vendure](http://vendure.io/) as a headless e-commerce platform.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Clone this repo and install its dependencies with `yarn install` or `npm install`
|
||||
2. Set the Vendure provider and API URL in your `.env.local` file:
|
||||
```
|
||||
COMMERCE_PROVIDER=vendure
|
||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=https://demo.vendure.io/shop-api
|
||||
NEXT_PUBLIC_VENDURE_LOCAL_URL=/vendure-shop-api
|
||||
```
|
||||
3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. Vendure does not ship with built-in wishlist functionality.
|
||||
2. Nor does it come with any kind of blog/page-building feature. Both of these can be created as Vendure plugins, however.
|
||||
3. The entire Vendure customer flow is carried out via its GraphQL API. This means that there is no external, pre-existing checkout flow. The checkout flow must be created as part of the Next.js app. See https://github.com/vercel/commerce/issues/64 for further discusion.
|
||||
4. By default, the sign-up flow in Vendure uses email verification. This means that using the existing "sign up" flow from this project will not grant a new user the ability to authenticate, since the new account must first be verified. Again, the necessary parts to support this flow can be created as part of the Next.js app.
|
||||
|
||||
## Code generation
|
||||
|
||||
This provider makes use of GraphQL code generation. The [schema.graphql](./schema.graphql) and [schema.d.ts](./schema.d.ts) files contain the generated types & schema introspection results.
|
||||
|
||||
When developing the provider, changes to any GraphQL operations should be followed by re-generation of the types and schema files:
|
||||
|
||||
From the project root dir, run
|
||||
|
||||
```sh
|
||||
graphql-codegen --config ./framework/vendure/codegen.json
|
||||
```
|
1
framework/local/api/endpoints/cart/index.ts
Normal file
1
framework/local/api/endpoints/cart/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/catalog/index.ts
Normal file
1
framework/local/api/endpoints/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/catalog/products.ts
Normal file
1
framework/local/api/endpoints/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/checkout/index.ts
Normal file
1
framework/local/api/endpoints/checkout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/customer/index.ts
Normal file
1
framework/local/api/endpoints/customer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/login/index.ts
Normal file
1
framework/local/api/endpoints/login/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/logout/index.ts
Normal file
1
framework/local/api/endpoints/logout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/signup/index.ts
Normal file
1
framework/local/api/endpoints/signup/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
framework/local/api/endpoints/wishlist/index.tsx
Normal file
1
framework/local/api/endpoints/wishlist/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
41
framework/local/api/index.ts
Normal file
41
framework/local/api/index.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { CommerceAPIConfig } from '@commerce/api'
|
||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api'
|
||||
|
||||
import getAllPages from './operations/get-all-pages'
|
||||
import getPage from './operations/get-page'
|
||||
import getSiteInfo from './operations/get-site-info'
|
||||
import getAllProductPaths from './operations/get-all-product-paths'
|
||||
import getAllProducts from './operations/get-all-products'
|
||||
import getProduct from './operations/get-product'
|
||||
|
||||
export interface VendureConfig extends CommerceAPIConfig {}
|
||||
|
||||
const ONE_DAY = 60 * 60 * 24
|
||||
const config: VendureConfig = {
|
||||
commerceUrl: '',
|
||||
apiToken: '',
|
||||
cartCookie: '',
|
||||
customerCookie: '',
|
||||
cartCookieMaxAge: ONE_DAY * 30,
|
||||
fetch: fetchGraphqlApi,
|
||||
}
|
||||
|
||||
const operations = {
|
||||
getAllPages,
|
||||
getPage,
|
||||
getSiteInfo,
|
||||
getAllProductPaths,
|
||||
getAllProducts,
|
||||
getProduct,
|
||||
}
|
||||
|
||||
export const provider = { config, operations }
|
||||
|
||||
export type Provider = typeof provider
|
||||
|
||||
export function getCommerceApi<P extends Provider>(
|
||||
customProvider: P = provider as any
|
||||
): CommerceAPI<P> {
|
||||
return commerceApi(customProvider)
|
||||
}
|
41
framework/local/api/operations/get-all-pages.ts
Normal file
41
framework/local/api/operations/get-all-pages.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { VendureConfig } from '../'
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider } from '../../../bigcommerce/api'
|
||||
|
||||
export type Page = any
|
||||
|
||||
export type GetAllPagesResult<
|
||||
T extends { pages: any[] } = { pages: Page[] }
|
||||
> = T
|
||||
|
||||
export default function getAllPagesOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllPages(opts?: {
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<GetAllPagesResult>
|
||||
|
||||
async function getAllPages<T extends { pages: any[] }>(opts: {
|
||||
url: string
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<GetAllPagesResult<T>>
|
||||
|
||||
async function getAllPages({
|
||||
config: cfg,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<GetAllPagesResult> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
|
||||
return {
|
||||
pages: [],
|
||||
}
|
||||
}
|
||||
|
||||
return getAllPages
|
||||
}
|
52
framework/local/api/operations/get-all-product-paths.ts
Normal file
52
framework/local/api/operations/get-all-product-paths.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { OperationContext, OperationOptions } from '@commerce/api/operations'
|
||||
import type { GetAllProductPathsQuery } from '../../schema'
|
||||
import { Provider } from '../index'
|
||||
import { getAllProductPathsQuery } from '../../utils/queries/get-all-product-paths-query'
|
||||
import { GetAllProductPathsOperation } from '@commerce/types/product'
|
||||
import { BigcommerceConfig } from '../../../bigcommerce/api'
|
||||
|
||||
export type GetAllProductPathsResult = {
|
||||
products: Array<{ node: { path: string } }>
|
||||
}
|
||||
|
||||
export default function getAllProductPathsOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllProductPaths<
|
||||
T extends GetAllProductPathsOperation
|
||||
>(opts?: {
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||
query = getAllProductPathsQuery,
|
||||
variables,
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
} = {}): Promise<T['data']> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||
// required in case there's a custom `query`
|
||||
const { data } = await config.fetch<GetAllProductPathsQuery>(query, {
|
||||
variables,
|
||||
})
|
||||
const products = data.products.items
|
||||
|
||||
return {
|
||||
products: products.map((p) => ({ path: `/${p.slug}` })),
|
||||
}
|
||||
}
|
||||
|
||||
return getAllProductPaths
|
||||
}
|
13
framework/local/api/operations/get-all-products.ts
Normal file
13
framework/local/api/operations/get-all-products.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Product } from '@commerce/types/product'
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
|
||||
export type ProductVariables = { first?: number }
|
||||
|
||||
export default function getAllProductsOperation() {
|
||||
function getAllProducts(): { products: Product[] | any[] } {
|
||||
return {
|
||||
products: [],
|
||||
}
|
||||
}
|
||||
return getAllProducts
|
||||
}
|
23
framework/local/api/operations/get-customer-wishlist.ts
Normal file
23
framework/local/api/operations/get-customer-wishlist.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider, VendureConfig } from '../'
|
||||
|
||||
export default function getCustomerWishlistOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getCustomerWishlist({
|
||||
config: cfg,
|
||||
variables,
|
||||
includeProducts,
|
||||
}: {
|
||||
url?: string
|
||||
variables: any
|
||||
config?: Partial<VendureConfig>
|
||||
includeProducts?: boolean
|
||||
}): Promise<any> {
|
||||
// Not implemented as Vendure does not ship with wishlist functionality at present
|
||||
const config = commerce.getConfig(cfg)
|
||||
return { wishlist: {} }
|
||||
}
|
||||
|
||||
return getCustomerWishlist
|
||||
}
|
45
framework/local/api/operations/get-page.ts
Normal file
45
framework/local/api/operations/get-page.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { VendureConfig, Provider } from '../'
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
|
||||
export type Page = any
|
||||
|
||||
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||
|
||||
export type PageVariables = {
|
||||
id: number
|
||||
}
|
||||
|
||||
export default function getPageOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getPage(opts: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult>
|
||||
|
||||
async function getPage<T extends { page?: any }, V = any>(opts: {
|
||||
url: string
|
||||
variables: V
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult<T>>
|
||||
|
||||
async function getPage({
|
||||
url,
|
||||
variables,
|
||||
config: cfg,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
return {}
|
||||
}
|
||||
|
||||
return getPage
|
||||
}
|
69
framework/local/api/operations/get-product.ts
Normal file
69
framework/local/api/operations/get-product.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { Product } from '@commerce/types/product'
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider, VendureConfig } from '../'
|
||||
import { GetProductQuery } from '../../schema'
|
||||
import { getProductQuery } from '../../utils/queries/get-product-query'
|
||||
|
||||
export default function getProductOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getProduct({
|
||||
query = getProductQuery,
|
||||
variables,
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables: { slug: string }
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<Product | {} | any> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
|
||||
const locale = config.locale
|
||||
const { data } = await config.fetch<GetProductQuery>(query, { variables })
|
||||
const product = data.product
|
||||
|
||||
if (product) {
|
||||
const getOptionGroupName = (id: string): string => {
|
||||
return product.optionGroups.find((og) => og.id === id)!.name
|
||||
}
|
||||
return {
|
||||
product: {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
slug: product.slug,
|
||||
images: product.assets.map((a) => ({
|
||||
url: a.preview,
|
||||
alt: a.name,
|
||||
})),
|
||||
variants: product.variants.map((v) => ({
|
||||
id: v.id,
|
||||
options: v.options.map((o) => ({
|
||||
// This __typename property is required in order for the correct
|
||||
// variant selection to work, see `components/product/helpers.ts`
|
||||
// `getVariant()` function.
|
||||
__typename: 'MultipleChoiceOption',
|
||||
id: o.id,
|
||||
displayName: getOptionGroupName(o.groupId),
|
||||
values: [{ label: o.name }],
|
||||
})),
|
||||
})),
|
||||
price: {
|
||||
value: product.variants[0].priceWithTax / 100,
|
||||
currencyCode: product.variants[0].currencyCode,
|
||||
},
|
||||
options: product.optionGroups.map((og) => ({
|
||||
id: og.id,
|
||||
displayName: og.name,
|
||||
values: og.options.map((o) => ({ label: o.name })),
|
||||
})),
|
||||
} as Product,
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
return getProduct
|
||||
}
|
20
framework/local/api/operations/get-site-info.ts
Normal file
20
framework/local/api/operations/get-site-info.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Category } from '@commerce/types/site'
|
||||
|
||||
export type GetSiteInfoResult<
|
||||
T extends { categories: any[]; brands: any[] } = {
|
||||
categories: Category[]
|
||||
brands: any[]
|
||||
}
|
||||
> = T
|
||||
|
||||
export default function getSiteInfoOperation({}: OperationContext<any>) {
|
||||
function getSiteInfo(): GetSiteInfoResult {
|
||||
return {
|
||||
categories: [],
|
||||
brands: [],
|
||||
}
|
||||
}
|
||||
|
||||
return getSiteInfo
|
||||
}
|
60
framework/local/api/operations/login.ts
Normal file
60
framework/local/api/operations/login.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import type { LoginOperation } from '../../types/login'
|
||||
import type { LoginMutation } from '../../schema'
|
||||
import { Provider, VendureConfig } from '..'
|
||||
import { loginMutation } from '../../utils/mutations/log-in-mutation'
|
||||
|
||||
export default function loginOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function login<T extends LoginOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<VendureConfig>
|
||||
res: ServerResponse
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function login<T extends LoginOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<VendureConfig>
|
||||
res: ServerResponse
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function login<T extends LoginOperation>({
|
||||
query = loginMutation,
|
||||
variables,
|
||||
res: response,
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables: T['variables']
|
||||
res: ServerResponse
|
||||
config?: Partial<VendureConfig>
|
||||
}): Promise<T['data']> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
|
||||
const { data, res } = await config.fetch<LoginMutation>(query, {
|
||||
variables,
|
||||
})
|
||||
switch (data.login.__typename) {
|
||||
case 'NativeAuthStrategyError':
|
||||
case 'InvalidCredentialsError':
|
||||
case 'NotVerifiedError':
|
||||
throw new ValidationError({
|
||||
code: data.login.errorCode,
|
||||
message: data.login.message,
|
||||
})
|
||||
}
|
||||
return {
|
||||
result: data.login.id,
|
||||
}
|
||||
}
|
||||
|
||||
return login
|
||||
}
|
36
framework/local/api/utils/fetch-graphql-api.ts
Normal file
36
framework/local/api/utils/fetch-graphql-api.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@commerce/api'
|
||||
import { getCommerceApi } from '../'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||
query: string,
|
||||
{ variables, preview } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
const config = getCommerceApi().getConfig()
|
||||
const res = await fetch(config.commerceUrl, {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...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' }],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
3
framework/local/api/utils/fetch.ts
Normal file
3
framework/local/api/utils/fetch.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
3
framework/local/auth/index.ts
Normal file
3
framework/local/auth/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as useLogin } from './use-login'
|
||||
export { default as useLogout } from './use-logout'
|
||||
export { default as useSignup } from './use-signup'
|
15
framework/local/auth/use-login.tsx
Normal file
15
framework/local/auth/use-login.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
15
framework/local/auth/use-logout.tsx
Normal file
15
framework/local/auth/use-logout.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
15
framework/local/auth/use-signup.tsx
Normal file
15
framework/local/auth/use-signup.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
4
framework/local/cart/index.ts
Normal file
4
framework/local/cart/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
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 useUpdateItem } from './use-update-item'
|
15
framework/local/cart/use-add-item.tsx
Normal file
15
framework/local/cart/use-add-item.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
15
framework/local/cart/use-cart.tsx
Normal file
15
framework/local/cart/use-cart.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
15
framework/local/cart/use-remove-item.tsx
Normal file
15
framework/local/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
15
framework/local/cart/use-update-item.tsx
Normal file
15
framework/local/cart/use-update-item.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
28
framework/local/codegen.json
Normal file
28
framework/local/codegen.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"schema": {
|
||||
"http://localhost:3001/shop-api": {}
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"./framework/vendure/**/*.{ts,tsx}": {
|
||||
"noRequire": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"generates": {
|
||||
"./framework/vendure/schema.d.ts": {
|
||||
"plugins": ["typescript", "typescript-operations"],
|
||||
"config": {
|
||||
"scalars": {
|
||||
"ID": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"./framework/vendure/schema.graphql": {
|
||||
"plugins": ["schema-ast"]
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"afterAllFileWrite": ["prettier --write"]
|
||||
}
|
||||
}
|
6
framework/local/commerce.config.json
Normal file
6
framework/local/commerce.config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"provider": "local",
|
||||
"features": {
|
||||
"wishlist": false
|
||||
}
|
||||
}
|
1
framework/local/customer/index.ts
Normal file
1
framework/local/customer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as useCustomer } from './use-customer'
|
14
framework/local/customer/use-customer.tsx
Normal file
14
framework/local/customer/use-customer.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
51
framework/local/fetcher.ts
Normal file
51
framework/local/fetcher.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Fetcher } from '@commerce/utils/types'
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
|
||||
async function getText(res: Response) {
|
||||
try {
|
||||
return (await res.text()) || res.statusText
|
||||
} catch (error) {
|
||||
return res.statusText
|
||||
}
|
||||
}
|
||||
|
||||
async function getError(res: Response) {
|
||||
if (res.headers.get('Content-Type')?.includes('application/json')) {
|
||||
const data = await res.json()
|
||||
return new FetcherError({ errors: data.errors, status: res.status })
|
||||
}
|
||||
return new FetcherError({ message: await getText(res), status: res.status })
|
||||
}
|
||||
|
||||
export const fetcher: Fetcher = async ({
|
||||
url,
|
||||
method = 'POST',
|
||||
variables,
|
||||
query,
|
||||
body: bodyObj,
|
||||
}) => {
|
||||
const shopApiUrl =
|
||||
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL ||
|
||||
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
|
||||
if (!shopApiUrl) {
|
||||
throw new Error(
|
||||
'The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local'
|
||||
)
|
||||
}
|
||||
const hasBody = Boolean(variables || query)
|
||||
const body = hasBody ? JSON.stringify({ query, variables }) : undefined
|
||||
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
||||
const res = await fetch(shopApiUrl, {
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
throw await getError(res)
|
||||
}
|
33
framework/local/index.tsx
Normal file
33
framework/local/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import * as React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import {
|
||||
CommerceConfig,
|
||||
CommerceProvider as CoreCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from '@commerce'
|
||||
import { vendureProvider } from './provider'
|
||||
|
||||
export const vendureConfig: CommerceConfig = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'session',
|
||||
}
|
||||
|
||||
export type VendureConfig = Partial<CommerceConfig>
|
||||
|
||||
export type VendureProps = {
|
||||
children?: ReactNode
|
||||
locale: string
|
||||
} & VendureConfig
|
||||
|
||||
export function CommerceProvider({ children, ...config }: VendureProps) {
|
||||
return (
|
||||
<CoreCommerceProvider
|
||||
provider={vendureProvider}
|
||||
config={{ ...vendureConfig, ...config }}
|
||||
>
|
||||
{children}
|
||||
</CoreCommerceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCommerce = () => useCoreCommerce()
|
8
framework/local/next.config.js
Normal file
8
framework/local/next.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
images: {
|
||||
domains: ['localhost', 'demo.vendure.io'],
|
||||
},
|
||||
}
|
2
framework/local/product/index.ts
Normal file
2
framework/local/product/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as usePrice } from './use-price'
|
||||
export { default as useSearch } from './use-search'
|
13
framework/local/product/use-price.tsx
Normal file
13
framework/local/product/use-price.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
|
15
framework/local/product/use-search.tsx
Normal file
15
framework/local/product/use-search.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export const handler = {}
|
||||
|
||||
export default emptyHook
|
21
framework/local/provider.ts
Normal file
21
framework/local/provider.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Provider } from '@commerce'
|
||||
import { handler as useCart } from './cart/use-cart'
|
||||
import { handler as useAddItem } from './cart/use-add-item'
|
||||
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||
import { handler as useCustomer } from './customer/use-customer'
|
||||
import { handler as useSearch } from './product/use-search'
|
||||
import { handler as useLogin } from './auth/use-login'
|
||||
import { handler as useLogout } from './auth/use-logout'
|
||||
import { handler as useSignup } from './auth/use-signup'
|
||||
import { fetcher } from './fetcher'
|
||||
|
||||
export const vendureProvider: Provider = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'session',
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
customer: { useCustomer },
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
}
|
3257
framework/local/schema.d.ts
vendored
Normal file
3257
framework/local/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4162
framework/local/schema.graphql
Normal file
4162
framework/local/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
13
framework/local/wishlist/use-add-item.tsx
Normal file
13
framework/local/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
|
13
framework/local/wishlist/use-remove-item.tsx
Normal file
13
framework/local/wishlist/use-remove-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
|
13
framework/local/wishlist/use-wishlist.tsx
Normal file
13
framework/local/wishlist/use-wishlist.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
|
@ -12,6 +12,13 @@ export async function getStaticProps({
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext) {
|
||||
// Disabling page if Feature is not available
|
||||
if (!process.env.COMMERCE_CART_ENABLED) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
const config = { locale, locales }
|
||||
const { pages } = await commerce.getAllPages({ config, preview })
|
||||
const { categories } = await commerce.getSiteInfo({ config, preview })
|
||||
|
@ -22,10 +22,26 @@
|
||||
"@components/*": ["components/*"],
|
||||
"@commerce": ["framework/commerce"],
|
||||
"@commerce/*": ["framework/commerce/*"],
|
||||
"@framework": ["framework/shopify"],
|
||||
"@framework/*": ["framework/shopify/*"]
|
||||
"@framework": ["framework/local"],
|
||||
"@framework/*": ["framework/local/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
"exclude": ["node_modules", "framework/swell", "framework/vendure"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"framework/swell",
|
||||
"framework/vendure",
|
||||
"components/cart",
|
||||
"components/auth",
|
||||
"components/wishlist",
|
||||
"components/cart",
|
||||
"components/auth",
|
||||
"components/wishlist",
|
||||
"components/cart",
|
||||
"components/auth",
|
||||
"components/wishlist",
|
||||
"components/cart",
|
||||
"components/auth",
|
||||
"components/wishlist"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user