mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +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:
4
packages/vendure/.env.template
Normal file
4
packages/vendure/.env.template
Normal file
@@ -0,0 +1,4 @@
|
||||
COMMERCE_PROVIDER=vendure
|
||||
|
||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=http://localhost:3001/shop-api
|
||||
NEXT_PUBLIC_VENDURE_LOCAL_URL=/vendure-shop-api
|
2
packages/vendure/.prettierignore
Normal file
2
packages/vendure/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
6
packages/vendure/.prettierrc
Normal file
6
packages/vendure/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
37
packages/vendure/README.md
Normal file
37
packages/vendure/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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`.
|
||||
|
||||
**Note:** The Vendure server needs to be configured to use the "cookie" tokenMethod rather than "bearer" to work with this provider. For more information see the [Managing Sessions docs](https://www.vendure.io/docs/storefront/managing-sessions/).
|
||||
|
||||
## 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 package dir, run
|
||||
|
||||
```sh
|
||||
yarn generate
|
||||
# or
|
||||
npm run generate
|
||||
```
|
28
packages/vendure/codegen.json
Normal file
28
packages/vendure/codegen.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"schema": {
|
||||
"http://localhost:3001/shop-api": {}
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"./src/**/*.{ts,tsx}": {
|
||||
"noRequire": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"generates": {
|
||||
"./schema.d.ts": {
|
||||
"plugins": ["typescript", "typescript-operations"],
|
||||
"config": {
|
||||
"scalars": {
|
||||
"ID": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"./schema.graphql": {
|
||||
"plugins": ["schema-ast"]
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"afterAllFileWrite": ["prettier --write"]
|
||||
}
|
||||
}
|
78
packages/vendure/package.json
Normal file
78
packages/vendure/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@vercel/commerce-vendure",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "rm -fr dist/* && tsc",
|
||||
"dev": "npm run build -- --watch",
|
||||
"prettier-fix": "prettier --write .",
|
||||
"generate": "graphql-codegen"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./*": [
|
||||
"./dist/*.js",
|
||||
"./dist/*/index.js"
|
||||
],
|
||||
"./next.config": "./dist/next.config.cjs"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*",
|
||||
"src/*/index"
|
||||
],
|
||||
"next.config": [
|
||||
"dist/next.config.d.cts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"schema.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"dist/*.d.ts",
|
||||
"dist/*/index.d.ts"
|
||||
],
|
||||
"next.config": [
|
||||
"dist/next.config.d.cts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/commerce": "^0.0.1",
|
||||
"@vercel/fetch": "^6.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12",
|
||||
"react": "^17",
|
||||
"react-dom": "^17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.3.1",
|
||||
"@graphql-codegen/schema-ast": "^2.4.1",
|
||||
"@graphql-codegen/typescript": "^2.4.2",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.2",
|
||||
"@types/node": "^17.0.8",
|
||||
"@types/react": "^17.0.38",
|
||||
"lint-staged": "^12.1.7",
|
||||
"next": "^12.0.8",
|
||||
"prettier": "^2.5.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx,json}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
3257
packages/vendure/schema.d.ts
vendored
Normal file
3257
packages/vendure/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4162
packages/vendure/schema.graphql
Normal file
4162
packages/vendure/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
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)
|
21
packages/vendure/tsconfig.json
Normal file
21
packages/vendure/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"outDir": "dist",
|
||||
"baseUrl": "src",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"declaration": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"incremental": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Reference in New Issue
Block a user