mirror of
https://github.com/vercel/commerce.git
synced 2025-03-14 14:42:31 +00:00
Vendure provider (#223)
* Minimal list/detail views working with Vendure * Implement useCart/useAddItem * Implement useUpdateItem & useRemoveItem * Implement useSearch * Add operations codegen, tidy up * Dummy checkout page * Implement auth/customer hooks * Use env var for Shop API url * Add some documentation * Improve error handling * Optimize preview image size * Fix accidental change * Update Vendure provider to latest changes * Vendure provider: split out gql operations, remove unused files * Update Vendure provider readme * Add local next.config to Vendure provider, update docs * Update to use demo server * Fix build errors * Use proxy for vendure api * Simplify instructions in Vendure readme * Refactor Vendure checkout api handler * Improve image quality
This commit is contained in:
parent
8fb6c7b206
commit
da4371090d
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,6 +18,7 @@ out/
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
.idea
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
|
@ -7,7 +7,7 @@ const fs = require('fs')
|
||||
const merge = require('deepmerge')
|
||||
const prettier = require('prettier')
|
||||
|
||||
const PROVIDERS = ['bigcommerce', 'shopify', 'swell']
|
||||
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure']
|
||||
|
||||
function getProviderName() {
|
||||
return (
|
||||
|
1
framework/vendure/.env.template
Normal file
1
framework/vendure/.env.template
Normal file
@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=http://localhost:3001/shop-api
|
33
framework/vendure/README.md
Normal file
33
framework/vendure/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Vendure Storefront Data Hooks
|
||||
|
||||
UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use [Vendure](http://vendure.io/) as a headless e-commerce platform.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Clone this repo and install its dependencies with `yarn install` or `npm install`
|
||||
2. Set the Vendure provider and API URL in your `.env.local` file:
|
||||
```
|
||||
COMMERCE_PROVIDER=vendure
|
||||
NEXT_PUBLIC_VENDURE_SHOP_API_URL=https://demo.vendure.io/shop-api
|
||||
NEXT_PUBLIC_VENDURE_LOCAL_URL=/vendure-shop-api
|
||||
```
|
||||
3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. Vendure does not ship with built-in wishlist functionality.
|
||||
2. Nor does it come with any kind of blog/page-building feature. Both of these can be created as Vendure plugins, however.
|
||||
3. The entire Vendure customer flow is carried out via its GraphQL API. This means that there is no external, pre-existing checkout flow. The checkout flow must be created as part of the Next.js app. See https://github.com/vercel/commerce/issues/64 for further discusion.
|
||||
4. By default, the sign-up flow in Vendure uses email verification. This means that using the existing "sign up" flow from this project will not grant a new user the ability to authenticate, since the new account must first be verified. Again, the necessary parts to support this flow can be created as part of the Next.js app.
|
||||
|
||||
## Code generation
|
||||
|
||||
This provider makes use of GraphQL code generation. The [schema.graphql](./schema.graphql) and [schema.d.ts](./schema.d.ts) files contain the generated types & schema introspection results.
|
||||
|
||||
When developing the provider, changes to any GraphQL operations should be followed by re-generation of the types and schema files:
|
||||
|
||||
From the project root dir, run
|
||||
|
||||
```sh
|
||||
graphql-codegen --config ./framework/vendure/codegen.json
|
||||
```
|
1
framework/vendure/api/cart/index.ts
Normal file
1
framework/vendure/api/cart/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/vendure/api/catalog/index.ts
Normal file
1
framework/vendure/api/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/vendure/api/catalog/products.ts
Normal file
1
framework/vendure/api/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
60
framework/vendure/api/checkout/index.ts
Normal file
60
framework/vendure/api/checkout/index.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { NextApiHandler } from 'next'
|
||||
|
||||
const checkoutApi = async (req: any, res: any, config: any) => {
|
||||
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'>
|
||||
<h1>Checkout not 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 function createApiHandler<T = any, H = {}, Options extends {} = {}>(
|
||||
handler: any,
|
||||
handlers: H,
|
||||
defaultOptions: Options
|
||||
) {
|
||||
return function getApiHandler({
|
||||
config,
|
||||
operations,
|
||||
options,
|
||||
}: {
|
||||
config?: any
|
||||
operations?: Partial<H>
|
||||
options?: Options extends {} ? Partial<Options> : never
|
||||
} = {}): NextApiHandler {
|
||||
const ops = { ...operations, ...handlers }
|
||||
const opts = { ...defaultOptions, ...options }
|
||||
|
||||
return function apiHandler(req, res) {
|
||||
return handler(req, res, config, ops, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createApiHandler(checkoutApi, {}, {})
|
1
framework/vendure/api/customers/index.ts
Normal file
1
framework/vendure/api/customers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/vendure/api/customers/login.ts
Normal file
1
framework/vendure/api/customers/login.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/vendure/api/customers/logout.ts
Normal file
1
framework/vendure/api/customers/logout.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/vendure/api/customers/signup.ts
Normal file
1
framework/vendure/api/customers/signup.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
51
framework/vendure/api/index.ts
Normal file
51
framework/vendure/api/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { CommerceAPIConfig } from '@commerce/api'
|
||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
|
||||
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`
|
||||
)
|
||||
}
|
||||
|
||||
export class Config {
|
||||
private config: VendureConfig
|
||||
|
||||
constructor(config: VendureConfig) {
|
||||
this.config = {
|
||||
...config,
|
||||
}
|
||||
}
|
||||
|
||||
getConfig(userConfig: Partial<VendureConfig> = {}) {
|
||||
return Object.entries(userConfig).reduce<VendureConfig>(
|
||||
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||
{ ...this.config }
|
||||
)
|
||||
}
|
||||
|
||||
setConfig(newConfig: Partial<VendureConfig>) {
|
||||
Object.assign(this.config, newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
const ONE_DAY = 60 * 60 * 24
|
||||
const config = new Config({
|
||||
commerceUrl: API_URL,
|
||||
apiToken: '',
|
||||
cartCookie: '',
|
||||
customerCookie: '',
|
||||
cartCookieMaxAge: ONE_DAY * 30,
|
||||
fetch: fetchGraphqlApi,
|
||||
})
|
||||
|
||||
export function getConfig(userConfig?: Partial<VendureConfig>) {
|
||||
return config.getConfig(userConfig)
|
||||
}
|
||||
|
||||
export function setConfig(newConfig: Partial<VendureConfig>) {
|
||||
return config.setConfig(newConfig)
|
||||
}
|
37
framework/vendure/api/utils/fetch-graphql-api.ts
Normal file
37
framework/vendure/api/utils/fetch-graphql-api.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@commerce/api'
|
||||
import { getConfig } from '..'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||
query: string,
|
||||
{ variables, preview } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl, {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [{ message: 'Failed to fetch Vendure API' }],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
3
framework/vendure/api/utils/fetch.ts
Normal file
3
framework/vendure/api/utils/fetch.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
2
framework/vendure/api/wishlist/index.tsx
Normal file
2
framework/vendure/api/wishlist/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export type WishlistItem = { product: any; id: number }
|
||||
export default function () {}
|
50
framework/vendure/auth/use-login.tsx
Normal file
50
framework/vendure/auth/use-login.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import { LoginMutation, LoginMutationVariables } from '../schema'
|
||||
import { loginMutation } from '../lib/mutations/log-in-mutation'
|
||||
|
||||
export default useLogin as UseLogin<typeof handler>
|
||||
|
||||
export const handler: MutationHook<null, {}, any> = {
|
||||
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 { revalidate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function login(input) {
|
||||
const data = await fetch({ input })
|
||||
await revalidate()
|
||||
return data
|
||||
},
|
||||
[fetch, revalidate]
|
||||
)
|
||||
},
|
||||
}
|
32
framework/vendure/auth/use-logout.tsx
Normal file
32
framework/vendure/auth/use-logout.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import { LogoutMutation } from '../schema'
|
||||
import { logoutMutation } from '../lib/mutations/log-out-mutation'
|
||||
|
||||
export default useLogout as UseLogout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<null> = {
|
||||
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]
|
||||
)
|
||||
},
|
||||
}
|
68
framework/vendure/auth/use-signup.tsx
Normal file
68
framework/vendure/auth/use-signup.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { useCallback } from 'react'
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import {
|
||||
RegisterCustomerInput,
|
||||
SignupMutation,
|
||||
SignupMutationVariables,
|
||||
} from '../schema'
|
||||
import { signupMutation } from '../lib/mutations/sign-up-mutation'
|
||||
|
||||
export default useSignup as UseSignup<typeof handler>
|
||||
|
||||
export type SignupInput = {
|
||||
email: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const handler: MutationHook<null, {}, SignupInput, SignupInput> = {
|
||||
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 { revalidate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function signup(input) {
|
||||
const data = await fetch({ input })
|
||||
await revalidate()
|
||||
return data
|
||||
},
|
||||
[fetch, revalidate]
|
||||
)
|
||||
},
|
||||
}
|
5
framework/vendure/cart/index.ts
Normal file
5
framework/vendure/cart/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { default as useCart } from './use-cart'
|
||||
export { default as useAddItem } from './use-add-item'
|
||||
export { default as useRemoveItem } from './use-remove-item'
|
||||
export { default as useWishlistActions } from './use-cart-actions'
|
||||
export { default as useUpdateItem } from './use-cart-actions'
|
52
framework/vendure/cart/use-add-item.tsx
Normal file
52
framework/vendure/cart/use-add-item.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { Cart, CartItemBody } from '@commerce/types'
|
||||
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import { useCallback } from 'react'
|
||||
import useCart from './use-cart'
|
||||
import { AddItemToOrderMutation } from '../schema'
|
||||
import { normalizeCart } from '../lib/normalize'
|
||||
import { addItemToOrderMutation } from '../lib/mutations/add-item-to-order-mutation'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
||||
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]
|
||||
)
|
||||
},
|
||||
}
|
13
framework/vendure/cart/use-cart-actions.tsx
Normal file
13
framework/vendure/cart/use-cart-actions.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import useAddItem from './use-add-item'
|
||||
import useRemoveItem from './use-remove-item'
|
||||
import useUpdateItem from './use-update-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 useCartActions() {
|
||||
const addItem = useAddItem()
|
||||
const updateItem = useUpdateItem()
|
||||
const removeItem = useRemoveItem()
|
||||
|
||||
return { addItem, updateItem, removeItem }
|
||||
}
|
49
framework/vendure/cart/use-cart.tsx
Normal file
49
framework/vendure/cart/use-cart.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Cart } from '@commerce/types'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useCart, { FetchCartInput, UseCart } from '@commerce/cart/use-cart'
|
||||
import { ActiveOrderQuery, CartFragment } from '../schema'
|
||||
import { normalizeCart } from '../lib/normalize'
|
||||
import { useMemo } from 'react'
|
||||
import { getCartQuery } from '../lib/queries/get-cart-query'
|
||||
|
||||
export type CartResult = {
|
||||
activeOrder?: CartFragment
|
||||
addItemToOrder?: CartFragment
|
||||
adjustOrderLine?: CartFragment
|
||||
removeOrderLine?: CartFragment
|
||||
}
|
||||
|
||||
export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<
|
||||
Cart | null,
|
||||
{},
|
||||
FetchCartInput,
|
||||
{ isEmpty?: boolean }
|
||||
> = {
|
||||
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]
|
||||
)
|
||||
},
|
||||
}
|
48
framework/vendure/cart/use-remove-item.tsx
Normal file
48
framework/vendure/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react'
|
||||
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
|
||||
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import useCart from './use-cart'
|
||||
import {
|
||||
RemoveOrderLineMutation,
|
||||
RemoveOrderLineMutationVariables,
|
||||
} from '../schema'
|
||||
import { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
|
||||
import { normalizeCart } from '../lib/normalize'
|
||||
import { removeOrderLineMutation } from '../lib/mutations/remove-order-line-mutation'
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
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()
|
||||
|
||||
return useCallback(
|
||||
async function removeItem(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
78
framework/vendure/cart/use-update-item.tsx
Normal file
78
framework/vendure/cart/use-update-item.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { useCallback } from 'react'
|
||||
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
|
||||
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||
import {
|
||||
Cart,
|
||||
CartItemBody,
|
||||
LineItem,
|
||||
UpdateCartItemBody,
|
||||
} from '@commerce/types'
|
||||
import useCart from './use-cart'
|
||||
import {
|
||||
AdjustOrderLineMutation,
|
||||
AdjustOrderLineMutationVariables,
|
||||
} from '../schema'
|
||||
import { normalizeCart } from '../lib/normalize'
|
||||
import { adjustOrderLineMutation } from '../lib/mutations/adjust-order-line-mutation'
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
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()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input: Partial<CartItemBody>) {
|
||||
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]
|
||||
)
|
||||
},
|
||||
}
|
28
framework/vendure/codegen.json
Normal file
28
framework/vendure/codegen.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"schema": {
|
||||
"http://localhost:3001/shop-api": {}
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"./framework/vendure/**/*.{ts,tsx}": {
|
||||
"noRequire": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"generates": {
|
||||
"./framework/vendure/schema.d.ts": {
|
||||
"plugins": ["typescript", "typescript-operations"],
|
||||
"config": {
|
||||
"scalars": {
|
||||
"ID": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"./framework/vendure/schema.graphql": {
|
||||
"plugins": ["schema-ast"]
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"afterAllFileWrite": ["prettier --write"]
|
||||
}
|
||||
}
|
6
framework/vendure/commerce.config.json
Normal file
6
framework/vendure/commerce.config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"provider": "vendure",
|
||||
"features": {
|
||||
"wishlist": false
|
||||
}
|
||||
}
|
35
framework/vendure/common/get-all-pages.ts
Normal file
35
framework/vendure/common/get-all-pages.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
|
||||
export type Page = any
|
||||
|
||||
export type GetAllPagesResult<
|
||||
T extends { pages: any[] } = { pages: Page[] }
|
||||
> = T
|
||||
|
||||
async function getAllPages(opts?: {
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetAllPagesResult>
|
||||
|
||||
async function getAllPages<T extends { pages: any[] }>(opts: {
|
||||
url: string
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetAllPagesResult<T>>
|
||||
|
||||
async function getAllPages({
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
} = {}): Promise<GetAllPagesResult> {
|
||||
config = getConfig(config)
|
||||
|
||||
return {
|
||||
pages: [],
|
||||
}
|
||||
}
|
||||
|
||||
export default getAllPages
|
40
framework/vendure/common/get-page.ts
Normal file
40
framework/vendure/common/get-page.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { VendureConfig, getConfig } from '../api'
|
||||
|
||||
export type Page = any
|
||||
|
||||
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||
|
||||
export type PageVariables = {
|
||||
id: number
|
||||
}
|
||||
|
||||
async function getPage(opts: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult>
|
||||
|
||||
async function getPage<T extends { page?: any }, V = any>(opts: {
|
||||
url: string
|
||||
variables: V
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult<T>>
|
||||
|
||||
async function getPage({
|
||||
url,
|
||||
variables,
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult> {
|
||||
config = getConfig(config)
|
||||
return {}
|
||||
}
|
||||
|
||||
export default getPage
|
49
framework/vendure/common/get-site-info.ts
Normal file
49
framework/vendure/common/get-site-info.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
import { GetCollectionsQuery } from '../schema'
|
||||
import { arrayToTree } from '../lib/array-to-tree'
|
||||
import { getCollectionsQuery } from '../lib/queries/get-collections-query'
|
||||
|
||||
export type Category = {
|
||||
entityId: string
|
||||
name: string
|
||||
path: string
|
||||
productCount: number
|
||||
}
|
||||
|
||||
export type GetSiteInfoResult<
|
||||
T extends { categories: any[]; brands: any[] } = {
|
||||
categories: Category[]
|
||||
brands: any[]
|
||||
}
|
||||
> = T
|
||||
|
||||
async function getSiteInfo({
|
||||
query = getCollectionsQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: any
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
} = {}): Promise<GetSiteInfoResult> {
|
||||
config = getConfig(config)
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
export default getSiteInfo
|
18
framework/vendure/customer/get-customer-wishlist.ts
Normal file
18
framework/vendure/customer/get-customer-wishlist.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
|
||||
async function getCustomerWishlist({
|
||||
config,
|
||||
variables,
|
||||
includeProducts,
|
||||
}: {
|
||||
url?: string
|
||||
variables: any
|
||||
config?: VendureConfig
|
||||
includeProducts?: boolean
|
||||
}): Promise<any> {
|
||||
// Not implemented as Vendure does not ship with wishlist functionality at present
|
||||
config = getConfig(config)
|
||||
return { wishlist: {} }
|
||||
}
|
||||
|
||||
export default getCustomerWishlist
|
1
framework/vendure/customer/index.ts
Normal file
1
framework/vendure/customer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as useCustomer } from './use-customer'
|
33
framework/vendure/customer/use-customer.tsx
Normal file
33
framework/vendure/customer/use-customer.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||
import { Customer } from '@commerce/types'
|
||||
import { ActiveCustomerQuery } from '../schema'
|
||||
import { activeCustomerQuery } from '../lib/queries/active-customer-query'
|
||||
|
||||
export default useCustomer as UseCustomer<typeof handler>
|
||||
|
||||
export const handler: SWRHook<Customer | null> = {
|
||||
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,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
51
framework/vendure/fetcher.ts
Normal file
51
framework/vendure/fetcher.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Fetcher } from '@commerce/utils/types'
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
|
||||
async function getText(res: Response) {
|
||||
try {
|
||||
return (await res.text()) || res.statusText
|
||||
} catch (error) {
|
||||
return res.statusText
|
||||
}
|
||||
}
|
||||
|
||||
async function getError(res: Response) {
|
||||
if (res.headers.get('Content-Type')?.includes('application/json')) {
|
||||
const data = await res.json()
|
||||
return new FetcherError({ errors: data.errors, status: res.status })
|
||||
}
|
||||
return new FetcherError({ message: await getText(res), status: res.status })
|
||||
}
|
||||
|
||||
export const fetcher: Fetcher = async ({
|
||||
url,
|
||||
method = 'POST',
|
||||
variables,
|
||||
query,
|
||||
body: bodyObj,
|
||||
}) => {
|
||||
const shopApiUrl =
|
||||
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL ||
|
||||
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
|
||||
if (!shopApiUrl) {
|
||||
throw new Error(
|
||||
'The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local'
|
||||
)
|
||||
}
|
||||
const hasBody = Boolean(variables || query)
|
||||
const body = hasBody ? JSON.stringify({ query, variables }) : undefined
|
||||
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
||||
const res = await fetch(shopApiUrl, {
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
throw await getError(res)
|
||||
}
|
33
framework/vendure/index.tsx
Normal file
33
framework/vendure/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import * as React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import {
|
||||
CommerceConfig,
|
||||
CommerceProvider as CoreCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from '@commerce'
|
||||
import { vendureProvider } from './provider'
|
||||
|
||||
export const vendureConfig: CommerceConfig = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'session',
|
||||
}
|
||||
|
||||
export type VendureConfig = Partial<CommerceConfig>
|
||||
|
||||
export type VendureProps = {
|
||||
children?: ReactNode
|
||||
locale: string
|
||||
} & VendureConfig
|
||||
|
||||
export function CommerceProvider({ children, ...config }: VendureProps) {
|
||||
return (
|
||||
<CoreCommerceProvider
|
||||
provider={vendureProvider}
|
||||
config={{ ...vendureConfig, ...config }}
|
||||
>
|
||||
{children}
|
||||
</CoreCommerceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCommerce = () => useCoreCommerce()
|
67
framework/vendure/lib/array-to-tree.ts
Normal file
67
framework/vendure/lib/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
|
||||
}
|
42
framework/vendure/lib/fragments/cart-fragment.ts
Normal file
42
framework/vendure/lib/fragments/cart-fragment.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export const cartFragment = /* GraphQL */ `
|
||||
fragment Cart on Order {
|
||||
id
|
||||
code
|
||||
createdAt
|
||||
totalQuantity
|
||||
subTotal
|
||||
subTotalWithTax
|
||||
total
|
||||
totalWithTax
|
||||
currencyCode
|
||||
customer {
|
||||
id
|
||||
}
|
||||
lines {
|
||||
id
|
||||
quantity
|
||||
linePriceWithTax
|
||||
discountedLinePriceWithTax
|
||||
featuredAsset {
|
||||
id
|
||||
preview
|
||||
}
|
||||
discounts {
|
||||
description
|
||||
amount
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
price
|
||||
priceWithTax
|
||||
stockLevel
|
||||
product {
|
||||
slug
|
||||
}
|
||||
productId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
24
framework/vendure/lib/fragments/search-result-fragment.ts
Normal file
24
framework/vendure/lib/fragments/search-result-fragment.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export const searchResultFragment = /* GraphQL */ `
|
||||
fragment SearchResult on SearchResult {
|
||||
productId
|
||||
productName
|
||||
description
|
||||
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
framework/vendure/lib/mutations/log-in-mutation.ts
Normal file
14
framework/vendure/lib/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
framework/vendure/lib/mutations/log-out-mutation.ts
Normal file
7
framework/vendure/lib/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
framework/vendure/lib/mutations/sign-up-mutation.ts
Normal file
14
framework/vendure/lib/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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
55
framework/vendure/lib/normalize.ts
Normal file
55
framework/vendure/lib/normalize.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Cart, Product } from '@commerce/types'
|
||||
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 + '?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.discountedLinePriceWithTax / 100,
|
||||
listPrice: l.linePriceWithTax / 100,
|
||||
image: {
|
||||
url: l.featuredAsset?.preview + '?preset=thumb' || '',
|
||||
},
|
||||
requiresShipping: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
10
framework/vendure/lib/queries/active-customer-query.ts
Normal file
10
framework/vendure/lib/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
framework/vendure/lib/queries/get-all-products-query.ts
Normal file
12
framework/vendure/lib/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
framework/vendure/lib/queries/get-cart-query.ts
Normal file
10
framework/vendure/lib/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
framework/vendure/lib/queries/get-collections-query.ts
Normal file
21
framework/vendure/lib/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
framework/vendure/lib/queries/get-product-query.ts
Normal file
41
framework/vendure/lib/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
framework/vendure/lib/queries/search-query.ts
Normal file
13
framework/vendure/lib/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}
|
||||
`
|
8
framework/vendure/next.config.js
Normal file
8
framework/vendure/next.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
images: {
|
||||
domains: ['localhost', 'demo.vendure.io'],
|
||||
},
|
||||
}
|
48
framework/vendure/product/get-all-product-paths.ts
Normal file
48
framework/vendure/product/get-all-product-paths.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type {
|
||||
GetAllProductPathsQuery,
|
||||
GetAllProductPathsQueryVariables,
|
||||
} from '../schema'
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
import { getAllProductPathsQuery } from '../lib/queries/get-all-product-paths-query'
|
||||
|
||||
export type GetAllProductPathsResult = {
|
||||
products: Array<{ node: { path: string } }>
|
||||
}
|
||||
|
||||
async function getAllProductPaths(opts?: {
|
||||
variables?: GetAllProductPathsQueryVariables
|
||||
config?: VendureConfig
|
||||
}): Promise<GetAllProductPathsResult>
|
||||
|
||||
async function getAllProductPaths<
|
||||
T extends { products: any[] },
|
||||
V = any
|
||||
>(opts: {
|
||||
query: string
|
||||
variables?: V
|
||||
config?: VendureConfig
|
||||
}): Promise<GetAllProductPathsResult>
|
||||
|
||||
async function getAllProductPaths({
|
||||
query = getAllProductPathsQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: GetAllProductPathsQueryVariables
|
||||
config?: VendureConfig
|
||||
} = {}): Promise<GetAllProductPathsResult> {
|
||||
config = getConfig(config)
|
||||
// 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) => ({ node: { path: `/${p.slug}` } })),
|
||||
}
|
||||
}
|
||||
|
||||
export default getAllProductPaths
|
39
framework/vendure/product/get-all-products.ts
Normal file
39
framework/vendure/product/get-all-products.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Product } from '@commerce/types'
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
import { GetAllProductsQuery } from '../schema'
|
||||
import { normalizeSearchResult } from '../lib/normalize'
|
||||
import { getAllProductsQuery } from '../lib/queries/get-all-products-query'
|
||||
|
||||
export type ProductVariables = { first?: number }
|
||||
|
||||
async function getAllProducts(opts?: {
|
||||
variables?: ProductVariables
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<{ products: Product[] }>
|
||||
|
||||
async function getAllProducts({
|
||||
query = getAllProductsQuery,
|
||||
variables: { ...vars } = {},
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: ProductVariables
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||
config = getConfig(config)
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
export default getAllProducts
|
57
framework/vendure/product/get-product.ts
Normal file
57
framework/vendure/product/get-product.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Product } from '@commerce/types'
|
||||
import { getConfig, VendureConfig } from '../api'
|
||||
import { GetProductQuery } from '../schema'
|
||||
import { getProductQuery } from '../lib/queries/get-product-query'
|
||||
|
||||
async function getProduct({
|
||||
query = getProductQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables: { slug: string }
|
||||
config?: VendureConfig
|
||||
preview?: boolean
|
||||
}): Promise<Product | {} | any> {
|
||||
config = getConfig(config)
|
||||
|
||||
const locale = config.locale
|
||||
const { data } = await config.fetch<GetProductQuery>(query, { variables })
|
||||
const product = data.product
|
||||
|
||||
if (product) {
|
||||
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) => ({
|
||||
id: o.id,
|
||||
displayName: o.name,
|
||||
values: o.group.options.map((_o) => ({ 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 {}
|
||||
}
|
||||
|
||||
export default getProduct
|
4
framework/vendure/product/index.ts
Normal file
4
framework/vendure/product/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as usePrice } from './use-price'
|
||||
export { default as useSearch } from './use-search'
|
||||
export { default as getProduct } from './get-product'
|
||||
export { default as getAllProducts } from './get-all-products'
|
2
framework/vendure/product/use-price.tsx
Normal file
2
framework/vendure/product/use-price.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export * from '@commerce/product/use-price'
|
||||
export { default } from '@commerce/product/use-price'
|
65
framework/vendure/product/use-search.tsx
Normal file
65
framework/vendure/product/use-search.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||
import { Product } from '@commerce/types'
|
||||
import { SearchQuery, SearchQueryVariables } from '../schema'
|
||||
import { normalizeSearchResult } from '../lib/normalize'
|
||||
import { searchQuery } from '../lib/queries/search-query'
|
||||
|
||||
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<
|
||||
SearchProductsData,
|
||||
SearchProductsInput,
|
||||
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],
|
||||
['categoryId', input.categoryId],
|
||||
['brandId', input.brandId],
|
||||
['sort', input.sort],
|
||||
],
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input.swrOptions,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
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: 'session',
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
customer: { useCustomer },
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
}
|
3122
framework/vendure/schema.d.ts
vendored
Normal file
3122
framework/vendure/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3860
framework/vendure/schema.graphql
Normal file
3860
framework/vendure/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
5
framework/vendure/types.ts
Normal file
5
framework/vendure/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as Core from '@commerce/types'
|
||||
|
||||
export interface LineItem extends Core.LineItem {
|
||||
options?: any[]
|
||||
}
|
13
framework/vendure/wishlist/use-add-item.tsx
Normal file
13
framework/vendure/wishlist/use-add-item.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export function emptyHook() {
|
||||
const useEmptyHook = async (options = {}) => {
|
||||
return useCallback(async function () {
|
||||
return Promise.resolve()
|
||||
}, [])
|
||||
}
|
||||
|
||||
return useEmptyHook
|
||||
}
|
||||
|
||||
export default emptyHook
|
17
framework/vendure/wishlist/use-remove-item.tsx
Normal file
17
framework/vendure/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
framework/vendure/wishlist/use-wishlist.tsx
Normal file
46
framework/vendure/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
|
||||
// Shopify doesn't have a wishlist
|
||||
|
||||
import { HookFetcher } from '@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)
|
@ -8,6 +8,7 @@ const provider = commerce.provider || getProviderName()
|
||||
const isBC = provider === 'bigcommerce'
|
||||
const isShopify = provider === 'shopify'
|
||||
const isSwell = provider === 'swell'
|
||||
const isVendure = provider === 'vendure'
|
||||
|
||||
module.exports = withCommerceConfig({
|
||||
commerce,
|
||||
@ -17,7 +18,7 @@ module.exports = withCommerceConfig({
|
||||
},
|
||||
rewrites() {
|
||||
return [
|
||||
(isBC || isShopify || isSwell) && {
|
||||
(isBC || isShopify || isSwell || isVendure) && {
|
||||
source: '/checkout',
|
||||
destination: '/api/bigcommerce/checkout',
|
||||
},
|
||||
@ -27,6 +28,13 @@ module.exports = withCommerceConfig({
|
||||
source: '/logout',
|
||||
destination: '/api/bigcommerce/customers/logout?redirect_to=/',
|
||||
},
|
||||
// For Vendure, rewrite the local api url to the remote (external) api url. This is required
|
||||
// to make the session cookies work.
|
||||
isVendure &&
|
||||
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL && {
|
||||
source: `${process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL}/:path*`,
|
||||
destination: `${process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL}/:path*`,
|
||||
},
|
||||
// Rewrites for /search
|
||||
{
|
||||
source: '/search/designers/:name',
|
||||
|
@ -9,6 +9,7 @@
|
||||
"prettier-fix": "prettier --write .",
|
||||
"find:unused": "next-unused",
|
||||
"generate": "graphql-codegen",
|
||||
"generate:vendure": "graphql-codegen --config framework/vendure/codegen.json",
|
||||
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
|
||||
},
|
||||
"sideEffects": false,
|
||||
|
@ -21,15 +21,15 @@ export async function getStaticProps({
|
||||
preview,
|
||||
})
|
||||
|
||||
const { categories, brands } = await getSiteInfo({ config, preview })
|
||||
const { pages } = await getAllPages({ config, preview })
|
||||
// const { categories, brands } = await getSiteInfo({ config, preview })
|
||||
// const { pages } = await getAllPages({ config, preview })
|
||||
|
||||
return {
|
||||
props: {
|
||||
products,
|
||||
categories,
|
||||
brands,
|
||||
pages,
|
||||
categories: [],
|
||||
brands: [],
|
||||
pages: [],
|
||||
},
|
||||
revalidate: 14400,
|
||||
}
|
||||
|
@ -22,8 +22,8 @@
|
||||
"@components/*": ["components/*"],
|
||||
"@commerce": ["framework/commerce"],
|
||||
"@commerce/*": ["framework/commerce/*"],
|
||||
"@framework": ["framework/bigcommerce"],
|
||||
"@framework/*": ["framework/bigcommerce/*"]
|
||||
"@framework": ["framework/vendure"],
|
||||
"@framework/*": ["framework/vendure/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
|
42
yarn.lock
42
yarn.lock
@ -3061,21 +3061,6 @@ get-stream@^5.0.0, get-stream@^5.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
glob-base@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
|
||||
integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
|
||||
dependencies:
|
||||
glob-parent "^2.0.0"
|
||||
is-glob "^2.0.0"
|
||||
|
||||
glob-parent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
|
||||
integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
|
||||
dependencies:
|
||||
is-glob "^2.0.0"
|
||||
|
||||
glob-parent@^5.1.0, glob-parent@~5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
|
||||
@ -3511,16 +3496,6 @@ is-date-object@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
|
||||
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
|
||||
|
||||
is-dotfile@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
||||
integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=
|
||||
|
||||
is-extglob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
|
||||
integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
@ -3550,13 +3525,6 @@ is-glob@4.0.1, is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-glob@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
|
||||
integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
|
||||
dependencies:
|
||||
is-extglob "^1.0.0"
|
||||
|
||||
is-interactive@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
|
||||
@ -4868,16 +4836,6 @@ parse-filepath@^1.0.2:
|
||||
map-cache "^0.2.0"
|
||||
path-root "^0.1.1"
|
||||
|
||||
parse-glob@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
|
||||
integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
|
||||
dependencies:
|
||||
glob-base "^0.3.0"
|
||||
is-dotfile "^1.0.0"
|
||||
is-extglob "^1.0.0"
|
||||
is-glob "^2.0.0"
|
||||
|
||||
parse-json@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||
|
Loading…
x
Reference in New Issue
Block a user