mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
Update Vendure provider to latest changes
This commit is contained in:
parent
41e59e9a85
commit
23c3412c17
@ -4,8 +4,6 @@ UI hooks and data fetching methods built from the ground up for e-commerce appli
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
As this is still under development, no npm package is yet provided. Here's how you can try it out:
|
|
||||||
|
|
||||||
1. First you'll need a Vendure server. You can your own local server up-and-running with just a single command:
|
1. First you'll need a Vendure server. You can your own local server up-and-running with just a single command:
|
||||||
```shell
|
```shell
|
||||||
npx @vendure/create my-app
|
npx @vendure/create my-app
|
||||||
@ -25,30 +23,41 @@ As this is still under development, no npm package is yet provided. Here's how y
|
|||||||
3. Clone this repo and install its dependencies with `yarn install` or `npm install`
|
3. Clone this repo and install its dependencies with `yarn install` or `npm install`
|
||||||
4. Change the paths in [tsconfig.json](../../tsconfig.json) to point to the Vendure hooks:
|
4. Change the paths in [tsconfig.json](../../tsconfig.json) to point to the Vendure hooks:
|
||||||
```diff
|
```diff
|
||||||
"paths": {
|
|
||||||
"@lib/*": ["lib/*"],
|
|
||||||
"@assets/*": ["assets/*"],
|
|
||||||
"@config/*": ["config/*"],
|
|
||||||
"@components/*": ["components/*"],
|
|
||||||
"@utils/*": ["utils/*"],
|
|
||||||
"@commerce/*": ["framework/commerce/*"],
|
|
||||||
"@commerce": ["framework/commerce"],
|
|
||||||
- "@framework/*": ["framework/bigcommerce/*"],
|
- "@framework/*": ["framework/bigcommerce/*"],
|
||||||
- "@framework": ["framework/bigcommerce"]
|
- "@framework": ["framework/bigcommerce"]
|
||||||
+ "@framework/*": ["framework/vendure/*"],
|
+ "@framework/*": ["framework/vendure/*"],
|
||||||
+ "@framework": ["framework/vendure"]
|
+ "@framework": ["framework/vendure"]
|
||||||
}
|
|
||||||
```
|
```
|
||||||
5. Set the Vendure Shop API URL in your `.env.local` file:
|
5. Set the Vendure Shop API URL in your `.env.local` file:
|
||||||
```sh
|
```sh
|
||||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=http://localhost:3001/shop-api
|
NEXT_PUBLIC_VENDURE_SHOP_API_URL=http://localhost:3001/shop-api
|
||||||
```
|
```
|
||||||
6. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
6. Add the `localhost` domain to the `images` property next.config.js file as per the [image optimization docs](https://nextjs.org/docs/basic-features/image-optimization#domains)
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
// ...
|
||||||
|
images: {
|
||||||
|
domains: ['example.com'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
7. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
||||||
|
|
||||||
## Known Limitations
|
## Known Limitations
|
||||||
|
|
||||||
1. Vendure does not ship with built-in wishlist functionality.
|
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.
|
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.
|
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.
|
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.
|
||||||
5. The mapping of products & variants may not totally match up with what the storefront expects. As the storefront matures and improves this mapping can be refined.
|
|
||||||
|
## Code generation
|
||||||
|
|
||||||
|
This provider makes use of GraphQL code generation. The [schema.graphql](./schema.graphql) and [schema.d.ts](./schema.d.ts) files contain the generated types & schema introspection results.
|
||||||
|
|
||||||
|
When developing the provider, changes to any GraphQL operations should be followed by re-generation of the types and schema files:
|
||||||
|
|
||||||
|
From the project root dir, run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
graphql-codegen --config ./framework/vendure/codegen.json
|
||||||
|
```
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { BigcommerceConfig, getConfig } from '../../bigcommerce/api'
|
import { BigcommerceConfig, getConfig } from '../../bigcommerce/api'
|
||||||
import { NextApiHandler } from 'next'
|
import { NextApiHandler } from 'next'
|
||||||
|
|
||||||
const checkoutApi= async (req: any, res: any, config: any) => {
|
const checkoutApi = async (req: any, res: any, config: any) => {
|
||||||
try {
|
try {
|
||||||
// TODO: make the embedded checkout work too!
|
|
||||||
const html = `
|
const html = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -15,6 +14,9 @@ const checkoutApi= async (req: any, res: any, config: any) => {
|
|||||||
<body>
|
<body>
|
||||||
<div style='margin: 10rem auto; text-align: center; font-family: SansSerif, "Segoe UI", Helvetica'>
|
<div style='margin: 10rem auto; text-align: center; font-family: SansSerif, "Segoe UI", Helvetica'>
|
||||||
<h1>Checkout not implemented :(</h1>
|
<h1>Checkout not implemented :(</h1>
|
||||||
|
<p>
|
||||||
|
See <a href='https://github.com/vercel/commerce/issues/64' target='_blank'>#64</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -33,11 +35,7 @@ const checkoutApi= async (req: any, res: any, config: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApiHandler<
|
export function createApiHandler<T = any, H = {}, Options extends {} = {}>(
|
||||||
T = any,
|
|
||||||
H = {},
|
|
||||||
Options extends {} = {}
|
|
||||||
>(
|
|
||||||
handler: any,
|
handler: any,
|
||||||
handlers: H,
|
handlers: H,
|
||||||
defaultOptions: Options
|
defaultOptions: Options
|
||||||
|
@ -2,6 +2,7 @@ export const cartFragment = /* GraphQL */ `
|
|||||||
fragment Cart on Order {
|
fragment Cart on Order {
|
||||||
id
|
id
|
||||||
code
|
code
|
||||||
|
createdAt
|
||||||
totalQuantity
|
totalQuantity
|
||||||
subTotal
|
subTotal
|
||||||
subTotalWithTax
|
subTotalWithTax
|
||||||
@ -14,13 +15,23 @@ export const cartFragment = /* GraphQL */ `
|
|||||||
lines {
|
lines {
|
||||||
id
|
id
|
||||||
quantity
|
quantity
|
||||||
|
linePriceWithTax
|
||||||
|
discountedLinePriceWithTax
|
||||||
featuredAsset {
|
featuredAsset {
|
||||||
id
|
id
|
||||||
preview
|
preview
|
||||||
}
|
}
|
||||||
|
discounts {
|
||||||
|
description
|
||||||
|
amount
|
||||||
|
}
|
||||||
productVariant {
|
productVariant {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
sku
|
||||||
|
price
|
||||||
|
priceWithTax
|
||||||
|
stockLevel
|
||||||
product {
|
product {
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
type Header = string | number | string[] | undefined
|
|
||||||
|
|
||||||
export default function concatHeader(prev: Header, val: Header) {
|
|
||||||
if (!val) return prev
|
|
||||||
if (!prev) return val
|
|
||||||
|
|
||||||
if (Array.isArray(prev)) return prev.concat(String(val))
|
|
||||||
|
|
||||||
prev = String(prev)
|
|
||||||
|
|
||||||
if (Array.isArray(val)) return [prev].concat(val)
|
|
||||||
|
|
||||||
return [prev, String(val)]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export { default as useLogin } from './use-login'
|
|
||||||
export { default as useLogout } from './use-logout'
|
|
||||||
export { default as useSignup } from './use-signup'
|
|
@ -1,82 +0,0 @@
|
|||||||
import type { ServerResponse } from 'http'
|
|
||||||
import type { LoginMutation, LoginMutationVariables } from '../schema'
|
|
||||||
import concatHeader from '../api/utils/concat-cookie'
|
|
||||||
import { getConfig, VendureConfig } from '../api'
|
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
|
||||||
import { ErrorResult } from '../schema'
|
|
||||||
|
|
||||||
export const loginMutation = /* GraphQL */ `
|
|
||||||
mutation loginServer($email: String!, $password: String!) {
|
|
||||||
login(username: $email, password: $password) {
|
|
||||||
__typename
|
|
||||||
... on CurrentUser {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
... on ErrorResult {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export type LoginResult<T extends { result?: any } = { result?: string }> = T
|
|
||||||
|
|
||||||
export type LoginVariables = LoginMutationVariables
|
|
||||||
|
|
||||||
async function login(opts: {
|
|
||||||
variables: LoginVariables
|
|
||||||
config?: VendureConfig
|
|
||||||
res: ServerResponse
|
|
||||||
}): Promise<LoginResult>
|
|
||||||
|
|
||||||
async function login<T extends { result?: any }, V = any>(opts: {
|
|
||||||
query: string
|
|
||||||
variables: V
|
|
||||||
res: ServerResponse
|
|
||||||
config?: VendureConfig
|
|
||||||
}): Promise<LoginResult<T>>
|
|
||||||
|
|
||||||
async function login({
|
|
||||||
query = loginMutation,
|
|
||||||
variables,
|
|
||||||
res: response,
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables: LoginVariables
|
|
||||||
res: ServerResponse
|
|
||||||
config?: VendureConfig
|
|
||||||
}): Promise<LoginResult> {
|
|
||||||
config = getConfig(config)
|
|
||||||
|
|
||||||
const { data, res } = await config.fetch<LoginMutation>(query, { variables })
|
|
||||||
|
|
||||||
if (data.login.__typename !== 'CurrentUser') {
|
|
||||||
throw new CommerceError({ message: (data.login as ErrorResult).message })
|
|
||||||
}
|
|
||||||
// Bigcommerce returns a Set-Cookie header with the auth cookie
|
|
||||||
let cookie = res.headers.get('Set-Cookie')
|
|
||||||
|
|
||||||
if (cookie && typeof cookie === 'string') {
|
|
||||||
// In development, don't set a secure cookie or the browser will ignore it
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
cookie = cookie.replace('; Secure', '')
|
|
||||||
// SameSite=none can't be set unless the cookie is Secure
|
|
||||||
// bc seems to sometimes send back SameSite=None rather than none so make
|
|
||||||
// this case insensitive
|
|
||||||
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
|
|
||||||
}
|
|
||||||
|
|
||||||
response.setHeader(
|
|
||||||
'Set-Cookie',
|
|
||||||
concatHeader(response.getHeader('Set-Cookie'), cookie)!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: data.login.id.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default login
|
|
@ -1,13 +1,9 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
import useCommerceLogin from '@commerce/use-login'
|
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
import {
|
import { LoginMutation, LoginMutationVariables } from '../schema'
|
||||||
ErrorResult,
|
|
||||||
LoginMutation,
|
|
||||||
LoginMutationVariables,
|
|
||||||
} from '@framework/schema'
|
|
||||||
|
|
||||||
export const loginMutation = /* GraphQL */ `
|
export const loginMutation = /* GraphQL */ `
|
||||||
mutation login($username: String!, $password: String!) {
|
mutation login($username: String!, $password: String!) {
|
||||||
@ -24,53 +20,45 @@ export const loginMutation = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const fetcher: HookFetcher<LoginMutation, LoginMutationVariables> = (
|
export default useLogin as UseLogin<typeof handler>
|
||||||
options,
|
|
||||||
{ username, password },
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
if (!(username && password)) {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: 'An email address and password are required to login',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch({
|
export const handler: MutationHook<null, {}, any> = {
|
||||||
...options,
|
fetchOptions: {
|
||||||
query: loginMutation,
|
query: loginMutation,
|
||||||
variables: { username, password },
|
},
|
||||||
})
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
}
|
if (!(email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'A email and password are required to login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
const variables: LoginMutationVariables = {
|
||||||
const useLogin = () => {
|
username: email,
|
||||||
|
password,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { login } = await fetch<LoginMutation>({
|
||||||
|
...options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (login.__typename !== 'CurrentUser') {
|
||||||
|
throw new ValidationError(login)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
const { revalidate } = useCustomer()
|
const { revalidate } = useCustomer()
|
||||||
const fn = useCommerceLogin<LoginMutation, LoginMutationVariables>(
|
|
||||||
{},
|
|
||||||
customFetcher
|
|
||||||
)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function login(input: { email: string; password: string }) {
|
async function login(input) {
|
||||||
const data = await fn({
|
const data = await fetch({ input })
|
||||||
username: input.email,
|
|
||||||
password: input.password,
|
|
||||||
})
|
|
||||||
if (data.login.__typename !== 'CurrentUser') {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: (data.login as ErrorResult).message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await revalidate()
|
await revalidate()
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
[fn]
|
[fetch, revalidate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useLogin.extend = extendHook
|
|
||||||
|
|
||||||
return useLogin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
import useCommerceLogout from '@commerce/use-logout'
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
import { LogoutMutation } from '@framework/schema'
|
import { LogoutMutation } from '../schema'
|
||||||
|
|
||||||
export const logoutMutation = /* GraphQL */ `
|
export const logoutMutation = /* GraphQL */ `
|
||||||
mutation logout {
|
mutation logout {
|
||||||
@ -12,31 +12,28 @@ export const logoutMutation = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const fetcher: HookFetcher<LogoutMutation> = (options, _, fetch) => {
|
export default useLogout as UseLogout<typeof handler>
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
query: logoutMutation,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
export const handler: MutationHook<null> = {
|
||||||
const useLogout = () => {
|
fetchOptions: {
|
||||||
|
query: logoutMutation,
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
await fetch<LogoutMutation>({
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
const { mutate } = useCustomer()
|
const { mutate } = useCustomer()
|
||||||
const fn = useCommerceLogout<LogoutMutation>({}, customFetcher)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function logout() {
|
async function logout() {
|
||||||
const data = await fn(null)
|
const data = await fetch()
|
||||||
await mutate(null as any, false)
|
await mutate(null, false)
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
[fn]
|
[fetch, mutate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useLogout.extend = extendHook
|
|
||||||
|
|
||||||
return useLogout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||||
import useCommerceSignup from '@commerce/use-signup'
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
import {
|
import {
|
||||||
ErrorResult,
|
RegisterCustomerInput,
|
||||||
SignupMutation,
|
SignupMutation,
|
||||||
SignupMutationVariables,
|
SignupMutationVariables,
|
||||||
} from '@framework/schema'
|
} from '../schema'
|
||||||
|
|
||||||
export const signupMutation = /* GraphQL */ `
|
export const signupMutation = /* GraphQL */ `
|
||||||
mutation signup($input: RegisterCustomerInput!) {
|
mutation signup($input: RegisterCustomerInput!) {
|
||||||
@ -24,66 +24,57 @@ export const signupMutation = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export type SignupInput = {
|
export default useSignup as UseSignup<typeof handler>
|
||||||
email: string
|
|
||||||
firstName: string
|
|
||||||
lastName: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetcher: HookFetcher<SignupMutation, SignupMutationVariables> = (
|
export const handler: MutationHook<
|
||||||
options,
|
null,
|
||||||
{ input },
|
{},
|
||||||
fetch
|
RegisterCustomerInput,
|
||||||
) => {
|
RegisterCustomerInput
|
||||||
const { firstName, lastName, emailAddress, password } = input
|
> = {
|
||||||
if (!(firstName && lastName && emailAddress && password)) {
|
fetchOptions: {
|
||||||
throw new CommerceError({
|
|
||||||
message:
|
|
||||||
'A first name, last name, email and password are required to signup',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
query: signupMutation,
|
query: signupMutation,
|
||||||
variables: { input },
|
},
|
||||||
})
|
async fetcher({
|
||||||
}
|
input: { firstName, lastName, emailAddress, password },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}) {
|
||||||
|
if (!(firstName && lastName && emailAddress && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to signup',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const variables: SignupMutationVariables = {
|
||||||
|
input: {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
emailAddress,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const { registerCustomerAccount } = await fetch<SignupMutation>({
|
||||||
|
...options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
if (registerCustomerAccount.__typename !== 'Success') {
|
||||||
const useSignup = () => {
|
throw new ValidationError(registerCustomerAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
const { revalidate } = useCustomer()
|
const { revalidate } = useCustomer()
|
||||||
const fn = useCommerceSignup<SignupMutation, SignupMutationVariables>(
|
|
||||||
{},
|
|
||||||
customFetcher
|
|
||||||
)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function signup(input: SignupInput) {
|
async function signup(input) {
|
||||||
const { registerCustomerAccount } = await fn({
|
const data = await fetch({ input })
|
||||||
input: {
|
|
||||||
firstName: input.firstName,
|
|
||||||
lastName: input.lastName,
|
|
||||||
emailAddress: input.email,
|
|
||||||
password: input.password,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (registerCustomerAccount.__typename !== 'Success') {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: (registerCustomerAccount as ErrorResult).message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await revalidate()
|
await revalidate()
|
||||||
return { registerCustomerAccount }
|
return data
|
||||||
},
|
},
|
||||||
[fn]
|
[fetch, revalidate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useSignup.extend = extendHook
|
|
||||||
|
|
||||||
return useSignup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
|
import { Cart, CartItemBody } from '@commerce/types'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import { HookFetcher } from '@commerce/utils/types'
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
import fetchGraphqlApi from '@framework/api/utils/fetch-graphql-api'
|
|
||||||
import useCartAddItem from '@commerce/cart/use-add-item'
|
|
||||||
import useCart from './use-cart'
|
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import useCart from './use-cart'
|
||||||
import { cartFragment } from '../api/fragments/cart'
|
import { cartFragment } from '../api/fragments/cart'
|
||||||
import {
|
import { AddItemToOrderMutation } from '../schema'
|
||||||
AddItemToOrderMutation,
|
import { normalizeCart } from '../lib/normalize'
|
||||||
AddItemToOrderMutationVariables,
|
|
||||||
ErrorResult,
|
|
||||||
} from '@framework/schema'
|
|
||||||
|
|
||||||
export const addItemToOrderMutation = /* GraphQL */ `
|
export const addItemToOrderMutation = /* GraphQL */ `
|
||||||
mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
|
mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
|
||||||
@ -25,56 +22,45 @@ export const addItemToOrderMutation = /* GraphQL */ `
|
|||||||
${cartFragment}
|
${cartFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
export type AddItemInput = {
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
productId?: number
|
|
||||||
variantId: number
|
|
||||||
quantity?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetcher: HookFetcher<
|
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
||||||
AddItemToOrderMutation,
|
fetchOptions: {
|
||||||
AddItemToOrderMutationVariables
|
|
||||||
> = (options, { variantId, quantity }, fetch) => {
|
|
||||||
if (quantity && (!Number.isInteger(quantity) || quantity! < 1)) {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: 'The item quantity has to be a valid integer greater than 0',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
query: addItemToOrderMutation,
|
query: addItemToOrderMutation,
|
||||||
variables: { variantId, quantity: quantity || 1 },
|
},
|
||||||
})
|
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',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
const { addItemToOrder } = await fetch<AddItemToOrderMutation>({
|
||||||
const useAddItem = () => {
|
...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()
|
const { mutate } = useCart()
|
||||||
const fn = useCartAddItem({}, customFetcher)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function addItem(input: AddItemInput) {
|
async function addItem(input) {
|
||||||
const { addItemToOrder } = await fn({
|
const data = await fetch({ input })
|
||||||
quantity: input.quantity || 1,
|
await mutate(data, false)
|
||||||
variantId: input.variantId,
|
return data
|
||||||
})
|
|
||||||
if (addItemToOrder.__typename === 'Order') {
|
|
||||||
await mutate({ addItemToOrder }, false)
|
|
||||||
} else {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: (addItemToOrder as ErrorResult).message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { addItemToOrder }
|
|
||||||
},
|
},
|
||||||
[fn, mutate]
|
[fetch, mutate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useAddItem.extend = extendHook
|
|
||||||
|
|
||||||
return useAddItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { HookFetcher } from '@commerce/utils/types'
|
import { Cart } from '@commerce/types'
|
||||||
import useData, { SwrOptions } from '@commerce/utils/use-data'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import useResponse from '@commerce/utils/use-response'
|
import useCart, { FetchCartInput, UseCart } from '@commerce/cart/use-cart'
|
||||||
import { cartFragment } from '../api/fragments/cart'
|
import { cartFragment } from '../api/fragments/cart'
|
||||||
import { CartFragment } from '../schema'
|
import { ActiveOrderQuery, CartFragment } from '../schema'
|
||||||
import { normalizeCart } from '@framework/lib/normalize'
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
export const getCartQuery = /* GraphQL */ `
|
export const getCartQuery = /* GraphQL */ `
|
||||||
query activeOrder {
|
query activeOrder {
|
||||||
@ -14,10 +15,6 @@ export const getCartQuery = /* GraphQL */ `
|
|||||||
${cartFragment}
|
${cartFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const fetcher: HookFetcher<any, null> = (options, input, fetch) => {
|
|
||||||
return fetch({ ...options, query: getCartQuery })
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CartResult = {
|
export type CartResult = {
|
||||||
activeOrder?: CartFragment
|
activeOrder?: CartFragment
|
||||||
addItemToOrder?: CartFragment
|
addItemToOrder?: CartFragment
|
||||||
@ -25,42 +22,37 @@ export type CartResult = {
|
|||||||
removeOrderLine?: CartFragment
|
removeOrderLine?: CartFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extendHook(
|
export default useCart as UseCart<typeof handler>
|
||||||
customFetcher: typeof fetcher,
|
|
||||||
swrOptions?: SwrOptions<any | null>
|
export const handler: SWRHook<
|
||||||
) {
|
Cart | null,
|
||||||
const useCart = () => {
|
{},
|
||||||
const response = useData<CartResult>(
|
FetchCartInput,
|
||||||
{ query: getCartQuery },
|
{ isEmpty?: boolean }
|
||||||
[],
|
> = {
|
||||||
customFetcher,
|
fetchOptions: {
|
||||||
swrOptions
|
query: getCartQuery,
|
||||||
)
|
},
|
||||||
const res = useResponse(response, {
|
async fetcher({ input: { cartId }, options, fetch }) {
|
||||||
normalizer: (data) => {
|
const { activeOrder } = await fetch<ActiveOrderQuery>(options)
|
||||||
const order =
|
return activeOrder ? normalizeCart(activeOrder) : null
|
||||||
data?.activeOrder ||
|
},
|
||||||
data?.addItemToOrder ||
|
useHook: ({ useData }) => (input) => {
|
||||||
data?.adjustOrderLine ||
|
const response = useData({
|
||||||
data?.removeOrderLine
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
return order ? normalizeCart(order) : null
|
|
||||||
},
|
|
||||||
descriptors: {
|
|
||||||
isEmpty: {
|
|
||||||
get() {
|
|
||||||
return response.data?.activeOrder?.totalQuantity === 0
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return res
|
return useMemo(
|
||||||
}
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
useCart.extend = extendHook
|
isEmpty: {
|
||||||
|
get() {
|
||||||
return useCart
|
return (response.data?.lineItems.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { HookFetcher } from '@commerce/utils/types'
|
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
|
||||||
import useCartRemoveItem from '@commerce/cart/use-remove-item'
|
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useCart from './use-cart'
|
import useCart from './use-cart'
|
||||||
import { cartFragment } from '@framework/api/fragments/cart'
|
import { cartFragment } from '../api/fragments/cart'
|
||||||
import {
|
import {
|
||||||
ErrorResult,
|
|
||||||
RemoveOrderLineMutation,
|
RemoveOrderLineMutation,
|
||||||
RemoveOrderLineMutationVariables,
|
RemoveOrderLineMutationVariables,
|
||||||
} from '@framework/schema'
|
} from '../schema'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
|
||||||
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
|
||||||
export const removeOrderLineMutation = /* GraphQL */ `
|
export const removeOrderLineMutation = /* GraphQL */ `
|
||||||
mutation removeOrderLine($orderLineId: ID!) {
|
mutation removeOrderLine($orderLineId: ID!) {
|
||||||
@ -24,44 +25,38 @@ export const removeOrderLineMutation = /* GraphQL */ `
|
|||||||
${cartFragment}
|
${cartFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const fetcher: HookFetcher<
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
RemoveOrderLineMutation,
|
|
||||||
RemoveOrderLineMutationVariables
|
|
||||||
> = (options, { orderLineId }, fetch) => {
|
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
query: removeOrderLineMutation,
|
|
||||||
variables: { orderLineId },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
export const handler = {
|
||||||
const useRemoveItem = (item?: any) => {
|
fetchOptions: {
|
||||||
|
query: removeOrderLineMutation,
|
||||||
|
},
|
||||||
|
async fetcher({ input, options, fetch }: HookFetcherContext<LineItem>) {
|
||||||
|
const variables: RemoveOrderLineMutationVariables = {
|
||||||
|
orderLineId: input.id,
|
||||||
|
}
|
||||||
|
const { removeOrderLine } = await fetch<RemoveOrderLineMutation>({
|
||||||
|
...options,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (removeOrderLine.__typename === 'Order') {
|
||||||
|
return normalizeCart(removeOrderLine)
|
||||||
|
}
|
||||||
|
throw new CommerceError(removeOrderLine)
|
||||||
|
},
|
||||||
|
useHook: ({
|
||||||
|
fetch,
|
||||||
|
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => (ctx = {}) => {
|
||||||
const { mutate } = useCart()
|
const { mutate } = useCart()
|
||||||
const fn = useCartRemoveItem<
|
|
||||||
RemoveOrderLineMutation,
|
|
||||||
RemoveOrderLineMutationVariables
|
|
||||||
>({}, customFetcher)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
async function removeItem(input: any) {
|
async function removeItem(input) {
|
||||||
const { removeOrderLine } = await fn({ orderLineId: input.id })
|
const data = await fetch({ input })
|
||||||
if (removeOrderLine.__typename === 'Order') {
|
await mutate(data, false)
|
||||||
await mutate({ removeOrderLine }, false)
|
return data
|
||||||
} else {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: (removeOrderLine as ErrorResult).message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { removeOrderLine }
|
|
||||||
},
|
},
|
||||||
[fn, mutate]
|
[fetch, mutate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useRemoveItem.extend = extendHook
|
|
||||||
|
|
||||||
return useRemoveItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import debounce from 'lodash.debounce'
|
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
|
||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||||
import useCartUpdateItem from '@commerce/cart/use-update-item'
|
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||||
|
import {
|
||||||
|
Cart,
|
||||||
|
CartItemBody,
|
||||||
|
LineItem,
|
||||||
|
UpdateCartItemBody,
|
||||||
|
} from '@commerce/types'
|
||||||
import useCart from './use-cart'
|
import useCart from './use-cart'
|
||||||
import { cartFragment } from '@framework/api/fragments/cart'
|
|
||||||
import {
|
import {
|
||||||
AdjustOrderLineMutation,
|
AdjustOrderLineMutation,
|
||||||
AdjustOrderLineMutationVariables,
|
AdjustOrderLineMutationVariables,
|
||||||
ErrorResult,
|
} from '../schema'
|
||||||
} from '@framework/schema'
|
import { cartFragment } from '../api/fragments/cart'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
|
||||||
export const adjustOrderLineMutation = /* GraphQL */ `
|
export const adjustOrderLineMutation = /* GraphQL */ `
|
||||||
mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
|
mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
|
||||||
@ -24,47 +29,64 @@ export const adjustOrderLineMutation = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
${cartFragment}
|
${cartFragment}
|
||||||
`
|
`
|
||||||
export const fetcher: HookFetcher<
|
|
||||||
AdjustOrderLineMutation,
|
|
||||||
AdjustOrderLineMutationVariables
|
|
||||||
> = (options, { orderLineId, quantity }, fetch) => {
|
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
query: adjustOrderLineMutation,
|
|
||||||
variables: { orderLineId, quantity },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
|
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||||
const useUpdateItem = (item?: any) => {
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
query: adjustOrderLineMutation,
|
||||||
|
},
|
||||||
|
async fetcher(context: HookFetcherContext<UpdateCartItemBody<CartItemBody>>) {
|
||||||
|
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<Cart | null, UpdateCartItemBody<CartItemBody>>) => (
|
||||||
|
ctx: {
|
||||||
|
item?: LineItem
|
||||||
|
wait?: number
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
const { item } = ctx
|
||||||
const { mutate } = useCart()
|
const { mutate } = useCart()
|
||||||
const fn = useCartUpdateItem<
|
|
||||||
AdjustOrderLineMutation,
|
|
||||||
AdjustOrderLineMutationVariables
|
|
||||||
>({}, customFetcher)
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
debounce(async (input: any) => {
|
async function addItem(input: CartItemBody) {
|
||||||
const { adjustOrderLine } = await fn({
|
const itemId = item?.id
|
||||||
orderLineId: item.id,
|
const productId = input.productId ?? item?.productId
|
||||||
quantity: input.quantity,
|
const variantId = input.productId ?? item?.variantId
|
||||||
})
|
if (!itemId || !productId || !variantId) {
|
||||||
if (adjustOrderLine.__typename === 'Order') {
|
throw new ValidationError({
|
||||||
await mutate({ adjustOrderLine }, false)
|
message: 'Invalid input used for this operation',
|
||||||
} else {
|
|
||||||
throw new CommerceError({
|
|
||||||
message: (adjustOrderLine as ErrorResult).message,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { adjustOrderLine }
|
const data = await fetch({
|
||||||
}, cfg?.wait ?? 500),
|
input: {
|
||||||
[fn, mutate]
|
item: {
|
||||||
|
productId,
|
||||||
|
variantId,
|
||||||
|
quantity: input.quantity,
|
||||||
|
},
|
||||||
|
itemId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
useUpdateItem.extend = extendHook
|
|
||||||
|
|
||||||
return useUpdateItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"plugins": ["typescript", "typescript-operations"],
|
"plugins": ["typescript", "typescript-operations"],
|
||||||
"config": {
|
"config": {
|
||||||
"scalars": {
|
"scalars": {
|
||||||
"ID": "number"
|
"ID": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
6
framework/vendure/commerce.config.json
Normal file
6
framework/vendure/commerce.config.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"provider": "vendure",
|
||||||
|
"features": {
|
||||||
|
"wishlist": false
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { VendureConfig, getConfig } from '../api'
|
import { VendureConfig, getConfig } from '../api'
|
||||||
import { GetCollectionsQuery } from '@framework/schema'
|
import { GetCollectionsQuery } from '../schema'
|
||||||
import { arrayToTree } from '@framework/lib/array-to-tree'
|
import { arrayToTree } from '../lib/array-to-tree'
|
||||||
|
|
||||||
export const getCollectionsQuery = /* GraphQL */ `
|
export const getCollectionsQuery = /* GraphQL */ `
|
||||||
query getCollections {
|
query getCollections {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import type { SwrOptions } from '@commerce/utils/use-data'
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
import useCommerceCustomer from '@commerce/use-customer'
|
import { Customer } from '@commerce/types'
|
||||||
import { ActiveCustomerQuery } from '@framework/schema'
|
import { ActiveCustomerQuery } from '../schema'
|
||||||
import useResponse from '@commerce/utils/use-response'
|
|
||||||
|
|
||||||
export const activeCustomerQuery = /* GraphQL */ `
|
export const activeCustomerQuery = /* GraphQL */ `
|
||||||
query activeCustomer {
|
query activeCustomer {
|
||||||
@ -15,40 +14,30 @@ export const activeCustomerQuery = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const fetcher: HookFetcher<ActiveCustomerQuery> = async (
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
options,
|
|
||||||
_,
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
return await fetch<ActiveCustomerQuery>({ query: activeCustomerQuery })
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extendHook(
|
export const handler: SWRHook<Customer | null> = {
|
||||||
customFetcher: typeof fetcher,
|
fetchOptions: {
|
||||||
swrOptions?: SwrOptions<ActiveCustomerQuery>
|
query: activeCustomerQuery,
|
||||||
) {
|
},
|
||||||
const useCustomer = () => {
|
async fetcher({ options, fetch }) {
|
||||||
const response = useCommerceCustomer({}, [], customFetcher, {
|
const { activeCustomer } = await fetch<ActiveCustomerQuery>({
|
||||||
revalidateOnFocus: false,
|
...options,
|
||||||
...swrOptions,
|
|
||||||
})
|
})
|
||||||
|
return activeCustomer
|
||||||
return useResponse(response, {
|
? ({
|
||||||
normalizer: (data) => {
|
firstName: activeCustomer.firstName ?? '',
|
||||||
return data?.activeCustomer
|
lastName: activeCustomer.lastName ?? '',
|
||||||
? {
|
email: activeCustomer.emailAddress ?? '',
|
||||||
firstName: data?.activeCustomer?.firstName ?? '',
|
} as any)
|
||||||
lastName: data?.activeCustomer?.lastName ?? '',
|
: null
|
||||||
email: data?.activeCustomer?.emailAddress ?? '',
|
},
|
||||||
}
|
useHook: ({ useData }) => (input) => {
|
||||||
: null
|
return useData({
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input?.swrOptions,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
|
||||||
useCustomer.extend = extendHook
|
|
||||||
|
|
||||||
return useCustomer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
49
framework/vendure/fetcher.ts
Normal file
49
framework/vendure/fetcher.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Fetcher } from '@commerce/utils/types'
|
||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
|
||||||
|
async function getText(res: Response) {
|
||||||
|
try {
|
||||||
|
return (await res.text()) || res.statusText
|
||||||
|
} catch (error) {
|
||||||
|
return res.statusText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getError(res: Response) {
|
||||||
|
if (res.headers.get('Content-Type')?.includes('application/json')) {
|
||||||
|
const data = await res.json()
|
||||||
|
return new FetcherError({ errors: data.errors, status: res.status })
|
||||||
|
}
|
||||||
|
return new FetcherError({ message: await getText(res), status: res.status })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetcher: Fetcher = async ({
|
||||||
|
url,
|
||||||
|
method = 'POST',
|
||||||
|
variables,
|
||||||
|
query,
|
||||||
|
body: bodyObj,
|
||||||
|
}) => {
|
||||||
|
const shopApiUrl = process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
|
||||||
|
if (!shopApiUrl) {
|
||||||
|
throw new Error(
|
||||||
|
'The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const hasBody = Boolean(variables || query)
|
||||||
|
const body = hasBody ? JSON.stringify({ query, variables }) : undefined
|
||||||
|
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
||||||
|
const res = await fetch(shopApiUrl, {
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
headers,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const { data } = await res.json()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
throw await getError(res)
|
||||||
|
}
|
@ -1,51 +1,15 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { CommerceConfig, CommerceProvider as CoreCommerceProvider, useCommerce as useCoreCommerce } from '@commerce'
|
import {
|
||||||
import { FetcherError } from '@commerce/utils/errors'
|
CommerceConfig,
|
||||||
|
CommerceProvider as CoreCommerceProvider,
|
||||||
async function getText(res: Response) {
|
useCommerce as useCoreCommerce,
|
||||||
try {
|
} from '@commerce'
|
||||||
return (await res.text()) || res.statusText
|
import { vendureProvider } from './provider'
|
||||||
} 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 vendureConfig: CommerceConfig = {
|
export const vendureConfig: CommerceConfig = {
|
||||||
locale: 'en-us',
|
locale: 'en-us',
|
||||||
cartCookie: 'bc_cartId',
|
cartCookie: 'session',
|
||||||
async fetcher({ url, method = 'POST', variables, query, body: bodyObj }) {
|
|
||||||
const shopApiUrl = process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
|
|
||||||
if (!shopApiUrl) {
|
|
||||||
throw new Error('The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local')
|
|
||||||
}
|
|
||||||
const hasBody = Boolean(variables || query)
|
|
||||||
const body = hasBody
|
|
||||||
? JSON.stringify({ query, variables })
|
|
||||||
: undefined
|
|
||||||
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
|
||||||
const res = await fetch(shopApiUrl, {
|
|
||||||
method,
|
|
||||||
body,
|
|
||||||
headers,
|
|
||||||
credentials: 'include'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const { data } = await res.json()
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
throw await getError(res)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VendureConfig = Partial<CommerceConfig>
|
export type VendureConfig = Partial<CommerceConfig>
|
||||||
@ -57,7 +21,10 @@ export type VendureProps = {
|
|||||||
|
|
||||||
export function CommerceProvider({ children, ...config }: VendureProps) {
|
export function CommerceProvider({ children, ...config }: VendureProps) {
|
||||||
return (
|
return (
|
||||||
<CoreCommerceProvider config={{ ...vendureConfig, ...config }}>
|
<CoreCommerceProvider
|
||||||
|
provider={vendureProvider}
|
||||||
|
config={{ ...vendureConfig, ...config }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</CoreCommerceProvider>
|
</CoreCommerceProvider>
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export type HasParent = { id: number; parent?: { id: number } | null }
|
export type HasParent = { id: string; parent?: { id: string } | null }
|
||||||
export type TreeNode<T extends HasParent> = T & {
|
export type TreeNode<T extends HasParent> = T & {
|
||||||
children: Array<TreeNode<T>>
|
children: Array<TreeNode<T>>
|
||||||
expanded: boolean
|
expanded: boolean
|
||||||
}
|
}
|
||||||
export type RootNode<T extends HasParent> = {
|
export type RootNode<T extends HasParent> = {
|
||||||
id?: number
|
id?: string
|
||||||
children: Array<TreeNode<T>>
|
children: Array<TreeNode<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ export function arrayToTree<T extends HasParent>(
|
|||||||
*/
|
*/
|
||||||
function treeToMap<T extends HasParent>(
|
function treeToMap<T extends HasParent>(
|
||||||
tree?: RootNode<T>
|
tree?: RootNode<T>
|
||||||
): Map<number, TreeNode<T>> {
|
): Map<string, TreeNode<T>> {
|
||||||
const nodeMap = new Map<number, TreeNode<T>>()
|
const nodeMap = new Map<string, TreeNode<T>>()
|
||||||
function visit(node: TreeNode<T>) {
|
function visit(node: TreeNode<T>) {
|
||||||
nodeMap.set(node.id, node)
|
nodeMap.set(node.id, node)
|
||||||
node.children.forEach(visit)
|
node.children.forEach(visit)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import update from '@framework/lib/immutability'
|
import { Cart, Product } from '@commerce/types'
|
||||||
import { CartFragment, SearchResultFragment } from '@framework/schema'
|
import update from '../lib/immutability'
|
||||||
|
import { CartFragment, SearchResultFragment } from '../schema'
|
||||||
|
|
||||||
function normalizeProductOption(productOption: any) {
|
function normalizeProductOption(productOption: any) {
|
||||||
const {
|
const {
|
||||||
@ -89,11 +90,14 @@ export function normalizeSearchResult(item: SearchResultFragment): Product {
|
|||||||
export function normalizeCart(order: CartFragment): Cart {
|
export function normalizeCart(order: CartFragment): Cart {
|
||||||
return {
|
return {
|
||||||
id: order.id.toString(),
|
id: order.id.toString(),
|
||||||
|
createdAt: order.createdAt,
|
||||||
|
taxesIncluded: true,
|
||||||
|
lineItemsSubtotalPrice: order.subTotalWithTax / 100,
|
||||||
currency: { code: order.currencyCode },
|
currency: { code: order.currencyCode },
|
||||||
subTotal: order.subTotalWithTax / 100,
|
subtotalPrice: order.subTotalWithTax / 100,
|
||||||
total: order.totalWithTax / 100,
|
totalPrice: order.totalWithTax / 100,
|
||||||
customerId: order.customer?.id as number,
|
customerId: order.customer?.id,
|
||||||
items: order.lines?.map((l) => ({
|
lineItems: order.lines?.map((l) => ({
|
||||||
id: l.id,
|
id: l.id,
|
||||||
name: l.productVariant.name,
|
name: l.productVariant.name,
|
||||||
quantity: l.quantity,
|
quantity: l.quantity,
|
||||||
@ -101,7 +105,19 @@ export function normalizeCart(order: CartFragment): Cart {
|
|||||||
variantId: l.productVariant.id,
|
variantId: l.productVariant.id,
|
||||||
productId: l.productVariant.productId,
|
productId: l.productVariant.productId,
|
||||||
images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }],
|
images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }],
|
||||||
prices: [],
|
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.discountedLinePriceWithTax / 100,
|
||||||
|
listPrice: l.linePriceWithTax / 100,
|
||||||
|
image: {
|
||||||
|
url: l.featuredAsset?.preview + '?preset=thumb' || '',
|
||||||
|
},
|
||||||
|
requiresShipping: true,
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { Product } from '@commerce/types'
|
||||||
import { getConfig, VendureConfig } from '../api'
|
import { getConfig, VendureConfig } from '../api'
|
||||||
import { searchResultFragment } from '@framework/api/fragments/search-result'
|
import { searchResultFragment } from '../api/fragments/search-result'
|
||||||
import { GetAllProductsQuery } from '@framework/schema'
|
import { GetAllProductsQuery } from '../schema'
|
||||||
import { normalizeSearchResult } from '@framework/lib/normalize'
|
import { normalizeSearchResult } from '../lib/normalize'
|
||||||
|
|
||||||
export const getAllProductsQuery = /* GraphQL */ `
|
export const getAllProductsQuery = /* GraphQL */ `
|
||||||
query getAllProducts($input: SearchInput!) {
|
query getAllProducts($input: SearchInput!) {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { Product } from '@commerce/types'
|
||||||
import { getConfig, VendureConfig } from '../api'
|
import { getConfig, VendureConfig } from '../api'
|
||||||
|
import { GetProductQuery } from '@framework/schema'
|
||||||
|
|
||||||
export const getProductQuery = /* GraphQL */ `
|
export const getProductQuery = /* GraphQL */ `
|
||||||
query getProduct($slug: String!) {
|
query getProduct($slug: String!) {
|
||||||
@ -21,12 +23,20 @@ export const getProductQuery = /* GraphQL */ `
|
|||||||
name
|
name
|
||||||
code
|
code
|
||||||
groupId
|
groupId
|
||||||
|
group {
|
||||||
|
id
|
||||||
|
options {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
optionGroups {
|
optionGroups {
|
||||||
|
id
|
||||||
code
|
code
|
||||||
name
|
name
|
||||||
options {
|
options {
|
||||||
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +57,7 @@ async function getProduct({
|
|||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
|
|
||||||
const locale = config.locale
|
const locale = config.locale
|
||||||
const { data } = await config.fetch(query, { variables })
|
const { data } = await config.fetch<GetProductQuery>(query, { variables })
|
||||||
const product = data.product
|
const product = data.product
|
||||||
|
|
||||||
if (product) {
|
if (product) {
|
||||||
@ -57,26 +67,28 @@ async function getProduct({
|
|||||||
name: product.name,
|
name: product.name,
|
||||||
description: product.description,
|
description: product.description,
|
||||||
slug: product.slug,
|
slug: product.slug,
|
||||||
images: product.assets.map((a: any) => ({
|
images: product.assets.map((a) => ({
|
||||||
url: a.preview,
|
url: a.preview,
|
||||||
alt: a.name,
|
alt: a.name,
|
||||||
})),
|
})),
|
||||||
variants: product.variants.map((v: any) => ({
|
variants: product.variants.map((v) => ({
|
||||||
id: v.id,
|
id: v.id,
|
||||||
options: v.options.map((o: any) => ({
|
options: v.options.map((o) => ({
|
||||||
|
id: o.id,
|
||||||
displayName: o.name,
|
displayName: o.name,
|
||||||
values: [],
|
values: o.group.options.map((_o) => ({ label: _o.name })),
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
price: {
|
price: {
|
||||||
value: product.variants[0].priceWithTax / 100,
|
value: product.variants[0].priceWithTax / 100,
|
||||||
currencyCode: product.variants[0].currencyCode,
|
currencyCode: product.variants[0].currencyCode,
|
||||||
},
|
},
|
||||||
options: product.optionGroups.map((og: any) => ({
|
options: product.optionGroups.map((og) => ({
|
||||||
|
id: og.id,
|
||||||
displayName: og.name,
|
displayName: og.name,
|
||||||
values: og.options.map((o: any) => ({ label: o.name })),
|
values: og.options.map((o) => ({ label: o.name })),
|
||||||
})),
|
})),
|
||||||
},
|
} as Product,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
export * from '@commerce/use-price'
|
export * from '@commerce/product/use-price'
|
||||||
export { default } from '@commerce/use-price'
|
export { default } from '@commerce/product/use-price'
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import type { SwrOptions } from '@commerce/utils/use-data'
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
import useCommerceSearch from '@commerce/products/use-search'
|
import { Product } from '@commerce/types'
|
||||||
import useResponse from '@commerce/utils/use-response'
|
import { SearchQuery, SearchQueryVariables } from '../schema'
|
||||||
import { searchResultFragment } from '@framework/api/fragments/search-result'
|
import { searchResultFragment } from '../api/fragments/search-result'
|
||||||
import { SearchQuery } from '@framework/schema'
|
import { normalizeSearchResult } from '../lib/normalize'
|
||||||
import { normalizeSearchResult } from '@framework/lib/normalize'
|
|
||||||
|
|
||||||
export const searchQuery = /* GraphQL */ `
|
export const searchQuery = /* GraphQL */ `
|
||||||
query search($input: SearchInput!) {
|
query search($input: SearchInput!) {
|
||||||
@ -18,61 +17,61 @@ export const searchQuery = /* GraphQL */ `
|
|||||||
${searchResultFragment}
|
${searchResultFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
export type SearchProductsInput = {
|
export type SearchProductsInput = {
|
||||||
search?: string
|
search?: string
|
||||||
categoryId?: number
|
categoryId?: string
|
||||||
brandId?: number
|
brandId?: string
|
||||||
sort?: string
|
sort?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetcher: HookFetcher<SearchQuery, SearchProductsInput> = (
|
export type SearchProductsData = {
|
||||||
options,
|
products: Product[]
|
||||||
{ search, categoryId, brandId, sort },
|
found: boolean
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
return fetch({
|
|
||||||
query: searchQuery,
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
term: search,
|
|
||||||
collectionId: categoryId,
|
|
||||||
groupByProduct: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extendHook(
|
export const handler: SWRHook<
|
||||||
customFetcher: typeof fetcher,
|
SearchProductsData,
|
||||||
swrOptions?: SwrOptions<any, SearchProductsInput>
|
SearchProductsInput,
|
||||||
) {
|
SearchProductsInput
|
||||||
const useSearch = (input: SearchProductsInput = {}) => {
|
> = {
|
||||||
const response = useCommerceSearch<SearchQuery, SearchProductsInput>(
|
fetchOptions: {
|
||||||
{},
|
query: searchQuery,
|
||||||
[
|
},
|
||||||
|
async fetcher({ input, options, fetch }) {
|
||||||
|
const { categoryId, brandId } = input
|
||||||
|
|
||||||
|
const variables: SearchQueryVariables = {
|
||||||
|
input: {
|
||||||
|
term: input.search,
|
||||||
|
collectionId: input.categoryId,
|
||||||
|
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],
|
['search', input.search],
|
||||||
['categoryId', input.categoryId],
|
['categoryId', input.categoryId],
|
||||||
['brandId', input.brandId],
|
['brandId', input.brandId],
|
||||||
['sort', input.sort],
|
['sort', input.sort],
|
||||||
],
|
],
|
||||||
customFetcher,
|
swrOptions: {
|
||||||
{ revalidateOnFocus: false, ...swrOptions }
|
revalidateOnFocus: false,
|
||||||
)
|
...input.swrOptions,
|
||||||
|
|
||||||
return useResponse(response, {
|
|
||||||
normalizer: (data) => {
|
|
||||||
return {
|
|
||||||
found: data?.search.totalItems && data?.search.totalItems > 0,
|
|
||||||
products:
|
|
||||||
data?.search.items.map((item) => normalizeSearchResult(item)) ?? [],
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
|
||||||
useSearch.extend = extendHook
|
|
||||||
|
|
||||||
return useSearch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
|
||||||
|
21
framework/vendure/provider.ts
Normal file
21
framework/vendure/provider.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Provider } from '@commerce'
|
||||||
|
import { handler as useCart } from './cart/use-cart'
|
||||||
|
import { handler as useAddItem } from './cart/use-add-item'
|
||||||
|
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||||
|
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||||
|
import { handler as useCustomer } from './customer/use-customer'
|
||||||
|
import { handler as useSearch } from './product/use-search'
|
||||||
|
import { handler as useLogin } from './auth/use-login'
|
||||||
|
import { handler as useLogout } from './auth/use-logout'
|
||||||
|
import { handler as useSignup } from './auth/use-signup'
|
||||||
|
import { fetcher } from './fetcher'
|
||||||
|
|
||||||
|
export const vendureProvider: Provider = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: '',
|
||||||
|
fetcher,
|
||||||
|
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||||
|
customer: { useCustomer },
|
||||||
|
products: { useSearch },
|
||||||
|
auth: { useLogin, useLogout, useSignup },
|
||||||
|
}
|
121
framework/vendure/schema.d.ts
vendored
121
framework/vendure/schema.d.ts
vendored
@ -8,7 +8,7 @@ export type MakeMaybe<T, K extends keyof T> = Omit<T, K> &
|
|||||||
{ [SubKey in K]: Maybe<T[SubKey]> }
|
{ [SubKey in K]: Maybe<T[SubKey]> }
|
||||||
/** All built-in and custom scalars, mapped to their actual values */
|
/** All built-in and custom scalars, mapped to their actual values */
|
||||||
export type Scalars = {
|
export type Scalars = {
|
||||||
ID: number
|
ID: string
|
||||||
String: string
|
String: string
|
||||||
Boolean: boolean
|
Boolean: boolean
|
||||||
Int: number
|
Int: number
|
||||||
@ -41,6 +41,8 @@ export type Query = {
|
|||||||
collection?: Maybe<Collection>
|
collection?: Maybe<Collection>
|
||||||
/** Returns a list of eligible shipping methods based on the current active Order */
|
/** Returns a list of eligible shipping methods based on the current active Order */
|
||||||
eligibleShippingMethods: Array<ShippingMethodQuote>
|
eligibleShippingMethods: Array<ShippingMethodQuote>
|
||||||
|
/** Returns a list of payment methods and their eligibility based on the current active Order */
|
||||||
|
eligiblePaymentMethods: Array<PaymentMethodQuote>
|
||||||
/** Returns information about the current authenticated User */
|
/** Returns information about the current authenticated User */
|
||||||
me?: Maybe<CurrentUser>
|
me?: Maybe<CurrentUser>
|
||||||
/** Returns the possible next states that the activeOrder can transition to */
|
/** Returns the possible next states that the activeOrder can transition to */
|
||||||
@ -329,6 +331,7 @@ export type Asset = Node & {
|
|||||||
source: Scalars['String']
|
source: Scalars['String']
|
||||||
preview: Scalars['String']
|
preview: Scalars['String']
|
||||||
focalPoint?: Maybe<Coordinate>
|
focalPoint?: Maybe<Coordinate>
|
||||||
|
customFields?: Maybe<Scalars['JSON']>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Coordinate = {
|
export type Coordinate = {
|
||||||
@ -376,6 +379,7 @@ export type Channel = Node & {
|
|||||||
defaultLanguageCode: LanguageCode
|
defaultLanguageCode: LanguageCode
|
||||||
currencyCode: CurrencyCode
|
currencyCode: CurrencyCode
|
||||||
pricesIncludeTax: Scalars['Boolean']
|
pricesIncludeTax: Scalars['Boolean']
|
||||||
|
customFields?: Maybe<Scalars['JSON']>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Collection = Node & {
|
export type Collection = Node & {
|
||||||
@ -534,6 +538,7 @@ export enum ErrorCode {
|
|||||||
OrderModificationError = 'ORDER_MODIFICATION_ERROR',
|
OrderModificationError = 'ORDER_MODIFICATION_ERROR',
|
||||||
IneligibleShippingMethodError = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
|
IneligibleShippingMethodError = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
|
||||||
OrderPaymentStateError = 'ORDER_PAYMENT_STATE_ERROR',
|
OrderPaymentStateError = 'ORDER_PAYMENT_STATE_ERROR',
|
||||||
|
IneligiblePaymentMethodError = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
|
||||||
PaymentFailedError = 'PAYMENT_FAILED_ERROR',
|
PaymentFailedError = 'PAYMENT_FAILED_ERROR',
|
||||||
PaymentDeclinedError = 'PAYMENT_DECLINED_ERROR',
|
PaymentDeclinedError = 'PAYMENT_DECLINED_ERROR',
|
||||||
CouponCodeInvalidError = 'COUPON_CODE_INVALID_ERROR',
|
CouponCodeInvalidError = 'COUPON_CODE_INVALID_ERROR',
|
||||||
@ -652,6 +657,8 @@ export type ConfigArgDefinition = {
|
|||||||
name: Scalars['String']
|
name: Scalars['String']
|
||||||
type: Scalars['String']
|
type: Scalars['String']
|
||||||
list: Scalars['Boolean']
|
list: Scalars['Boolean']
|
||||||
|
required: Scalars['Boolean']
|
||||||
|
defaultValue?: Maybe<Scalars['JSON']>
|
||||||
label?: Maybe<Scalars['String']>
|
label?: Maybe<Scalars['String']>
|
||||||
description?: Maybe<Scalars['String']>
|
description?: Maybe<Scalars['String']>
|
||||||
ui?: Maybe<Scalars['JSON']>
|
ui?: Maybe<Scalars['JSON']>
|
||||||
@ -678,6 +685,7 @@ export type DeletionResponse = {
|
|||||||
|
|
||||||
export type ConfigArgInput = {
|
export type ConfigArgInput = {
|
||||||
name: Scalars['String']
|
name: Scalars['String']
|
||||||
|
/** A JSON stringified representation of the actual value */
|
||||||
value: Scalars['String']
|
value: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,6 +797,25 @@ export type Success = {
|
|||||||
success: Scalars['Boolean']
|
success: Scalars['Boolean']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ShippingMethodQuote = {
|
||||||
|
__typename?: 'ShippingMethodQuote'
|
||||||
|
id: Scalars['ID']
|
||||||
|
price: Scalars['Int']
|
||||||
|
priceWithTax: Scalars['Int']
|
||||||
|
name: Scalars['String']
|
||||||
|
description: Scalars['String']
|
||||||
|
/** Any optional metadata returned by the ShippingCalculator in the ShippingCalculationResult */
|
||||||
|
metadata?: Maybe<Scalars['JSON']>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentMethodQuote = {
|
||||||
|
__typename?: 'PaymentMethodQuote'
|
||||||
|
id: Scalars['ID']
|
||||||
|
code: Scalars['String']
|
||||||
|
isEligible: Scalars['Boolean']
|
||||||
|
eligibilityMessage?: Maybe<Scalars['String']>
|
||||||
|
}
|
||||||
|
|
||||||
export type Country = Node & {
|
export type Country = Node & {
|
||||||
__typename?: 'Country'
|
__typename?: 'Country'
|
||||||
id: Scalars['ID']
|
id: Scalars['ID']
|
||||||
@ -1827,16 +1854,6 @@ export type OrderList = PaginatedList & {
|
|||||||
totalItems: Scalars['Int']
|
totalItems: Scalars['Int']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShippingMethodQuote = {
|
|
||||||
__typename?: 'ShippingMethodQuote'
|
|
||||||
id: Scalars['ID']
|
|
||||||
price: Scalars['Int']
|
|
||||||
priceWithTax: Scalars['Int']
|
|
||||||
name: Scalars['String']
|
|
||||||
description: Scalars['String']
|
|
||||||
metadata?: Maybe<Scalars['JSON']>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShippingLine = {
|
export type ShippingLine = {
|
||||||
__typename?: 'ShippingLine'
|
__typename?: 'ShippingLine'
|
||||||
shippingMethod: ShippingMethod
|
shippingMethod: ShippingMethod
|
||||||
@ -1897,6 +1914,10 @@ export type OrderLine = Node & {
|
|||||||
unitPrice: Scalars['Int']
|
unitPrice: Scalars['Int']
|
||||||
/** The price of a single unit, including tax but excluding discounts */
|
/** The price of a single unit, including tax but excluding discounts */
|
||||||
unitPriceWithTax: Scalars['Int']
|
unitPriceWithTax: Scalars['Int']
|
||||||
|
/** Non-zero if the unitPrice has changed since it was initially added to Order */
|
||||||
|
unitPriceChangeSinceAdded: Scalars['Int']
|
||||||
|
/** Non-zero if the unitPriceWithTax has changed since it was initially added to Order */
|
||||||
|
unitPriceWithTaxChangeSinceAdded: Scalars['Int']
|
||||||
/**
|
/**
|
||||||
* The price of a single unit including discounts, excluding tax.
|
* The price of a single unit including discounts, excluding tax.
|
||||||
*
|
*
|
||||||
@ -2173,6 +2194,7 @@ export type ProductVariant = Node & {
|
|||||||
/** @deprecated price now always excludes tax */
|
/** @deprecated price now always excludes tax */
|
||||||
priceIncludesTax: Scalars['Boolean']
|
priceIncludesTax: Scalars['Boolean']
|
||||||
priceWithTax: Scalars['Int']
|
priceWithTax: Scalars['Int']
|
||||||
|
stockLevel: Scalars['String']
|
||||||
taxRateApplied: TaxRate
|
taxRateApplied: TaxRate
|
||||||
taxCategory: TaxCategory
|
taxCategory: TaxCategory
|
||||||
options: Array<ProductOption>
|
options: Array<ProductOption>
|
||||||
@ -2279,6 +2301,7 @@ export type TaxCategory = Node & {
|
|||||||
createdAt: Scalars['DateTime']
|
createdAt: Scalars['DateTime']
|
||||||
updatedAt: Scalars['DateTime']
|
updatedAt: Scalars['DateTime']
|
||||||
name: Scalars['String']
|
name: Scalars['String']
|
||||||
|
isDefault: Scalars['Boolean']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaxRate = Node & {
|
export type TaxRate = Node & {
|
||||||
@ -2337,7 +2360,7 @@ export type OrderModificationError = ErrorResult & {
|
|||||||
message: Scalars['String']
|
message: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returned when attempting to set a ShippingMethod for which the order is not eligible */
|
/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
|
||||||
export type IneligibleShippingMethodError = ErrorResult & {
|
export type IneligibleShippingMethodError = ErrorResult & {
|
||||||
__typename?: 'IneligibleShippingMethodError'
|
__typename?: 'IneligibleShippingMethodError'
|
||||||
errorCode: ErrorCode
|
errorCode: ErrorCode
|
||||||
@ -2351,6 +2374,14 @@ export type OrderPaymentStateError = ErrorResult & {
|
|||||||
message: Scalars['String']
|
message: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returned when attempting to add a Payment using a PaymentMethod for which the Order is not eligible. */
|
||||||
|
export type IneligiblePaymentMethodError = ErrorResult & {
|
||||||
|
__typename?: 'IneligiblePaymentMethodError'
|
||||||
|
errorCode: ErrorCode
|
||||||
|
message: Scalars['String']
|
||||||
|
eligibilityCheckerMessage?: Maybe<Scalars['String']>
|
||||||
|
}
|
||||||
|
|
||||||
/** Returned when a Payment fails due to an error. */
|
/** Returned when a Payment fails due to an error. */
|
||||||
export type PaymentFailedError = ErrorResult & {
|
export type PaymentFailedError = ErrorResult & {
|
||||||
__typename?: 'PaymentFailedError'
|
__typename?: 'PaymentFailedError'
|
||||||
@ -2546,6 +2577,7 @@ export type ApplyCouponCodeResult =
|
|||||||
export type AddPaymentToOrderResult =
|
export type AddPaymentToOrderResult =
|
||||||
| Order
|
| Order
|
||||||
| OrderPaymentStateError
|
| OrderPaymentStateError
|
||||||
|
| IneligiblePaymentMethodError
|
||||||
| PaymentFailedError
|
| PaymentFailedError
|
||||||
| PaymentDeclinedError
|
| PaymentDeclinedError
|
||||||
| OrderStateTransitionError
|
| OrderStateTransitionError
|
||||||
@ -2704,6 +2736,7 @@ export type ProductVariantFilterParameter = {
|
|||||||
currencyCode?: Maybe<StringOperators>
|
currencyCode?: Maybe<StringOperators>
|
||||||
priceIncludesTax?: Maybe<BooleanOperators>
|
priceIncludesTax?: Maybe<BooleanOperators>
|
||||||
priceWithTax?: Maybe<NumberOperators>
|
priceWithTax?: Maybe<NumberOperators>
|
||||||
|
stockLevel?: Maybe<StringOperators>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductVariantSortParameter = {
|
export type ProductVariantSortParameter = {
|
||||||
@ -2715,6 +2748,7 @@ export type ProductVariantSortParameter = {
|
|||||||
name?: Maybe<SortOrder>
|
name?: Maybe<SortOrder>
|
||||||
price?: Maybe<SortOrder>
|
price?: Maybe<SortOrder>
|
||||||
priceWithTax?: Maybe<SortOrder>
|
priceWithTax?: Maybe<SortOrder>
|
||||||
|
stockLevel?: Maybe<SortOrder>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CustomerFilterParameter = {
|
export type CustomerFilterParameter = {
|
||||||
@ -2800,6 +2834,7 @@ export type CartFragment = { __typename?: 'Order' } & Pick<
|
|||||||
Order,
|
Order,
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'code'
|
| 'code'
|
||||||
|
| 'createdAt'
|
||||||
| 'totalQuantity'
|
| 'totalQuantity'
|
||||||
| 'subTotal'
|
| 'subTotal'
|
||||||
| 'subTotalWithTax'
|
| 'subTotalWithTax'
|
||||||
@ -2809,13 +2844,28 @@ export type CartFragment = { __typename?: 'Order' } & Pick<
|
|||||||
> & {
|
> & {
|
||||||
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id'>>
|
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id'>>
|
||||||
lines: Array<
|
lines: Array<
|
||||||
{ __typename?: 'OrderLine' } & Pick<OrderLine, 'id' | 'quantity'> & {
|
{ __typename?: 'OrderLine' } & Pick<
|
||||||
|
OrderLine,
|
||||||
|
'id' | 'quantity' | 'linePriceWithTax' | 'discountedLinePriceWithTax'
|
||||||
|
> & {
|
||||||
featuredAsset?: Maybe<
|
featuredAsset?: Maybe<
|
||||||
{ __typename?: 'Asset' } & Pick<Asset, 'id' | 'preview'>
|
{ __typename?: 'Asset' } & Pick<Asset, 'id' | 'preview'>
|
||||||
>
|
>
|
||||||
|
discounts: Array<
|
||||||
|
{ __typename?: 'Adjustment' } & Pick<
|
||||||
|
Adjustment,
|
||||||
|
'description' | 'amount'
|
||||||
|
>
|
||||||
|
>
|
||||||
productVariant: { __typename?: 'ProductVariant' } & Pick<
|
productVariant: { __typename?: 'ProductVariant' } & Pick<
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
'id' | 'name' | 'productId'
|
| 'id'
|
||||||
|
| 'name'
|
||||||
|
| 'sku'
|
||||||
|
| 'price'
|
||||||
|
| 'priceWithTax'
|
||||||
|
| 'stockLevel'
|
||||||
|
| 'productId'
|
||||||
> & { product: { __typename?: 'Product' } & Pick<Product, 'slug'> }
|
> & { product: { __typename?: 'Product' } & Pick<Product, 'slug'> }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -2836,28 +2886,6 @@ export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick<
|
|||||||
| ({ __typename?: 'SinglePrice' } & Pick<SinglePrice, 'value'>)
|
| ({ __typename?: 'SinglePrice' } & Pick<SinglePrice, 'value'>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LoginServerMutationVariables = Exact<{
|
|
||||||
email: Scalars['String']
|
|
||||||
password: Scalars['String']
|
|
||||||
}>
|
|
||||||
|
|
||||||
export type LoginServerMutation = { __typename?: 'Mutation' } & {
|
|
||||||
login:
|
|
||||||
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
|
|
||||||
| ({ __typename: 'InvalidCredentialsError' } & Pick<
|
|
||||||
InvalidCredentialsError,
|
|
||||||
'errorCode' | 'message'
|
|
||||||
>)
|
|
||||||
| ({ __typename: 'NotVerifiedError' } & Pick<
|
|
||||||
NotVerifiedError,
|
|
||||||
'errorCode' | 'message'
|
|
||||||
>)
|
|
||||||
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
|
|
||||||
NativeAuthStrategyError,
|
|
||||||
'errorCode' | 'message'
|
|
||||||
>)
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LoginMutationVariables = Exact<{
|
export type LoginMutationVariables = Exact<{
|
||||||
username: Scalars['String']
|
username: Scalars['String']
|
||||||
password: Scalars['String']
|
password: Scalars['String']
|
||||||
@ -3049,17 +3077,32 @@ export type GetProductQuery = { __typename?: 'Query' } & {
|
|||||||
{ __typename?: 'ProductOption' } & Pick<
|
{ __typename?: 'ProductOption' } & Pick<
|
||||||
ProductOption,
|
ProductOption,
|
||||||
'id' | 'name' | 'code' | 'groupId'
|
'id' | 'name' | 'code' | 'groupId'
|
||||||
>
|
> & {
|
||||||
|
group: { __typename?: 'ProductOptionGroup' } & Pick<
|
||||||
|
ProductOptionGroup,
|
||||||
|
'id'
|
||||||
|
> & {
|
||||||
|
options: Array<
|
||||||
|
{ __typename?: 'ProductOption' } & Pick<
|
||||||
|
ProductOption,
|
||||||
|
'name'
|
||||||
|
>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
optionGroups: Array<
|
optionGroups: Array<
|
||||||
{ __typename?: 'ProductOptionGroup' } & Pick<
|
{ __typename?: 'ProductOptionGroup' } & Pick<
|
||||||
ProductOptionGroup,
|
ProductOptionGroup,
|
||||||
'code' | 'name'
|
'id' | 'code' | 'name'
|
||||||
> & {
|
> & {
|
||||||
options: Array<
|
options: Array<
|
||||||
{ __typename?: 'ProductOption' } & Pick<ProductOption, 'name'>
|
{ __typename?: 'ProductOption' } & Pick<
|
||||||
|
ProductOption,
|
||||||
|
'id' | 'name'
|
||||||
|
>
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -36,6 +36,11 @@ type Query {
|
|||||||
"""
|
"""
|
||||||
eligibleShippingMethods: [ShippingMethodQuote!]!
|
eligibleShippingMethods: [ShippingMethodQuote!]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns a list of payment methods and their eligibility based on the current active Order
|
||||||
|
"""
|
||||||
|
eligiblePaymentMethods: [PaymentMethodQuote!]!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returns information about the current authenticated User
|
Returns information about the current authenticated User
|
||||||
"""
|
"""
|
||||||
@ -289,6 +294,7 @@ type Asset implements Node {
|
|||||||
source: String!
|
source: String!
|
||||||
preview: String!
|
preview: String!
|
||||||
focalPoint: Coordinate
|
focalPoint: Coordinate
|
||||||
|
customFields: JSON
|
||||||
}
|
}
|
||||||
|
|
||||||
type Coordinate {
|
type Coordinate {
|
||||||
@ -331,6 +337,7 @@ type Channel implements Node {
|
|||||||
defaultLanguageCode: LanguageCode!
|
defaultLanguageCode: LanguageCode!
|
||||||
currencyCode: CurrencyCode!
|
currencyCode: CurrencyCode!
|
||||||
pricesIncludeTax: Boolean!
|
pricesIncludeTax: Boolean!
|
||||||
|
customFields: JSON
|
||||||
}
|
}
|
||||||
|
|
||||||
type Collection implements Node {
|
type Collection implements Node {
|
||||||
@ -568,6 +575,7 @@ enum ErrorCode {
|
|||||||
ORDER_MODIFICATION_ERROR
|
ORDER_MODIFICATION_ERROR
|
||||||
INELIGIBLE_SHIPPING_METHOD_ERROR
|
INELIGIBLE_SHIPPING_METHOD_ERROR
|
||||||
ORDER_PAYMENT_STATE_ERROR
|
ORDER_PAYMENT_STATE_ERROR
|
||||||
|
INELIGIBLE_PAYMENT_METHOD_ERROR
|
||||||
PAYMENT_FAILED_ERROR
|
PAYMENT_FAILED_ERROR
|
||||||
PAYMENT_DECLINED_ERROR
|
PAYMENT_DECLINED_ERROR
|
||||||
COUPON_CODE_INVALID_ERROR
|
COUPON_CODE_INVALID_ERROR
|
||||||
@ -704,6 +712,8 @@ type ConfigArgDefinition {
|
|||||||
name: String!
|
name: String!
|
||||||
type: String!
|
type: String!
|
||||||
list: Boolean!
|
list: Boolean!
|
||||||
|
required: Boolean!
|
||||||
|
defaultValue: JSON
|
||||||
label: String
|
label: String
|
||||||
description: String
|
description: String
|
||||||
ui: JSON
|
ui: JSON
|
||||||
@ -727,6 +737,10 @@ type DeletionResponse {
|
|||||||
|
|
||||||
input ConfigArgInput {
|
input ConfigArgInput {
|
||||||
name: String!
|
name: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
A JSON stringified representation of the actual value
|
||||||
|
"""
|
||||||
value: String!
|
value: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -839,6 +853,26 @@ type Success {
|
|||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShippingMethodQuote {
|
||||||
|
id: ID!
|
||||||
|
price: Int!
|
||||||
|
priceWithTax: Int!
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Any optional metadata returned by the ShippingCalculator in the ShippingCalculationResult
|
||||||
|
"""
|
||||||
|
metadata: JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentMethodQuote {
|
||||||
|
id: ID!
|
||||||
|
code: String!
|
||||||
|
isEligible: Boolean!
|
||||||
|
eligibilityMessage: String
|
||||||
|
}
|
||||||
|
|
||||||
type Country implements Node {
|
type Country implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
@ -2817,15 +2851,6 @@ type OrderList implements PaginatedList {
|
|||||||
totalItems: Int!
|
totalItems: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShippingMethodQuote {
|
|
||||||
id: ID!
|
|
||||||
price: Int!
|
|
||||||
priceWithTax: Int!
|
|
||||||
name: String!
|
|
||||||
description: String!
|
|
||||||
metadata: JSON
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShippingLine {
|
type ShippingLine {
|
||||||
shippingMethod: ShippingMethod!
|
shippingMethod: ShippingMethod!
|
||||||
price: Int!
|
price: Int!
|
||||||
@ -2904,6 +2929,16 @@ type OrderLine implements Node {
|
|||||||
"""
|
"""
|
||||||
unitPriceWithTax: Int!
|
unitPriceWithTax: Int!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Non-zero if the unitPrice has changed since it was initially added to Order
|
||||||
|
"""
|
||||||
|
unitPriceChangeSinceAdded: Int!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Non-zero if the unitPriceWithTax has changed since it was initially added to Order
|
||||||
|
"""
|
||||||
|
unitPriceWithTaxChangeSinceAdded: Int!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The price of a single unit including discounts, excluding tax.
|
The price of a single unit including discounts, excluding tax.
|
||||||
|
|
||||||
@ -3197,6 +3232,7 @@ type ProductVariant implements Node {
|
|||||||
priceIncludesTax: Boolean!
|
priceIncludesTax: Boolean!
|
||||||
@deprecated(reason: "price now always excludes tax")
|
@deprecated(reason: "price now always excludes tax")
|
||||||
priceWithTax: Int!
|
priceWithTax: Int!
|
||||||
|
stockLevel: String!
|
||||||
taxRateApplied: TaxRate!
|
taxRateApplied: TaxRate!
|
||||||
taxCategory: TaxCategory!
|
taxCategory: TaxCategory!
|
||||||
options: [ProductOption!]!
|
options: [ProductOption!]!
|
||||||
@ -3292,6 +3328,7 @@ type TaxCategory implements Node {
|
|||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
updatedAt: DateTime!
|
updatedAt: DateTime!
|
||||||
name: String!
|
name: String!
|
||||||
|
isDefault: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaxRate implements Node {
|
type TaxRate implements Node {
|
||||||
@ -3347,7 +3384,7 @@ type OrderModificationError implements ErrorResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returned when attempting to set a ShippingMethod for which the order is not eligible
|
Returned when attempting to set a ShippingMethod for which the Order is not eligible
|
||||||
"""
|
"""
|
||||||
type IneligibleShippingMethodError implements ErrorResult {
|
type IneligibleShippingMethodError implements ErrorResult {
|
||||||
errorCode: ErrorCode!
|
errorCode: ErrorCode!
|
||||||
@ -3362,6 +3399,15 @@ type OrderPaymentStateError implements ErrorResult {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returned when attempting to add a Payment using a PaymentMethod for which the Order is not eligible.
|
||||||
|
"""
|
||||||
|
type IneligiblePaymentMethodError implements ErrorResult {
|
||||||
|
errorCode: ErrorCode!
|
||||||
|
message: String!
|
||||||
|
eligibilityCheckerMessage: String
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returned when a Payment fails due to an error.
|
Returned when a Payment fails due to an error.
|
||||||
"""
|
"""
|
||||||
@ -3562,6 +3608,7 @@ union ApplyCouponCodeResult =
|
|||||||
union AddPaymentToOrderResult =
|
union AddPaymentToOrderResult =
|
||||||
Order
|
Order
|
||||||
| OrderPaymentStateError
|
| OrderPaymentStateError
|
||||||
|
| IneligiblePaymentMethodError
|
||||||
| PaymentFailedError
|
| PaymentFailedError
|
||||||
| PaymentDeclinedError
|
| PaymentDeclinedError
|
||||||
| OrderStateTransitionError
|
| OrderStateTransitionError
|
||||||
@ -3718,6 +3765,7 @@ input ProductVariantFilterParameter {
|
|||||||
currencyCode: StringOperators
|
currencyCode: StringOperators
|
||||||
priceIncludesTax: BooleanOperators
|
priceIncludesTax: BooleanOperators
|
||||||
priceWithTax: NumberOperators
|
priceWithTax: NumberOperators
|
||||||
|
stockLevel: StringOperators
|
||||||
}
|
}
|
||||||
|
|
||||||
input ProductVariantSortParameter {
|
input ProductVariantSortParameter {
|
||||||
@ -3729,6 +3777,7 @@ input ProductVariantSortParameter {
|
|||||||
name: SortOrder
|
name: SortOrder
|
||||||
price: SortOrder
|
price: SortOrder
|
||||||
priceWithTax: SortOrder
|
priceWithTax: SortOrder
|
||||||
|
stockLevel: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
input CustomerFilterParameter {
|
input CustomerFilterParameter {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export { default as useAddItem } from './use-add-item'
|
|
||||||
export { default as useWishlist } from './use-wishlist'
|
|
||||||
export { default as useRemoveItem } from './use-remove-item'
|
|
||||||
export { default as useWishlistActions } from './use-wishlist-actions'
|
|
@ -1,57 +1,13 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { HookFetcher } from '@commerce/utils/types'
|
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
|
||||||
import useWishlistAddItem from '@commerce/wishlist/use-add-item'
|
|
||||||
import type { ItemBody, AddItemBody } from '../api/wishlist'
|
|
||||||
import useCustomer from '../customer/use-customer'
|
|
||||||
import useWishlist, { UseWishlistOptions, Wishlist } from './use-wishlist'
|
|
||||||
|
|
||||||
const defaultOpts = {
|
export function emptyHook() {
|
||||||
url: '/api/bigcommerce/wishlist',
|
const useEmptyHook = async (options = {}) => {
|
||||||
method: 'POST',
|
return useCallback(async function () {
|
||||||
}
|
return Promise.resolve()
|
||||||
|
}, [])
|
||||||
export type AddItemInput = ItemBody
|
|
||||||
|
|
||||||
export const fetcher: HookFetcher<Wishlist, AddItemBody> = (
|
|
||||||
options,
|
|
||||||
{ item },
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
// TODO: add validations before doing the fetch
|
|
||||||
return fetch({
|
|
||||||
...defaultOpts,
|
|
||||||
...options,
|
|
||||||
body: { item },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
|
||||||
const useAddItem = (opts?: UseWishlistOptions) => {
|
|
||||||
const { data: customer } = useCustomer()
|
|
||||||
const { revalidate } = useWishlist(opts)
|
|
||||||
const fn = useWishlistAddItem(defaultOpts, customFetcher)
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
async function addItem(input: AddItemInput) {
|
|
||||||
if (!customer) {
|
|
||||||
// A signed customer is required in order to have a wishlist
|
|
||||||
throw new CommerceError({
|
|
||||||
message: 'Signed customer not found',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await fn({ item: input })
|
|
||||||
await revalidate()
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
[fn, revalidate, customer]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useAddItem.extend = extendHook
|
return useEmptyHook
|
||||||
|
|
||||||
return useAddItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
export default emptyHook
|
||||||
|
@ -1,61 +1,17 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { HookFetcher } from '@commerce/utils/types'
|
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
|
||||||
import useWishlistRemoveItem from '@commerce/wishlist/use-remove-item'
|
|
||||||
import type { RemoveItemBody } from '../api/wishlist'
|
|
||||||
import useCustomer from '../customer/use-customer'
|
|
||||||
import useWishlist, { UseWishlistOptions, Wishlist } from './use-wishlist'
|
|
||||||
|
|
||||||
const defaultOpts = {
|
type Options = {
|
||||||
url: '/api/bigcommerce/wishlist',
|
includeProducts?: boolean
|
||||||
method: 'DELETE',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RemoveItemInput = {
|
export function emptyHook(options?: Options) {
|
||||||
id: string | number
|
const useEmptyHook = async ({ id }: { id: string | number }) => {
|
||||||
}
|
return useCallback(async function () {
|
||||||
|
return Promise.resolve()
|
||||||
export const fetcher: HookFetcher<Wishlist | null, RemoveItemBody> = (
|
}, [])
|
||||||
options,
|
|
||||||
{ itemId },
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
return fetch({
|
|
||||||
...defaultOpts,
|
|
||||||
...options,
|
|
||||||
body: { itemId },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
|
||||||
const useRemoveItem = (opts?: UseWishlistOptions) => {
|
|
||||||
const { data: customer } = useCustomer()
|
|
||||||
const { revalidate } = useWishlist(opts)
|
|
||||||
const fn = useWishlistRemoveItem<Wishlist | null, RemoveItemBody>(
|
|
||||||
defaultOpts,
|
|
||||||
customFetcher
|
|
||||||
)
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
async function removeItem(input: RemoveItemInput) {
|
|
||||||
if (!customer) {
|
|
||||||
// A signed customer is required in order to have a wishlist
|
|
||||||
throw new CommerceError({
|
|
||||||
message: 'Signed customer not found',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await fn({ itemId: String(input.id) })
|
|
||||||
await revalidate()
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
[fn, revalidate, customer]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useRemoveItem.extend = extendHook
|
return useEmptyHook
|
||||||
|
|
||||||
return useRemoveItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default extendHook(fetcher)
|
export default emptyHook
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import useAddItem from './use-add-item'
|
|
||||||
import useRemoveItem from './use-remove-item'
|
|
||||||
|
|
||||||
// This hook is probably not going to be used, but it's here
|
|
||||||
// to show how a commerce should be structuring it
|
|
||||||
export default function useWishlistActions() {
|
|
||||||
const addItem = useAddItem()
|
|
||||||
const removeItem = useRemoveItem()
|
|
||||||
|
|
||||||
return { addItem, removeItem }
|
|
||||||
}
|
|
@ -1,17 +1,22 @@
|
|||||||
|
// TODO: replace this hook and other wishlist hooks with a handler, or remove them if
|
||||||
|
// Shopify doesn't have a wishlist
|
||||||
|
|
||||||
import { HookFetcher } from '@commerce/utils/types'
|
import { HookFetcher } from '@commerce/utils/types'
|
||||||
import { SwrOptions } from '@commerce/utils/use-data'
|
import { Product } from '../schema'
|
||||||
import defineProperty from '@commerce/utils/define-property'
|
|
||||||
import useCommerceWishlist from '@commerce/wishlist/use-wishlist'
|
|
||||||
import type { Wishlist } from '../api/wishlist'
|
|
||||||
import useCustomer from '../customer/use-customer'
|
|
||||||
|
|
||||||
const defaultOpts = {
|
const defaultOpts = {}
|
||||||
url: '/api/bigcommerce/wishlist',
|
|
||||||
method: 'GET',
|
export type Wishlist = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
id: number
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { Wishlist }
|
|
||||||
|
|
||||||
export interface UseWishlistOptions {
|
export interface UseWishlistOptions {
|
||||||
includeProducts?: boolean
|
includeProducts?: boolean
|
||||||
}
|
}
|
||||||
@ -20,55 +25,17 @@ export interface UseWishlistInput extends UseWishlistOptions {
|
|||||||
customerId?: number
|
customerId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = (
|
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
|
||||||
options,
|
return null
|
||||||
{ customerId, includeProducts },
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
if (!customerId) return null
|
|
||||||
|
|
||||||
// Use a dummy base as we only care about the relative path
|
|
||||||
const url = new URL(options?.url ?? defaultOpts.url, 'http://a')
|
|
||||||
|
|
||||||
if (includeProducts) url.searchParams.set('products', '1')
|
|
||||||
|
|
||||||
return fetch({
|
|
||||||
url: url.pathname + url.search,
|
|
||||||
method: options?.method ?? defaultOpts.method,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extendHook(
|
export function extendHook(
|
||||||
customFetcher: typeof fetcher,
|
customFetcher: typeof fetcher,
|
||||||
swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
|
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
|
||||||
|
swrOptions?: any
|
||||||
) {
|
) {
|
||||||
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
|
||||||
const { data: customer } = useCustomer()
|
return { data: null }
|
||||||
const response = useCommerceWishlist(
|
|
||||||
defaultOpts,
|
|
||||||
[
|
|
||||||
['customerId', customer?.id],
|
|
||||||
['includeProducts', includeProducts],
|
|
||||||
],
|
|
||||||
customFetcher,
|
|
||||||
{
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
...swrOptions,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Uses a getter to only calculate the prop when required
|
|
||||||
// response.data is also a getter and it's better to not trigger it early
|
|
||||||
if (!('isEmpty' in response)) {
|
|
||||||
defineProperty(response, 'isEmpty', {
|
|
||||||
get() {
|
|
||||||
return (response.data?.items?.length || 0) <= 0
|
|
||||||
},
|
|
||||||
set: (x) => x,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useWishlist.extend = extendHook
|
useWishlist.extend = extendHook
|
||||||
|
Loading…
x
Reference in New Issue
Block a user