mirror of
https://github.com/vercel/commerce.git
synced 2025-07-25 11:11:24 +00:00
Monorepo with Turborepo (#651)
* Moved everything * Figuring out how to make imports work * Updated exports * Added missing exports * Added @vercel/commerce-local to `site` * Updated commerce config * Updated exports and commerce config * Updated commerce hoc * Fixed exports in local * Added publish config * Updated imports in site * It's actually working * Don't use debugger in dev for better speeds * Improved DX when editing packages * Set up eslint with husky * Updated prettier config * Added prettier setup to every package * Moved bigcommerce * Moved Bigcommerce to src and package updates * Updated setup of bigcommerce * Moved definitions script * Moved commercejs * Move to src * Fixed types in commercejs * Moved kibocommerce * Moved kibocommerce to src * Added package/tsconfig to kibocommerce * Fixed imports and other things * Moved ordercloud * Moved ordercloud to src * Fixed imports * Added missing prettier files * Moved Saleor * Moved Saleor to src * Fixed imports * Replaced all imports to @commerce * Added prettierignore/rc to all providers * Moved shopify to src * Build shopify in packages * Moved Spree * Moved spree to src * Updated spree * Moved swell * Moved swell to src * Fixed type imports in swell * Moved Vendure to packages * Moved vendure to src * Fixed imports in vendure * Added codegen to saleor * Updated codegen setup for shopify * Added codegen to vendure * Added codegen to kibocommerce * Added all packages to site's deps * Updated codegen setup in bigcommerce * Minor fixes * Updated providers' names in site * Updated packages based on Bel's changes * Updated turbo to latest * Fixed ts complains * Set npm engine in root * New lockfile install * remove engines * Regen lockfile * Switched from npm to yarn * Updated typesVersions in all packages * Moved dep * Updated SWR to the just released 1.2.0 * Removed "isolatedModules" from packages * Updated list of providers and default * Updated swell declaration * Removed next import from kibocommerce * Added COMMERCE_PROVIDER log * Added another log * Updated turbo config * Updated docs * Removed test logs Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
This commit is contained in:
1
packages/vendure/src/api/endpoints/cart/index.ts
Normal file
1
packages/vendure/src/api/endpoints/cart/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/catalog/index.ts
Normal file
1
packages/vendure/src/api/endpoints/catalog/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/catalog/products.ts
Normal file
1
packages/vendure/src/api/endpoints/catalog/products.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
58
packages/vendure/src/api/endpoints/checkout/index.ts
Normal file
58
packages/vendure/src/api/endpoints/checkout/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextApiHandler } from 'next'
|
||||
import { CommerceAPI, createEndpoint, GetAPISchema } from '@vercel/commerce/api'
|
||||
import { CheckoutSchema } from '@vercel/commerce/types/checkout'
|
||||
import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout'
|
||||
|
||||
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
|
||||
req,
|
||||
res,
|
||||
config,
|
||||
}) => {
|
||||
try {
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Checkout</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style='margin: 10rem auto; text-align: center; font-family: SansSerif, "Segoe UI", Helvetica; color: #888;'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style='height: 60px; width: 60px;' fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<h1>Checkout not yet implemented :(</h1>
|
||||
<p>
|
||||
See <a href='https://github.com/vercel/commerce/issues/64' target='_blank'>#64</a>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
res.status(200)
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.write(html)
|
||||
res.end()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message = 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export type CheckoutAPI = GetAPISchema<CommerceAPI, CheckoutSchema>
|
||||
|
||||
export type CheckoutEndpoint = CheckoutAPI['endpoint']
|
||||
|
||||
export const handlers: CheckoutEndpoint['handlers'] = { getCheckout }
|
||||
|
||||
const checkoutApi = createEndpoint<CheckoutAPI>({
|
||||
handler: checkoutEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default checkoutApi
|
1
packages/vendure/src/api/endpoints/customer/address.ts
Normal file
1
packages/vendure/src/api/endpoints/customer/address.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/customer/card.ts
Normal file
1
packages/vendure/src/api/endpoints/customer/card.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/customer/index.ts
Normal file
1
packages/vendure/src/api/endpoints/customer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/login/index.ts
Normal file
1
packages/vendure/src/api/endpoints/login/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/logout/index.ts
Normal file
1
packages/vendure/src/api/endpoints/logout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/signup/index.ts
Normal file
1
packages/vendure/src/api/endpoints/signup/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/vendure/src/api/endpoints/wishlist/index.tsx
Normal file
1
packages/vendure/src/api/endpoints/wishlist/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
56
packages/vendure/src/api/index.ts
Normal file
56
packages/vendure/src/api/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { CommerceAPIConfig } from '@vercel/commerce/api'
|
||||
import {
|
||||
CommerceAPI,
|
||||
getCommerceApi as commerceApi,
|
||||
} from '@vercel/commerce/api'
|
||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
|
||||
import login from './operations/login'
|
||||
import getAllPages from './operations/get-all-pages'
|
||||
import getPage from './operations/get-page'
|
||||
import getSiteInfo from './operations/get-site-info'
|
||||
import getCustomerWishlist from './operations/get-customer-wishlist'
|
||||
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 API_URL = process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
|
||||
|
||||
if (!API_URL) {
|
||||
throw new Error(
|
||||
`The environment variable NEXT_PUBLIC_VENDURE_SHOP_API_URL is missing and it's required to access your store`
|
||||
)
|
||||
}
|
||||
|
||||
const ONE_DAY = 60 * 60 * 24
|
||||
const config: VendureConfig = {
|
||||
commerceUrl: API_URL,
|
||||
apiToken: '',
|
||||
cartCookie: '',
|
||||
customerCookie: '',
|
||||
cartCookieMaxAge: ONE_DAY * 30,
|
||||
fetch: fetchGraphqlApi,
|
||||
}
|
||||
|
||||
const operations = {
|
||||
login,
|
||||
getAllPages,
|
||||
getPage,
|
||||
getSiteInfo,
|
||||
getCustomerWishlist,
|
||||
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)
|
||||
}
|
40
packages/vendure/src/api/operations/get-all-pages.ts
Normal file
40
packages/vendure/src/api/operations/get-all-pages.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { VendureConfig } from '../'
|
||||
import { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import { Provider } from '../'
|
||||
|
||||
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
|
||||
}
|
55
packages/vendure/src/api/operations/get-all-product-paths.ts
Normal file
55
packages/vendure/src/api/operations/get-all-product-paths.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@vercel/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 '@vercel/commerce/types/product'
|
||||
import { VendureConfig } from '../'
|
||||
|
||||
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?: VendureConfig
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: VendureConfig
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||
query = getAllProductPathsQuery,
|
||||
variables,
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: VendureConfig
|
||||
} = {}): 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
|
||||
}
|
46
packages/vendure/src/api/operations/get-all-products.ts
Normal file
46
packages/vendure/src/api/operations/get-all-products.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { Provider, VendureConfig } from '../'
|
||||
import { GetAllProductsQuery } from '../../../schema'
|
||||
import { normalizeSearchResult } from '../../utils/normalize'
|
||||
import { getAllProductsQuery } from '../../utils/queries/get-all-products-query'
|
||||
import { OperationContext } from '@vercel/commerce/api/operations'
|
||||
|
||||
export type ProductVariables = { first?: number }
|
||||
|
||||
export default function getAllProductsOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllProducts(opts?: {
|
||||
variables?: ProductVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<{ products: Product[] }>
|
||||
|
||||
async function getAllProducts({
|
||||
query = getAllProductsQuery,
|
||||
variables: { ...vars } = {},
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: ProductVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
const variables = {
|
||||
input: {
|
||||
take: vars.first,
|
||||
groupByProduct: true,
|
||||
},
|
||||
}
|
||||
const { data } = await config.fetch<GetAllProductsQuery>(query, {
|
||||
variables,
|
||||
})
|
||||
|
||||
return {
|
||||
products: data.search.items.map((item) => normalizeSearchResult(item)),
|
||||
}
|
||||
}
|
||||
|
||||
return getAllProducts
|
||||
}
|
23
packages/vendure/src/api/operations/get-customer-wishlist.ts
Normal file
23
packages/vendure/src/api/operations/get-customer-wishlist.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { OperationContext } from '@vercel/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
packages/vendure/src/api/operations/get-page.ts
Normal file
45
packages/vendure/src/api/operations/get-page.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { VendureConfig, Provider } from '../'
|
||||
import { OperationContext } from '@vercel/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
packages/vendure/src/api/operations/get-product.ts
Normal file
69
packages/vendure/src/api/operations/get-product.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { OperationContext } from '@vercel/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
|
||||
}
|
50
packages/vendure/src/api/operations/get-site-info.ts
Normal file
50
packages/vendure/src/api/operations/get-site-info.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Provider, VendureConfig } from '../'
|
||||
import { GetCollectionsQuery } from '../../../schema'
|
||||
import { arrayToTree } from '../../utils/array-to-tree'
|
||||
import { getCollectionsQuery } from '../../utils/queries/get-collections-query'
|
||||
import { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import { Category } from '@vercel/commerce/types/site'
|
||||
|
||||
export type GetSiteInfoResult<
|
||||
T extends { categories: any[]; brands: any[] } = {
|
||||
categories: Category[]
|
||||
brands: any[]
|
||||
}
|
||||
> = T
|
||||
|
||||
export default function getSiteInfoOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getSiteInfo({
|
||||
query = getCollectionsQuery,
|
||||
variables,
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: any
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<GetSiteInfoResult> {
|
||||
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<GetCollectionsQuery>(query, {
|
||||
variables,
|
||||
})
|
||||
const collections = data.collections?.items.map((i) => ({
|
||||
...i,
|
||||
entityId: i.id,
|
||||
path: i.slug,
|
||||
productCount: i.productVariants.totalItems,
|
||||
}))
|
||||
const categories = arrayToTree(collections).children
|
||||
const brands = [] as any[]
|
||||
|
||||
return {
|
||||
categories: categories ?? [],
|
||||
brands,
|
||||
}
|
||||
}
|
||||
|
||||
return getSiteInfo
|
||||
}
|
60
packages/vendure/src/api/operations/login.ts
Normal file
60
packages/vendure/src/api/operations/login.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@vercel/commerce/api/operations'
|
||||
import { ValidationError } from '@vercel/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
packages/vendure/src/api/utils/fetch-graphql-api.ts
Normal file
36
packages/vendure/src/api/utils/fetch-graphql-api.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@vercel/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 Vendure API' }],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
3
packages/vendure/src/api/utils/fetch.ts
Normal file
3
packages/vendure/src/api/utils/fetch.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
3
packages/vendure/src/auth/index.ts
Normal file
3
packages/vendure/src/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'
|
53
packages/vendure/src/auth/use-login.tsx
Normal file
53
packages/vendure/src/auth/use-login.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
|
||||
import { LoginHook } from '../types/login'
|
||||
import { CommerceError, ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import { LoginMutation, LoginMutationVariables } from '../../schema'
|
||||
import { loginMutation } from '../utils/mutations/log-in-mutation'
|
||||
|
||||
export default useLogin as UseLogin<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LoginHook> = {
|
||||
fetchOptions: {
|
||||
query: loginMutation,
|
||||
},
|
||||
async fetcher({ input: { email, password }, options, fetch }) {
|
||||
if (!(email && password)) {
|
||||
throw new CommerceError({
|
||||
message: 'A email and password are required to login',
|
||||
})
|
||||
}
|
||||
|
||||
const variables: LoginMutationVariables = {
|
||||
username: email,
|
||||
password,
|
||||
}
|
||||
|
||||
const { login } = await fetch<LoginMutation>({
|
||||
...options,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (login.__typename !== 'CurrentUser') {
|
||||
throw new ValidationError(login)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function login(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate()
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
35
packages/vendure/src/auth/use-logout.tsx
Normal file
35
packages/vendure/src/auth/use-logout.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import { LogoutMutation } from '../../schema'
|
||||
import { logoutMutation } from '../utils/mutations/log-out-mutation'
|
||||
import { LogoutHook } from '../types/logout'
|
||||
|
||||
export default useLogout as UseLogout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LogoutHook> = {
|
||||
fetchOptions: {
|
||||
query: logoutMutation,
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
await fetch<LogoutMutation>({
|
||||
...options,
|
||||
})
|
||||
return null
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function logout() {
|
||||
const data = await fetch()
|
||||
await mutate(null, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
71
packages/vendure/src/auth/use-signup.tsx
Normal file
71
packages/vendure/src/auth/use-signup.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import { CommerceError, ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import {
|
||||
RegisterCustomerInput,
|
||||
SignupMutation,
|
||||
SignupMutationVariables,
|
||||
} from '../../schema'
|
||||
import { signupMutation } from '../utils/mutations/sign-up-mutation'
|
||||
import { SignupHook } from '../types/signup'
|
||||
|
||||
export default useSignup as UseSignup<typeof handler>
|
||||
|
||||
export type SignupInput = {
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const handler: MutationHook<SignupHook> = {
|
||||
fetchOptions: {
|
||||
query: signupMutation,
|
||||
},
|
||||
async fetcher({
|
||||
input: { firstName, lastName, email, password },
|
||||
options,
|
||||
fetch,
|
||||
}) {
|
||||
if (!(firstName && lastName && email && password)) {
|
||||
throw new CommerceError({
|
||||
message:
|
||||
'A first name, last name, email and password are required to signup',
|
||||
})
|
||||
}
|
||||
const variables: SignupMutationVariables = {
|
||||
input: {
|
||||
firstName,
|
||||
lastName,
|
||||
emailAddress: email,
|
||||
password,
|
||||
},
|
||||
}
|
||||
const { registerCustomerAccount } = await fetch<SignupMutation>({
|
||||
...options,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (registerCustomerAccount.__typename !== 'Success') {
|
||||
throw new ValidationError(registerCustomerAccount)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function signup(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate()
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
4
packages/vendure/src/cart/index.ts
Normal file
4
packages/vendure/src/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'
|
54
packages/vendure/src/cart/use-add-item.tsx
Normal file
54
packages/vendure/src/cart/use-add-item.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import { useCallback } from 'react'
|
||||
import useCart from './use-cart'
|
||||
import { AddItemToOrderMutation } from '../../schema'
|
||||
import { normalizeCart } from '../utils/normalize'
|
||||
import { addItemToOrderMutation } from '../utils/mutations/add-item-to-order-mutation'
|
||||
import { AddItemHook } from '../types/cart'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
query: addItemToOrderMutation,
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {
|
||||
if (
|
||||
input.quantity &&
|
||||
(!Number.isInteger(input.quantity) || input.quantity! < 1)
|
||||
) {
|
||||
throw new CommerceError({
|
||||
message: 'The item quantity has to be a valid integer greater than 0',
|
||||
})
|
||||
}
|
||||
|
||||
const { addItemToOrder } = await fetch<AddItemToOrderMutation>({
|
||||
...options,
|
||||
variables: {
|
||||
quantity: input.quantity || 1,
|
||||
variantId: input.variantId,
|
||||
},
|
||||
})
|
||||
|
||||
if (addItemToOrder.__typename === 'Order') {
|
||||
return normalizeCart(addItemToOrder)
|
||||
}
|
||||
throw new CommerceError(addItemToOrder)
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
46
packages/vendure/src/cart/use-cart.tsx
Normal file
46
packages/vendure/src/cart/use-cart.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
|
||||
import { ActiveOrderQuery, CartFragment } from '../../schema'
|
||||
import { normalizeCart } from '../utils/normalize'
|
||||
import { useMemo } from 'react'
|
||||
import { getCartQuery } from '../utils/queries/get-cart-query'
|
||||
import { GetCartHook } from '../types/cart'
|
||||
|
||||
export type CartResult = {
|
||||
activeOrder?: CartFragment
|
||||
addItemToOrder?: CartFragment
|
||||
adjustOrderLine?: CartFragment
|
||||
removeOrderLine?: CartFragment
|
||||
}
|
||||
|
||||
export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCartHook> = {
|
||||
fetchOptions: {
|
||||
query: getCartQuery,
|
||||
},
|
||||
async fetcher({ input: { cartId }, options, fetch }) {
|
||||
const { activeOrder } = await fetch<ActiveOrderQuery>(options)
|
||||
return activeOrder ? normalizeCart(activeOrder) : null
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
56
packages/vendure/src/cart/use-remove-item.tsx
Normal file
56
packages/vendure/src/cart/use-remove-item.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
HookFetcherContext,
|
||||
MutationHook,
|
||||
MutationHookContext,
|
||||
SWRHook,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import useRemoveItem, {
|
||||
UseRemoveItem,
|
||||
} from '@vercel/commerce/cart/use-remove-item'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
import { Cart } from '@vercel/commerce/types/cart'
|
||||
import useCart from './use-cart'
|
||||
import {
|
||||
RemoveOrderLineMutation,
|
||||
RemoveOrderLineMutationVariables,
|
||||
} from '../../schema'
|
||||
import { normalizeCart } from '../utils/normalize'
|
||||
import { RemoveItemHook } from '../types/cart'
|
||||
import { removeOrderLineMutation } from '../utils/mutations/remove-order-line-mutation'
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<RemoveItemHook> = {
|
||||
fetchOptions: {
|
||||
query: removeOrderLineMutation,
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {
|
||||
const variables: RemoveOrderLineMutationVariables = {
|
||||
orderLineId: input.itemId,
|
||||
}
|
||||
const { removeOrderLine } = await fetch<RemoveOrderLineMutation>({
|
||||
...options,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (removeOrderLine.__typename === 'Order') {
|
||||
return normalizeCart(removeOrderLine)
|
||||
}
|
||||
throw new CommerceError(removeOrderLine)
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function removeItem(input) {
|
||||
const data = await fetch({ input: { itemId: input.id } })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
84
packages/vendure/src/cart/use-update-item.tsx
Normal file
84
packages/vendure/src/cart/use-update-item.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
HookFetcherContext,
|
||||
MutationHook,
|
||||
MutationHookContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { CommerceError, ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useUpdateItem, {
|
||||
UseUpdateItem,
|
||||
} from '@vercel/commerce/cart/use-update-item'
|
||||
import { CartItemBody, LineItem } from '@vercel/commerce/types/cart'
|
||||
import useCart from './use-cart'
|
||||
import {
|
||||
AdjustOrderLineMutation,
|
||||
AdjustOrderLineMutationVariables,
|
||||
} from '../../schema'
|
||||
import { normalizeCart } from '../utils/normalize'
|
||||
import { adjustOrderLineMutation } from '../utils/mutations/adjust-order-line-mutation'
|
||||
import { UpdateItemHook } from '../types/cart'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
: UpdateItemHook['actionInput']
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
query: adjustOrderLineMutation,
|
||||
},
|
||||
async fetcher(context: HookFetcherContext<UpdateItemHook>) {
|
||||
const { input, options, fetch } = context
|
||||
const variables: AdjustOrderLineMutationVariables = {
|
||||
quantity: input.item.quantity || 1,
|
||||
orderLineId: input.itemId,
|
||||
}
|
||||
const { adjustOrderLine } = await fetch<AdjustOrderLineMutation>({
|
||||
...options,
|
||||
variables,
|
||||
})
|
||||
|
||||
if (adjustOrderLine.__typename === 'Order') {
|
||||
return normalizeCart(adjustOrderLine)
|
||||
}
|
||||
throw new CommerceError(adjustOrderLine)
|
||||
},
|
||||
useHook:
|
||||
({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||
(
|
||||
ctx: {
|
||||
item?: LineItem
|
||||
wait?: number
|
||||
} = {}
|
||||
) => {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input: UpdateItemActionInput) {
|
||||
const itemId = item?.id
|
||||
const productId = input.productId ?? item?.productId
|
||||
const variantId = input.productId ?? item?.variantId
|
||||
if (!itemId || !productId || !variantId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
const data = await fetch({
|
||||
input: {
|
||||
item: {
|
||||
productId,
|
||||
variantId,
|
||||
quantity: input.quantity,
|
||||
},
|
||||
itemId,
|
||||
},
|
||||
})
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
16
packages/vendure/src/checkout/use-checkout.tsx
Normal file
16
packages/vendure/src/checkout/use-checkout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCheckout, {
|
||||
UseCheckout,
|
||||
} from '@vercel/commerce/checkout/use-checkout'
|
||||
|
||||
export default useCheckout as UseCheckout<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
async (input) => ({}),
|
||||
}
|
6
packages/vendure/src/commerce.config.json
Normal file
6
packages/vendure/src/commerce.config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"provider": "vendure",
|
||||
"features": {
|
||||
"wishlist": false
|
||||
}
|
||||
}
|
17
packages/vendure/src/customer/address/use-add-item.tsx
Normal file
17
packages/vendure/src/customer/address/use-add-item.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import useAddItem, {
|
||||
UseAddItem,
|
||||
} from '@vercel/commerce/customer/address/use-add-item'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() =>
|
||||
async () => ({}),
|
||||
}
|
17
packages/vendure/src/customer/card/use-add-item.tsx
Normal file
17
packages/vendure/src/customer/card/use-add-item.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import useAddItem, {
|
||||
UseAddItem,
|
||||
} from '@vercel/commerce/customer/card/use-add-item'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() =>
|
||||
async () => ({}),
|
||||
}
|
1
packages/vendure/src/customer/index.ts
Normal file
1
packages/vendure/src/customer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as useCustomer } from './use-customer'
|
37
packages/vendure/src/customer/use-customer.tsx
Normal file
37
packages/vendure/src/customer/use-customer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCustomer, {
|
||||
UseCustomer,
|
||||
} from '@vercel/commerce/customer/use-customer'
|
||||
import { ActiveCustomerQuery } from '../../schema'
|
||||
import { activeCustomerQuery } from '../utils/queries/active-customer-query'
|
||||
import { CustomerHook } from '../types/customer'
|
||||
|
||||
export default useCustomer as UseCustomer<typeof handler>
|
||||
|
||||
export const handler: SWRHook<CustomerHook> = {
|
||||
fetchOptions: {
|
||||
query: activeCustomerQuery,
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
const { activeCustomer } = await fetch<ActiveCustomerQuery>({
|
||||
...options,
|
||||
})
|
||||
return activeCustomer
|
||||
? ({
|
||||
firstName: activeCustomer.firstName ?? '',
|
||||
lastName: activeCustomer.lastName ?? '',
|
||||
email: activeCustomer.emailAddress ?? '',
|
||||
} as any)
|
||||
: null
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
return useData({
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input?.swrOptions,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
53
packages/vendure/src/fetcher.ts
Normal file
53
packages/vendure/src/fetcher.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Fetcher } from '@vercel/commerce/utils/types'
|
||||
import { FetcherError } from '@vercel/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, errors } = await res.json()
|
||||
if (errors) {
|
||||
throw await new FetcherError({ status: res.status, errors })
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
throw await getError(res)
|
||||
}
|
12
packages/vendure/src/index.tsx
Normal file
12
packages/vendure/src/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {
|
||||
getCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from '@vercel/commerce'
|
||||
import { vendureProvider, VendureProvider } from './provider'
|
||||
|
||||
export { vendureProvider }
|
||||
export type { VendureProvider }
|
||||
|
||||
export const CommerceProvider = getCommerceProvider(vendureProvider)
|
||||
|
||||
export const useCommerce = () => useCoreCommerce()
|
8
packages/vendure/src/next.config.cjs
Normal file
8
packages/vendure/src/next.config.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
images: {
|
||||
domains: ['localhost', 'demo.vendure.io'],
|
||||
},
|
||||
}
|
2
packages/vendure/src/product/index.ts
Normal file
2
packages/vendure/src/product/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as usePrice } from './use-price'
|
||||
export { default as useSearch } from './use-search'
|
2
packages/vendure/src/product/use-price.tsx
Normal file
2
packages/vendure/src/product/use-price.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from '@vercel/commerce/product/use-price'
|
||||
export { default } from '@vercel/commerce/product/use-price'
|
64
packages/vendure/src/product/use-search.tsx
Normal file
64
packages/vendure/src/product/use-search.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useSearch, { UseSearch } from '@vercel/commerce/product/use-search'
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { SearchQuery, SearchQueryVariables } from '../../schema'
|
||||
import { normalizeSearchResult } from '../utils/normalize'
|
||||
import { searchQuery } from '../utils/queries/search-query'
|
||||
import { SearchProductsHook } from '../types/product'
|
||||
|
||||
export default useSearch as UseSearch<typeof handler>
|
||||
|
||||
export type SearchProductsInput = {
|
||||
search?: string
|
||||
categoryId?: string
|
||||
brandId?: string
|
||||
sort?: string
|
||||
}
|
||||
|
||||
export type SearchProductsData = {
|
||||
products: Product[]
|
||||
found: boolean
|
||||
}
|
||||
|
||||
export const handler: SWRHook<SearchProductsHook> = {
|
||||
fetchOptions: {
|
||||
query: searchQuery,
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {
|
||||
const { categoryId, brandId } = input
|
||||
|
||||
const variables: SearchQueryVariables = {
|
||||
input: {
|
||||
term: input.search,
|
||||
collectionId: input.categoryId?.toString(),
|
||||
groupByProduct: true,
|
||||
// TODO: what is the "sort" value?
|
||||
},
|
||||
}
|
||||
const { search } = await fetch<SearchQuery>({
|
||||
query: searchQuery,
|
||||
variables,
|
||||
})
|
||||
|
||||
return {
|
||||
found: search.totalItems > 0,
|
||||
products: search.items.map((item) => normalizeSearchResult(item)) ?? [],
|
||||
}
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input = {}) => {
|
||||
return useData({
|
||||
input: [
|
||||
['search', input.search],
|
||||
['categoryId', input.categoryId],
|
||||
['brandId', input.brandId],
|
||||
['sort', input.sort],
|
||||
],
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input.swrOptions,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
22
packages/vendure/src/provider.ts
Normal file
22
packages/vendure/src/provider.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
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 = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'session',
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
customer: { useCustomer },
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
}
|
||||
|
||||
export type VendureProvider = typeof vendureProvider
|
1
packages/vendure/src/types/cart.ts
Normal file
1
packages/vendure/src/types/cart.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/cart'
|
1
packages/vendure/src/types/checkout.ts
Normal file
1
packages/vendure/src/types/checkout.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/checkout'
|
1
packages/vendure/src/types/common.ts
Normal file
1
packages/vendure/src/types/common.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/common'
|
1
packages/vendure/src/types/customer.ts
Normal file
1
packages/vendure/src/types/customer.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/customer'
|
25
packages/vendure/src/types/index.ts
Normal file
25
packages/vendure/src/types/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as Cart from './cart'
|
||||
import * as Checkout from './checkout'
|
||||
import * as Common from './common'
|
||||
import * as Customer from './customer'
|
||||
import * as Login from './login'
|
||||
import * as Logout from './logout'
|
||||
import * as Page from './page'
|
||||
import * as Product from './product'
|
||||
import * as Signup from './signup'
|
||||
import * as Site from './site'
|
||||
import * as Wishlist from './wishlist'
|
||||
|
||||
export type {
|
||||
Cart,
|
||||
Checkout,
|
||||
Common,
|
||||
Customer,
|
||||
Login,
|
||||
Logout,
|
||||
Page,
|
||||
Product,
|
||||
Signup,
|
||||
Site,
|
||||
Wishlist,
|
||||
}
|
12
packages/vendure/src/types/login.ts
Normal file
12
packages/vendure/src/types/login.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as Core from '@vercel/commerce/types/login'
|
||||
import type { LoginMutationVariables } from '../../schema'
|
||||
import { LoginBody, LoginTypes } from '@vercel/commerce/types/login'
|
||||
|
||||
export * from '@vercel/commerce/types/login'
|
||||
|
||||
export type LoginHook<T extends LoginTypes = LoginTypes> = {
|
||||
data: null
|
||||
actionInput: LoginBody
|
||||
fetcherInput: LoginBody
|
||||
body: T['body']
|
||||
}
|
1
packages/vendure/src/types/logout.ts
Normal file
1
packages/vendure/src/types/logout.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/logout'
|
1
packages/vendure/src/types/page.ts
Normal file
1
packages/vendure/src/types/page.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/page'
|
1
packages/vendure/src/types/product.ts
Normal file
1
packages/vendure/src/types/product.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/product'
|
1
packages/vendure/src/types/signup.ts
Normal file
1
packages/vendure/src/types/signup.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/signup'
|
1
packages/vendure/src/types/site.ts
Normal file
1
packages/vendure/src/types/site.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/site'
|
1
packages/vendure/src/types/wishlist.ts
Normal file
1
packages/vendure/src/types/wishlist.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@vercel/commerce/types/wishlist'
|
67
packages/vendure/src/utils/array-to-tree.ts
Normal file
67
packages/vendure/src/utils/array-to-tree.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export type HasParent = { id: string; parent?: { id: string } | null }
|
||||
export type TreeNode<T extends HasParent> = T & {
|
||||
children: Array<TreeNode<T>>
|
||||
expanded: boolean
|
||||
}
|
||||
export type RootNode<T extends HasParent> = {
|
||||
id?: string
|
||||
children: Array<TreeNode<T>>
|
||||
}
|
||||
|
||||
export function arrayToTree<T extends HasParent>(
|
||||
nodes: T[],
|
||||
currentState?: RootNode<T>
|
||||
): RootNode<T> {
|
||||
const topLevelNodes: Array<TreeNode<T>> = []
|
||||
const mappedArr: { [id: string]: TreeNode<T> } = {}
|
||||
const currentStateMap = treeToMap(currentState)
|
||||
|
||||
// First map the nodes of the array to an object -> create a hash table.
|
||||
for (const node of nodes) {
|
||||
mappedArr[node.id] = { ...(node as any), children: [] }
|
||||
}
|
||||
|
||||
for (const id of nodes.map((n) => n.id)) {
|
||||
if (mappedArr.hasOwnProperty(id)) {
|
||||
const mappedElem = mappedArr[id]
|
||||
mappedElem.expanded = currentStateMap.get(id)?.expanded ?? false
|
||||
const parent = mappedElem.parent
|
||||
if (!parent) {
|
||||
continue
|
||||
}
|
||||
// If the element is not at the root level, add it to its parent array of children.
|
||||
const parentIsRoot = !mappedArr[parent.id]
|
||||
if (!parentIsRoot) {
|
||||
if (mappedArr[parent.id]) {
|
||||
mappedArr[parent.id].children.push(mappedElem)
|
||||
} else {
|
||||
mappedArr[parent.id] = { children: [mappedElem] } as any
|
||||
}
|
||||
} else {
|
||||
topLevelNodes.push(mappedElem)
|
||||
}
|
||||
}
|
||||
}
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
const rootId = topLevelNodes.length ? topLevelNodes[0].parent!.id : undefined
|
||||
return { id: rootId, children: topLevelNodes }
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an existing tree (as generated by the arrayToTree function) into a flat
|
||||
* Map. This is used to persist certain states (e.g. `expanded`) when re-building the
|
||||
* tree.
|
||||
*/
|
||||
function treeToMap<T extends HasParent>(
|
||||
tree?: RootNode<T>
|
||||
): Map<string, TreeNode<T>> {
|
||||
const nodeMap = new Map<string, TreeNode<T>>()
|
||||
function visit(node: TreeNode<T>) {
|
||||
nodeMap.set(node.id, node)
|
||||
node.children.forEach(visit)
|
||||
}
|
||||
if (tree) {
|
||||
visit(tree as TreeNode<T>)
|
||||
}
|
||||
return nodeMap
|
||||
}
|
44
packages/vendure/src/utils/fragments/cart-fragment.ts
Normal file
44
packages/vendure/src/utils/fragments/cart-fragment.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export const cartFragment = /* GraphQL */ `
|
||||
fragment Cart on Order {
|
||||
id
|
||||
code
|
||||
createdAt
|
||||
totalQuantity
|
||||
subTotal
|
||||
subTotalWithTax
|
||||
total
|
||||
totalWithTax
|
||||
currencyCode
|
||||
customer {
|
||||
id
|
||||
}
|
||||
lines {
|
||||
id
|
||||
quantity
|
||||
linePriceWithTax
|
||||
discountedLinePriceWithTax
|
||||
unitPriceWithTax
|
||||
discountedUnitPriceWithTax
|
||||
featuredAsset {
|
||||
id
|
||||
preview
|
||||
}
|
||||
discounts {
|
||||
description
|
||||
amount
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
price
|
||||
priceWithTax
|
||||
stockLevel
|
||||
product {
|
||||
slug
|
||||
}
|
||||
productId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,23 @@
|
||||
export const searchResultFragment = /* GraphQL */ `
|
||||
fragment SearchResult on SearchResult {
|
||||
productId
|
||||
productName
|
||||
description
|
||||
slug
|
||||
sku
|
||||
currencyCode
|
||||
productAsset {
|
||||
id
|
||||
preview
|
||||
}
|
||||
priceWithTax {
|
||||
... on SinglePrice {
|
||||
value
|
||||
}
|
||||
... on PriceRange {
|
||||
min
|
||||
max
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,15 @@
|
||||
import { cartFragment } from '../fragments/cart-fragment'
|
||||
|
||||
export const addItemToOrderMutation = /* GraphQL */ `
|
||||
mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
|
||||
addItemToOrder(productVariantId: $variantId, quantity: $quantity) {
|
||||
__typename
|
||||
...Cart
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
${cartFragment}
|
||||
`
|
@@ -0,0 +1,15 @@
|
||||
import { cartFragment } from '../fragments/cart-fragment'
|
||||
|
||||
export const adjustOrderLineMutation = /* GraphQL */ `
|
||||
mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
|
||||
adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {
|
||||
__typename
|
||||
...Cart
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
${cartFragment}
|
||||
`
|
14
packages/vendure/src/utils/mutations/log-in-mutation.ts
Normal file
14
packages/vendure/src/utils/mutations/log-in-mutation.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const loginMutation = /* GraphQL */ `
|
||||
mutation login($username: String!, $password: String!) {
|
||||
login(username: $username, password: $password) {
|
||||
__typename
|
||||
... on CurrentUser {
|
||||
id
|
||||
}
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
7
packages/vendure/src/utils/mutations/log-out-mutation.ts
Normal file
7
packages/vendure/src/utils/mutations/log-out-mutation.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const logoutMutation = /* GraphQL */ `
|
||||
mutation logout {
|
||||
logout {
|
||||
success
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,15 @@
|
||||
import { cartFragment } from '../fragments/cart-fragment'
|
||||
|
||||
export const removeOrderLineMutation = /* GraphQL */ `
|
||||
mutation removeOrderLine($orderLineId: ID!) {
|
||||
removeOrderLine(orderLineId: $orderLineId) {
|
||||
__typename
|
||||
...Cart
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
${cartFragment}
|
||||
`
|
14
packages/vendure/src/utils/mutations/sign-up-mutation.ts
Normal file
14
packages/vendure/src/utils/mutations/sign-up-mutation.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const signupMutation = /* GraphQL */ `
|
||||
mutation signup($input: RegisterCustomerInput!) {
|
||||
registerCustomerAccount(input: $input) {
|
||||
__typename
|
||||
... on Success {
|
||||
success
|
||||
}
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
62
packages/vendure/src/utils/normalize.ts
Normal file
62
packages/vendure/src/utils/normalize.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { Cart } from '@vercel/commerce/types/cart'
|
||||
import { CartFragment, SearchResultFragment } from '../../schema'
|
||||
|
||||
export function normalizeSearchResult(item: SearchResultFragment): Product {
|
||||
return {
|
||||
id: item.productId,
|
||||
name: item.productName,
|
||||
description: item.description,
|
||||
slug: item.slug,
|
||||
path: item.slug,
|
||||
images: [
|
||||
{
|
||||
url: item.productAsset?.preview
|
||||
? item.productAsset?.preview + '?w=800&mode=crop'
|
||||
: '',
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
price: {
|
||||
value: (item.priceWithTax as any).min / 100,
|
||||
currencyCode: item.currencyCode,
|
||||
},
|
||||
options: [],
|
||||
sku: item.sku,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCart(order: CartFragment): Cart {
|
||||
return {
|
||||
id: order.id.toString(),
|
||||
createdAt: order.createdAt,
|
||||
taxesIncluded: true,
|
||||
lineItemsSubtotalPrice: order.subTotalWithTax / 100,
|
||||
currency: { code: order.currencyCode },
|
||||
subtotalPrice: order.subTotalWithTax / 100,
|
||||
totalPrice: order.totalWithTax / 100,
|
||||
customerId: order.customer?.id,
|
||||
lineItems: order.lines?.map((l) => ({
|
||||
id: l.id,
|
||||
name: l.productVariant.name,
|
||||
quantity: l.quantity,
|
||||
url: l.productVariant.product.slug,
|
||||
variantId: l.productVariant.id,
|
||||
productId: l.productVariant.productId,
|
||||
images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }],
|
||||
discounts: l.discounts.map((d) => ({ value: d.amount / 100 })),
|
||||
path: '',
|
||||
variant: {
|
||||
id: l.productVariant.id,
|
||||
name: l.productVariant.name,
|
||||
sku: l.productVariant.sku,
|
||||
price: l.discountedUnitPriceWithTax / 100,
|
||||
listPrice: l.unitPriceWithTax / 100,
|
||||
image: {
|
||||
url: l.featuredAsset?.preview + '?preset=thumb' || '',
|
||||
},
|
||||
requiresShipping: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
10
packages/vendure/src/utils/queries/active-customer-query.ts
Normal file
10
packages/vendure/src/utils/queries/active-customer-query.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const activeCustomerQuery = /* GraphQL */ `
|
||||
query activeCustomer {
|
||||
activeCustomer {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
emailAddress
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,9 @@
|
||||
export const getAllProductPathsQuery = /* GraphQL */ `
|
||||
query getAllProductPaths($first: Int = 100) {
|
||||
products(options: { take: $first }) {
|
||||
items {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
12
packages/vendure/src/utils/queries/get-all-products-query.ts
Normal file
12
packages/vendure/src/utils/queries/get-all-products-query.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { searchResultFragment } from '../fragments/search-result-fragment'
|
||||
|
||||
export const getAllProductsQuery = /* GraphQL */ `
|
||||
query getAllProducts($input: SearchInput!) {
|
||||
search(input: $input) {
|
||||
items {
|
||||
...SearchResult
|
||||
}
|
||||
}
|
||||
}
|
||||
${searchResultFragment}
|
||||
`
|
10
packages/vendure/src/utils/queries/get-cart-query.ts
Normal file
10
packages/vendure/src/utils/queries/get-cart-query.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { cartFragment } from '../fragments/cart-fragment'
|
||||
|
||||
export const getCartQuery = /* GraphQL */ `
|
||||
query activeOrder {
|
||||
activeOrder {
|
||||
...Cart
|
||||
}
|
||||
}
|
||||
${cartFragment}
|
||||
`
|
21
packages/vendure/src/utils/queries/get-collections-query.ts
Normal file
21
packages/vendure/src/utils/queries/get-collections-query.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const getCollectionsQuery = /* GraphQL */ `
|
||||
query getCollections {
|
||||
collections {
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
slug
|
||||
productVariants {
|
||||
totalItems
|
||||
}
|
||||
parent {
|
||||
id
|
||||
}
|
||||
children {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
41
packages/vendure/src/utils/queries/get-product-query.ts
Normal file
41
packages/vendure/src/utils/queries/get-product-query.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export const getProductQuery = /* GraphQL */ `
|
||||
query getProduct($slug: String!) {
|
||||
product(slug: $slug) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
description
|
||||
assets {
|
||||
id
|
||||
preview
|
||||
name
|
||||
}
|
||||
variants {
|
||||
id
|
||||
priceWithTax
|
||||
currencyCode
|
||||
options {
|
||||
id
|
||||
name
|
||||
code
|
||||
groupId
|
||||
group {
|
||||
id
|
||||
options {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
optionGroups {
|
||||
id
|
||||
code
|
||||
name
|
||||
options {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
13
packages/vendure/src/utils/queries/search-query.ts
Normal file
13
packages/vendure/src/utils/queries/search-query.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { searchResultFragment } from '../fragments/search-result-fragment'
|
||||
|
||||
export const searchQuery = /* GraphQL */ `
|
||||
query search($input: SearchInput!) {
|
||||
search(input: $input) {
|
||||
items {
|
||||
...SearchResult
|
||||
}
|
||||
totalItems
|
||||
}
|
||||
}
|
||||
${searchResultFragment}
|
||||
`
|
13
packages/vendure/src/wishlist/use-add-item.tsx
Normal file
13
packages/vendure/src/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
packages/vendure/src/wishlist/use-remove-item.tsx
Normal file
17
packages/vendure/src/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
|
46
packages/vendure/src/wishlist/use-wishlist.tsx
Normal file
46
packages/vendure/src/wishlist/use-wishlist.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// TODO: replace this hook and other wishlist hooks with a handler, or remove them if
|
||||
// Vendure doesn't have a built-in wishlist
|
||||
|
||||
import { HookFetcher } from '@vercel/commerce/utils/types'
|
||||
import { Product } from '../../schema'
|
||||
|
||||
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>
|
||||
swrOptions?: any
|
||||
) {
|
||||
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
||||
return { data: null }
|
||||
}
|
||||
|
||||
useWishlist.extend = extendHook
|
||||
|
||||
return useWishlist
|
||||
}
|
||||
|
||||
export default extendHook(fetcher)
|
Reference in New Issue
Block a user