diff --git a/.gitignore b/.gitignore index 50d4285ba..22f1bf4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ out/ # misc .DS_Store *.pem +.idea # debug npm-debug.log* diff --git a/framework/vendure/README.md b/framework/vendure/README.md new file mode 100644 index 000000000..7beee6c14 --- /dev/null +++ b/framework/vendure/README.md @@ -0,0 +1,383 @@ + +Table of Contents +================= + + * [BigCommerce Storefront Data Hooks](#bigcommerce-storefront-data-hooks) + * [Installation](#installation) + * [General Usage](#general-usage) + * [CommerceProvider](#commerceprovider) + * [useLogin hook](#uselogin-hook) + * [useLogout](#uselogout) + * [useCustomer](#usecustomer) + * [useSignup](#usesignup) + * [usePrice](#useprice) + * [Cart Hooks](#cart-hooks) + * [useCart](#usecart) + * [useAddItem](#useadditem) + * [useUpdateItem](#useupdateitem) + * [useRemoveItem](#useremoveitem) + * [Wishlist Hooks](#wishlist-hooks) + * [Product Hooks and API](#product-hooks-and-api) + * [useSearch](#usesearch) + * [getAllProducts](#getallproducts) + * [getProduct](#getproduct) + * [More](#more) + +# BigCommerce Storefront Data Hooks + +> This project is under active development, new features and updates will be continuously added over time + +UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use BigCommerce as a headless e-commerce platform. The package provides: + +- Code splitted hooks for data fetching using [SWR](https://swr.vercel.app/), and to handle common user actions +- Code splitted data fetching methods for initial data population and static generation of content +- Helpers to create the API endpoints that connect to the hooks, very well suited for Next.js applications + +## Installation + +To install: + +``` +yarn add storefront-data-hooks +``` + +After install, the first thing you do is: set your environment variables in `.env.local` + +```sh +BIGCOMMERCE_STOREFRONT_API_URL=<> +BIGCOMMERCE_STOREFRONT_API_TOKEN=<> +BIGCOMMERCE_STORE_API_URL=<> +BIGCOMMERCE_STORE_API_TOKEN=<> +BIGCOMMERCE_STORE_API_CLIENT_ID=<> +``` + +## General Usage + +### CommerceProvider + +This component is a provider pattern component that creates commerce context for it's children. It takes config values for the locale and an optional `fetcherRef` object for data fetching. + +```jsx +... +import { CommerceProvider } from '@bigcommerce/storefront-data-hooks' + +const App = ({ locale = 'en-US', children }) => { + return ( + + {children} + + ) +} +... +``` + +### useLogin hook + +Hook for bigcommerce user login functionality, returns `login` function to handle user login. + +```jsx +... +import useLogin from '@bigcommerce/storefront-data-hooks/use-login' + +const LoginView = () => { + const login = useLogin() + + const handleLogin = async () => { + await login({ + email, + password, + }) + } + + return ( +
+ {children} +
+ ) +} +... +``` + +### useLogout + +Hook to logout user. + +```jsx +... +import useLogout from '@bigcommerce/storefront-data-hooks/use-logout' + +const LogoutLink = () => { + const logout = useLogout() + return ( + logout()}> + Logout + + ) +} +``` + +### useCustomer + +Hook for getting logged in customer data, and fetching customer info. + +```jsx +... +import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer' +... + +const Profile = () => { + const { data } = useCustomer() + + if (!data) { + return null + } + + return ( +
Hello, {data.firstName}
+ ) +} +``` + +### useSignup + +Hook for bigcommerce user signup, returns `signup` function to handle user signups. + +```jsx +... +import useSignup from '@bigcommerce/storefront-data-hooks/use-login' + +const SignupView = () => { + const signup = useSignup() + + const handleSignup = async () => { + await signup({ + email, + firstName, + lastName, + password, + }) + } + + return ( +
+ {children} +
+ ) +} +... +``` + +### usePrice + +Helper hook to format price according to commerce locale, and return discount if available. + +```jsx +import usePrice from '@bigcommerce/storefront-data-hooks/use-price' +... + const { price, discount, basePrice } = usePrice( + data && { + amount: data.cart_amount, + currencyCode: data.currency.code, + } + ) +... +``` + +## Cart Hooks + +### useCart + +Returns the current cart data for use + +```jsx +... +import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart' + +const countItem = (count: number, item: any) => count + item.quantity +const countItems = (count: number, items: any[]) => + items.reduce(countItem, count) + +const CartNumber = () => { + const { data } = useCart() + const itemsCount = Object.values(data?.line_items ?? {}).reduce(countItems, 0) + + return itemsCount > 0 ? {itemsCount} : null +} +``` + +### useAddItem + +```jsx +... +import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item' + +const AddToCartButton = ({ productId, variantId }) => { + const addItem = useAddItem() + + const addToCart = async () => { + await addItem({ + productId, + variantId, + }) + } + + return +} +... +``` + +### useUpdateItem + +```jsx +... +import useUpdateItem from '@bigcommerce/storefront-data-hooks/cart/use-update-item' + +const CartItem = ({ item }) => { + const [quantity, setQuantity] = useState(item.quantity) + const updateItem = useUpdateItem(item) + + const updateQuantity = async (e) => { + const val = e.target.value + await updateItem({ quantity: val }) + } + + return ( + + ) +} +... +``` + +### useRemoveItem + +Provided with a cartItemId, will remove an item from the cart: + +```jsx +... +import useRemoveItem from '@bigcommerce/storefront-data-hooks/cart/use-remove-item' + +const RemoveButton = ({ item }) => { + const removeItem = useRemoveItem() + + const handleRemove = async () => { + await removeItem({ id: item.id }) + } + + return +} +... +``` + +## Wishlist Hooks + +Wishlist hooks are similar to cart hooks. See the below example for how to use `useWishlist`, `useAddItem`, and `useRemoveItem`. + +```jsx +import useAddItem from '@bigcommerce/storefront-data-hooks/wishlist/use-add-item' +import useRemoveItem from '@bigcommerce/storefront-data-hooks/wishlist/use-remove-item' +import useWishlist from '@bigcommerce/storefront-data-hooks/wishlist/use-wishlist' + +const WishlistButton = ({ productId, variant }) => { + const addItem = useAddItem() + const removeItem = useRemoveItem() + const { data } = useWishlist() + const { data: customer } = useCustomer() + const itemInWishlist = data?.items?.find( + (item) => + item.product_id === productId && + item.variant_id === variant?.node.entityId + ) + + const handleWishlistChange = async (e) => { + e.preventDefault() + + if (!customer) { + return + } + + if (itemInWishlist) { + await removeItem({ id: itemInWishlist.id! }) + } else { + await addItem({ + productId, + variantId: variant?.node.entityId!, + }) + } + } + + return ( + + ) +} +``` + +## Product Hooks and API + +### useSearch + +`useSearch` handles searching the bigcommerce storefront product catalog by catalog, brand, and query string. + +```jsx +... +import useSearch from '@bigcommerce/storefront-data-hooks/products/use-search' + +const SearchPage = ({ searchString, category, brand, sortStr }) => { + const { data } = useSearch({ + search: searchString || '', + categoryId: category?.entityId, + brandId: brand?.entityId, + sort: sortStr || '', + }) + + return ( + + {data.products.map(({ node }) => ( + + ))} + + ) +} +``` + +### getAllProducts + +API function to retrieve a product list. + +```js +import { getConfig } from '@bigcommerce/storefront-data-hooks/api' +import getAllProducts from '@bigcommerce/storefront-data-hooks/api/operations/get-all-products' + +const { products } = await getAllProducts({ + variables: { field: 'featuredProducts', first: 6 }, + config, + preview, +}) +``` + +### getProduct + +API product to retrieve a single product when provided with the product +slug string. + +```js +import { getConfig } from '@bigcommerce/storefront-data-hooks/api' +import getProduct from '@bigcommerce/storefront-data-hooks/api/operations/get-product' + +const { product } = await getProduct({ + variables: { slug }, + config, + preview, +}) +``` + +## More + +Feel free to read through the source for more usage, and check the commerce vercel demo and commerce repo for usage examples: ([demo.vercel.store](https://demo.vercel.store/)) ([repo](https://github.com/vercel/commerce)) diff --git a/framework/vendure/api/cart/handlers/add-item.ts b/framework/vendure/api/cart/handlers/add-item.ts new file mode 100644 index 000000000..f58a6c43c --- /dev/null +++ b/framework/vendure/api/cart/handlers/add-item.ts @@ -0,0 +1,39 @@ +import { parseCartItem } from '../../utils/parse-item' +import getCartCookie from '../../utils/get-cart-cookie' +import type { CartHandlers } from '..' + +const addItem: CartHandlers['addItem'] = async ({ + res, + body: { cartId, item }, + config, +}) => { + if (!item) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Missing item' }], + }) + } + if (!item.quantity) item.quantity = 1 + + const options = { + method: 'POST', + body: JSON.stringify({ + line_items: [parseCartItem(item)], + ...(!cartId && config.storeChannelId + ? { channel_id: config.storeChannelId } + : {}), + }), + } + const { data } = cartId + ? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options) + : await config.storeApiFetch('/v3/carts', options) + + // Create or update the cart cookie + res.setHeader( + 'Set-Cookie', + getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge) + ) + res.status(200).json({ data }) +} + +export default addItem diff --git a/framework/vendure/api/cart/handlers/get-cart.ts b/framework/vendure/api/cart/handlers/get-cart.ts new file mode 100644 index 000000000..9fb42d730 --- /dev/null +++ b/framework/vendure/api/cart/handlers/get-cart.ts @@ -0,0 +1,29 @@ +import { BigcommerceApiError } from '../../utils/errors' +import getCartCookie from '../../utils/get-cart-cookie' +import type { Cart, CartHandlers } from '..' + +// Return current cart info +const getCart: CartHandlers['getCart'] = async ({ + res, + body: { cartId }, + config, +}) => { + let result: { data?: Cart } = {} + + if (cartId) { + try { + result = await config.storeApiFetch(`/v3/carts/${cartId}`) + } catch (error) { + if (error instanceof BigcommerceApiError && error.status === 404) { + // Remove the cookie if it exists but the cart wasn't found + res.setHeader('Set-Cookie', getCartCookie(config.cartCookie)) + } else { + throw error + } + } + } + + res.status(200).json({ data: result.data ?? null }) +} + +export default getCart diff --git a/framework/vendure/api/cart/handlers/remove-item.ts b/framework/vendure/api/cart/handlers/remove-item.ts new file mode 100644 index 000000000..c980cbb42 --- /dev/null +++ b/framework/vendure/api/cart/handlers/remove-item.ts @@ -0,0 +1,33 @@ +import getCartCookie from '../../utils/get-cart-cookie' +import type { CartHandlers } from '..' + +const removeItem: CartHandlers['removeItem'] = async ({ + res, + body: { cartId, itemId }, + config, +}) => { + if (!cartId || !itemId) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const result = await config.storeApiFetch<{ data: any } | null>( + `/v3/carts/${cartId}/items/${itemId}`, + { method: 'DELETE' } + ) + const data = result?.data ?? null + + res.setHeader( + 'Set-Cookie', + data + ? // Update the cart cookie + getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge) + : // Remove the cart cookie if the cart was removed (empty items) + getCartCookie(config.cartCookie) + ) + res.status(200).json({ data }) +} + +export default removeItem diff --git a/framework/vendure/api/cart/handlers/update-item.ts b/framework/vendure/api/cart/handlers/update-item.ts new file mode 100644 index 000000000..df9ccaee8 --- /dev/null +++ b/framework/vendure/api/cart/handlers/update-item.ts @@ -0,0 +1,35 @@ +import { parseCartItem } from '../../utils/parse-item' +import getCartCookie from '../../utils/get-cart-cookie' +import type { CartHandlers } from '..' + +const updateItem: CartHandlers['updateItem'] = async ({ + res, + body: { cartId, itemId, item }, + config, +}) => { + if (!cartId || !itemId || !item) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const { data } = await config.storeApiFetch( + `/v3/carts/${cartId}/items/${itemId}`, + { + method: 'PUT', + body: JSON.stringify({ + line_item: parseCartItem(item), + }), + } + ) + + // Update the cart cookie + res.setHeader( + 'Set-Cookie', + getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge) + ) + res.status(200).json({ data }) +} + +export default updateItem diff --git a/framework/vendure/api/cart/index.ts b/framework/vendure/api/cart/index.ts new file mode 100644 index 000000000..5ff2d975b --- /dev/null +++ b/framework/vendure/api/cart/index.ts @@ -0,0 +1,116 @@ +import isAllowedMethod from '../utils/is-allowed-method' +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import { BigcommerceApiError } from '../utils/errors' +import getCart from './handlers/get-cart' +import addItem from './handlers/add-item' +import updateItem from './handlers/update-item' +import removeItem from './handlers/remove-item' + +type OptionSelections = { + option_id: Number + option_value: Number|String +} + +export type ItemBody = { + productId: number + variantId: number + quantity?: number + optionSelections?: OptionSelections +} + +export type AddItemBody = { item: ItemBody } + +export type UpdateItemBody = { itemId: string; item: ItemBody } + +export type RemoveItemBody = { itemId: string } + +// TODO: this type should match: +// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses +export type Cart = { + id: string + parent_id?: string + customer_id: number + email: string + currency: { code: string } + tax_included: boolean + base_amount: number + discount_amount: number + cart_amount: number + line_items: { + custom_items: any[] + digital_items: any[] + gift_certificates: any[] + physical_items: any[] + } + // TODO: add missing fields +} + +export type CartHandlers = { + getCart: BigcommerceHandler + addItem: BigcommerceHandler> + updateItem: BigcommerceHandler< + Cart, + { cartId?: string } & Partial + > + removeItem: BigcommerceHandler< + Cart, + { cartId?: string } & Partial + > +} + +const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] + +// TODO: a complete implementation should have schema validation for `req.body` +const cartApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + const { cookies } = req + const cartId = cookies[config.cartCookie] + + try { + // Return current cart info + if (req.method === 'GET') { + const body = { cartId } + return await handlers['getCart']({ req, res, config, body }) + } + + // Create or add an item to the cart + if (req.method === 'POST') { + const body = { ...req.body, cartId } + return await handlers['addItem']({ req, res, config, body }) + } + + // Update item in cart + if (req.method === 'PUT') { + const body = { ...req.body, cartId } + return await handlers['updateItem']({ req, res, config, body }) + } + + // Remove an item from the cart + if (req.method === 'DELETE') { + const body = { ...req.body, cartId } + return await handlers['removeItem']({ req, res, config, body }) + } + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export const handlers = { getCart, addItem, updateItem, removeItem } + +export default createApiHandler(cartApi, handlers, {}) diff --git a/framework/vendure/api/catalog/handlers/get-products.ts b/framework/vendure/api/catalog/handlers/get-products.ts new file mode 100644 index 000000000..894dc5cf3 --- /dev/null +++ b/framework/vendure/api/catalog/handlers/get-products.ts @@ -0,0 +1,79 @@ +import { Product } from 'framework/types' +import getAllProducts, { ProductEdge } from '../../../product/get-all-products' +import type { ProductsHandlers } from '../products' + +const SORT: { [key: string]: string | undefined } = { + latest: 'id', + trending: 'total_sold', + price: 'price', +} + +const LIMIT = 12 + +// Return current cart info +const getProducts: ProductsHandlers['getProducts'] = async ({ + res, + body: { search, category, brand, sort }, + config, +}) => { + // Use a dummy base as we only care about the relative path + const url = new URL('/v3/catalog/products', 'http://a') + + url.searchParams.set('is_visible', 'true') + url.searchParams.set('limit', String(LIMIT)) + + if (search) url.searchParams.set('keyword', search) + + if (category && Number.isInteger(Number(category))) + url.searchParams.set('categories:in', category) + + if (brand && Number.isInteger(Number(brand))) + url.searchParams.set('brand_id', brand) + + if (sort) { + const [_sort, direction] = sort.split('-') + const sortValue = SORT[_sort] + + if (sortValue && direction) { + url.searchParams.set('sort', sortValue) + url.searchParams.set('direction', direction) + } + } + + // We only want the id of each product + url.searchParams.set('include_fields', 'id') + + const { data } = await config.storeApiFetch<{ data: { id: number }[] }>( + url.pathname + url.search + ) + + const entityIds = data.map((p) => p.id) + const found = entityIds.length > 0 + + // We want the GraphQL version of each product + const graphqlData = await getAllProducts({ + variables: { first: LIMIT, entityIds }, + config, + }) + + // Put the products in an object that we can use to get them by id + const productsById = graphqlData.products.reduce<{ + [k: number]: Product + }>((prods, p) => { + prods[p.id] = p + return prods + }, {}) + + const products: Product[] = found ? [] : graphqlData.products + + // Populate the products array with the graphql products, in the order + // assigned by the list of entity ids + entityIds.forEach((id) => { + const product = productsById[id] + if (product) products.push(product) + }) + + res.status(200).json({ data: { products, found } }) +} + +export default getProducts diff --git a/framework/vendure/api/catalog/products.ts b/framework/vendure/api/catalog/products.ts new file mode 100644 index 000000000..fe5b807c6 --- /dev/null +++ b/framework/vendure/api/catalog/products.ts @@ -0,0 +1,48 @@ +import isAllowedMethod from '../utils/is-allowed-method' +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import { BigcommerceApiError } from '../utils/errors' +import getProducts from './handlers/get-products' +import { Product } from 'framework/types' + +export type SearchProductsData = { + products: Product[] + found: boolean +} + +export type ProductsHandlers = { + getProducts: BigcommerceHandler< + SearchProductsData, + { search?: 'string'; category?: string; brand?: string; sort?: string } + > +} + +const METHODS = ['GET'] + +// TODO(lf): a complete implementation should have schema validation for `req.body` +const productsApi: BigcommerceApiHandler< + SearchProductsData, + ProductsHandlers +> = async (req, res, config, handlers) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + const body = req.query + return await handlers['getProducts']({ req, res, config, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export const handlers = { getProducts } + +export default createApiHandler(productsApi, handlers, {}) diff --git a/framework/vendure/api/checkout.ts b/framework/vendure/api/checkout.ts new file mode 100644 index 000000000..530f4c40a --- /dev/null +++ b/framework/vendure/api/checkout.ts @@ -0,0 +1,77 @@ +import isAllowedMethod from './utils/is-allowed-method' +import createApiHandler, { + BigcommerceApiHandler, +} from './utils/create-api-handler' +import { BigcommerceApiError } from './utils/errors' + +const METHODS = ['GET'] +const fullCheckout = true + +// TODO: a complete implementation should have schema validation for `req.body` +const checkoutApi: BigcommerceApiHandler = async (req, res, config) => { + if (!isAllowedMethod(req, res, METHODS)) return + + const { cookies } = req + const cartId = cookies[config.cartCookie] + + try { + if (!cartId) { + res.redirect('/cart') + return + } + + const { data } = await config.storeApiFetch( + `/v3/carts/${cartId}/redirect_urls`, + { + method: 'POST', + } + ) + + if (fullCheckout) { + res.redirect(data.checkout_url) + return + } + + // TODO: make the embedded checkout work too! + const html = ` + + + + + + Checkout + + + + +
+ + + ` + + res.status(200) + res.setHeader('Content-Type', 'text/html') + res.write(html) + res.end() + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export default createApiHandler(checkoutApi, {}, {}) diff --git a/framework/vendure/api/customers/handlers/get-logged-in-customer.ts b/framework/vendure/api/customers/handlers/get-logged-in-customer.ts new file mode 100644 index 000000000..698235dda --- /dev/null +++ b/framework/vendure/api/customers/handlers/get-logged-in-customer.ts @@ -0,0 +1,59 @@ +import type { GetLoggedInCustomerQuery } from '../../../schema' +import type { CustomersHandlers } from '..' + +export const getLoggedInCustomerQuery = /* GraphQL */ ` + query getLoggedInCustomer { + customer { + entityId + firstName + lastName + email + company + customerGroupId + notes + phone + addressCount + attributeCount + storeCredit { + value + currencyCode + } + } + } +` + +export type Customer = NonNullable + +const getLoggedInCustomer: CustomersHandlers['getLoggedInCustomer'] = async ({ + req, + res, + config, +}) => { + const token = req.cookies[config.customerCookie] + + if (token) { + const { data } = await config.fetch( + getLoggedInCustomerQuery, + undefined, + { + headers: { + cookie: `${config.customerCookie}=${token}`, + }, + } + ) + const { customer } = data + + if (!customer) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Customer not found', code: 'not_found' }], + }) + } + + return res.status(200).json({ data: { customer } }) + } + + res.status(200).json({ data: null }) +} + +export default getLoggedInCustomer diff --git a/framework/vendure/api/customers/handlers/login.ts b/framework/vendure/api/customers/handlers/login.ts new file mode 100644 index 000000000..9e019f3a0 --- /dev/null +++ b/framework/vendure/api/customers/handlers/login.ts @@ -0,0 +1,49 @@ +import { FetcherError } from '@commerce/utils/errors' +import login from '../../../auth/login' +import type { LoginHandlers } from '../login' + +const invalidCredentials = /invalid credentials/i + +const loginHandler: LoginHandlers['login'] = async ({ + res, + body: { email, password }, + config, +}) => { + // TODO: Add proper validations with something like Ajv + if (!(email && password)) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + // TODO: validate the password and email + // Passwords must be at least 7 characters and contain both alphabetic + // and numeric characters. + + try { + await login({ variables: { email, password }, config, res }) + } catch (error) { + // Check if the email and password didn't match an existing account + if ( + error instanceof FetcherError && + invalidCredentials.test(error.message) + ) { + return res.status(401).json({ + data: null, + errors: [ + { + message: + 'Cannot find an account that matches the provided credentials', + code: 'invalid_credentials', + }, + ], + }) + } + + throw error + } + + res.status(200).json({ data: null }) +} + +export default loginHandler diff --git a/framework/vendure/api/customers/handlers/logout.ts b/framework/vendure/api/customers/handlers/logout.ts new file mode 100644 index 000000000..937ce0954 --- /dev/null +++ b/framework/vendure/api/customers/handlers/logout.ts @@ -0,0 +1,23 @@ +import { serialize } from 'cookie' +import { LogoutHandlers } from '../logout' + +const logoutHandler: LogoutHandlers['logout'] = async ({ + res, + body: { redirectTo }, + config, +}) => { + // Remove the cookie + res.setHeader( + 'Set-Cookie', + serialize(config.customerCookie, '', { maxAge: -1, path: '/' }) + ) + + // Only allow redirects to a relative URL + if (redirectTo?.startsWith('/')) { + res.redirect(redirectTo) + } else { + res.status(200).json({ data: null }) + } +} + +export default logoutHandler diff --git a/framework/vendure/api/customers/handlers/signup.ts b/framework/vendure/api/customers/handlers/signup.ts new file mode 100644 index 000000000..1b24db0cc --- /dev/null +++ b/framework/vendure/api/customers/handlers/signup.ts @@ -0,0 +1,62 @@ +import { BigcommerceApiError } from '../../utils/errors' +import login from '../../../auth/login' +import { SignupHandlers } from '../signup' + +const signup: SignupHandlers['signup'] = async ({ + res, + body: { firstName, lastName, email, password }, + config, +}) => { + // TODO: Add proper validations with something like Ajv + if (!(firstName && lastName && email && password)) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + // TODO: validate the password and email + // Passwords must be at least 7 characters and contain both alphabetic + // and numeric characters. + + try { + await config.storeApiFetch('/v3/customers', { + method: 'POST', + body: JSON.stringify([ + { + first_name: firstName, + last_name: lastName, + email, + authentication: { + new_password: password, + }, + }, + ]), + }) + } catch (error) { + if (error instanceof BigcommerceApiError && error.status === 422) { + const hasEmailError = '0.email' in error.data?.errors + + // If there's an error with the email, it most likely means it's duplicated + if (hasEmailError) { + return res.status(400).json({ + data: null, + errors: [ + { + message: 'The email is already in use', + code: 'duplicated_email', + }, + ], + }) + } + } + + throw error + } + + // Login the customer right after creating it + await login({ variables: { email, password }, res, config }) + + res.status(200).json({ data: null }) +} + +export default signup diff --git a/framework/vendure/api/customers/index.ts b/framework/vendure/api/customers/index.ts new file mode 100644 index 000000000..5af4d1d1d --- /dev/null +++ b/framework/vendure/api/customers/index.ts @@ -0,0 +1,46 @@ +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import isAllowedMethod from '../utils/is-allowed-method' +import { BigcommerceApiError } from '../utils/errors' +import getLoggedInCustomer, { + Customer, +} from './handlers/get-logged-in-customer' + +export type { Customer } + +export type CustomerData = { + customer: Customer +} + +export type CustomersHandlers = { + getLoggedInCustomer: BigcommerceHandler +} + +const METHODS = ['GET'] + +const customersApi: BigcommerceApiHandler< + CustomerData, + CustomersHandlers +> = async (req, res, config, handlers) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + const body = null + return await handlers['getLoggedInCustomer']({ req, res, config, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +const handlers = { getLoggedInCustomer } + +export default createApiHandler(customersApi, handlers, {}) diff --git a/framework/vendure/api/customers/login.ts b/framework/vendure/api/customers/login.ts new file mode 100644 index 000000000..e8f24a92d --- /dev/null +++ b/framework/vendure/api/customers/login.ts @@ -0,0 +1,45 @@ +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import isAllowedMethod from '../utils/is-allowed-method' +import { BigcommerceApiError } from '../utils/errors' +import login from './handlers/login' + +export type LoginBody = { + email: string + password: string +} + +export type LoginHandlers = { + login: BigcommerceHandler> +} + +const METHODS = ['POST'] + +const loginApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + const body = req.body ?? {} + return await handlers['login']({ req, res, config, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +const handlers = { login } + +export default createApiHandler(loginApi, handlers, {}) diff --git a/framework/vendure/api/customers/logout.ts b/framework/vendure/api/customers/logout.ts new file mode 100644 index 000000000..0a4965245 --- /dev/null +++ b/framework/vendure/api/customers/logout.ts @@ -0,0 +1,42 @@ +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import isAllowedMethod from '../utils/is-allowed-method' +import { BigcommerceApiError } from '../utils/errors' +import logout from './handlers/logout' + +export type LogoutHandlers = { + logout: BigcommerceHandler +} + +const METHODS = ['GET'] + +const logoutApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + const redirectTo = req.query.redirect_to + const body = typeof redirectTo === 'string' ? { redirectTo } : {} + + return await handlers['logout']({ req, res, config, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +const handlers = { logout } + +export default createApiHandler(logoutApi, handlers, {}) diff --git a/framework/vendure/api/customers/signup.ts b/framework/vendure/api/customers/signup.ts new file mode 100644 index 000000000..aa26f78cf --- /dev/null +++ b/framework/vendure/api/customers/signup.ts @@ -0,0 +1,50 @@ +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import isAllowedMethod from '../utils/is-allowed-method' +import { BigcommerceApiError } from '../utils/errors' +import signup from './handlers/signup' + +export type SignupBody = { + firstName: string + lastName: string + email: string + password: string +} + +export type SignupHandlers = { + signup: BigcommerceHandler> +} + +const METHODS = ['POST'] + +const signupApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + const { cookies } = req + const cartId = cookies[config.cartCookie] + + try { + const body = { ...req.body, cartId } + return await handlers['signup']({ req, res, config, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +const handlers = { signup } + +export default createApiHandler(signupApi, handlers, {}) diff --git a/framework/vendure/api/definitions/catalog.ts b/framework/vendure/api/definitions/catalog.ts new file mode 100644 index 000000000..2c483f781 --- /dev/null +++ b/framework/vendure/api/definitions/catalog.ts @@ -0,0 +1,2993 @@ +/** + * This file was auto-generated by swagger-to-ts. + * Do not make direct changes to the file. + */ + +export interface definitions { + /** + * Common Modifier properties. + */ + productModifier_Base: { + /** + * BigCommerce API, which determines how it will display on the storefront. Acceptable values: `date`, `checkbox`, `file`, `text`, `multi_line_text`, `numbers_only_text`, `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. Required in a /POST. + */ + type: + | 'date' + | 'checkbox' + | 'file' + | 'text' + | 'multi_line_text' + | 'numbers_only_text' + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + /** + * Whether or not this modifer is required or not at checkout. Required in a /POST. + */ + required: boolean + /** + * The order the modifiers display on the product detail page. + */ + sort_order?: number + config?: definitions['config_Full'] + /** + * The name of the option shown on the storefront. + */ + display_name?: string + } + /** + * Product Modifier + */ + productModifier_Full: definitions['productModifier_Base'] & { + /** + * The unique numeric ID of the modifier; increments sequentially. + */ + id?: number + /** + * The unique numeric ID of the product to which the option belongs. + */ + product_id?: number + /** + * The unique option name. Auto-generated from the display name, a timestamp, and the product ID. + */ + name?: string + option_values?: definitions['productModifierOptionValue_Full'][] + } + /** + * The model for a POST to create a modifier on a product. + */ + productModifier_Post: { + /** + * BigCommerce API, which determines how it will display on the storefront. Acceptable values: `date`, `checkbox`, `file`, `text`, `multi_line_text`, `numbers_only_text`, `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. Required in a /POST. + */ + type: + | 'date' + | 'checkbox' + | 'file' + | 'text' + | 'multi_line_text' + | 'numbers_only_text' + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + /** + * Whether or not this modifer is required or not at checkout. Required in a /POST. + */ + required: boolean + /** + * The order the modifiers display on the product detail page. + */ + sort_order?: number + /** + * The values for option config can vary based on the Modifier created. + */ + config?: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + option_values?: (({ + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + adjusters?: { + /** + * Adjuster for Complex Rules. + */ + price?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * Adjuster for Complex Rules. + */ + weight?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * The URL for an image displayed on the storefront when the modifier value is selected.Limit of 8MB per file. + */ + image_url?: string + purchasing_disabled?: { + /** + * Flag for whether the modifier value disables purchasing when selected on the storefront. This can be used for temporarily disabling a particular modifier value. + */ + status?: boolean + /** + * The message displayed on the storefront when the purchasing disabled status is `true`. + */ + message?: string + } + } + }) & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + })[] + } & { + /** + * The name of the option shown on the storefront. + */ + display_name: string + } + /** + * The model for a PUT to update a modifier on a product. + */ + productModifier_Put: { + /** + * BigCommerce API, which determines how it will display on the storefront. Acceptable values: `date`, `checkbox`, `file`, `text`, `multi_line_text`, `numbers_only_text`, `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. Required in a /POST. + */ + type: + | 'date' + | 'checkbox' + | 'file' + | 'text' + | 'multi_line_text' + | 'numbers_only_text' + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + /** + * Whether or not this modifer is required or not at checkout. Required in a /POST. + */ + required: boolean + /** + * The order the modifiers display on the product detail page. + */ + sort_order?: number + /** + * The values for option config can vary based on the Modifier created. + */ + config?: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + option_values?: (({ + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + adjusters?: { + /** + * Adjuster for Complex Rules. + */ + price?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * Adjuster for Complex Rules. + */ + weight?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * The URL for an image displayed on the storefront when the modifier value is selected.Limit of 8MB per file. + */ + image_url?: string + purchasing_disabled?: { + /** + * Flag for whether the modifier value disables purchasing when selected on the storefront. This can be used for temporarily disabling a particular modifier value. + */ + status?: boolean + /** + * The message displayed on the storefront when the purchasing disabled status is `true`. + */ + message?: string + } + } + }) & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + })[] + } + /** + * Common Product Modifer `option_value` properties. + */ + productModifierOptionValue_Base: { + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. If no data is available, returns `null`. + */ + value_data?: { [key: string]: any } + adjusters?: definitions['adjusters_Full'] + } + /** + * Product Modifer `option_value`. + */ + productModifierOptionValue_Full: definitions['productModifierOptionValue_Base'] & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + option_id?: number + } + /** + * The model for a POST to create a modifier value on a product. + */ + productModifierOptionValue_Post: { + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + adjusters?: { + /** + * Adjuster for Complex Rules. + */ + price?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * Adjuster for Complex Rules. + */ + weight?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * The URL for an image displayed on the storefront when the modifier value is selected.Limit of 8MB per file. + */ + image_url?: string + purchasing_disabled?: { + /** + * Flag for whether the modifier value disables purchasing when selected on the storefront. This can be used for temporarily disabling a particular modifier value. + */ + status?: boolean + /** + * The message displayed on the storefront when the purchasing disabled status is `true`. + */ + message?: string + } + } + } + /** + * The model for a PUT to update a modifier value on a product. + */ + productModifierOptionValue_Put: ({ + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + adjusters?: { + /** + * Adjuster for Complex Rules. + */ + price?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * Adjuster for Complex Rules. + */ + weight?: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * The URL for an image displayed on the storefront when the modifier value is selected.Limit of 8MB per file. + */ + image_url?: string + purchasing_disabled?: { + /** + * Flag for whether the modifier value disables purchasing when selected on the storefront. This can be used for temporarily disabling a particular modifier value. + */ + status?: boolean + /** + * The message displayed on the storefront when the purchasing disabled status is `true`. + */ + message?: string + } + } + }) & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + } + resp_productionOption: { + data?: definitions['productOption_Full'] + /** + * Empty meta object; may be used later. + */ + meta?: { ''?: string } + } + /** + * Common Option properties. + */ + productOption_Base: { + /** + * The unique numerical ID of the option, increments sequentially. + */ + id?: number + /** + * The unique numerical ID of the product to which the option belongs. + */ + product_id?: number + /** + * The name of the option shown on the storefront. + */ + display_name?: string + /** + * The type of option, which determines how it will display on the storefront. Acceptable values: `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. For reference, the former v2 API values are: RB = radio_buttons, RT = rectangles, S = dropdown, P = product_list, PI = product_list_with_images, CS = swatch. + */ + type?: + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + config?: definitions['productOptionConfig_Full'] + /** + * Order in which the option is displayed on the storefront. + */ + sort_order?: number + option_values?: definitions['productOptionOptionValue_Full'] + } + productOption_Full: definitions['productOption_Base'] & { + /** + * The unique option name, auto-generated from the display name, a timestamp, and the product ID. + */ + name?: string + } + /** + * The model for a POST to create options on a product. + */ + productOption_Post: { + /** + * The unique numerical ID of the option, increments sequentially. + */ + id?: number + /** + * The unique numerical ID of the product to which the option belongs. + */ + product_id?: number + /** + * The name of the option shown on the storefront. + */ + display_name?: string + /** + * The type of option, which determines how it will display on the storefront. Acceptable values: `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. For reference, the former v2 API values are: RB = radio_buttons, RT = rectangles, S = dropdown, P = product_list, PI = product_list_with_images, CS = swatch. + */ + type?: + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + /** + * The values for option config can vary based on the Modifier created. + */ + config?: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + /** + * Order in which the option is displayed on the storefront. + */ + sort_order?: number + option_values?: ({ + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + })[] + /** + * Publicly available image url + */ + image_url?: string + } + /** + * The model for a PUT to update options on a product. + */ + productOption_Put: { + /** + * The unique numerical ID of the option, increments sequentially. + */ + id?: number + /** + * The unique numerical ID of the product to which the option belongs. + */ + product_id?: number + /** + * The name of the option shown on the storefront. + */ + display_name?: string + /** + * The type of option, which determines how it will display on the storefront. Acceptable values: `radio_buttons`, `rectangles`, `dropdown`, `product_list`, `product_list_with_images`, `swatch`. For reference, the former v2 API values are: RB = radio_buttons, RT = rectangles, S = dropdown, P = product_list, PI = product_list_with_images, CS = swatch. + */ + type?: + | 'radio_buttons' + | 'rectangles' + | 'dropdown' + | 'product_list' + | 'product_list_with_images' + | 'swatch' + /** + * The values for option config can vary based on the Modifier created. + */ + config?: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + /** + * Order in which the option is displayed on the storefront. + */ + sort_order?: number + option_values?: ({ + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + })[] + /** + * Publicly available image url + */ + image_url?: string + } + /** + * Returns the categories tree, a nested lineage of the categories with parent->child relationship. The Category objects returned are simplified versions of the category objects returned in the rest of this API. + */ + categoriesTree_Resp: { + data?: definitions['categoriesTreeNode_Full'][] + meta?: definitions['metaEmpty_Full'] + } + /** + * Used to reflect parent <> child category relationships. Used by Category Tree. + */ + categoriesTreeNode_Full: { + /** + * The unique numeric ID of the category; increments sequentially. + */ + id?: number + /** + * The unique numeric ID of the category's parent. This field controls where the category sits in the tree of categories that organize the catalog. + */ + parent_id?: number + /** + * The name displayed for the category. Name is unique with respect to the category's siblings. + */ + name?: string + /** + * Flag to determine whether the product should be displayed to customers browsing the store. If `true`, the category will be displayed. If `false`, the category will be hidden from view. + */ + is_visible?: boolean + /** + * The custom URL for the category on the storefront. + */ + url?: string + /** + * The list of children of the category. + */ + children?: definitions['categoriesTreeNode_Full'][] + } + /** + * Common Category object properties. + */ + category_Full: { + /** + * Unique ID of the *Category*. Increments sequentially. + * Read-Only. + */ + id?: number + /** + * The unique numeric ID of the category's parent. This field controls where the category sits in the tree of categories that organize the catalog. + * Required in a POST if creating a child category. + */ + parent_id: number + /** + * The name displayed for the category. Name is unique with respect to the category's siblings. + * Required in a POST. + */ + name: string + /** + * The product description, which can include HTML formatting. + */ + description?: string + /** + * Number of views the category has on the storefront. + */ + views?: number + /** + * Priority this category will be given when included in the menu and category pages. The lower the number, the closer to the top of the results the category will be. + */ + sort_order?: number + /** + * Custom title for the category page. If not defined, the category name will be used as the meta title. + */ + page_title?: string + /** + * A comma-separated list of keywords that can be used to locate the category when searching the store. + */ + search_keywords?: string + /** + * Custom meta keywords for the category page. If not defined, the store's default keywords will be used. Must post as an array like: ["awesome","sauce"]. + */ + meta_keywords?: string[] + /** + * Custom meta description for the category page. If not defined, the store's default meta description will be used. + */ + meta_description?: string + /** + * A valid layout file. (Please refer to [this article](https://support.bigcommerce.com/articles/Public/Creating-Custom-Template-Files/) on creating category files.) This field is writable only for stores with a Blueprint theme applied. + */ + layout_file?: string + /** + * Flag to determine whether the product should be displayed to customers browsing the store. If `true`, the category will be displayed. If `false`, the category will be hidden from view. + */ + is_visible?: boolean + /** + * Determines how the products are sorted on category page load. + */ + default_product_sort?: + | 'use_store_settings' + | 'featured' + | 'newest' + | 'best_selling' + | 'alpha_asc' + | 'alpha_desc' + | 'avg_customer_review' + | 'price_asc' + | 'price_desc' + /** + * Image URL used for this category on the storefront. Images can be uploaded via form file post to `/categories/{categoryId}/image`, or by providing a publicly accessible URL in this field. + */ + image_url?: string + custom_url?: definitions['customUrl_Full'] + } + /** + * Common Brand properties. + */ + brand_Full: { + /** + * Unique ID of the *Brand*. Read-Only. + */ + id?: number + /** + * The name of the brand. Must be unique. + * Required in POST. + */ + name: string + /** + * The title shown in the browser while viewing the brand. + */ + page_title?: string + /** + * Comma-separated list of meta keywords to include in the HTML. + */ + meta_keywords?: string[] + /** + * A meta description to include. + */ + meta_description?: string + /** + * A comma-separated list of keywords that can be used to locate this brand. + */ + search_keywords?: string + /** + * Image URL used for this category on the storefront. Images can be uploaded via form file post to `/brands/{brandId}/image`, or by providing a publicly accessible URL in this field. + */ + image_url?: string + custom_url?: definitions['customUrl_Full'] + } + /** + * Common Variant properties. + */ + productVariant_Base: { + /** + * The cost price of the variant. Not affected by Price List prices. + */ + cost_price?: number + /** + * This variant's base price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is `null`, the product's default price (set in the Product resource's `price` field) will be used as the base price. + */ + price?: number + /** + * This variant's sale price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's sale price (set in the Product resource's `price` field) will be used as the sale price. + */ + sale_price?: number + /** + * This variant's retail price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's retail price (set in the Product resource's `price` field) will be used as the retail price. + */ + retail_price?: number + /** + * This variant's base weight on the storefront. If this value is null, the product's default weight (set in the Product resource's weight field) will be used as the base weight. + */ + weight?: number + /** + * Width of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default width (set in the Product resource's `width` field) will be used as the base width. + */ + width?: number + /** + * Height of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default height (set in the Product resource's `height` field) will be used as the base height. + */ + height?: number + /** + * Depth of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default depth (set in the Product resource's `depth` field) will be used as the base depth. + */ + depth?: number + /** + * Flag used to indicate whether the variant has free shipping. If `true`, the shipping cost for the variant will be zero. + */ + is_free_shipping?: boolean + /** + * A fixed shipping cost for the variant. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * If `true`, this variant will not be purchasable on the storefront. + */ + purchasing_disabled?: boolean + /** + * If `purchasing_disabled` is `true`, this message should show on the storefront when the variant is selected. + */ + purchasing_disabled_message?: string + /** + * The UPC code used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * Inventory level for the variant, which is used when the product's inventory_tracking is set to `variant`. + */ + inventory_level?: number + /** + * When the variant hits this inventory level, it is considered low stock. + */ + inventory_warning_level?: number + /** + * Identifies where in a warehouse the variant is located. + */ + bin_picking_number?: string + } + productVariant_Full: definitions['productVariant_Base'] & { + id?: number + product_id?: number + sku?: string + /** + * Read-only reference to v2 API's SKU ID. Null if it is a base variant. + */ + sku_id?: number + /** + * Array of option and option values IDs that make up this variant. Will be empty if the variant is the product's base variant. + */ + option_values?: definitions['productVariantOptionValue_Base'][] + /** + * The price of the variant as seen on the storefront. This price takes into account `sale_price` and any price adjustment rules that are applicable to this variant. + */ + calculated_price?: number + } + /** + * The model for a POST to create variants on a product. + */ + productVariant_Post: { + /** + * The cost price of the variant. Not affected by Price List prices. + */ + cost_price?: number + /** + * This variant's base price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is `null`, the product's default price (set in the Product resource's `price` field) will be used as the base price. + */ + price?: number + /** + * This variant's sale price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's sale price (set in the Product resource's `price` field) will be used as the sale price. + */ + sale_price?: number + /** + * This variant's retail price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's retail price (set in the Product resource's `price` field) will be used as the retail price. + */ + retail_price?: number + /** + * This variant's base weight on the storefront. If this value is null, the product's default weight (set in the Product resource's weight field) will be used as the base weight. + */ + weight?: number + /** + * Width of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default width (set in the Product resource's `width` field) will be used as the base width. + */ + width?: number + /** + * Height of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default height (set in the Product resource's `height` field) will be used as the base height. + */ + height?: number + /** + * Depth of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default depth (set in the Product resource's `depth` field) will be used as the base depth. + */ + depth?: number + /** + * Flag used to indicate whether the variant has free shipping. If `true`, the shipping cost for the variant will be zero. + */ + is_free_shipping?: boolean + /** + * A fixed shipping cost for the variant. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * If `true`, this variant will not be purchasable on the storefront. + */ + purchasing_disabled?: boolean + /** + * If `purchasing_disabled` is `true`, this message should show on the storefront when the variant is selected. + */ + purchasing_disabled_message?: string + /** + * The UPC code used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * Inventory level for the variant, which is used when the product's inventory_tracking is set to `variant`. + */ + inventory_level?: number + /** + * When the variant hits this inventory level, it is considered low stock. + */ + inventory_warning_level?: number + /** + * Identifies where in a warehouse the variant is located. + */ + bin_picking_number?: string + } & { + product_id?: number + sku?: string + /** + * Array of option and option values IDs that make up this variant. Will be empty if the variant is the product's base variant. + */ + option_values?: { id?: number; option_id?: number }[] + } + variantCollection_Put: definitions['productVariant_Full'][] + /** + * The model for a PUT to update variants on a product. + */ + variant_Put: { + /** + * The cost price of the variant. Not affected by Price List prices. + */ + cost_price?: number + /** + * This variant's base price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is `null`, the product's default price (set in the Product resource's `price` field) will be used as the base price. + */ + price?: number + /** + * This variant's sale price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's sale price (set in the Product resource's `price` field) will be used as the sale price. + */ + sale_price?: number + /** + * This variant's retail price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's retail price (set in the Product resource's `price` field) will be used as the retail price. + */ + retail_price?: number + /** + * This variant's base weight on the storefront. If this value is null, the product's default weight (set in the Product resource's weight field) will be used as the base weight. + */ + weight?: number + /** + * Width of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default width (set in the Product resource's `width` field) will be used as the base width. + */ + width?: number + /** + * Height of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default height (set in the Product resource's `height` field) will be used as the base height. + */ + height?: number + /** + * Depth of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default depth (set in the Product resource's `depth` field) will be used as the base depth. + */ + depth?: number + /** + * Flag used to indicate whether the variant has free shipping. If `true`, the shipping cost for the variant will be zero. + */ + is_free_shipping?: boolean + /** + * A fixed shipping cost for the variant. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * If `true`, this variant will not be purchasable on the storefront. + */ + purchasing_disabled?: boolean + /** + * If `purchasing_disabled` is `true`, this message should show on the storefront when the variant is selected. + */ + purchasing_disabled_message?: string + /** + * The UPC code used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * Inventory level for the variant, which is used when the product's inventory_tracking is set to `variant`. + */ + inventory_level?: number + /** + * When the variant hits this inventory level, it is considered low stock. + */ + inventory_warning_level?: number + /** + * Identifies where in a warehouse the variant is located. + */ + bin_picking_number?: string + } & { id?: number } + /** + * The model for a POST to create variants on a product. + */ + productVariant_Post_Product: definitions['productVariant_Base'] & { + sku?: string + option_values?: { + /** + * The name of the option. + */ + option_display_name?: string + /** + * The label of the option value. + */ + label?: string + }[] + } + /** + * The model for a PUT to update variants on a product. + */ + productVariant_Put_Product: { + /** + * The cost price of the variant. Not affected by Price List prices. + */ + cost_price?: number + /** + * This variant's base price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is `null`, the product's default price (set in the Product resource's `price` field) will be used as the base price. + */ + price?: number + /** + * This variant's sale price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's sale price (set in the Product resource's `price` field) will be used as the sale price. + */ + sale_price?: number + /** + * This variant's retail price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's retail price (set in the Product resource's `price` field) will be used as the retail price. + */ + retail_price?: number + /** + * This variant's base weight on the storefront. If this value is null, the product's default weight (set in the Product resource's weight field) will be used as the base weight. + */ + weight?: number + /** + * Width of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default width (set in the Product resource's `width` field) will be used as the base width. + */ + width?: number + /** + * Height of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default height (set in the Product resource's `height` field) will be used as the base height. + */ + height?: number + /** + * Depth of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default depth (set in the Product resource's `depth` field) will be used as the base depth. + */ + depth?: number + /** + * Flag used to indicate whether the variant has free shipping. If `true`, the shipping cost for the variant will be zero. + */ + is_free_shipping?: boolean + /** + * A fixed shipping cost for the variant. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * If `true`, this variant will not be purchasable on the storefront. + */ + purchasing_disabled?: boolean + /** + * If `purchasing_disabled` is `true`, this message should show on the storefront when the variant is selected. + */ + purchasing_disabled_message?: string + /** + * The UPC code used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * Inventory level for the variant, which is used when the product's inventory_tracking is set to `variant`. + */ + inventory_level?: number + /** + * When the variant hits this inventory level, it is considered low stock. + */ + inventory_warning_level?: number + /** + * Identifies where in a warehouse the variant is located. + */ + bin_picking_number?: string + product_id?: number + sku?: string + } + productVariantOptionValue_Full: { + /** + * The name of the option. + */ + option_display_name?: string + /** + * The label of the option value. + */ + label?: string + } & definitions['productVariantOptionValue_Base'] + /** + * The model for a POST to create option values on a product. + */ + productOptionValue_Post_Product: { + /** + * The name of the option. + */ + option_display_name?: string + /** + * The label of the option value. + */ + label?: string + } + /** + * Common Product Variant Option properties. + */ + productVariantOptionValue_Base: { id?: number; option_id?: number } + /** + * The model for a POST to create option values on a variant. + */ + productVariantOptionValue_Post: { id?: number; option_id?: number } + resp_productOptionValue: { + data?: definitions['productOptionOptionValue_Full'] + /** + * Empty meta object; may be used later. + */ + meta?: { ''?: string } + } + /** + * Common Product Option `option_value` properties. + */ + productOptionOptionValue_Base: { + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. If no data is available, returns `null`. + */ + value_data?: { [key: string]: any } + } + /** + * Product Option `option_value`. + */ + productOptionOptionValue_Full: definitions['productOptionOptionValue_Base'] & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + } + /** + * The model for a POST to create option values on a product. + */ + productOptionValue_Post: { + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } + /** + * The model for a PUT to update option values on a product. + */ + productOptionValue_Put: { + /** + * The flag for preselecting a value as the default on the storefront. This field is not supported for swatch options/modifiers. + */ + is_default?: boolean + /** + * The text display identifying the value on the storefront. Required in a /POST. + */ + label: string + /** + * The order in which the value will be displayed on the product page. Required in a /POST. + */ + sort_order: number + /** + * Extra data describing the value, based on the type of option or modifier with which the value is associated. The `swatch` type option can accept an array of `colors`, with up to three hexidecimal color keys; or an `image_url`, which is a full image URL path including protocol. The `product list` type option requires a `product_id`. The `checkbox` type option requires a boolean flag, called `checked_value`, to determine which value is considered to be the checked state. + */ + value_data?: { [key: string]: any } + } & { + /** + * The unique numeric ID of the value; increments sequentially. + */ + id?: number + } + /** + * Common ProductImage properties. + */ + productImage_Base: { + /** + * The local path to the original image file uploaded to BigCommerce. + */ + image_file?: string + /** + * Flag for identifying whether the image is used as the product's thumbnail. + */ + is_thumbnail?: boolean + /** + * The order in which the image will be displayed on the product page. Higher integers give the image a lower priority. When updating, if the image is given a lower priority, all images with a `sort_order` the same as or greater than the image's new `sort_order` value will have their `sort_order`s reordered. + */ + sort_order?: number + /** + * The description for the image. + */ + description?: string + /** + * Must be a fully qualified URL path, including protocol. Limit of 8MB per file. + */ + image_url?: string + } + /** + * The model for a POST to create an image on a product. + */ + productImage_Post: { + /** + * The unique numeric ID of the image; increments sequentially. + */ + id?: number + /** + * The unique numeric identifier for the product with which the image is associated. + */ + product_id?: number + /** + * The local path to the original image file uploaded to BigCommerce. + */ + image_file?: string + /** + * The zoom URL for this image. By default, this is used as the zoom image on product pages when zoom images are enabled. + */ + url_zoom?: string + /** + * The standard URL for this image. By default, this is used for product-page images. + */ + url_standard?: string + /** + * The thumbnail URL for this image. By default, this is the image size used on the category page and in side panels. + */ + url_thumbnail?: string + /** + * The tiny URL for this image. By default, this is the image size used for thumbnails beneath the product image on a product page. + */ + url_tiny?: string + /** + * The date on which the product image was modified. + */ + date_modified?: string + /** + * Flag for identifying whether the image is used as the product's thumbnail. + */ + is_thumbnail?: boolean + /** + * The order in which the image will be displayed on the product page. Higher integers give the image a lower priority. When updating, if the image is given a lower priority, all images with a `sort_order` the same as or greater than the image's new `sort_order` value will have their `sort_order`s reordered. + */ + sort_order?: number + /** + * The description for the image. + */ + description?: string + } & { + /** + * Must be a fully qualified URL path, including protocol. Limit of 8MB per file. + */ + image_url?: string + /** + * Must be sent as a multipart/form-data field in the request body. + */ + image_file?: string + } + /** + * The model for a PUT to update applicable Product Image fields. + */ + productImage_Put: { + /** + * The unique numeric ID of the image; increments sequentially. + */ + id?: number + /** + * The unique numeric identifier for the product with which the image is associated. + */ + product_id?: number + /** + * The local path to the original image file uploaded to BigCommerce. + */ + image_file?: string + /** + * The zoom URL for this image. By default, this is used as the zoom image on product pages when zoom images are enabled. + */ + url_zoom?: string + /** + * The standard URL for this image. By default, this is used for product-page images. + */ + url_standard?: string + /** + * The thumbnail URL for this image. By default, this is the image size used on the category page and in side panels. + */ + url_thumbnail?: string + /** + * The tiny URL for this image. By default, this is the image size used for thumbnails beneath the product image on a product page. + */ + url_tiny?: string + /** + * The date on which the product image was modified. + */ + date_modified?: string + /** + * Flag for identifying whether the image is used as the product's thumbnail. + */ + is_thumbnail?: boolean + /** + * The order in which the image will be displayed on the product page. Higher integers give the image a lower priority. When updating, if the image is given a lower priority, all images with a `sort_order` the same as or greater than the image's new `sort_order` value will have their `sort_order`s reordered. + */ + sort_order?: number + /** + * The description for the image. + */ + description?: string + } + /** + * The model for a POST to create a video on a product. + */ + productVideo_Base: { + /** + * The title for the video. If left blank, this will be filled in according to data on a host site. + */ + title?: string + /** + * The description for the video. If left blank, this will be filled in according to data on a host site. + */ + description?: string + /** + * The order in which the video will be displayed on the product page. Higher integers give the video a lower priority. When updating, if the video is given a lower priority, all videos with a `sort_order` the same as or greater than the video's new `sort_order` value will have their `sort_order`s reordered. + */ + sort_order?: number + /** + * The video type (a short name of a host site). + */ + type?: 'youtube' + /** + * The ID of the video on a host site. + */ + video_id?: string + } + /** + * A product video model. + */ + productVideo_Full: definitions['productVideo_Base'] & { + /** + * The unique numeric ID of the product video; increments sequentially. + */ + id?: number + /** + * The unique numeric identifier for the product with which the image is associated. + */ + product_id?: number + /** + * Length of the video. This will be filled in according to data on a host site. + */ + length?: string + } + /** + * The model for a POST to create a video on a product. + */ + productVideo_Post: definitions['productVideo_Base'] + /** + * The model for a PUT to update a video on a product. + */ + productVideo_Put: definitions['productVideo_Base'] & { + /** + * The unique numeric ID of the product video; increments sequentially. + */ + id?: number + } + productReview_Base: { + /** + * The title for the product review. + * Required in /POST. + */ + title: string + /** + * The text for the product review. + */ + text?: string + /** + * The status of the product review. Must be one of `approved`, `disapproved` or `pending`. + */ + status?: string + /** + * The rating of the product review. Must be one of 0, 1, 2, 3, 4, 5. + */ + rating?: number + /** + * The email of the reviewer. Must be a valid email, or an empty string. + */ + email?: string + /** + * The name of the reviewer. + */ + name?: string + /** + * Date the product was reviewed. Required in /POST. + */ + date_reviewed: string + } + /** + * A product review model. + */ + productReview_Full: definitions['productReview_Base'] & { + /** + * The unique numeric ID of the product review; increments sequentially. + */ + id?: number + /** + * The unique numeric identifier for the product with which the review is associated. + */ + product_id?: number + /** + * Date the product review was created. + */ + date_created?: string + /** + * Date the product review was modified. + */ + date_modified?: string + } + /** + * The model for a POST to create a product review. + */ + productReview_Post: { + /** + * The title for the product review. + * Required in /POST. + */ + title: string + /** + * The text for the product review. + */ + text?: string + /** + * The status of the product review. Must be one of `approved`, `disapproved` or `pending`. + */ + status?: string + /** + * The rating of the product review. Must be one of 0, 1, 2, 3, 4, 5. + */ + rating?: number + /** + * The email of the reviewer. Must be a valid email, or an empty string. + */ + email?: string + /** + * The name of the reviewer. + */ + name?: string + /** + * Date the product was reviewed. Required in /POST. + */ + date_reviewed: string + } + /** + * The model for a PUT to update a product review. + */ + productReview_Put: { + /** + * The title for the product review. + * Required in /POST. + */ + title: string + /** + * The text for the product review. + */ + text?: string + /** + * The status of the product review. Must be one of `approved`, `disapproved` or `pending`. + */ + status?: string + /** + * The rating of the product review. Must be one of 0, 1, 2, 3, 4, 5. + */ + rating?: number + /** + * The email of the reviewer. Must be a valid email, or an empty string. + */ + email?: string + /** + * The name of the reviewer. + */ + name?: string + /** + * Date the product was reviewed. Required in /POST. + */ + date_reviewed: string + } + /** + * Image Response returns for: + * * Create Variant Image + * * Create Modifier Image + * * Create Category Image + * * Create Brand Image + */ + resp_productImage: { + data?: definitions['productImage_Full'] + /** + * Empty meta object; may be used later. + */ + meta?: { [key: string]: any } + } + /** + * An object containing a publicly accessible image URL, or a form post that contains an image file. + */ + resourceImage_Full: { + /** + * A public URL for a GIF, JPEG, or PNG image. Limit of 8MB per file. + */ + image_url?: string + } + product_Post: definitions['product_Base'] & { + variants?: definitions['productVariant_Post_Product'] + } + /** + * The model for a PUT to update a product. + */ + product_Put: { + /** + * The unique numerical ID of the product; increments sequentially. + */ + id?: number + } & definitions['product_Base'] & { + variants?: definitions['productVariant_Put_Product'] + } + /** + * Catalog Summary object describes a lightweight summary of the catalog. + */ + catalogSummary_Full: { + /** + * A count of all inventory items in the catalog. + */ + inventory_count?: number + /** + * Total value of store's inventory. + */ + inventory_value?: number + /** + * ID of the category containing the most products. + */ + primary_category_id?: number + /** + * Name of the category containing the most products. + */ + primary_category_name?: string + /** + * Total number of variants + */ + variant_count?: number + /** + * Highest priced variant + */ + highest_variant_price?: number + /** + * Average price of all variants + */ + average_variant_price?: number + /** + * Lowest priced variant in the store + */ + lowest_variant_price?: string + oldest_variant_date?: string + newest_variant_date?: string + } + /** + * Metafield for products, categories, variants, and brands. The max number of metafields allowed on each product, category, variant, or brand is fifty. For more information, see [Platform Limits](https://support.bigcommerce.com/s/article/Platform-Limits) in the Help Center. + */ + metafield_Base: { + /** + * Unique ID of the *Metafield*. Read-Only. + */ + id?: number + /** + * Determines the visibility and writeability of the field by other API consumers. + * + * |Value|Description + * |-|-| + * |`app_only`|Private to the app that owns the field| + * |`read`|Visible to other API consumers| + * |`write`|Open for reading and writing by other API consumers| + * |`read_and_sf_access`|Visible to other API consumers, including on storefront| + * |`write_and_sf_access`|Open for reading and writing by other API consumers, including on storefront| + */ + permission_set: + | 'app_only' + | 'read' + | 'write' + | 'read_and_sf_access' + | 'write_and_sf_access' + /** + * Namespace for the metafield, for organizational purposes. This is set set by the developer. Required for POST. + */ + namespace: string + /** + * The name of the field, for example: `location_id`, `color`. Required for POST. + */ + key: string + /** + * The value of the field, for example: `1`, `blue`. Required for POST. + */ + value: string + /** + * Description for the metafields. + */ + description?: string + /** + * The type of resource with which the metafield is associated. + */ + resource_type?: 'category' | 'brand' | 'product' | 'variant' + /** + * The ID for the resource with which the metafield is associated. + */ + resource_id?: number + /** + * Date and time of the metafield's creation. Read-Only. + */ + date_created?: string + /** + * Date and time when the metafield was last updated. Read-Only. + */ + date_modified?: string + } + /** + * Common ComplexRule properties. + */ + complexRule_Base: { + /** + * The unique numeric ID of the rule; increments sequentially. + * Read-Only + */ + id?: number + /** + * The unique numeric ID of the product with which the rule is associated; increments sequentially. + */ + product_id?: number + /** + * The priority to give this rule when making adjustments to the product properties. + */ + sort_order?: number + /** + * Flag for determining whether the rule is to be used when adjusting a product's price, weight, image, or availabilty. + */ + enabled?: boolean + /** + * Flag for determining whether other rules should not be applied after this rule has been applied. + */ + stop?: boolean + /** + * Flag for determining whether the rule should disable purchasing of a product when the conditions are applied. + */ + purchasing_disabled?: boolean + /** + * Message displayed on the storefront when a rule disables the purchasing of a product. + */ + purchasing_disabled_message?: string + /** + * Flag for determining whether the rule should hide purchasing of a product when the conditions are applied. + */ + purchasing_hidden?: boolean + /** + * The URL for an image displayed on the storefront when the conditions are applied. Limit of 8MB per file. + */ + image_url?: string + price_adjuster?: definitions['adjuster_Full'] + weight_adjuster?: definitions['adjuster_Full'] + conditions?: definitions['complexRuleConditionBase'][] + } + /** + * Gets custom fields associated with a product. These allow you to specify additional information that will appear on the product's page, such as a book's ISBN or a DVD's release date. + */ + productCustomField_Base: { + /** + * The unique numeric ID of the custom field; increments sequentially. + * Read-Only + */ + id?: number + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + name: string + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + value: string + } + /** + * The model for a POST to create a custom field on a product. + */ + productCustomField_Post: { + /** + * The unique numeric ID of the custom field; increments sequentially. + * Read-Only + */ + id?: number + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + name: string + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + value: string + } + /** + * The model for a PUT to update a custom field on a product. + */ + productCustomField_Put: { + /** + * The unique numeric ID of the custom field; increments sequentially. + * Read-Only + */ + id?: number + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + name: string + /** + * The name of the field, shown on the storefront, orders, etc. Required for /POST + */ + value: string + } + /** + * Complex rules may return with conditions that apply to one or more variants, or with a single modifier value (if the rules were created using the v2 API or the control panel). Complex rules created or updated in the v3 API must have conditions that either reference multiple `modifier_value_id`'s, or else reference a `modifier_value_id` and a `variant_id`. + */ + complexRuleConditionBase: { + /** + * The unique numeric ID of the rule condition; increments sequentially. Read-Only + */ + id?: number + /** + * The unique numeric ID of the rule with which the condition is associated. + * Read-Only + */ + rule_id?: number + /** + * The unique numeric ID of the modifier with which the rule condition is associated. + * Required in /POST. + */ + modifier_id: number + /** + * The unique numeric ID of the modifier value with which the rule condition is associated. + * Required in /POST. + */ + modifier_value_id: number + /** + * The unique numeric ID of the variant with which the rule condition is associated. + * Required in /POST. + */ + variant_id: number + /** + * (READ-ONLY:) The unique numeric ID of the SKU (v2 API), or Combination, with which the rule condition is associated. This is to maintain cross-compatibility between v2 and v3. + */ + combination_id?: number + } + /** + * The custom URL for the category on the storefront. + */ + customUrl_Full: { + /** + * Category URL on the storefront. + */ + url?: string + /** + * Returns `true` if the URL has been changed from its default state (the auto-assigned URL that BigCommerce provides). + */ + is_customized?: boolean + } + /** + * Common Bulk Pricing Rule properties + */ + bulkPricingRule_Full: { + /** + * Unique ID of the *Bulk Pricing Rule*. Read-Only. + */ + id?: number + /** + * The minimum inclusive quantity of a product to satisfy this rule. Must be greater than or equal to zero. + * Required in /POST. + */ + quantity_min: number + /** + * The maximum inclusive quantity of a product to satisfy this rule. Must be greater than the `quantity_min` value – unless this field has a value of 0 (zero), in which case there will be no maximum bound for this rule. + * Required in /POST. + */ + quantity_max: number + /** + * The type of adjustment that is made. Values: `price` - the adjustment amount per product; `percent` - the adjustment as a percentage of the original price; `fixed` - the adjusted absolute price of the product. + * Required in /POST. + */ + type: 'price' | 'percent' | 'fixed' + /** + * The discount can be a fixed dollar amount or a percentage. For a fixed dollar amount enter it as an integer and the response will return as an integer. For percentage enter the amount as the percentage divided by 100 using string format. For example 10% percent would be “.10”. The response will return as an integer. + * Required in /POST. + */ + amount: number + } + /** + * The values for option config can vary based on the Modifier created. + */ + productOptionConfig_Full: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + /** + * Adjuster for Complex Rules. + */ + adjuster_Full: { + /** + * The type of adjuster for either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster?: 'relative' | 'percentage' + /** + * The numeric amount by which the adjuster will change either the price or the weight of the variant, when the modifier value is selected on the storefront. + */ + adjuster_value?: number + } + /** + * Errors during batch usage for the BigCommerce API. + */ + resp_variantBatchError: { + batch_errors?: (definitions['error_Base'] & { + errors?: { additionalProperties?: string } + })[] + } + /** + * Data about the response, including pagination and collection totals. + */ + metaCollection_Full: { pagination?: definitions['pagination_Full'] } + /** + * Data about the response, including pagination and collection totals. + */ + pagination_Full: { + /** + * Total number of items in the result set. + */ + total?: number + /** + * Total number of items in the collection response. + */ + count?: number + /** + * The amount of items returned in the collection per page, controlled by the limit parameter. + */ + per_page?: number + /** + * The page you are currently on within the collection. + */ + current_page?: number + /** + * The total number of pages in the collection. + */ + total_pages?: number + /** + * Pagination links for the previous and next parts of the whole collection. + */ + links?: { + /** + * Link to the previous page returned in the response. + */ + previous?: string + /** + * Link to the current page returned in the response. + */ + current?: string + /** + * Link to the next page returned in the response. + */ + next?: string + } + } + /** + * Empty meta object; may be used later. + */ + metaEmpty_Full: { [key: string]: any } + errorResponse_Full: definitions['error_Base'] & { + errors?: definitions['detailedErrors'] + } + /** + * Error payload for the BigCommerce API. + */ + error_Base: { + /** + * The HTTP status code. + */ + status?: number + /** + * The error title describing the particular error. + */ + title?: string + type?: string + instance?: string + } + /** + * Error payload for the BigCommerce API. + */ + errorNotFound: { + /** + * 404 HTTP status code. + */ + status?: number + /** + * The error title describing the particular error. + */ + title?: string + type?: string + instance?: string + } + /** + * A gift-certificate model. + */ + giftCertificate_Full: { + /** + * The gift-certificate code. + */ + code?: string + /** + * The balance on a gift certificate when it was purchased. + */ + original_balance?: number + /** + * The balance on a gift certificate at the time of this purchase. + */ + starting_balance?: number + /** + * The remaining balance on a gift certificate. + */ + remaining_balance?: number + /** + * The status of a gift certificate: `active` - gift certificate is active; `pending` - gift certificate purchase is pending; `disabled` - gift certificate is disabled; `expired` - gift certificate is expired. + */ + status?: 'active' | 'pending' | 'disabled' | 'expired' + } + /** + * No-content response for the BigCommerce API. + */ + errorNoContent: { + /** + * 204 HTTP status code. + */ + status?: number + /** + * The error title describing the situation. + */ + title?: string + type?: string + instance?: string + } + detailedErrors: { additionalProperties?: string } + product_Full: definitions['product_Base'] & { + /** + * The date on which the product was created. + */ + date_created?: string + /** + * The date on which the product was modified. + */ + date_modified?: string + /** + * ID of the product. Read Only. + */ + id?: number + /** + * The unique identifier of the base variant associated with a simple product. This value is `null` for complex products. + */ + base_variant_id?: number + /** + * The price of the product as seen on the storefront. It will be equal to the `sale_price`, if set, and the `price` if there is not a `sale_price`. + */ + calculated_price?: number + options?: definitions['productOption_Base'][] + modifiers?: definitions['productModifier_Full'][] + /** + * Minimum Advertised Price. + */ + map_price?: number + /** + * Indicates that the product is in an Option Set (legacy V2 concept). + */ + option_set_id?: number + /** + * Legacy template setting which controls if the option set shows up to the side of or below the product image and description. + */ + option_set_display?: string + } & { variants?: definitions['productVariant_Full'] } + /** + * Common ProductImage properties. + */ + productImage_Full: definitions['productImage_Base'] & { + /** + * The unique numeric ID of the image; increments sequentially. + */ + id?: number + /** + * The unique numeric identifier for the product with which the image is associated. + */ + product_id?: number + /** + * The zoom URL for this image. By default, this is used as the zoom image on product pages when zoom images are enabled. + */ + url_zoom?: string + /** + * The standard URL for this image. By default, this is used for product-page images. + */ + url_standard?: string + /** + * The thumbnail URL for this image. By default, this is the image size used on the category page and in side panels. + */ + url_thumbnail?: string + /** + * The tiny URL for this image. By default, this is the image size used for thumbnails beneath the product image on a product page. + */ + url_tiny?: string + /** + * The date on which the product image was modified. + */ + date_modified?: string + } + metafield_Post: definitions['metafield_Base'] + /** + * The model for batch updating products. + */ + product_Put_Collection: ({ + /** + * The unique numerical ID of the product; increments sequentially. Required on batch product `PUT` requests. + */ + id: number + } & definitions['product_Base'])[] + /** + * The values for option config can vary based on the Modifier created. + */ + config_Full: { + /** + * (date, text, multi_line_text, numbers_only_text) The default value. Shown on a date option as an ISO-8601–formatted string, or on a text option as a string. + */ + default_value?: string + /** + * (checkbox) Flag for setting the checkbox to be checked by default. + */ + checked_by_default?: boolean + /** + * (checkbox) Label displayed for the checkbox option. + */ + checkbox_label?: string + /** + * (date) Flag to limit the dates allowed to be entered on a date option. + */ + date_limited?: boolean + /** + * (date) The type of limit that is allowed to be entered on a date option. + */ + date_limit_mode?: 'earliest' | 'range' | 'latest' + /** + * (date) The earliest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_earliest_value?: string + /** + * (date) The latest date allowed to be entered on the date option, as an ISO-8601 formatted string. + */ + date_latest_value?: string + /** + * (file) The kind of restriction on the file types that can be uploaded with a file upload option. Values: `specific` - restricts uploads to particular file types; `all` - allows all file types. + */ + file_types_mode?: 'specific' | 'all' + /** + * (file) The type of files allowed to be uploaded if the `file_type_option` is set to `specific`. Values: + * `images` - Allows upload of image MIME types (`bmp`, `gif`, `jpg`, `jpeg`, `jpe`, `jif`, `jfif`, `jfi`, `png`, `wbmp`, `xbm`, `tiff`). `documents` - Allows upload of document MIME types (`txt`, `pdf`, `rtf`, `doc`, `docx`, `xls`, `xlsx`, `accdb`, `mdb`, `one`, `pps`, `ppsx`, `ppt`, `pptx`, `pub`, `odt`, `ods`, `odp`, `odg`, `odf`). + * `other` - Allows file types defined in the `file_types_other` array. + */ + file_types_supported?: string[] + /** + * (file) A list of other file types allowed with the file upload option. + */ + file_types_other?: string[] + /** + * (file) The maximum size for a file that can be used with the file upload option. This will still be limited by the server. + */ + file_max_size?: number + /** + * (text, multi_line_text) Flag to validate the length of a text or multi-line text input. + */ + text_characters_limited?: boolean + /** + * (text, multi_line_text) The minimum length allowed for a text or multi-line text option. + */ + text_min_length?: number + /** + * (text, multi_line_text) The maximum length allowed for a text or multi line text option. + */ + text_max_length?: number + /** + * (multi_line_text) Flag to validate the maximum number of lines allowed on a multi-line text input. + */ + text_lines_limited?: boolean + /** + * (multi_line_text) The maximum number of lines allowed on a multi-line text input. + */ + text_max_lines?: number + /** + * (numbers_only_text) Flag to limit the value of a number option. + */ + number_limited?: boolean + /** + * (numbers_only_text) The type of limit on values entered for a number option. + */ + number_limit_mode?: 'lowest' | 'highest' | 'range' + /** + * (numbers_only_text) The lowest allowed value for a number option if `number_limited` is true. + */ + number_lowest_value?: number + /** + * (numbers_only_text) The highest allowed value for a number option if `number_limited` is true. + */ + number_highest_value?: number + /** + * (numbers_only_text) Flag to limit the input on a number option to whole numbers only. + */ + number_integers_only?: boolean + /** + * (product_list, product_list_with_images) Flag for automatically adjusting inventory on a product included in the list. + */ + product_list_adjusts_inventory?: boolean + /** + * (product_list, product_list_with_images) Flag to add the optional product's price to the main product's price. + */ + product_list_adjusts_pricing?: boolean + /** + * (product_list, product_list_with_images) How to factor the optional product's weight and package dimensions into the shipping quote. Values: `none` - don't adjust; `weight` - use shipping weight only; `package` - use weight and dimensions. + */ + product_list_shipping_calc?: 'none' | 'weight' | 'package' + } + adjusters_Full: { + price?: definitions['adjuster_Full'] + weight?: definitions['adjuster_Full'] + /** + * The URL for an image displayed on the storefront when the modifier value is selected.Limit of 8MB per file. + */ + image_url?: string + purchasing_disabled?: { + /** + * Flag for whether the modifier value disables purchasing when selected on the storefront. This can be used for temporarily disabling a particular modifier value. + */ + status?: boolean + /** + * The message displayed on the storefront when the purchasing disabled status is `true`. + */ + message?: string + } + } + /** + * Variant properties used on: + * * `/catalog/products/variants` + * * `/catalog/variants` + */ + variant_Base: { + /** + * The cost price of the variant. Not affected by Price List prices. + */ + cost_price?: number + /** + * This variant's base price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is `null`, the product's default price (set in the Product resource's `price` field) will be used as the base price. + */ + price?: number + /** + * This variant's sale price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's sale price (set in the Product resource's `price` field) will be used as the sale price. + */ + sale_price?: number + /** + * This variant's retail price on the storefront. If a Price List ID is used, the Price List value will be used. If a Price List ID is not used, and this value is null, the product's retail price (set in the Product resource's `price` field) will be used as the retail price. + */ + retail_price?: number + /** + * This variant's base weight on the storefront. If this value is null, the product's default weight (set in the Product resource's weight field) will be used as the base weight. + */ + weight?: number + /** + * Width of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default width (set in the Product resource's `width` field) will be used as the base width. + */ + width?: number + /** + * Height of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default height (set in the Product resource's `height` field) will be used as the base height. + */ + height?: number + /** + * Depth of the variant, which can be used when calculating shipping costs. If this value is `null`, the product's default depth (set in the Product resource's `depth` field) will be used as the base depth. + */ + depth?: number + /** + * Flag used to indicate whether the variant has free shipping. If `true`, the shipping cost for the variant will be zero. + */ + is_free_shipping?: boolean + /** + * A fixed shipping cost for the variant. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * If `true`, this variant will not be purchasable on the storefront. + */ + purchasing_disabled?: boolean + /** + * If `purchasing_disabled` is `true`, this message should show on the storefront when the variant is selected. + */ + purchasing_disabled_message?: string + /** + * The UPC code used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * Inventory level for the variant, which is used when the product's inventory_tracking is set to `variant`. + */ + inventory_level?: number + /** + * When the variant hits this inventory level, it is considered low stock. + */ + inventory_warning_level?: number + /** + * Identifies where in a warehouse the variant is located. + */ + bin_picking_number?: string + } + /** + * Shared `Product` properties used in: + * * `POST` + * * `PUT` + * * `GET` + */ + product_Base: { + /** + * The product name. + */ + name?: string + /** + * The product type. One of: `physical` - a physical stock unit, `digital` - a digital download. + */ + type?: 'physical' | 'digital' + /** + * User defined product code/stock keeping unit (SKU). + */ + sku?: string + /** + * The product description, which can include HTML formatting. + */ + description?: string + /** + * Weight of the product, which can be used when calculating shipping costs. This is based on the unit set on the store + */ + weight?: number + /** + * Width of the product, which can be used when calculating shipping costs. + */ + width?: number + /** + * Depth of the product, which can be used when calculating shipping costs. + */ + depth?: number + /** + * Height of the product, which can be used when calculating shipping costs. + */ + height?: number + /** + * The price of the product. The price should include or exclude tax, based on the store settings. + */ + price?: number + /** + * The cost price of the product. Stored for reference only; it is not used or displayed anywhere on the store. + */ + cost_price?: number + /** + * The retail cost of the product. If entered, the retail cost price will be shown on the product page. + */ + retail_price?: number + /** + * If entered, the sale price will be used instead of value in the price field when calculating the product's cost. + */ + sale_price?: number + /** + * The ID of the tax class applied to the product. (NOTE: Value ignored if automatic tax is enabled.) + */ + tax_class_id?: number + /** + * Accepts AvaTax System Tax Codes, which identify products and services that fall into special sales-tax categories. By using these codes, merchants who subscribe to BigCommerce's Avalara Premium integration can calculate sales taxes more accurately. Stores without Avalara Premium will ignore the code when calculating sales tax. Do not pass more than one code. The codes are case-sensitive. For details, please see Avalara's documentation. + */ + product_tax_code?: string + /** + * An array of IDs for the categories to which this product belongs. When updating a product, if an array of categories is supplied, all product categories will be overwritten. Does not accept more than 1,000 ID values. + */ + categories?: number[] + /** + * A product can be added to an existing brand during a product /PUT or /POST. + */ + brand_id?: number + /** + * Current inventory level of the product. Simple inventory tracking must be enabled (See the `inventory_tracking` field) for this to take any effect. + */ + inventory_level?: number + /** + * Inventory warning level for the product. When the product's inventory level drops below the warning level, the store owner will be informed. Simple inventory tracking must be enabled (see the `inventory_tracking` field) for this to take any effect. + */ + inventory_warning_level?: number + /** + * The type of inventory tracking for the product. Values are: `none` - inventory levels will not be tracked; `product` - inventory levels will be tracked using the `inventory_level` and `inventory_warning_level` fields; `variant` - inventory levels will be tracked based on variants, which maintain their own warning levels and inventory levels. + */ + inventory_tracking?: 'none' | 'product' | 'variant' + /** + * A fixed shipping cost for the product. If defined, this value will be used during checkout instead of normal shipping-cost calculation. + */ + fixed_cost_shipping_price?: number + /** + * Flag used to indicate whether the product has free shipping. If `true`, the shipping cost for the product will be zero. + */ + is_free_shipping?: boolean + /** + * Flag to determine whether the product should be displayed to customers browsing the store. If `true`, the product will be displayed. If `false`, the product will be hidden from view. + */ + is_visible?: boolean + /** + * Flag to determine whether the product should be included in the `featured products` panel when viewing the store. + */ + is_featured?: boolean + /** + * An array of IDs for the related products. + */ + related_products?: number[] + /** + * Warranty information displayed on the product page. Can include HTML formatting. + */ + warranty?: string + /** + * The BIN picking number for the product. + */ + bin_picking_number?: string + /** + * The layout template file used to render this product category. This field is writable only for stores with a Blueprint theme applied. + */ + layout_file?: string + /** + * The product UPC code, which is used in feeds for shopping comparison sites and external channel integrations. + */ + upc?: string + /** + * A comma-separated list of keywords that can be used to locate the product when searching the store. + */ + search_keywords?: string + /** + * Availability of the product. Availability options are: `available` - the product can be purchased on the storefront; `disabled` - the product is listed in the storefront, but cannot be purchased; `preorder` - the product is listed for pre-orders. + */ + availability?: 'available' | 'disabled' | 'preorder' + /** + * Availability text displayed on the checkout page, under the product title. Tells the customer how long it will normally take to ship this product, such as: 'Usually ships in 24 hours.' + */ + availability_description?: string + /** + * Type of gift-wrapping options. Values: `any` - allow any gift-wrapping options in the store; `none` - disallow gift-wrapping on the product; `list` – provide a list of IDs in the `gift_wrapping_options_list` field. + */ + gift_wrapping_options_type?: 'any' | 'none' | 'list' + /** + * A list of gift-wrapping option IDs. + */ + gift_wrapping_options_list?: number[] + /** + * Priority to give this product when included in product lists on category pages and in search results. Lower integers will place the product closer to the top of the results. + */ + sort_order?: number + /** + * The product condition. Will be shown on the product page if the `is_condition_shown` field's value is `true`. Possible values: `New`, `Used`, `Refurbished`. + */ + condition?: 'New' | 'Used' | 'Refurbished' + /** + * Flag used to determine whether the product condition is shown to the customer on the product page. + */ + is_condition_shown?: boolean + /** + * The minimum quantity an order must contain, to be eligible to purchase this product. + */ + order_quantity_minimum?: number + /** + * The maximum quantity an order can contain when purchasing the product. + */ + order_quantity_maximum?: number + /** + * Custom title for the product page. If not defined, the product name will be used as the meta title. + */ + page_title?: string + /** + * Custom meta keywords for the product page. If not defined, the store's default keywords will be used. + */ + meta_keywords?: string[] + /** + * Custom meta description for the product page. If not defined, the store's default meta description will be used. + */ + meta_description?: string + /** + * The number of times the product has been viewed. + */ + view_count?: number + /** + * Pre-order release date. See the `availability` field for details on setting a product's availability to accept pre-orders. + */ + preorder_release_date?: string + /** + * Custom expected-date message to display on the product page. If undefined, the message defaults to the storewide setting. Can contain the `%%DATE%%` placeholder, which will be substituted for the release date. + */ + preorder_message?: string + /** + * If set to true then on the preorder release date the preorder status will automatically be removed. + * If set to false, then on the release date the preorder status **will not** be removed. It will need to be changed manually either in the + * control panel or using the API. Using the API set `availability` to `available`. + */ + is_preorder_only?: boolean + /** + * False by default, indicating that this product's price should be shown on the product page. If set to `true`, the price is hidden. (NOTE: To successfully set `is_price_hidden` to `true`, the `availability` value must be `disabled`.) + */ + is_price_hidden?: boolean + /** + * By default, an empty string. If `is_price_hidden` is `true`, the value of `price_hidden_label` is displayed instead of the price. (NOTE: To successfully set a non-empty string value with `is_price_hidden` set to `true`, the `availability` value must be `disabled`.) + */ + price_hidden_label?: string + custom_url?: definitions['customUrl_Full'] + /** + * Type of product, defaults to `product`. + */ + open_graph_type?: + | 'product' + | 'album' + | 'book' + | 'drink' + | 'food' + | 'game' + | 'movie' + | 'song' + | 'tv_show' + /** + * Title of the product, if not specified the product name will be used instead. + */ + open_graph_title?: string + /** + * Description to use for the product, if not specified then the meta_description will be used instead. + */ + open_graph_description?: string + /** + * Flag to determine if product description or open graph description is used. + */ + open_graph_use_meta_description?: boolean + /** + * Flag to determine if product name or open graph name is used. + */ + open_graph_use_product_name?: boolean + /** + * Flag to determine if product image or open graph image is used. + */ + open_graph_use_image?: boolean + /** + * The brand can be created during a product PUT or POST request. If the brand already exists then the product will be added. If not the brand will be created and the product added. If using `brand_name` it performs a fuzzy match and adds the brand. eg. "Common Good" and "Common good" are the same. Brand name does not return as part of a product response. Only the `brand_id`. + */ + 'brand_name or brand_id'?: string + /** + * Global Trade Item Number + */ + gtin?: string + /** + * Manufacturer Part Number + */ + mpn?: string + /** + * The total rating for the product. + */ + reviews_rating_sum?: number + /** + * The number of times the product has been rated. + */ + reviews_count?: number + /** + * The total quantity of this product sold. + */ + total_sold?: number + custom_fields?: definitions['productCustomField_Put'][] + bulk_pricing_rules?: definitions['bulkPricingRule_Full'][] + images?: definitions['productImage_Full'][] + primary_image?: definitions['productImage_Full'] + videos?: definitions['productVideo_Full'][] + } + /** + * Properties for updating metafields. + */ + metafield_Put: { + /** + * Unique ID of the *Metafield*. Read-Only. + */ + id?: number + } & definitions['metafield_Base'] + metafield_Full: definitions['metafield_Put'] & { + /** + * Date and time of the metafield's creation. Read-Only. + */ + date_created?: string + /** + * Date and time when the metafield was last updated. Read-Only. + */ + date_modified?: string + } + /** + * The model for a PUT to update variants on a product. + */ + productVariant_Put: definitions['productVariant_Base'] & { + product_id?: number + sku?: string + } +} diff --git a/framework/vendure/api/definitions/store-content.ts b/framework/vendure/api/definitions/store-content.ts new file mode 100644 index 000000000..f00c28844 --- /dev/null +++ b/framework/vendure/api/definitions/store-content.ts @@ -0,0 +1,329 @@ +/** + * This file was auto-generated by swagger-to-ts. + * Do not make direct changes to the file. + */ + +export interface definitions { + blogPost_Full: { + /** + * ID of this blog post. (READ-ONLY) + */ + id?: number + } & definitions['blogPost_Base'] + addresses: { + /** + * Full URL of where the resource is located. + */ + url?: string + /** + * Resource being accessed. + */ + resource?: string + } + formField: { + /** + * Name of the form field + */ + name?: string + /** + * Value of the form field + */ + value?: string + } + page_Full: { + /** + * ID of the page. + */ + id?: number + } & definitions['page_Base'] + redirect: { + /** + * Numeric ID of the redirect. + */ + id?: number + /** + * The path from which to redirect. + */ + path: string + forward: definitions['forward'] + /** + * URL of the redirect. READ-ONLY + */ + url?: string + } + forward: { + /** + * The type of redirect. If it is a `manual` redirect then type will always be manual. Dynamic redirects will have the type of the page. Such as product or category. + */ + type?: string + /** + * Reference of the redirect. Dynamic redirects will have the category or product number. Manual redirects will have the url that is being directed to. + */ + ref?: number + } + customer_Full: { + /** + * Unique numeric ID of this customer. This is a READ-ONLY field; do not set or modify its value in a POST or PUT request. + */ + id?: number + /** + * Not returned in any responses, but accepts up to two fields allowing you to set the customer’s password. If a password is not supplied, it is generated automatically. For further information about using this object, please see the Customers resource documentation. + */ + _authentication?: { + force_reset?: string + password?: string + password_confirmation?: string + } + /** + * The name of the company for which the customer works. + */ + company?: string + /** + * First name of the customer. + */ + first_name: string + /** + * Last name of the customer. + */ + last_name: string + /** + * Email address of the customer. + */ + email: string + /** + * Phone number of the customer. + */ + phone?: string + /** + * Date on which the customer registered from the storefront or was created in the control panel. This is a READ-ONLY field; do not set or modify its value in a POST or PUT request. + */ + date_created?: string + /** + * Date on which the customer updated their details in the storefront or was updated in the control panel. This is a READ-ONLY field; do not set or modify its value in a POST or PUT request. + */ + date_modified?: string + /** + * The amount of credit the customer has. (Float, Float as String, Integer) + */ + store_credit?: string + /** + * The customer’s IP address when they signed up. + */ + registration_ip_address?: string + /** + * The group to which the customer belongs. + */ + customer_group_id?: number + /** + * Store-owner notes on the customer. + */ + notes?: string + /** + * Used to identify customers who fall into special sales-tax categories – in particular, those who are fully or partially exempt from paying sales tax. Can be blank, or can contain a single AvaTax code. (The codes are case-sensitive.) Stores that subscribe to BigCommerce’s Avalara Premium integration will use this code to determine how/whether to apply sales tax. Does not affect sales-tax calculations for stores that do not subscribe to Avalara Premium. + */ + tax_exempt_category?: string + /** + * Records whether the customer would like to receive marketing content from this store. READ-ONLY.This is a READ-ONLY field; do not set or modify its value in a POST or PUT request. + */ + accepts_marketing?: boolean + addresses?: definitions['addresses'] + /** + * Array of custom fields. This is a READ-ONLY field; do not set or modify its value in a POST or PUT request. + */ + form_fields?: definitions['formField'][] + /** + * Force a password change on next login. + */ + reset_pass_on_login?: boolean + } + categoryAccessLevel: { + /** + * + `all` - Customers can access all categories + * + `specific` - Customers can access a specific list of categories + * + `none` - Customers are prevented from viewing any of the categories in this group. + */ + type?: 'all' | 'specific' | 'none' + /** + * Is an array of category IDs and should be supplied only if `type` is specific. + */ + categories?: string[] + } + timeZone: { + /** + * a string identifying the time zone, in the format: /. + */ + name?: string + /** + * a negative or positive number, identifying the offset from UTC/GMT, in seconds, during winter/standard time. + */ + raw_offset?: number + /** + * "-/+" offset from UTC/GMT, in seconds, during summer/daylight saving time. + */ + dst_offset?: number + /** + * a boolean indicating whether this time zone observes daylight saving time. + */ + dst_correction?: boolean + date_format?: definitions['dateFormat'] + } + count_Response: { count?: number } + dateFormat: { + /** + * string that defines dates’ display format, in the pattern: M jS Y + */ + display?: string + /** + * string that defines the CSV export format for orders, customers, and products, in the pattern: M jS Y + */ + export?: string + /** + * string that defines dates’ extended-display format, in the pattern: M jS Y @ g:i A. + */ + extended_display?: string + } + blogTags: { tag?: string; post_ids?: number[] }[] + blogPost_Base: { + /** + * Title of this blog post. + */ + title: string + /** + * URL for the public blog post. + */ + url?: string + /** + * URL to preview the blog post. (READ-ONLY) + */ + preview_url?: string + /** + * Text body of the blog post. + */ + body: string + /** + * Tags to characterize the blog post. + */ + tags?: string[] + /** + * Summary of the blog post. (READ-ONLY) + */ + summary?: string + /** + * Whether the blog post is published. + */ + is_published?: boolean + published_date?: definitions['publishedDate'] + /** + * Published date in `ISO 8601` format. + */ + published_date_iso8601?: string + /** + * Description text for this blog post’s `` element. + */ + meta_description?: string + /** + * Keywords for this blog post’s `` element. + */ + meta_keywords?: string + /** + * Name of the blog post’s author. + */ + author?: string + /** + * Local path to a thumbnail uploaded to `product_images/` via [WebDav](https://support.bigcommerce.com/s/article/File-Access-WebDAV). + */ + thumbnail_path?: string + } + publishedDate: { timezone_type?: string; date?: string; timezone?: string } + /** + * Not returned in any responses, but accepts up to two fields allowing you to set the customer’s password. If a password is not supplied, it is generated automatically. For further information about using this object, please see the Customers resource documentation. + */ + authentication: { + force_reset?: string + password?: string + password_confirmation?: string + } + customer_Base: { [key: string]: any } + page_Base: { + /** + * ID of any parent Web page. + */ + parent_id?: number + /** + * `page`: free-text page + * `link`: link to another web address + * `rss_feed`: syndicated content from an RSS feed + * `contact_form`: When the store's contact form is used. + */ + type: 'page' | 'rss_feed' | 'contact_form' | 'raw' | 'link' + /** + * Where the page’s type is a contact form: object whose members are the fields enabled (in the control panel) for storefront display. Possible members are:`fullname`: full name of the customer submitting the form; `phone`: customer’s phone number, as submitted on the form; `companyname`: customer’s submitted company name; `orderno`: customer’s submitted order number; `rma`: customer’s submitted RMA (Return Merchandise Authorization) number. + */ + contact_fields?: string + /** + * Where the page’s type is a contact form: email address that receives messages sent via the form. + */ + email?: string + /** + * Page name, as displayed on the storefront. + */ + name: string + /** + * Relative URL on the storefront for this page. + */ + url?: string + /** + * Description contained within this page’s `` element. + */ + meta_description?: string + /** + * HTML or variable that populates this page’s `` element, in default/desktop view. Required in POST if page type is `raw`. + */ + body: string + /** + * HTML to use for this page's body when viewed in the mobile template (deprecated). + */ + mobile_body?: string + /** + * If true, this page has a mobile version. + */ + has_mobile_version?: boolean + /** + * If true, this page appears in the storefront’s navigation menu. + */ + is_visible?: boolean + /** + * If true, this page is the storefront’s home page. + */ + is_homepage?: boolean + /** + * Text specified for this page’s `` element. (If empty, the value of the name property is used.) + */ + meta_title?: string + /** + * Layout template for this page. This field is writable only for stores with a Blueprint theme applied. + */ + layout_file?: string + /** + * Order in which this page should display on the storefront. (Lower integers specify earlier display.) + */ + sort_order?: number + /** + * Comma-separated list of keywords that shoppers can use to locate this page when searching the store. + */ + search_keywords?: string + /** + * Comma-separated list of SEO-relevant keywords to include in the page’s `<meta/>` element. + */ + meta_keywords?: string + /** + * If page type is `rss_feed` the n this field is visisble. Required in POST required for `rss page` type. + */ + feed: string + /** + * If page type is `link` this field is returned. Required in POST to create a `link` page. + */ + link: string + content_type?: 'application/json' | 'text/javascript' | 'text/html' + } +} diff --git a/framework/vendure/api/definitions/wishlist.ts b/framework/vendure/api/definitions/wishlist.ts new file mode 100644 index 000000000..6ec21c103 --- /dev/null +++ b/framework/vendure/api/definitions/wishlist.ts @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by swagger-to-ts. + * Do not make direct changes to the file. + */ + +export interface definitions { + wishlist_Post: { + /** + * The customer id. + */ + customer_id: number + /** + * Whether the wishlist is available to the public. + */ + is_public?: boolean + /** + * The title of the wishlist. + */ + name?: string + /** + * Array of Wishlist items. + */ + items?: { + /** + * The ID of the product. + */ + product_id?: number + /** + * The variant ID of the product. + */ + variant_id?: number + }[] + } + wishlist_Put: { + /** + * The customer id. + */ + customer_id: number + /** + * Whether the wishlist is available to the public. + */ + is_public?: boolean + /** + * The title of the wishlist. + */ + name?: string + /** + * Array of Wishlist items. + */ + items?: { + /** + * The ID of the item + */ + id?: number + /** + * The ID of the product. + */ + product_id?: number + /** + * The variant ID of the item. + */ + variant_id?: number + }[] + } + wishlist_Full: { + /** + * Wishlist ID, provided after creating a wishlist with a POST. + */ + id?: number + /** + * The ID the customer to which the wishlist belongs. + */ + customer_id?: number + /** + * The Wishlist's name. + */ + name?: string + /** + * Whether the Wishlist is available to the public. + */ + is_public?: boolean + /** + * The token of the Wishlist. This is created internally within BigCommerce. The Wishlist ID is to be used for external apps. Read-Only + */ + token?: string + /** + * Array of Wishlist items + */ + items?: definitions['wishlistItem_Full'][] + } + wishlistItem_Full: { + /** + * The ID of the item + */ + id?: number + /** + * The ID of the product. + */ + product_id?: number + /** + * The variant ID of the item. + */ + variant_id?: number + } + wishlistItem_Post: { + /** + * The ID of the product. + */ + product_id?: number + /** + * The variant ID of the product. + */ + variant_id?: number + } + /** + * Data about the response, including pagination and collection totals. + */ + pagination: { + /** + * Total number of items in the result set. + */ + total?: number + /** + * Total number of items in the collection response. + */ + count?: number + /** + * The amount of items returned in the collection per page, controlled by the limit parameter. + */ + per_page?: number + /** + * The page you are currently on within the collection. + */ + current_page?: number + /** + * The total number of pages in the collection. + */ + total_pages?: number + } + error: { status?: number; title?: string; type?: string } + metaCollection: { pagination?: definitions['pagination'] } +} diff --git a/framework/vendure/api/fragments/category-tree.ts b/framework/vendure/api/fragments/category-tree.ts new file mode 100644 index 000000000..e26f17195 --- /dev/null +++ b/framework/vendure/api/fragments/category-tree.ts @@ -0,0 +1,9 @@ +export const categoryTreeItemFragment = /* GraphQL */ ` + fragment categoryTreeItem on CategoryTreeItem { + entityId + name + path + description + productCount + } +` diff --git a/framework/vendure/api/fragments/product.ts b/framework/vendure/api/fragments/product.ts new file mode 100644 index 000000000..d266b8c92 --- /dev/null +++ b/framework/vendure/api/fragments/product.ts @@ -0,0 +1,113 @@ +export const productPrices = /* GraphQL */ ` + fragment productPrices on Prices { + price { + value + currencyCode + } + salePrice { + value + currencyCode + } + retailPrice { + value + currencyCode + } + } +` + +export const swatchOptionFragment = /* GraphQL */ ` + fragment swatchOption on SwatchOptionValue { + isDefault + hexColors + } +` + +export const multipleChoiceOptionFragment = /* GraphQL */ ` + fragment multipleChoiceOption on MultipleChoiceOption { + values { + edges { + node { + label + ...swatchOption + } + } + } + } + + ${swatchOptionFragment} +` + +export const productInfoFragment = /* GraphQL */ ` + fragment productInfo on Product { + entityId + name + path + brand { + entityId + } + description + prices { + ...productPrices + } + images { + edges { + node { + urlOriginal + altText + isDefault + } + } + } + variants { + edges { + node { + entityId + defaultImage { + urlOriginal + altText + isDefault + } + } + } + } + productOptions { + edges { + node { + __typename + entityId + displayName + ...multipleChoiceOption + } + } + } + localeMeta: metafields(namespace: $locale, keys: ["name", "description"]) + @include(if: $hasLocale) { + edges { + node { + key + value + } + } + } + } + + ${productPrices} + ${multipleChoiceOptionFragment} +` + +export const productConnectionFragment = /* GraphQL */ ` + fragment productConnnection on ProductConnection { + pageInfo { + startCursor + endCursor + } + edges { + cursor + node { + ...productInfo + } + } + } + + ${productInfoFragment} +` diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts new file mode 100644 index 000000000..1760ff935 --- /dev/null +++ b/framework/vendure/api/index.ts @@ -0,0 +1,53 @@ +import type { RequestInit } from '@vercel/fetch' +import type { CommerceAPIConfig } from '@commerce/api' +import fetchGraphqlApi from './utils/fetch-graphql-api' +import fetchStoreApi from './utils/fetch-store-api' + +export interface VendureConfig extends CommerceAPIConfig {} + +const API_URL = process.env.VENDURE_SHOP_API_URL + +if (!API_URL) { + throw new Error( + `The environment variable BIGCOMMERCE_STOREFRONT_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) +} diff --git a/framework/vendure/api/utils/concat-cookie.ts b/framework/vendure/api/utils/concat-cookie.ts new file mode 100644 index 000000000..362e12e99 --- /dev/null +++ b/framework/vendure/api/utils/concat-cookie.ts @@ -0,0 +1,14 @@ +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)] +} diff --git a/framework/vendure/api/utils/create-api-handler.ts b/framework/vendure/api/utils/create-api-handler.ts new file mode 100644 index 000000000..dee29d60c --- /dev/null +++ b/framework/vendure/api/utils/create-api-handler.ts @@ -0,0 +1,58 @@ +import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' +import { VendureConfig, getConfig } from '..' + +export type BigcommerceApiHandler< + T = any, + H extends BigcommerceHandlers = {}, + Options extends {} = {} +> = ( + req: NextApiRequest, + res: NextApiResponse<BigcommerceApiResponse<T>>, + config: VendureConfig, + handlers: H, + // Custom configs that may be used by a particular handler + options: Options +) => void | Promise<void> + +export type BigcommerceHandler<T = any, Body = null> = (options: { + req: NextApiRequest + res: NextApiResponse<BigcommerceApiResponse<T>> + config: VendureConfig + body: Body +}) => void | Promise<void> + +export type BigcommerceHandlers<T = any> = { + [k: string]: BigcommerceHandler<T, any> +} + +export type BigcommerceApiResponse<T> = { + data: T | null + errors?: { message: string; code?: string }[] +} + +export default function createApiHandler< + T = any, + H extends BigcommerceHandlers = {}, + Options extends {} = {} +>( + handler: BigcommerceApiHandler<T, H, Options>, + handlers: H, + defaultOptions: Options +) { + return function getApiHandler({ + config, + operations, + options, + }: { + config?: VendureConfig + 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, getConfig(config), ops, opts) + } + } +} diff --git a/framework/vendure/api/utils/errors.ts b/framework/vendure/api/utils/errors.ts new file mode 100644 index 000000000..77e2007fc --- /dev/null +++ b/framework/vendure/api/utils/errors.ts @@ -0,0 +1,25 @@ +import type { Response } from '@vercel/fetch' + +// Used for GraphQL errors +export class BigcommerceGraphQLError extends Error {} + +export class BigcommerceApiError extends Error { + status: number + res: Response + data: any + + constructor(msg: string, res: Response, data?: any) { + super(msg) + this.name = 'BigcommerceApiError' + this.status = res.status + this.res = res + this.data = data + } +} + +export class BigcommerceNetworkError extends Error { + constructor(msg: string) { + super(msg) + this.name = 'BigcommerceNetworkError' + } +} diff --git a/framework/vendure/api/utils/fetch-graphql-api.ts b/framework/vendure/api/utils/fetch-graphql-api.ts new file mode 100644 index 000000000..ff0ab6e71 --- /dev/null +++ b/framework/vendure/api/utils/fetch-graphql-api.ts @@ -0,0 +1,38 @@ +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 +) => { + // log.warn(query) + 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 Bigcommerce API' }], + status: res.status, + }) + } + + return { data: json.data, res } +} + +export default fetchGraphqlApi diff --git a/framework/vendure/api/utils/fetch-store-api.ts b/framework/vendure/api/utils/fetch-store-api.ts new file mode 100644 index 000000000..7e59b9f06 --- /dev/null +++ b/framework/vendure/api/utils/fetch-store-api.ts @@ -0,0 +1,71 @@ +import type { RequestInit, Response } from '@vercel/fetch' +import { getConfig } from '..' +import { BigcommerceApiError, BigcommerceNetworkError } from './errors' +import fetch from './fetch' + +export default async function fetchStoreApi<T>( + endpoint: string, + options?: RequestInit +): Promise<T> { + const config = getConfig() + let res: Response + + try { + res = await fetch(config.storeApiUrl + endpoint, { + ...options, + headers: { + ...options?.headers, + 'Content-Type': 'application/json', + 'X-Auth-Token': config.storeApiToken, + 'X-Auth-Client': config.storeApiClientId, + }, + }) + } catch (error) { + throw new BigcommerceNetworkError( + `Fetch to Bigcommerce failed: ${error.message}` + ) + } + + const contentType = res.headers.get('Content-Type') + const isJSON = contentType?.includes('application/json') + + if (!res.ok) { + const data = isJSON ? await res.json() : await getTextOrNull(res) + const headers = getRawHeaders(res) + const msg = `Big Commerce API error (${ + res.status + }) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${ + typeof data === 'string' ? data : JSON.stringify(data, null, 2) + }` + + throw new BigcommerceApiError(msg, res, data) + } + + if (res.status !== 204 && !isJSON) { + throw new BigcommerceApiError( + `Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`, + res + ) + } + + // If something was removed, the response will be empty + return res.status === 204 ? null : await res.json() +} + +function getRawHeaders(res: Response) { + const headers: { [key: string]: string } = {} + + res.headers.forEach((value, key) => { + headers[key] = value + }) + + return headers +} + +function getTextOrNull(res: Response) { + try { + return res.text() + } catch (err) { + return null + } +} diff --git a/framework/vendure/api/utils/fetch.ts b/framework/vendure/api/utils/fetch.ts new file mode 100644 index 000000000..9d9fff3ed --- /dev/null +++ b/framework/vendure/api/utils/fetch.ts @@ -0,0 +1,3 @@ +import zeitFetch from '@vercel/fetch' + +export default zeitFetch() diff --git a/framework/vendure/api/utils/filter-edges.ts b/framework/vendure/api/utils/filter-edges.ts new file mode 100644 index 000000000..09cd20640 --- /dev/null +++ b/framework/vendure/api/utils/filter-edges.ts @@ -0,0 +1,5 @@ +export default function filterEdges<T>( + edges: (T | null | undefined)[] | null | undefined +) { + return edges?.filter((edge): edge is T => !!edge) ?? [] +} diff --git a/framework/vendure/api/utils/get-cart-cookie.ts b/framework/vendure/api/utils/get-cart-cookie.ts new file mode 100644 index 000000000..7ca6cd5e4 --- /dev/null +++ b/framework/vendure/api/utils/get-cart-cookie.ts @@ -0,0 +1,20 @@ +import { serialize, CookieSerializeOptions } from 'cookie' + +export default function getCartCookie( + name: string, + cartId?: string, + maxAge?: number +) { + const options: CookieSerializeOptions = + cartId && maxAge + ? { + maxAge, + expires: new Date(Date.now() + maxAge * 1000), + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + } + : { maxAge: -1, path: '/' } // Removes the cookie + + return serialize(name, cartId || '', options) +} diff --git a/framework/vendure/api/utils/is-allowed-method.ts b/framework/vendure/api/utils/is-allowed-method.ts new file mode 100644 index 000000000..78bbba568 --- /dev/null +++ b/framework/vendure/api/utils/is-allowed-method.ts @@ -0,0 +1,28 @@ +import type { NextApiRequest, NextApiResponse } from 'next' + +export default function isAllowedMethod( + req: NextApiRequest, + res: NextApiResponse, + allowedMethods: string[] +) { + const methods = allowedMethods.includes('OPTIONS') + ? allowedMethods + : [...allowedMethods, 'OPTIONS'] + + if (!req.method || !methods.includes(req.method)) { + res.status(405) + res.setHeader('Allow', methods.join(', ')) + res.end() + return false + } + + if (req.method === 'OPTIONS') { + res.status(200) + res.setHeader('Allow', methods.join(', ')) + res.setHeader('Content-Length', '0') + res.end() + return false + } + + return true +} diff --git a/framework/vendure/api/utils/parse-item.ts b/framework/vendure/api/utils/parse-item.ts new file mode 100644 index 000000000..132f269f6 --- /dev/null +++ b/framework/vendure/api/utils/parse-item.ts @@ -0,0 +1,14 @@ +import type { ItemBody as WishlistItemBody } from '../wishlist' +import type { ItemBody } from '../cart' + +export const parseWishlistItem = (item: WishlistItemBody) => ({ + product_id: item.productId, + variant_id: item.variantId, +}) + +export const parseCartItem = (item: ItemBody) => ({ + quantity: item.quantity, + product_id: item.productId, + variant_id: item.variantId, + option_selections: item.optionSelections +}) diff --git a/framework/vendure/api/utils/set-product-locale-meta.ts b/framework/vendure/api/utils/set-product-locale-meta.ts new file mode 100644 index 000000000..974a197bd --- /dev/null +++ b/framework/vendure/api/utils/set-product-locale-meta.ts @@ -0,0 +1,21 @@ +import type { ProductNode } from '../../product/get-all-products' +import type { RecursivePartial } from './types' + +export default function setProductLocaleMeta( + node: RecursivePartial<ProductNode> +) { + if (node.localeMeta?.edges) { + node.localeMeta.edges = node.localeMeta.edges.filter((edge) => { + const { key, value } = edge?.node ?? {} + if (key && key in node) { + ;(node as any)[key] = value + return false + } + return true + }) + + if (!node.localeMeta.edges.length) { + delete node.localeMeta + } + } +} diff --git a/framework/vendure/api/utils/types.ts b/framework/vendure/api/utils/types.ts new file mode 100644 index 000000000..56f9c1728 --- /dev/null +++ b/framework/vendure/api/utils/types.ts @@ -0,0 +1,7 @@ +export type RecursivePartial<T> = { + [P in keyof T]?: RecursivePartial<T[P]> +} + +export type RecursiveRequired<T> = { + [P in keyof T]-?: RecursiveRequired<T[P]> +} diff --git a/framework/vendure/api/wishlist/handlers/add-item.ts b/framework/vendure/api/wishlist/handlers/add-item.ts new file mode 100644 index 000000000..00d7b06bd --- /dev/null +++ b/framework/vendure/api/wishlist/handlers/add-item.ts @@ -0,0 +1,56 @@ +import type { WishlistHandlers } from '..' +import getCustomerId from '../../../customer/get-customer-id' +import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import { parseWishlistItem } from '../../utils/parse-item' + +// Returns the wishlist of the signed customer +const addItem: WishlistHandlers['addItem'] = async ({ + res, + body: { customerToken, item }, + config, +}) => { + if (!item) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Missing item' }], + }) + } + + const customerId = + customerToken && (await getCustomerId({ customerToken, config })) + + if (!customerId) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const { wishlist } = await getCustomerWishlist({ + variables: { customerId }, + config, + }) + const options = { + method: 'POST', + body: JSON.stringify( + wishlist + ? { + items: [parseWishlistItem(item)], + } + : { + name: 'Wishlist', + customer_id: customerId, + items: [parseWishlistItem(item)], + is_public: false, + } + ), + } + + const { data } = wishlist + ? await config.storeApiFetch(`/v3/wishlists/${wishlist.id}/items`, options) + : await config.storeApiFetch('/v3/wishlists', options) + + res.status(200).json({ data }) +} + +export default addItem diff --git a/framework/vendure/api/wishlist/handlers/get-wishlist.ts b/framework/vendure/api/wishlist/handlers/get-wishlist.ts new file mode 100644 index 000000000..3737c033a --- /dev/null +++ b/framework/vendure/api/wishlist/handlers/get-wishlist.ts @@ -0,0 +1,37 @@ +import getCustomerId from '../../../customer/get-customer-id' +import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import type { Wishlist, WishlistHandlers } from '..' + +// Return wishlist info +const getWishlist: WishlistHandlers['getWishlist'] = async ({ + res, + body: { customerToken, includeProducts }, + config, +}) => { + let result: { data?: Wishlist } = {} + + if (customerToken) { + const customerId = + customerToken && (await getCustomerId({ customerToken, config })) + + if (!customerId) { + // If the customerToken is invalid, then this request is too + return res.status(404).json({ + data: null, + errors: [{ message: 'Wishlist not found' }], + }) + } + + const { wishlist } = await getCustomerWishlist({ + variables: { customerId }, + includeProducts, + config, + }) + + result = { data: wishlist } + } + + res.status(200).json({ data: result.data ?? null }) +} + +export default getWishlist diff --git a/framework/vendure/api/wishlist/handlers/remove-item.ts b/framework/vendure/api/wishlist/handlers/remove-item.ts new file mode 100644 index 000000000..a9cfd9db5 --- /dev/null +++ b/framework/vendure/api/wishlist/handlers/remove-item.ts @@ -0,0 +1,39 @@ +import getCustomerId from '../../../customer/get-customer-id' +import getCustomerWishlist, { + Wishlist, +} from '../../../customer/get-customer-wishlist' +import type { WishlistHandlers } from '..' + +// Return current wishlist info +const removeItem: WishlistHandlers['removeItem'] = async ({ + res, + body: { customerToken, itemId }, + config, +}) => { + const customerId = + customerToken && (await getCustomerId({ customerToken, config })) + const { wishlist } = + (customerId && + (await getCustomerWishlist({ + variables: { customerId }, + config, + }))) || + {} + + if (!wishlist || !itemId) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + const result = await config.storeApiFetch<{ data: Wishlist } | null>( + `/v3/wishlists/${wishlist.id}/items/${itemId}`, + { method: 'DELETE' } + ) + const data = result?.data ?? null + + res.status(200).json({ data }) +} + +export default removeItem diff --git a/framework/vendure/api/wishlist/index.ts b/framework/vendure/api/wishlist/index.ts new file mode 100644 index 000000000..e892d2e78 --- /dev/null +++ b/framework/vendure/api/wishlist/index.ts @@ -0,0 +1,103 @@ +import isAllowedMethod from '../utils/is-allowed-method' +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from '../utils/create-api-handler' +import { BigcommerceApiError } from '../utils/errors' +import type { + Wishlist, + WishlistItem, +} from '../../customer/get-customer-wishlist' +import getWishlist from './handlers/get-wishlist' +import addItem from './handlers/add-item' +import removeItem from './handlers/remove-item' + +export type { Wishlist, WishlistItem } + +export type ItemBody = { + productId: Product['id'] + variantId: ProductVariant['id'] +} + +export type AddItemBody = { item: ItemBody } + +export type RemoveItemBody = { itemId: Product['id'] } + +export type WishlistBody = { + customer_id: Customer['id'] + is_public: number + name: string + items: any[] +} + +export type AddWishlistBody = { wishlist: WishlistBody } + +export type WishlistHandlers = { + getWishlist: BigcommerceHandler< + Wishlist, + { customerToken?: string; includeProducts?: boolean } + > + addItem: BigcommerceHandler< + Wishlist, + { customerToken?: string } & Partial<AddItemBody> + > + removeItem: BigcommerceHandler< + Wishlist, + { customerToken?: string } & Partial<RemoveItemBody> + > +} + +const METHODS = ['GET', 'POST', 'DELETE'] + +// TODO: a complete implementation should have schema validation for `req.body` +const wishlistApi: BigcommerceApiHandler<Wishlist, WishlistHandlers> = async ( + req, + res, + config, + handlers +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + const { cookies } = req + const customerToken = cookies[config.customerCookie] + + try { + // Return current wishlist info + if (req.method === 'GET') { + const body = { + customerToken, + includeProducts: req.query.products === '1', + } + return await handlers['getWishlist']({ req, res, config, body }) + } + + // Add an item to the wishlist + if (req.method === 'POST') { + const body = { ...req.body, customerToken } + return await handlers['addItem']({ req, res, config, body }) + } + + // Remove an item from the wishlist + if (req.method === 'DELETE') { + const body = { ...req.body, customerToken } + return await handlers['removeItem']({ req, res, config, body }) + } + } catch (error) { + console.error(error) + + const message = + error instanceof BigcommerceApiError + ? 'An unexpected error ocurred with the Bigcommerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export const handlers = { + getWishlist, + addItem, + removeItem, +} + +export default createApiHandler(wishlistApi, handlers, {}) diff --git a/framework/vendure/auth/index.ts b/framework/vendure/auth/index.ts new file mode 100644 index 000000000..36e757a89 --- /dev/null +++ b/framework/vendure/auth/index.ts @@ -0,0 +1,3 @@ +export { default as useLogin } from './use-login' +export { default as useLogout } from './use-logout' +export { default as useSignup } from './use-signup' diff --git a/framework/vendure/auth/login.ts b/framework/vendure/auth/login.ts new file mode 100644 index 000000000..25119757b --- /dev/null +++ b/framework/vendure/auth/login.ts @@ -0,0 +1,73 @@ +import type { ServerResponse } from 'http' +import type { LoginMutation, LoginMutationVariables } from '../schema' +import type { RecursivePartial } from '../api/utils/types' +import concatHeader from '../api/utils/concat-cookie' +import { VendureConfig, getConfig } from '../api' + +export const loginMutation = /* GraphQL */ ` + mutation login($email: String!, $password: String!) { + login(email: $email, password: $password) { + result + } + } +` + +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<RecursivePartial<LoginMutation>>( + query, + { variables } + ) + // 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?.result, + } +} + +export default login diff --git a/framework/vendure/auth/use-login.tsx b/framework/vendure/auth/use-login.tsx new file mode 100644 index 000000000..fa2294666 --- /dev/null +++ b/framework/vendure/auth/use-login.tsx @@ -0,0 +1,54 @@ +import { useCallback } from 'react' +import type { HookFetcher } from '@commerce/utils/types' +import { CommerceError } from '@commerce/utils/errors' +import useCommerceLogin from '@commerce/use-login' +import type { LoginBody } from '../api/customers/login' +import useCustomer from '../customer/use-customer' + +const defaultOpts = { + url: '/api/bigcommerce/customers/login', + method: 'POST', +} + +export type LoginInput = LoginBody + +export const fetcher: HookFetcher<null, LoginBody> = ( + options, + { email, password }, + fetch +) => { + if (!(email && password)) { + throw new CommerceError({ + message: + 'A first name, last name, email and password are required to login', + }) + } + + return fetch({ + ...defaultOpts, + ...options, + body: { email, password }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useLogin = () => { + const { revalidate } = useCustomer() + const fn = useCommerceLogin<null, LoginInput>(defaultOpts, customFetcher) + + return useCallback( + async function login(input: LoginInput) { + const data = await fn(input) + await revalidate() + return data + }, + [fn] + ) + } + + useLogin.extend = extendHook + + return useLogin +} + +export default extendHook(fetcher) diff --git a/framework/vendure/auth/use-logout.tsx b/framework/vendure/auth/use-logout.tsx new file mode 100644 index 000000000..6aaee29f9 --- /dev/null +++ b/framework/vendure/auth/use-logout.tsx @@ -0,0 +1,38 @@ +import { useCallback } from 'react' +import type { HookFetcher } from '@commerce/utils/types' +import useCommerceLogout from '@commerce/use-logout' +import useCustomer from '../customer/use-customer' + +const defaultOpts = { + url: '/api/bigcommerce/customers/logout', + method: 'GET', +} + +export const fetcher: HookFetcher<null> = (options, _, fetch) => { + return fetch({ + ...defaultOpts, + ...options, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useLogout = () => { + const { mutate } = useCustomer() + const fn = useCommerceLogout<null>(defaultOpts, customFetcher) + + return useCallback( + async function login() { + const data = await fn(null) + await mutate(null, false) + return data + }, + [fn] + ) + } + + useLogout.extend = extendHook + + return useLogout +} + +export default extendHook(fetcher) diff --git a/framework/vendure/auth/use-signup.tsx b/framework/vendure/auth/use-signup.tsx new file mode 100644 index 000000000..c68ce7b7a --- /dev/null +++ b/framework/vendure/auth/use-signup.tsx @@ -0,0 +1,54 @@ +import { useCallback } from 'react' +import type { HookFetcher } from '@commerce/utils/types' +import { CommerceError } from '@commerce/utils/errors' +import useCommerceSignup from '@commerce/use-signup' +import type { SignupBody } from '../api/customers/signup' +import useCustomer from '../customer/use-customer' + +const defaultOpts = { + url: '/api/bigcommerce/customers/signup', + method: 'POST', +} + +export type SignupInput = SignupBody + +export const fetcher: HookFetcher<null, SignupBody> = ( + options, + { firstName, lastName, email, password }, + fetch +) => { + if (!(firstName && lastName && email && password)) { + throw new CommerceError({ + message: + 'A first name, last name, email and password are required to signup', + }) + } + + return fetch({ + ...defaultOpts, + ...options, + body: { firstName, lastName, email, password }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useSignup = () => { + const { revalidate } = useCustomer() + const fn = useCommerceSignup<null, SignupInput>(defaultOpts, customFetcher) + + return useCallback( + async function signup(input: SignupInput) { + const data = await fn(input) + await revalidate() + return data + }, + [fn] + ) + } + + useSignup.extend = extendHook + + return useSignup +} + +export default extendHook(fetcher) diff --git a/framework/vendure/cart/index.ts b/framework/vendure/cart/index.ts new file mode 100644 index 000000000..43c6db2b7 --- /dev/null +++ b/framework/vendure/cart/index.ts @@ -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' diff --git a/framework/vendure/cart/use-add-item.tsx b/framework/vendure/cart/use-add-item.tsx new file mode 100644 index 000000000..ab8e634a2 --- /dev/null +++ b/framework/vendure/cart/use-add-item.tsx @@ -0,0 +1,56 @@ +import { useCallback } from 'react' +import type { HookFetcher } from '@commerce/utils/types' +import { CommerceError } from '@commerce/utils/errors' +import useCartAddItem from '@commerce/cart/use-add-item' +import type { ItemBody, AddItemBody } from '../api/cart' +import useCart, { Cart } from './use-cart' + +const defaultOpts = { + url: '/api/bigcommerce/cart', + method: 'POST', +} + +export type AddItemInput = ItemBody + +export const fetcher: HookFetcher<Cart, AddItemBody> = ( + options, + { item }, + fetch +) => { + if ( + item.quantity && + (!Number.isInteger(item.quantity) || item.quantity! < 1) + ) { + throw new CommerceError({ + message: 'The item quantity has to be a valid integer greater than 0', + }) + } + + return fetch({ + ...defaultOpts, + ...options, + body: { item }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useAddItem = () => { + const { mutate } = useCart() + const fn = useCartAddItem(defaultOpts, customFetcher) + + return useCallback( + async function addItem(input: AddItemInput) { + const data = await fn({ item: input }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) + } + + useAddItem.extend = extendHook + + return useAddItem +} + +export default extendHook(fetcher) diff --git a/framework/vendure/cart/use-cart-actions.tsx b/framework/vendure/cart/use-cart-actions.tsx new file mode 100644 index 000000000..abb4a998e --- /dev/null +++ b/framework/vendure/cart/use-cart-actions.tsx @@ -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 } +} diff --git a/framework/vendure/cart/use-cart.tsx b/framework/vendure/cart/use-cart.tsx new file mode 100644 index 000000000..c1a9bed7a --- /dev/null +++ b/framework/vendure/cart/use-cart.tsx @@ -0,0 +1,54 @@ +import { normalizeCart } from '../lib/normalize' +import type { HookFetcher } from '@commerce/utils/types' +import type { SwrOptions } from '@commerce/utils/use-data' +import useResponse from '@commerce/utils/use-response' +import useCommerceCart, { CartInput } from '@commerce/cart/use-cart' +import type { Cart as BigCommerceCart } from '../api/cart' + +const defaultOpts = { + url: '/api/bigcommerce/cart', + method: 'GET', +} + +type UseCartResponse = BigCommerceCart & Cart + +export const fetcher: HookFetcher<UseCartResponse | null, CartInput> = ( + options, + { cartId }, + fetch +) => { + return cartId ? fetch({ ...defaultOpts, ...options }) : null +} + +export function extendHook( + customFetcher: typeof fetcher, + swrOptions?: SwrOptions<UseCartResponse | null, CartInput> +) { + const useCart = () => { + const response = useCommerceCart(defaultOpts, [], customFetcher, { + revalidateOnFocus: false, + ...swrOptions, + }) + const res = useResponse(response, { + normalizer: normalizeCart, + descriptors: { + isEmpty: { + get() { + return Object.values(response.data?.line_items ?? {}).every( + (items) => !items.length + ) + }, + enumerable: true, + }, + }, + }) + + return res + } + + useCart.extend = extendHook + + return useCart +} + +export default extendHook(fetcher) diff --git a/framework/vendure/cart/use-remove-item.tsx b/framework/vendure/cart/use-remove-item.tsx new file mode 100644 index 000000000..e38c267af --- /dev/null +++ b/framework/vendure/cart/use-remove-item.tsx @@ -0,0 +1,51 @@ +import { useCallback } from 'react' +import { HookFetcher } from '@commerce/utils/types' +import useCartRemoveItem from '@commerce/cart/use-remove-item' +import type { RemoveItemBody } from '../api/cart' +import useCart, { Cart } from './use-cart' + +const defaultOpts = { + url: '/api/bigcommerce/cart', + method: 'DELETE', +} + +export type RemoveItemInput = { + id: string +} + +export const fetcher: HookFetcher<Cart | null, RemoveItemBody> = ( + options, + { itemId }, + fetch +) => { + return fetch({ + ...defaultOpts, + ...options, + body: { itemId }, + }) +} + +export function extendHook(customFetcher: typeof fetcher) { + const useRemoveItem = (item?: any) => { + const { mutate } = useCart() + const fn = useCartRemoveItem<Cart | null, RemoveItemBody>( + defaultOpts, + customFetcher + ) + + return useCallback( + async function removeItem(input: RemoveItemInput) { + const data = await fn({ itemId: input.id ?? item?.id }) + await mutate(data, false) + return data + }, + [fn, mutate] + ) + } + + useRemoveItem.extend = extendHook + + return useRemoveItem +} + +export default extendHook(fetcher) diff --git a/framework/vendure/cart/use-update-item.tsx b/framework/vendure/cart/use-update-item.tsx new file mode 100644 index 000000000..ca5703505 --- /dev/null +++ b/framework/vendure/cart/use-update-item.tsx @@ -0,0 +1,70 @@ +import { useCallback } from 'react' +import debounce from 'lodash.debounce' +import type { HookFetcher } from '@commerce/utils/types' +import { CommerceError } from '@commerce/utils/errors' +import useCartUpdateItem from '@commerce/cart/use-update-item' +import type { ItemBody, UpdateItemBody } from '../api/cart' +import { fetcher as removeFetcher } from './use-remove-item' +import useCart, { Cart } from './use-cart' + +const defaultOpts = { + url: '/api/bigcommerce/cart', + method: 'PUT', +} + +export type UpdateItemInput = Partial<{ id: string } & ItemBody> + +export const fetcher: HookFetcher<Cart | null, UpdateItemBody> = ( + options, + { itemId, item }, + fetch +) => { + if (Number.isInteger(item.quantity)) { + // Also allow the update hook to remove an item if the quantity is lower than 1 + if (item.quantity! < 1) { + return removeFetcher(null, { itemId }, fetch) + } + } else if (item.quantity) { + throw new CommerceError({ + message: 'The item quantity has to be a valid integer', + }) + } + + return fetch({ + ...defaultOpts, + ...options, + body: { itemId, item }, + }) +} + +function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { + const useUpdateItem = (item?: any) => { + const { mutate } = useCart() + const fn = useCartUpdateItem<Cart | null, UpdateItemBody>( + defaultOpts, + customFetcher + ) + + return useCallback( + debounce(async (input: UpdateItemInput) => { + const data = await fn({ + itemId: input.id ?? item?.id, + item: { + productId: input.productId ?? item?.product_id, + variantId: input.productId ?? item?.variant_id, + quantity: input.quantity, + }, + }) + await mutate(data, false) + return data + }, cfg?.wait ?? 500), + [fn, mutate] + ) + } + + useUpdateItem.extend = extendHook + + return useUpdateItem +} + +export default extendHook(fetcher) diff --git a/framework/vendure/codegen.json b/framework/vendure/codegen.json new file mode 100644 index 000000000..f4d3b798c --- /dev/null +++ b/framework/vendure/codegen.json @@ -0,0 +1,22 @@ +{ + "schema": { + "http://localhost:3001/shop-api": {} + }, + "generates": { + "./framework/vendure/schema.d.ts": { + "plugins": [ + "typescript" + ] + }, + "./framework/vendure/schema.graphql": { + "plugins": [ + "schema-ast" + ] + } + }, + "hooks": { + "afterAllFileWrite": [ + "prettier --write" + ] + } +} diff --git a/framework/vendure/common/get-all-pages.ts b/framework/vendure/common/get-all-pages.ts new file mode 100644 index 000000000..37018e2a8 --- /dev/null +++ b/framework/vendure/common/get-all-pages.ts @@ -0,0 +1,37 @@ +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import { VendureConfig, getConfig } from '../api' +import { definitions } from '../api/definitions/store-content' + +export type Page = definitions['page_Full'] + +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 diff --git a/framework/vendure/common/get-page.ts b/framework/vendure/common/get-page.ts new file mode 100644 index 000000000..32ce4dadd --- /dev/null +++ b/framework/vendure/common/get-page.ts @@ -0,0 +1,53 @@ +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import { VendureConfig, getConfig } from '../api' +import { definitions } from '../api/definitions/store-content' + +export type Page = definitions['page_Full'] + +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) + // RecursivePartial forces the method to check for every prop in the data, which is + // required in case there's a custom `url` + const { data } = await config.storeApiFetch<RecursivePartial<{ data: Page[] }>>( + url || `/v3/content/pages?id=${variables.id}&include=body` + ) + const firstPage = data?.[0] + const page = firstPage as RecursiveRequired<typeof firstPage> + + if (preview || page?.is_visible) { + return { page } + } + return {} +} + +export default getPage diff --git a/framework/vendure/common/get-site-info.ts b/framework/vendure/common/get-site-info.ts new file mode 100644 index 000000000..07e8d8ea4 --- /dev/null +++ b/framework/vendure/common/get-site-info.ts @@ -0,0 +1,106 @@ +import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../schema' +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import filterEdges from '../api/utils/filter-edges' +import { VendureConfig, getConfig } from '../api' +import { categoryTreeItemFragment } from '../api/fragments/category-tree' + +// Get 3 levels of categories +export const getSiteInfoQuery = /* GraphQL */ ` + query getSiteInfo { + site { + categoryTree { + ...categoryTreeItem + children { + ...categoryTreeItem + children { + ...categoryTreeItem + } + } + } + brands { + pageInfo { + startCursor + endCursor + } + edges { + cursor + node { + entityId + name + defaultImage { + urlOriginal + altText + } + pageTitle + metaDesc + metaKeywords + searchKeywords + path + } + } + } + } + } + ${categoryTreeItemFragment} +` + +export type CategoriesTree = NonNullable< + GetSiteInfoQuery['site']['categoryTree'] +> + +export type BrandEdge = NonNullable< + NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0] +> + +export type Brands = BrandEdge[] + +export type GetSiteInfoResult< + T extends { categories: any[]; brands: any[] } = { + categories: CategoriesTree + brands: Brands + } +> = T + +async function getSiteInfo(opts?: { + variables?: GetSiteInfoQueryVariables + config?: VendureConfig + preview?: boolean +}): Promise<GetSiteInfoResult> + +async function getSiteInfo< + T extends { categories: any[]; brands: any[] }, + V = any +>(opts: { + query: string + variables?: V + config?: VendureConfig + preview?: boolean +}): Promise<GetSiteInfoResult<T>> + +async function getSiteInfo({ + query = getSiteInfoQuery, + variables, + config, +}: { + query?: string + variables?: GetSiteInfoQueryVariables + 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<RecursivePartial<GetSiteInfoQuery>>( + query, + { variables } + ) + const categories = data.site?.categoryTree + const brands = data.site?.brands?.edges + + return { + categories: (categories as RecursiveRequired<typeof categories>) ?? [], + brands: filterEdges(brands as RecursiveRequired<typeof brands>), + } +} + +export default getSiteInfo diff --git a/framework/vendure/customer/get-customer-id.ts b/framework/vendure/customer/get-customer-id.ts new file mode 100644 index 000000000..d8a15af85 --- /dev/null +++ b/framework/vendure/customer/get-customer-id.ts @@ -0,0 +1,34 @@ +import { GetCustomerIdQuery } from '../schema' +import { VendureConfig, getConfig } from '../api' + +export const getCustomerIdQuery = /* GraphQL */ ` + query getCustomerId { + customer { + entityId + } + } +` + +async function getCustomerId({ + customerToken, + config, +}: { + customerToken: string + config?: VendureConfig +}): Promise<number | undefined> { + config = getConfig(config) + + const { data } = await config.fetch<GetCustomerIdQuery>( + getCustomerIdQuery, + undefined, + { + headers: { + cookie: `${config.customerCookie}=${customerToken}`, + }, + } + ) + + return data?.customer?.entityId +} + +export default getCustomerId diff --git a/framework/vendure/customer/get-customer-wishlist.ts b/framework/vendure/customer/get-customer-wishlist.ts new file mode 100644 index 000000000..ccacf87b9 --- /dev/null +++ b/framework/vendure/customer/get-customer-wishlist.ts @@ -0,0 +1,87 @@ +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import { definitions } from '../api/definitions/wishlist' +import { VendureConfig, getConfig } from '../api' +import getAllProducts, { ProductEdge } from '../product/get-all-products' + +export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & { + items?: WishlistItem[] +} + +export type WishlistItem = NonNullable< + definitions['wishlist_Full']['items'] +>[0] & { + product?: ProductEdge['node'] +} + +export type GetCustomerWishlistResult< + T extends { wishlist?: any } = { wishlist?: Wishlist } +> = T + +export type GetCustomerWishlistVariables = { + customerId: number +} + +async function getCustomerWishlist(opts: { + variables: GetCustomerWishlistVariables + config?: VendureConfig + includeProducts?: boolean +}): Promise<GetCustomerWishlistResult> + +async function getCustomerWishlist< + T extends { wishlist?: any }, + V = any +>(opts: { + url: string + variables: V + config?: VendureConfig + includeProducts?: boolean +}): Promise<GetCustomerWishlistResult<T>> + +async function getCustomerWishlist({ + config, + variables, + includeProducts, +}: { + url?: string + variables: GetCustomerWishlistVariables + config?: VendureConfig + includeProducts?: boolean +}): Promise<GetCustomerWishlistResult> { + config = getConfig(config) + + const { data = [] } = await config.storeApiFetch< + RecursivePartial<{ data: Wishlist[] }> + >(`/v3/wishlists?customer_id=${variables.customerId}`) + const wishlist = data[0] + + if (includeProducts && wishlist?.items?.length) { + const entityIds = wishlist.items + ?.map((item) => item?.product_id) + .filter((id): id is number => !!id) + + if (entityIds?.length) { + const graphqlData = await getAllProducts({ + variables: { first: 100, entityIds }, + config, + }) + // Put the products in an object that we can use to get them by id + const productsById = graphqlData.products.reduce<{ + [k: number]: ProductEdge + }>((prods, p) => { + prods[p.node.entityId] = p + return prods + }, {}) + // Populate the wishlist items with the graphql products + wishlist.items.forEach((item) => { + const product = item && productsById[item.product_id!] + if (item && product) { + item.product = product.node + } + }) + } + } + + return { wishlist: wishlist as RecursiveRequired<typeof wishlist> } +} + +export default getCustomerWishlist diff --git a/framework/vendure/customer/index.ts b/framework/vendure/customer/index.ts new file mode 100644 index 000000000..6c903ecc5 --- /dev/null +++ b/framework/vendure/customer/index.ts @@ -0,0 +1 @@ +export { default as useCustomer } from './use-customer' diff --git a/framework/vendure/customer/use-customer.tsx b/framework/vendure/customer/use-customer.tsx new file mode 100644 index 000000000..f44f16c1f --- /dev/null +++ b/framework/vendure/customer/use-customer.tsx @@ -0,0 +1,38 @@ +import type { HookFetcher } from '@commerce/utils/types' +import type { SwrOptions } from '@commerce/utils/use-data' +import useCommerceCustomer from '@commerce/use-customer' +import type { Customer, CustomerData } from '../api/customers' + +const defaultOpts = { + url: '/api/bigcommerce/customers', + method: 'GET', +} + +export type { Customer } + +export const fetcher: HookFetcher<Customer | null> = async ( + options, + _, + fetch +) => { + const data = await fetch<CustomerData | null>({ ...defaultOpts, ...options }) + return data?.customer ?? null +} + +export function extendHook( + customFetcher: typeof fetcher, + swrOptions?: SwrOptions<Customer | null> +) { + const useCustomer = () => { + return useCommerceCustomer(defaultOpts, [], customFetcher, { + revalidateOnFocus: false, + ...swrOptions, + }) + } + + useCustomer.extend = extendHook + + return useCustomer +} + +export default extendHook(fetcher) diff --git a/framework/vendure/index.tsx b/framework/vendure/index.tsx new file mode 100644 index 000000000..fd14f765e --- /dev/null +++ b/framework/vendure/index.tsx @@ -0,0 +1,61 @@ +import { ReactNode } from 'react' +import * as React from 'react' +import { + CommerceConfig, + CommerceProvider as CoreCommerceProvider, + useCommerce as useCoreCommerce, +} from '@commerce' +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 vendureConfig: CommerceConfig = { + locale: 'en-us', + cartCookie: 'bc_cartId', + async fetcher({ url, method = 'GET', variables, body: bodyObj }) { + const hasBody = Boolean(variables || bodyObj) + const body = hasBody + ? JSON.stringify(variables ? { variables } : bodyObj) + : undefined + const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined + const res = await fetch(url!, { method, body, headers }) + + if (res.ok) { + const { data } = await res.json() + return data + } + + throw await getError(res) + }, +} + +export type VendureConfig = Partial<CommerceConfig> + +export type VendureProps = { + children?: ReactNode + locale: string +} & VendureConfig + +export function CommerceProvider({ children, ...config }: VendureProps) { + return ( + <CoreCommerceProvider config={{ ...vendureConfig, ...config }}> + {children} + </CoreCommerceProvider> + ) +} + +export const useCommerce = () => useCoreCommerce() diff --git a/framework/vendure/lib/immutability.ts b/framework/vendure/lib/immutability.ts new file mode 100644 index 000000000..dc0177f8a --- /dev/null +++ b/framework/vendure/lib/immutability.ts @@ -0,0 +1,18 @@ + +import update, { Context } from 'immutability-helper'; + +const c = new Context(); + +c.extend('$auto', function(value, object) { + return object ? + c.update(object, value): + c.update({}, value); +}); + +c.extend('$autoArray', function(value, object) { + return object ? + c.update(object, value): + c.update([], value); +}); + +export default c.update \ No newline at end of file diff --git a/framework/vendure/lib/normalize.ts b/framework/vendure/lib/normalize.ts new file mode 100644 index 000000000..bbce17214 --- /dev/null +++ b/framework/vendure/lib/normalize.ts @@ -0,0 +1,118 @@ +import update from '@framework/lib/immutability' + +function normalizeProductOption(productOption: any) { + const { + node: { + entityId, + values: { edges }, + ...rest + }, + } = productOption + + return { + id: entityId, + values: edges?.map(({ node }: any) => node), + ...rest, + } +} + +export function normalizeProduct(productNode: any): Product { + const { + entityId: id, + productOptions, + prices, + path, + id: _, + options: _0, + } = productNode + + return update(productNode, { + id: { $set: String(id) }, + images: { + $apply: ({ edges }: any) => + edges?.map(({ node: { urlOriginal, altText, ...rest } }: any) => ({ + url: urlOriginal, + alt: altText, + ...rest, + })), + }, + variants: { + $apply: ({ edges }: any) => + edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({ + id: entityId, + options: productOptions?.edges + ? productOptions.edges.map(normalizeProductOption) + : [], + ...rest, + })), + }, + options: { + $set: productOptions.edges + ? productOptions?.edges.map(normalizeProductOption) + : [], + }, + brand: { + $apply: (brand: any) => (brand?.entityId ? brand?.entityId : null), + }, + slug: { + $set: path?.replace(/^\/+|\/+$/g, ''), + }, + price: { + $set: { + value: prices?.price.value, + currencyCode: prices?.price.currencyCode, + }, + }, + $unset: ['entityId'], + }) +} + +export function normalizeCart(data: any): Cart { + return update(data, { + $auto: { + items: { $set: data?.line_items?.physical_items?.map(itemsToProducts) }, + subTotal: { $set: data?.base_amount }, + total: { $set: data?.cart_amount }, + }, + $unset: ['created_time', 'coupons', 'line_items', 'email'], + }) +} + +function itemsToProducts(item: any): CartItem { + const { + id, + name, + quantity, + product_id, + variant_id, + image_url, + list_price, + sale_price, + extended_list_price, + extended_sale_price, + ...rest + } = item + + return update(item, { + $auto: { + prices: { + $auto: { + listPrice: { $set: list_price }, + salePrice: { $set: sale_price }, + extendedListPrice: { $set: extended_list_price }, + extendedSalePrice: { $set: extended_sale_price }, + }, + }, + images: { + $set: [ + { + alt: name, + url: image_url, + }, + ], + }, + productId: { $set: product_id }, + variantId: { $set: variant_id }, + }, + }) +} diff --git a/framework/vendure/product/get-all-product-paths.ts b/framework/vendure/product/get-all-product-paths.ts new file mode 100644 index 000000000..b320e9295 --- /dev/null +++ b/framework/vendure/product/get-all-product-paths.ts @@ -0,0 +1,67 @@ +import type { + GetAllProductPathsQuery, + GetAllProductPathsQueryVariables, +} from '../schema' +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import filterEdges from '../api/utils/filter-edges' +import { VendureConfig, getConfig } from '../api' + +export const getAllProductPathsQuery = /* GraphQL */ ` + query getAllProductPaths($first: Int = 100) { + products(options: { take: $first }) { + items { + slug + } + } + } +` + +export type ProductPath = NonNullable< + NonNullable<GetAllProductPathsQuery['site']['products']['edges']>[0] +> + +export type ProductPaths = ProductPath[] + +export type { GetAllProductPathsQueryVariables } + +export type GetAllProductPathsResult< + T extends { products: any[] } = { products: ProductPaths } +> = T + +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<T>> + +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< + RecursivePartial<GetAllProductPathsQuery> + >(query, { variables }) + const products = data.products.items + + return { + products: products.map(p => ({ node: { path: `/${p.slug}` } })), + } +} + +export default getAllProductPaths diff --git a/framework/vendure/product/get-all-products.ts b/framework/vendure/product/get-all-products.ts new file mode 100644 index 000000000..dfb1255b4 --- /dev/null +++ b/framework/vendure/product/get-all-products.ts @@ -0,0 +1,83 @@ +import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' +import filterEdges from '../api/utils/filter-edges' +import setProductLocaleMeta from '../api/utils/set-product-locale-meta' +import { productConnectionFragment } from '../api/fragments/product' +import { VendureConfig, getConfig } from '../api' +import { normalizeProduct } from '../lib/normalize' + +export const getAllProductsQuery = /* GraphQL */ ` + query getAllProducts( + $input: SearchInput! + ) { + search(input: $input) { + items { + productId + productName + description + description + slug + sku + currencyCode + productAsset { + id + preview + } + priceWithTax { + ... on SinglePrice { value } + ... on PriceRange { min max } + } + } + } + } +` + +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( + query, + { variables } + ) + + return { products: data.search.items.map((item: any) => { + return { + id: item.productId, + name: item.productName, + description: item.description, + slug: item.slug, + path: item.slug, + images: [{ url: item.productAsset?.preview }], + variants: [], + price: { + value: (item.priceWithTax.min / 100), + currencyCode: item.currencyCode, + }, + options: [], + sku: item.sku, + } + }) } +} + +export default getAllProducts diff --git a/framework/vendure/product/get-product.ts b/framework/vendure/product/get-product.ts new file mode 100644 index 000000000..efc76ef7e --- /dev/null +++ b/framework/vendure/product/get-product.ts @@ -0,0 +1,84 @@ +import setProductLocaleMeta from '../api/utils/set-product-locale-meta' +import { productInfoFragment } from '../api/fragments/product' +import { VendureConfig, getConfig } from '../api' +import { normalizeProduct } from '@framework/lib/normalize' + +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 + } + } + optionGroups { + code + name + options { + name + } + } + } + } +` + +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(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: any) => ({ url: a.preview, alt: a.name })), + variants: product.variants.map((v: any) => ({ + id: v.id, + options: v.options.map((o: any) => ({ displayName: o.name, values: [] })), + })), + price: { + value: product.variants[0].priceWithTax / 100, + currencyCode: product.variants[0].currencyCode, + }, + options: product.optionGroups.map((og: any) => ({ + displayName: og.name, + values: og.options.map((o: any) => ({ label: o.name + ' hello' })), + })), + } + }; + } + + return {} +} + +export default getProduct + diff --git a/framework/vendure/product/index.ts b/framework/vendure/product/index.ts new file mode 100644 index 000000000..b290c189f --- /dev/null +++ b/framework/vendure/product/index.ts @@ -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' diff --git a/framework/vendure/product/use-price.tsx b/framework/vendure/product/use-price.tsx new file mode 100644 index 000000000..a79940a76 --- /dev/null +++ b/framework/vendure/product/use-price.tsx @@ -0,0 +1,2 @@ +export * from '@commerce/use-price' +export { default } from '@commerce/use-price' diff --git a/framework/vendure/product/use-search.tsx b/framework/vendure/product/use-search.tsx new file mode 100644 index 000000000..ade0bbca2 --- /dev/null +++ b/framework/vendure/product/use-search.tsx @@ -0,0 +1,64 @@ +import type { HookFetcher } from '@commerce/utils/types' +import type { SwrOptions } from '@commerce/utils/use-data' +import useCommerceSearch from '@commerce/products/use-search' +import type { SearchProductsData } from '../api/catalog/products' + +const defaultOpts = { + url: '/api/bigcommerce/catalog/products', + method: 'GET', +} + +export type SearchProductsInput = { + search?: string + categoryId?: number + brandId?: number + sort?: string +} + +export const fetcher: HookFetcher<SearchProductsData, SearchProductsInput> = ( + options, + { search, categoryId, brandId, sort }, + fetch +) => { + // Use a dummy base as we only care about the relative path + const url = new URL(options?.url ?? defaultOpts.url, 'http://a') + + if (search) url.searchParams.set('search', search) + if (Number.isInteger(categoryId)) + url.searchParams.set('category', String(categoryId)) + if (Number.isInteger(categoryId)) + url.searchParams.set('brand', String(brandId)) + if (sort) url.searchParams.set('sort', sort) + + return fetch({ + url: url.pathname + url.search, + method: options?.method ?? defaultOpts.method, + }) +} + +export function extendHook( + customFetcher: typeof fetcher, + swrOptions?: SwrOptions<SearchProductsData, SearchProductsInput> +) { + const useSearch = (input: SearchProductsInput = {}) => { + const response = useCommerceSearch( + defaultOpts, + [ + ['search', input.search], + ['categoryId', input.categoryId], + ['brandId', input.brandId], + ['sort', input.sort], + ], + customFetcher, + { revalidateOnFocus: false, ...swrOptions } + ) + + return response + } + + useSearch.extend = extendHook + + return useSearch +} + +export default extendHook(fetcher) diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts new file mode 100644 index 000000000..8d8594cab --- /dev/null +++ b/framework/vendure/schema.d.ts @@ -0,0 +1,2797 @@ +export type Maybe<T> = T | null +export type Exact<T extends { [key: string]: unknown }> = { + [K in keyof T]: T[K] +} +export type MakeOptional<T, K extends keyof T> = Omit<T, K> & + { [SubKey in K]?: Maybe<T[SubKey]> } +export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & + { [SubKey in K]: Maybe<T[SubKey]> } +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string + String: string + Boolean: boolean + Int: number + Float: number + /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSON: any + /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ + DateTime: any + /** The `Upload` scalar type represents a file upload. */ + Upload: any +} + +export type Query = { + __typename?: 'Query' + /** The active Channel */ + activeChannel: Channel + /** The active Customer */ + activeCustomer?: Maybe<Customer> + /** + * The active Order. Will be `null` until an Order is created via `addItemToOrder`. Once an Order reaches the + * state of `PaymentApproved` or `PaymentSettled`, then that Order is no longer considered "active" and this + * query will once again return `null`. + */ + activeOrder?: Maybe<Order> + /** An array of supported Countries */ + availableCountries: Array<Country> + /** A list of Collections available to the shop */ + collections: CollectionList + /** Returns a Collection either by its id or slug. If neither 'id' nor 'slug' is speicified, an error will result. */ + collection?: Maybe<Collection> + /** Returns a list of eligible shipping methods based on the current active Order */ + eligibleShippingMethods: Array<ShippingMethodQuote> + /** Returns information about the current authenticated User */ + me?: Maybe<CurrentUser> + /** Returns the possible next states that the activeOrder can transition to */ + nextOrderStates: Array<Scalars['String']> + /** + * Returns an Order based on the id. Note that in the Shop API, only orders belonging to the + * currently-authenticated User may be queried. + */ + order?: Maybe<Order> + /** + * Returns an Order based on the order `code`. For guest Orders (i.e. Orders placed by non-authenticated Customers) + * this query will only return the Order within 2 hours of the Order being placed. This allows an Order confirmation + * screen to be shown immediately after completion of a guest checkout, yet prevents security risks of allowing + * general anonymous access to Order data. + */ + orderByCode?: Maybe<Order> + /** Get a Product either by id or slug. If neither 'id' nor 'slug' is speicified, an error will result. */ + product?: Maybe<Product> + /** Get a list of Products */ + products: ProductList + /** Search Products based on the criteria set by the `SearchInput` */ + search: SearchResponse +} + +export type QueryCollectionsArgs = { + options?: Maybe<CollectionListOptions> +} + +export type QueryCollectionArgs = { + id?: Maybe<Scalars['ID']> + slug?: Maybe<Scalars['String']> +} + +export type QueryOrderArgs = { + id: Scalars['ID'] +} + +export type QueryOrderByCodeArgs = { + code: Scalars['String'] +} + +export type QueryProductArgs = { + id?: Maybe<Scalars['ID']> + slug?: Maybe<Scalars['String']> +} + +export type QueryProductsArgs = { + options?: Maybe<ProductListOptions> +} + +export type QuerySearchArgs = { + input: SearchInput +} + +export type Mutation = { + __typename?: 'Mutation' + /** Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */ + addItemToOrder: UpdateOrderItemsResult + /** Remove an OrderLine from the Order */ + removeOrderLine: RemoveOrderItemsResult + /** Remove all OrderLine from the Order */ + removeAllOrderLines: RemoveOrderItemsResult + /** Adjusts an OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */ + adjustOrderLine: UpdateOrderItemsResult + /** Applies the given coupon code to the active Order */ + applyCouponCode: ApplyCouponCodeResult + /** Removes the given coupon code from the active Order */ + removeCouponCode?: Maybe<Order> + /** Transitions an Order to a new state. Valid next states can be found by querying `nextOrderStates` */ + transitionOrderToState?: Maybe<TransitionOrderToStateResult> + /** Sets the shipping address for this order */ + setOrderShippingAddress: ActiveOrderResult + /** Sets the billing address for this order */ + setOrderBillingAddress: ActiveOrderResult + /** Allows any custom fields to be set for the active order */ + setOrderCustomFields: ActiveOrderResult + /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethods` query */ + setOrderShippingMethod: SetOrderShippingMethodResult + /** Add a Payment to the Order */ + addPaymentToOrder: AddPaymentToOrderResult + /** Set the Customer for the Order. Required only if the Customer is not currently logged in */ + setCustomerForOrder: SetCustomerForOrderResult + /** Authenticates the user using the native authentication strategy. This mutation is an alias for `authenticate({ native: { ... }})` */ + login: NativeAuthenticationResult + /** Authenticates the user using a named authentication strategy */ + authenticate: AuthenticationResult + /** End the current authenticated session */ + logout: Success + /** + * Register a Customer account with the given credentials. There are three possible registration flows: + * + * _If `authOptions.requireVerification` is set to `true`:_ + * + * 1. **The Customer is registered _with_ a password**. A verificationToken will be created (and typically emailed to the Customer). That + * verificationToken would then be passed to the `verifyCustomerAccount` mutation _without_ a password. The Customer is then + * verified and authenticated in one step. + * 2. **The Customer is registered _without_ a password**. A verificationToken will be created (and typically emailed to the Customer). That + * verificationToken would then be passed to the `verifyCustomerAccount` mutation _with_ the chosed password of the Customer. The Customer is then + * verified and authenticated in one step. + * + * _If `authOptions.requireVerification` is set to `false`:_ + * + * 3. The Customer _must_ be registered _with_ a password. No further action is needed - the Customer is able to authenticate immediately. + */ + registerCustomerAccount: RegisterCustomerAccountResult + /** Regenerate and send a verification token for a new Customer registration. Only applicable if `authOptions.requireVerification` is set to true. */ + refreshCustomerVerification: RefreshCustomerVerificationResult + /** Update an existing Customer */ + updateCustomer: Customer + /** Create a new Customer Address */ + createCustomerAddress: Address + /** Update an existing Address */ + updateCustomerAddress: Address + /** Delete an existing Address */ + deleteCustomerAddress: Success + /** + * Verify a Customer email address with the token sent to that address. Only applicable if `authOptions.requireVerification` is set to true. + * + * If the Customer was not registered with a password in the `registerCustomerAccount` mutation, the a password _must_ be + * provided here. + */ + verifyCustomerAccount: VerifyCustomerAccountResult + /** Update the password of the active Customer */ + updateCustomerPassword: UpdateCustomerPasswordResult + /** + * Request to update the emailAddress of the active Customer. If `authOptions.requireVerification` is enabled + * (as is the default), then the `identifierChangeToken` will be assigned to the current User and + * a IdentifierChangeRequestEvent will be raised. This can then be used e.g. by the EmailPlugin to email + * that verification token to the Customer, which is then used to verify the change of email address. + */ + requestUpdateCustomerEmailAddress: RequestUpdateCustomerEmailAddressResult + /** + * Confirm the update of the emailAddress with the provided token, which has been generated by the + * `requestUpdateCustomerEmailAddress` mutation. + */ + updateCustomerEmailAddress: UpdateCustomerEmailAddressResult + /** Requests a password reset email to be sent */ + requestPasswordReset?: Maybe<RequestPasswordResetResult> + /** Resets a Customer's password based on the provided token */ + resetPassword: ResetPasswordResult +} + +export type MutationAddItemToOrderArgs = { + productVariantId: Scalars['ID'] + quantity: Scalars['Int'] +} + +export type MutationRemoveOrderLineArgs = { + orderLineId: Scalars['ID'] +} + +export type MutationAdjustOrderLineArgs = { + orderLineId: Scalars['ID'] + quantity: Scalars['Int'] +} + +export type MutationApplyCouponCodeArgs = { + couponCode: Scalars['String'] +} + +export type MutationRemoveCouponCodeArgs = { + couponCode: Scalars['String'] +} + +export type MutationTransitionOrderToStateArgs = { + state: Scalars['String'] +} + +export type MutationSetOrderShippingAddressArgs = { + input: CreateAddressInput +} + +export type MutationSetOrderBillingAddressArgs = { + input: CreateAddressInput +} + +export type MutationSetOrderCustomFieldsArgs = { + input: UpdateOrderInput +} + +export type MutationSetOrderShippingMethodArgs = { + shippingMethodId: Scalars['ID'] +} + +export type MutationAddPaymentToOrderArgs = { + input: PaymentInput +} + +export type MutationSetCustomerForOrderArgs = { + input: CreateCustomerInput +} + +export type MutationLoginArgs = { + username: Scalars['String'] + password: Scalars['String'] + rememberMe?: Maybe<Scalars['Boolean']> +} + +export type MutationAuthenticateArgs = { + input: AuthenticationInput + rememberMe?: Maybe<Scalars['Boolean']> +} + +export type MutationRegisterCustomerAccountArgs = { + input: RegisterCustomerInput +} + +export type MutationRefreshCustomerVerificationArgs = { + emailAddress: Scalars['String'] +} + +export type MutationUpdateCustomerArgs = { + input: UpdateCustomerInput +} + +export type MutationCreateCustomerAddressArgs = { + input: CreateAddressInput +} + +export type MutationUpdateCustomerAddressArgs = { + input: UpdateAddressInput +} + +export type MutationDeleteCustomerAddressArgs = { + id: Scalars['ID'] +} + +export type MutationVerifyCustomerAccountArgs = { + token: Scalars['String'] + password?: Maybe<Scalars['String']> +} + +export type MutationUpdateCustomerPasswordArgs = { + currentPassword: Scalars['String'] + newPassword: Scalars['String'] +} + +export type MutationRequestUpdateCustomerEmailAddressArgs = { + password: Scalars['String'] + newEmailAddress: Scalars['String'] +} + +export type MutationUpdateCustomerEmailAddressArgs = { + token: Scalars['String'] +} + +export type MutationRequestPasswordResetArgs = { + emailAddress: Scalars['String'] +} + +export type MutationResetPasswordArgs = { + token: Scalars['String'] + password: Scalars['String'] +} + +export type Address = Node & { + __typename?: 'Address' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + fullName?: Maybe<Scalars['String']> + company?: Maybe<Scalars['String']> + streetLine1: Scalars['String'] + streetLine2?: Maybe<Scalars['String']> + city?: Maybe<Scalars['String']> + province?: Maybe<Scalars['String']> + postalCode?: Maybe<Scalars['String']> + country: Country + phoneNumber?: Maybe<Scalars['String']> + defaultShippingAddress?: Maybe<Scalars['Boolean']> + defaultBillingAddress?: Maybe<Scalars['Boolean']> + customFields?: Maybe<Scalars['JSON']> +} + +export type Asset = Node & { + __typename?: 'Asset' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + name: Scalars['String'] + type: AssetType + fileSize: Scalars['Int'] + mimeType: Scalars['String'] + width: Scalars['Int'] + height: Scalars['Int'] + source: Scalars['String'] + preview: Scalars['String'] + focalPoint?: Maybe<Coordinate> +} + +export type Coordinate = { + __typename?: 'Coordinate' + x: Scalars['Float'] + y: Scalars['Float'] +} + +export type AssetList = PaginatedList & { + __typename?: 'AssetList' + items: Array<Asset> + totalItems: Scalars['Int'] +} + +export enum AssetType { + Image = 'IMAGE', + Video = 'VIDEO', + Binary = 'BINARY', +} + +export type CurrentUser = { + __typename?: 'CurrentUser' + id: Scalars['ID'] + identifier: Scalars['String'] + channels: Array<CurrentUserChannel> +} + +export type CurrentUserChannel = { + __typename?: 'CurrentUserChannel' + id: Scalars['ID'] + token: Scalars['String'] + code: Scalars['String'] + permissions: Array<Permission> +} + +export type Channel = Node & { + __typename?: 'Channel' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + code: Scalars['String'] + token: Scalars['String'] + defaultTaxZone?: Maybe<Zone> + defaultShippingZone?: Maybe<Zone> + defaultLanguageCode: LanguageCode + currencyCode: CurrencyCode + pricesIncludeTax: Scalars['Boolean'] +} + +export type Collection = Node & { + __typename?: 'Collection' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode?: Maybe<LanguageCode> + name: Scalars['String'] + slug: Scalars['String'] + breadcrumbs: Array<CollectionBreadcrumb> + position: Scalars['Int'] + description: Scalars['String'] + featuredAsset?: Maybe<Asset> + assets: Array<Asset> + parent?: Maybe<Collection> + children?: Maybe<Array<Collection>> + filters: Array<ConfigurableOperation> + translations: Array<CollectionTranslation> + productVariants: ProductVariantList + customFields?: Maybe<Scalars['JSON']> +} + +export type CollectionProductVariantsArgs = { + options?: Maybe<ProductVariantListOptions> +} + +export type CollectionBreadcrumb = { + __typename?: 'CollectionBreadcrumb' + id: Scalars['ID'] + name: Scalars['String'] + slug: Scalars['String'] +} + +export type CollectionTranslation = { + __typename?: 'CollectionTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] + slug: Scalars['String'] + description: Scalars['String'] +} + +export type CollectionList = PaginatedList & { + __typename?: 'CollectionList' + items: Array<Collection> + totalItems: Scalars['Int'] +} + +export type ProductVariantList = PaginatedList & { + __typename?: 'ProductVariantList' + items: Array<ProductVariant> + totalItems: Scalars['Int'] +} + +export enum GlobalFlag { + True = 'TRUE', + False = 'FALSE', + Inherit = 'INHERIT', +} + +export enum AdjustmentType { + Promotion = 'PROMOTION', + DistributedOrderPromotion = 'DISTRIBUTED_ORDER_PROMOTION', +} + +export enum DeletionResult { + /** The entity was successfully deleted */ + Deleted = 'DELETED', + /** Deletion did not take place, reason given in message */ + NotDeleted = 'NOT_DELETED', +} + +/** + * @description + * Permissions for administrators and customers. Used to control access to + * GraphQL resolvers via the {@link Allow} decorator. + * + * @docsCategory common + */ +export enum Permission { + /** Authenticated means simply that the user is logged in */ + Authenticated = 'Authenticated', + /** SuperAdmin has unrestricted access to all operations */ + SuperAdmin = 'SuperAdmin', + /** Owner means the user owns this entity, e.g. a Customer's own Order */ + Owner = 'Owner', + /** Public means any unauthenticated user may perform the operation */ + Public = 'Public', + /** Grants permission to create Catalog */ + CreateCatalog = 'CreateCatalog', + /** Grants permission to read Catalog */ + ReadCatalog = 'ReadCatalog', + /** Grants permission to update Catalog */ + UpdateCatalog = 'UpdateCatalog', + /** Grants permission to delete Catalog */ + DeleteCatalog = 'DeleteCatalog', + /** Grants permission to create Customer */ + CreateCustomer = 'CreateCustomer', + /** Grants permission to read Customer */ + ReadCustomer = 'ReadCustomer', + /** Grants permission to update Customer */ + UpdateCustomer = 'UpdateCustomer', + /** Grants permission to delete Customer */ + DeleteCustomer = 'DeleteCustomer', + /** Grants permission to create Administrator */ + CreateAdministrator = 'CreateAdministrator', + /** Grants permission to read Administrator */ + ReadAdministrator = 'ReadAdministrator', + /** Grants permission to update Administrator */ + UpdateAdministrator = 'UpdateAdministrator', + /** Grants permission to delete Administrator */ + DeleteAdministrator = 'DeleteAdministrator', + /** Grants permission to create Order */ + CreateOrder = 'CreateOrder', + /** Grants permission to read Order */ + ReadOrder = 'ReadOrder', + /** Grants permission to update Order */ + UpdateOrder = 'UpdateOrder', + /** Grants permission to delete Order */ + DeleteOrder = 'DeleteOrder', + /** Grants permission to create Promotion */ + CreatePromotion = 'CreatePromotion', + /** Grants permission to read Promotion */ + ReadPromotion = 'ReadPromotion', + /** Grants permission to update Promotion */ + UpdatePromotion = 'UpdatePromotion', + /** Grants permission to delete Promotion */ + DeletePromotion = 'DeletePromotion', + /** Grants permission to create Settings */ + CreateSettings = 'CreateSettings', + /** Grants permission to read Settings */ + ReadSettings = 'ReadSettings', + /** Grants permission to update Settings */ + UpdateSettings = 'UpdateSettings', + /** Grants permission to delete Settings */ + DeleteSettings = 'DeleteSettings', +} + +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC', +} + +export enum ErrorCode { + UnknownError = 'UNKNOWN_ERROR', + NativeAuthStrategyError = 'NATIVE_AUTH_STRATEGY_ERROR', + InvalidCredentialsError = 'INVALID_CREDENTIALS_ERROR', + OrderStateTransitionError = 'ORDER_STATE_TRANSITION_ERROR', + EmailAddressConflictError = 'EMAIL_ADDRESS_CONFLICT_ERROR', + OrderLimitError = 'ORDER_LIMIT_ERROR', + NegativeQuantityError = 'NEGATIVE_QUANTITY_ERROR', + InsufficientStockError = 'INSUFFICIENT_STOCK_ERROR', + OrderModificationError = 'ORDER_MODIFICATION_ERROR', + IneligibleShippingMethodError = 'INELIGIBLE_SHIPPING_METHOD_ERROR', + OrderPaymentStateError = 'ORDER_PAYMENT_STATE_ERROR', + PaymentFailedError = 'PAYMENT_FAILED_ERROR', + PaymentDeclinedError = 'PAYMENT_DECLINED_ERROR', + CouponCodeInvalidError = 'COUPON_CODE_INVALID_ERROR', + CouponCodeExpiredError = 'COUPON_CODE_EXPIRED_ERROR', + CouponCodeLimitError = 'COUPON_CODE_LIMIT_ERROR', + AlreadyLoggedInError = 'ALREADY_LOGGED_IN_ERROR', + MissingPasswordError = 'MISSING_PASSWORD_ERROR', + PasswordAlreadySetError = 'PASSWORD_ALREADY_SET_ERROR', + VerificationTokenInvalidError = 'VERIFICATION_TOKEN_INVALID_ERROR', + VerificationTokenExpiredError = 'VERIFICATION_TOKEN_EXPIRED_ERROR', + IdentifierChangeTokenInvalidError = 'IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR', + IdentifierChangeTokenExpiredError = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR', + PasswordResetTokenInvalidError = 'PASSWORD_RESET_TOKEN_INVALID_ERROR', + PasswordResetTokenExpiredError = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR', + NotVerifiedError = 'NOT_VERIFIED_ERROR', + NoActiveOrderError = 'NO_ACTIVE_ORDER_ERROR', +} + +export enum LogicalOperator { + And = 'AND', + Or = 'OR', +} + +/** Retured when attempting an operation that relies on the NativeAuthStrategy, if that strategy is not configured. */ +export type NativeAuthStrategyError = ErrorResult & { + __typename?: 'NativeAuthStrategyError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Returned if the user authentication credentials are not valid */ +export type InvalidCredentialsError = ErrorResult & { + __typename?: 'InvalidCredentialsError' + errorCode: ErrorCode + message: Scalars['String'] + authenticationError: Scalars['String'] +} + +/** Returned if there is an error in transitioning the Order state */ +export type OrderStateTransitionError = ErrorResult & { + __typename?: 'OrderStateTransitionError' + errorCode: ErrorCode + message: Scalars['String'] + transitionError: Scalars['String'] + fromState: Scalars['String'] + toState: Scalars['String'] +} + +/** Retured when attemting to create a Customer with an email address already registered to an existing User. */ +export type EmailAddressConflictError = ErrorResult & { + __typename?: 'EmailAddressConflictError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Retured when the maximum order size limit has been reached. */ +export type OrderLimitError = ErrorResult & { + __typename?: 'OrderLimitError' + errorCode: ErrorCode + message: Scalars['String'] + maxItems: Scalars['Int'] +} + +/** Retured when attemting to set a negative OrderLine quantity. */ +export type NegativeQuantityError = ErrorResult & { + __typename?: 'NegativeQuantityError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Returned when attempting to add more items to the Order than are available */ +export type InsufficientStockError = ErrorResult & { + __typename?: 'InsufficientStockError' + errorCode: ErrorCode + message: Scalars['String'] + quantityAvailable: Scalars['Int'] + order: Order +} + +export type PaginatedList = { + items: Array<Node> + totalItems: Scalars['Int'] +} + +export type Node = { + id: Scalars['ID'] +} + +export type ErrorResult = { + errorCode: ErrorCode + message: Scalars['String'] +} + +export type Adjustment = { + __typename?: 'Adjustment' + adjustmentSource: Scalars['String'] + type: AdjustmentType + description: Scalars['String'] + amount: Scalars['Int'] +} + +export type TaxLine = { + __typename?: 'TaxLine' + description: Scalars['String'] + taxRate: Scalars['Float'] +} + +export type ConfigArg = { + __typename?: 'ConfigArg' + name: Scalars['String'] + value: Scalars['String'] +} + +export type ConfigArgDefinition = { + __typename?: 'ConfigArgDefinition' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Scalars['String']> + description?: Maybe<Scalars['String']> + ui?: Maybe<Scalars['JSON']> +} + +export type ConfigurableOperation = { + __typename?: 'ConfigurableOperation' + code: Scalars['String'] + args: Array<ConfigArg> +} + +export type ConfigurableOperationDefinition = { + __typename?: 'ConfigurableOperationDefinition' + code: Scalars['String'] + args: Array<ConfigArgDefinition> + description: Scalars['String'] +} + +export type DeletionResponse = { + __typename?: 'DeletionResponse' + result: DeletionResult + message?: Maybe<Scalars['String']> +} + +export type ConfigArgInput = { + name: Scalars['String'] + value: Scalars['String'] +} + +export type ConfigurableOperationInput = { + code: Scalars['String'] + arguments: Array<ConfigArgInput> +} + +export type StringOperators = { + eq?: Maybe<Scalars['String']> + notEq?: Maybe<Scalars['String']> + contains?: Maybe<Scalars['String']> + notContains?: Maybe<Scalars['String']> + in?: Maybe<Array<Scalars['String']>> + notIn?: Maybe<Array<Scalars['String']>> + regex?: Maybe<Scalars['String']> +} + +export type BooleanOperators = { + eq?: Maybe<Scalars['Boolean']> +} + +export type NumberRange = { + start: Scalars['Float'] + end: Scalars['Float'] +} + +export type NumberOperators = { + eq?: Maybe<Scalars['Float']> + lt?: Maybe<Scalars['Float']> + lte?: Maybe<Scalars['Float']> + gt?: Maybe<Scalars['Float']> + gte?: Maybe<Scalars['Float']> + between?: Maybe<NumberRange> +} + +export type DateRange = { + start: Scalars['DateTime'] + end: Scalars['DateTime'] +} + +export type DateOperators = { + eq?: Maybe<Scalars['DateTime']> + before?: Maybe<Scalars['DateTime']> + after?: Maybe<Scalars['DateTime']> + between?: Maybe<DateRange> +} + +export type SearchInput = { + term?: Maybe<Scalars['String']> + facetValueIds?: Maybe<Array<Scalars['ID']>> + facetValueOperator?: Maybe<LogicalOperator> + collectionId?: Maybe<Scalars['ID']> + collectionSlug?: Maybe<Scalars['String']> + groupByProduct?: Maybe<Scalars['Boolean']> + take?: Maybe<Scalars['Int']> + skip?: Maybe<Scalars['Int']> + sort?: Maybe<SearchResultSortParameter> +} + +export type SearchResultSortParameter = { + name?: Maybe<SortOrder> + price?: Maybe<SortOrder> +} + +export type CreateCustomerInput = { + title?: Maybe<Scalars['String']> + firstName: Scalars['String'] + lastName: Scalars['String'] + phoneNumber?: Maybe<Scalars['String']> + emailAddress: Scalars['String'] + customFields?: Maybe<Scalars['JSON']> +} + +export type CreateAddressInput = { + fullName?: Maybe<Scalars['String']> + company?: Maybe<Scalars['String']> + streetLine1: Scalars['String'] + streetLine2?: Maybe<Scalars['String']> + city?: Maybe<Scalars['String']> + province?: Maybe<Scalars['String']> + postalCode?: Maybe<Scalars['String']> + countryCode: Scalars['String'] + phoneNumber?: Maybe<Scalars['String']> + defaultShippingAddress?: Maybe<Scalars['Boolean']> + defaultBillingAddress?: Maybe<Scalars['Boolean']> + customFields?: Maybe<Scalars['JSON']> +} + +export type UpdateAddressInput = { + id: Scalars['ID'] + fullName?: Maybe<Scalars['String']> + company?: Maybe<Scalars['String']> + streetLine1?: Maybe<Scalars['String']> + streetLine2?: Maybe<Scalars['String']> + city?: Maybe<Scalars['String']> + province?: Maybe<Scalars['String']> + postalCode?: Maybe<Scalars['String']> + countryCode?: Maybe<Scalars['String']> + phoneNumber?: Maybe<Scalars['String']> + defaultShippingAddress?: Maybe<Scalars['Boolean']> + defaultBillingAddress?: Maybe<Scalars['Boolean']> + customFields?: Maybe<Scalars['JSON']> +} + +/** Indicates that an operation succeeded, where we do not want to return any more specific information. */ +export type Success = { + __typename?: 'Success' + success: Scalars['Boolean'] +} + +export type Country = Node & { + __typename?: 'Country' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + code: Scalars['String'] + name: Scalars['String'] + enabled: Scalars['Boolean'] + translations: Array<CountryTranslation> +} + +export type CountryTranslation = { + __typename?: 'CountryTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type CountryList = PaginatedList & { + __typename?: 'CountryList' + items: Array<Country> + totalItems: Scalars['Int'] +} + +/** + * @description + * ISO 4217 currency code + * + * @docsCategory common + */ +export enum CurrencyCode { + /** United Arab Emirates dirham */ + Aed = 'AED', + /** Afghan afghani */ + Afn = 'AFN', + /** Albanian lek */ + All = 'ALL', + /** Armenian dram */ + Amd = 'AMD', + /** Netherlands Antillean guilder */ + Ang = 'ANG', + /** Angolan kwanza */ + Aoa = 'AOA', + /** Argentine peso */ + Ars = 'ARS', + /** Australian dollar */ + Aud = 'AUD', + /** Aruban florin */ + Awg = 'AWG', + /** Azerbaijani manat */ + Azn = 'AZN', + /** Bosnia and Herzegovina convertible mark */ + Bam = 'BAM', + /** Barbados dollar */ + Bbd = 'BBD', + /** Bangladeshi taka */ + Bdt = 'BDT', + /** Bulgarian lev */ + Bgn = 'BGN', + /** Bahraini dinar */ + Bhd = 'BHD', + /** Burundian franc */ + Bif = 'BIF', + /** Bermudian dollar */ + Bmd = 'BMD', + /** Brunei dollar */ + Bnd = 'BND', + /** Boliviano */ + Bob = 'BOB', + /** Brazilian real */ + Brl = 'BRL', + /** Bahamian dollar */ + Bsd = 'BSD', + /** Bhutanese ngultrum */ + Btn = 'BTN', + /** Botswana pula */ + Bwp = 'BWP', + /** Belarusian ruble */ + Byn = 'BYN', + /** Belize dollar */ + Bzd = 'BZD', + /** Canadian dollar */ + Cad = 'CAD', + /** Congolese franc */ + Cdf = 'CDF', + /** Swiss franc */ + Chf = 'CHF', + /** Chilean peso */ + Clp = 'CLP', + /** Renminbi (Chinese) yuan */ + Cny = 'CNY', + /** Colombian peso */ + Cop = 'COP', + /** Costa Rican colon */ + Crc = 'CRC', + /** Cuban convertible peso */ + Cuc = 'CUC', + /** Cuban peso */ + Cup = 'CUP', + /** Cape Verde escudo */ + Cve = 'CVE', + /** Czech koruna */ + Czk = 'CZK', + /** Djiboutian franc */ + Djf = 'DJF', + /** Danish krone */ + Dkk = 'DKK', + /** Dominican peso */ + Dop = 'DOP', + /** Algerian dinar */ + Dzd = 'DZD', + /** Egyptian pound */ + Egp = 'EGP', + /** Eritrean nakfa */ + Ern = 'ERN', + /** Ethiopian birr */ + Etb = 'ETB', + /** Euro */ + Eur = 'EUR', + /** Fiji dollar */ + Fjd = 'FJD', + /** Falkland Islands pound */ + Fkp = 'FKP', + /** Pound sterling */ + Gbp = 'GBP', + /** Georgian lari */ + Gel = 'GEL', + /** Ghanaian cedi */ + Ghs = 'GHS', + /** Gibraltar pound */ + Gip = 'GIP', + /** Gambian dalasi */ + Gmd = 'GMD', + /** Guinean franc */ + Gnf = 'GNF', + /** Guatemalan quetzal */ + Gtq = 'GTQ', + /** Guyanese dollar */ + Gyd = 'GYD', + /** Hong Kong dollar */ + Hkd = 'HKD', + /** Honduran lempira */ + Hnl = 'HNL', + /** Croatian kuna */ + Hrk = 'HRK', + /** Haitian gourde */ + Htg = 'HTG', + /** Hungarian forint */ + Huf = 'HUF', + /** Indonesian rupiah */ + Idr = 'IDR', + /** Israeli new shekel */ + Ils = 'ILS', + /** Indian rupee */ + Inr = 'INR', + /** Iraqi dinar */ + Iqd = 'IQD', + /** Iranian rial */ + Irr = 'IRR', + /** Icelandic króna */ + Isk = 'ISK', + /** Jamaican dollar */ + Jmd = 'JMD', + /** Jordanian dinar */ + Jod = 'JOD', + /** Japanese yen */ + Jpy = 'JPY', + /** Kenyan shilling */ + Kes = 'KES', + /** Kyrgyzstani som */ + Kgs = 'KGS', + /** Cambodian riel */ + Khr = 'KHR', + /** Comoro franc */ + Kmf = 'KMF', + /** North Korean won */ + Kpw = 'KPW', + /** South Korean won */ + Krw = 'KRW', + /** Kuwaiti dinar */ + Kwd = 'KWD', + /** Cayman Islands dollar */ + Kyd = 'KYD', + /** Kazakhstani tenge */ + Kzt = 'KZT', + /** Lao kip */ + Lak = 'LAK', + /** Lebanese pound */ + Lbp = 'LBP', + /** Sri Lankan rupee */ + Lkr = 'LKR', + /** Liberian dollar */ + Lrd = 'LRD', + /** Lesotho loti */ + Lsl = 'LSL', + /** Libyan dinar */ + Lyd = 'LYD', + /** Moroccan dirham */ + Mad = 'MAD', + /** Moldovan leu */ + Mdl = 'MDL', + /** Malagasy ariary */ + Mga = 'MGA', + /** Macedonian denar */ + Mkd = 'MKD', + /** Myanmar kyat */ + Mmk = 'MMK', + /** Mongolian tögrög */ + Mnt = 'MNT', + /** Macanese pataca */ + Mop = 'MOP', + /** Mauritanian ouguiya */ + Mru = 'MRU', + /** Mauritian rupee */ + Mur = 'MUR', + /** Maldivian rufiyaa */ + Mvr = 'MVR', + /** Malawian kwacha */ + Mwk = 'MWK', + /** Mexican peso */ + Mxn = 'MXN', + /** Malaysian ringgit */ + Myr = 'MYR', + /** Mozambican metical */ + Mzn = 'MZN', + /** Namibian dollar */ + Nad = 'NAD', + /** Nigerian naira */ + Ngn = 'NGN', + /** Nicaraguan córdoba */ + Nio = 'NIO', + /** Norwegian krone */ + Nok = 'NOK', + /** Nepalese rupee */ + Npr = 'NPR', + /** New Zealand dollar */ + Nzd = 'NZD', + /** Omani rial */ + Omr = 'OMR', + /** Panamanian balboa */ + Pab = 'PAB', + /** Peruvian sol */ + Pen = 'PEN', + /** Papua New Guinean kina */ + Pgk = 'PGK', + /** Philippine peso */ + Php = 'PHP', + /** Pakistani rupee */ + Pkr = 'PKR', + /** Polish złoty */ + Pln = 'PLN', + /** Paraguayan guaraní */ + Pyg = 'PYG', + /** Qatari riyal */ + Qar = 'QAR', + /** Romanian leu */ + Ron = 'RON', + /** Serbian dinar */ + Rsd = 'RSD', + /** Russian ruble */ + Rub = 'RUB', + /** Rwandan franc */ + Rwf = 'RWF', + /** Saudi riyal */ + Sar = 'SAR', + /** Solomon Islands dollar */ + Sbd = 'SBD', + /** Seychelles rupee */ + Scr = 'SCR', + /** Sudanese pound */ + Sdg = 'SDG', + /** Swedish krona/kronor */ + Sek = 'SEK', + /** Singapore dollar */ + Sgd = 'SGD', + /** Saint Helena pound */ + Shp = 'SHP', + /** Sierra Leonean leone */ + Sll = 'SLL', + /** Somali shilling */ + Sos = 'SOS', + /** Surinamese dollar */ + Srd = 'SRD', + /** South Sudanese pound */ + Ssp = 'SSP', + /** São Tomé and Príncipe dobra */ + Stn = 'STN', + /** Salvadoran colón */ + Svc = 'SVC', + /** Syrian pound */ + Syp = 'SYP', + /** Swazi lilangeni */ + Szl = 'SZL', + /** Thai baht */ + Thb = 'THB', + /** Tajikistani somoni */ + Tjs = 'TJS', + /** Turkmenistan manat */ + Tmt = 'TMT', + /** Tunisian dinar */ + Tnd = 'TND', + /** Tongan paʻanga */ + Top = 'TOP', + /** Turkish lira */ + Try = 'TRY', + /** Trinidad and Tobago dollar */ + Ttd = 'TTD', + /** New Taiwan dollar */ + Twd = 'TWD', + /** Tanzanian shilling */ + Tzs = 'TZS', + /** Ukrainian hryvnia */ + Uah = 'UAH', + /** Ugandan shilling */ + Ugx = 'UGX', + /** United States dollar */ + Usd = 'USD', + /** Uruguayan peso */ + Uyu = 'UYU', + /** Uzbekistan som */ + Uzs = 'UZS', + /** Venezuelan bolívar soberano */ + Ves = 'VES', + /** Vietnamese đồng */ + Vnd = 'VND', + /** Vanuatu vatu */ + Vuv = 'VUV', + /** Samoan tala */ + Wst = 'WST', + /** CFA franc BEAC */ + Xaf = 'XAF', + /** East Caribbean dollar */ + Xcd = 'XCD', + /** CFA franc BCEAO */ + Xof = 'XOF', + /** CFP franc (franc Pacifique) */ + Xpf = 'XPF', + /** Yemeni rial */ + Yer = 'YER', + /** South African rand */ + Zar = 'ZAR', + /** Zambian kwacha */ + Zmw = 'ZMW', + /** Zimbabwean dollar */ + Zwl = 'ZWL', +} + +export type CustomField = { + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> +} + +export type StringCustomFieldConfig = CustomField & { + __typename?: 'StringCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + length?: Maybe<Scalars['Int']> + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + pattern?: Maybe<Scalars['String']> + options?: Maybe<Array<StringFieldOption>> +} + +export type StringFieldOption = { + __typename?: 'StringFieldOption' + value: Scalars['String'] + label?: Maybe<Array<LocalizedString>> +} + +export type LocaleStringCustomFieldConfig = CustomField & { + __typename?: 'LocaleStringCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + length?: Maybe<Scalars['Int']> + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + pattern?: Maybe<Scalars['String']> +} + +export type IntCustomFieldConfig = CustomField & { + __typename?: 'IntCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + min?: Maybe<Scalars['Int']> + max?: Maybe<Scalars['Int']> + step?: Maybe<Scalars['Int']> +} + +export type FloatCustomFieldConfig = CustomField & { + __typename?: 'FloatCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + min?: Maybe<Scalars['Float']> + max?: Maybe<Scalars['Float']> + step?: Maybe<Scalars['Float']> +} + +export type BooleanCustomFieldConfig = CustomField & { + __typename?: 'BooleanCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> +} + +/** + * Expects the same validation formats as the `<input type="datetime-local">` HTML element. + * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes + */ +export type DateTimeCustomFieldConfig = CustomField & { + __typename?: 'DateTimeCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + min?: Maybe<Scalars['String']> + max?: Maybe<Scalars['String']> + step?: Maybe<Scalars['Int']> +} + +export type RelationCustomFieldConfig = CustomField & { + __typename?: 'RelationCustomFieldConfig' + name: Scalars['String'] + type: Scalars['String'] + list: Scalars['Boolean'] + label?: Maybe<Array<LocalizedString>> + description?: Maybe<Array<LocalizedString>> + readonly?: Maybe<Scalars['Boolean']> + internal?: Maybe<Scalars['Boolean']> + entity: Scalars['String'] + scalarFields: Array<Scalars['String']> +} + +export type LocalizedString = { + __typename?: 'LocalizedString' + languageCode: LanguageCode + value: Scalars['String'] +} + +export type CustomFieldConfig = + | StringCustomFieldConfig + | LocaleStringCustomFieldConfig + | IntCustomFieldConfig + | FloatCustomFieldConfig + | BooleanCustomFieldConfig + | DateTimeCustomFieldConfig + | RelationCustomFieldConfig + +export type CustomerGroup = Node & { + __typename?: 'CustomerGroup' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + name: Scalars['String'] + customers: CustomerList +} + +export type CustomerGroupCustomersArgs = { + options?: Maybe<CustomerListOptions> +} + +export type Customer = Node & { + __typename?: 'Customer' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + title?: Maybe<Scalars['String']> + firstName: Scalars['String'] + lastName: Scalars['String'] + phoneNumber?: Maybe<Scalars['String']> + emailAddress: Scalars['String'] + addresses?: Maybe<Array<Address>> + orders: OrderList + user?: Maybe<User> + customFields?: Maybe<Scalars['JSON']> +} + +export type CustomerOrdersArgs = { + options?: Maybe<OrderListOptions> +} + +export type CustomerList = PaginatedList & { + __typename?: 'CustomerList' + items: Array<Customer> + totalItems: Scalars['Int'] +} + +export type FacetValue = Node & { + __typename?: 'FacetValue' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + facet: Facet + name: Scalars['String'] + code: Scalars['String'] + translations: Array<FacetValueTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type FacetValueTranslation = { + __typename?: 'FacetValueTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type Facet = Node & { + __typename?: 'Facet' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] + code: Scalars['String'] + values: Array<FacetValue> + translations: Array<FacetTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type FacetTranslation = { + __typename?: 'FacetTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type FacetList = PaginatedList & { + __typename?: 'FacetList' + items: Array<Facet> + totalItems: Scalars['Int'] +} + +export type HistoryEntry = Node & { + __typename?: 'HistoryEntry' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + type: HistoryEntryType + data: Scalars['JSON'] +} + +export enum HistoryEntryType { + CustomerRegistered = 'CUSTOMER_REGISTERED', + CustomerVerified = 'CUSTOMER_VERIFIED', + CustomerDetailUpdated = 'CUSTOMER_DETAIL_UPDATED', + CustomerAddedToGroup = 'CUSTOMER_ADDED_TO_GROUP', + CustomerRemovedFromGroup = 'CUSTOMER_REMOVED_FROM_GROUP', + CustomerAddressCreated = 'CUSTOMER_ADDRESS_CREATED', + CustomerAddressUpdated = 'CUSTOMER_ADDRESS_UPDATED', + CustomerAddressDeleted = 'CUSTOMER_ADDRESS_DELETED', + CustomerPasswordUpdated = 'CUSTOMER_PASSWORD_UPDATED', + CustomerPasswordResetRequested = 'CUSTOMER_PASSWORD_RESET_REQUESTED', + CustomerPasswordResetVerified = 'CUSTOMER_PASSWORD_RESET_VERIFIED', + CustomerEmailUpdateRequested = 'CUSTOMER_EMAIL_UPDATE_REQUESTED', + CustomerEmailUpdateVerified = 'CUSTOMER_EMAIL_UPDATE_VERIFIED', + CustomerNote = 'CUSTOMER_NOTE', + OrderStateTransition = 'ORDER_STATE_TRANSITION', + OrderPaymentTransition = 'ORDER_PAYMENT_TRANSITION', + OrderFulfillment = 'ORDER_FULFILLMENT', + OrderCancellation = 'ORDER_CANCELLATION', + OrderRefundTransition = 'ORDER_REFUND_TRANSITION', + OrderFulfillmentTransition = 'ORDER_FULFILLMENT_TRANSITION', + OrderNote = 'ORDER_NOTE', + OrderCouponApplied = 'ORDER_COUPON_APPLIED', + OrderCouponRemoved = 'ORDER_COUPON_REMOVED', + OrderModified = 'ORDER_MODIFIED', +} + +export type HistoryEntryList = PaginatedList & { + __typename?: 'HistoryEntryList' + items: Array<HistoryEntry> + totalItems: Scalars['Int'] +} + +/** + * @description + * Languages in the form of a ISO 639-1 language code with optional + * region or script modifier (e.g. de_AT). The selection available is based + * on the [Unicode CLDR summary list](https://unicode-org.github.io/cldr-staging/charts/37/summary/root.html) + * and includes the major spoken languages of the world and any widely-used variants. + * + * @docsCategory common + */ +export enum LanguageCode { + /** Afrikaans */ + Af = 'af', + /** Akan */ + Ak = 'ak', + /** Albanian */ + Sq = 'sq', + /** Amharic */ + Am = 'am', + /** Arabic */ + Ar = 'ar', + /** Armenian */ + Hy = 'hy', + /** Assamese */ + As = 'as', + /** Azerbaijani */ + Az = 'az', + /** Bambara */ + Bm = 'bm', + /** Bangla */ + Bn = 'bn', + /** Basque */ + Eu = 'eu', + /** Belarusian */ + Be = 'be', + /** Bosnian */ + Bs = 'bs', + /** Breton */ + Br = 'br', + /** Bulgarian */ + Bg = 'bg', + /** Burmese */ + My = 'my', + /** Catalan */ + Ca = 'ca', + /** Chechen */ + Ce = 'ce', + /** Chinese */ + Zh = 'zh', + /** Simplified Chinese */ + ZhHans = 'zh_Hans', + /** Traditional Chinese */ + ZhHant = 'zh_Hant', + /** Church Slavic */ + Cu = 'cu', + /** Cornish */ + Kw = 'kw', + /** Corsican */ + Co = 'co', + /** Croatian */ + Hr = 'hr', + /** Czech */ + Cs = 'cs', + /** Danish */ + Da = 'da', + /** Dutch */ + Nl = 'nl', + /** Flemish */ + NlBe = 'nl_BE', + /** Dzongkha */ + Dz = 'dz', + /** English */ + En = 'en', + /** Australian English */ + EnAu = 'en_AU', + /** Canadian English */ + EnCa = 'en_CA', + /** British English */ + EnGb = 'en_GB', + /** American English */ + EnUs = 'en_US', + /** Esperanto */ + Eo = 'eo', + /** Estonian */ + Et = 'et', + /** Ewe */ + Ee = 'ee', + /** Faroese */ + Fo = 'fo', + /** Finnish */ + Fi = 'fi', + /** French */ + Fr = 'fr', + /** Canadian French */ + FrCa = 'fr_CA', + /** Swiss French */ + FrCh = 'fr_CH', + /** Fulah */ + Ff = 'ff', + /** Galician */ + Gl = 'gl', + /** Ganda */ + Lg = 'lg', + /** Georgian */ + Ka = 'ka', + /** German */ + De = 'de', + /** Austrian German */ + DeAt = 'de_AT', + /** Swiss High German */ + DeCh = 'de_CH', + /** Greek */ + El = 'el', + /** Gujarati */ + Gu = 'gu', + /** Haitian Creole */ + Ht = 'ht', + /** Hausa */ + Ha = 'ha', + /** Hebrew */ + He = 'he', + /** Hindi */ + Hi = 'hi', + /** Hungarian */ + Hu = 'hu', + /** Icelandic */ + Is = 'is', + /** Igbo */ + Ig = 'ig', + /** Indonesian */ + Id = 'id', + /** Interlingua */ + Ia = 'ia', + /** Irish */ + Ga = 'ga', + /** Italian */ + It = 'it', + /** Japanese */ + Ja = 'ja', + /** Javanese */ + Jv = 'jv', + /** Kalaallisut */ + Kl = 'kl', + /** Kannada */ + Kn = 'kn', + /** Kashmiri */ + Ks = 'ks', + /** Kazakh */ + Kk = 'kk', + /** Khmer */ + Km = 'km', + /** Kikuyu */ + Ki = 'ki', + /** Kinyarwanda */ + Rw = 'rw', + /** Korean */ + Ko = 'ko', + /** Kurdish */ + Ku = 'ku', + /** Kyrgyz */ + Ky = 'ky', + /** Lao */ + Lo = 'lo', + /** Latin */ + La = 'la', + /** Latvian */ + Lv = 'lv', + /** Lingala */ + Ln = 'ln', + /** Lithuanian */ + Lt = 'lt', + /** Luba-Katanga */ + Lu = 'lu', + /** Luxembourgish */ + Lb = 'lb', + /** Macedonian */ + Mk = 'mk', + /** Malagasy */ + Mg = 'mg', + /** Malay */ + Ms = 'ms', + /** Malayalam */ + Ml = 'ml', + /** Maltese */ + Mt = 'mt', + /** Manx */ + Gv = 'gv', + /** Maori */ + Mi = 'mi', + /** Marathi */ + Mr = 'mr', + /** Mongolian */ + Mn = 'mn', + /** Nepali */ + Ne = 'ne', + /** North Ndebele */ + Nd = 'nd', + /** Northern Sami */ + Se = 'se', + /** Norwegian Bokmål */ + Nb = 'nb', + /** Norwegian Nynorsk */ + Nn = 'nn', + /** Nyanja */ + Ny = 'ny', + /** Odia */ + Or = 'or', + /** Oromo */ + Om = 'om', + /** Ossetic */ + Os = 'os', + /** Pashto */ + Ps = 'ps', + /** Persian */ + Fa = 'fa', + /** Dari */ + FaAf = 'fa_AF', + /** Polish */ + Pl = 'pl', + /** Portuguese */ + Pt = 'pt', + /** Brazilian Portuguese */ + PtBr = 'pt_BR', + /** European Portuguese */ + PtPt = 'pt_PT', + /** Punjabi */ + Pa = 'pa', + /** Quechua */ + Qu = 'qu', + /** Romanian */ + Ro = 'ro', + /** Moldavian */ + RoMd = 'ro_MD', + /** Romansh */ + Rm = 'rm', + /** Rundi */ + Rn = 'rn', + /** Russian */ + Ru = 'ru', + /** Samoan */ + Sm = 'sm', + /** Sango */ + Sg = 'sg', + /** Sanskrit */ + Sa = 'sa', + /** Scottish Gaelic */ + Gd = 'gd', + /** Serbian */ + Sr = 'sr', + /** Shona */ + Sn = 'sn', + /** Sichuan Yi */ + Ii = 'ii', + /** Sindhi */ + Sd = 'sd', + /** Sinhala */ + Si = 'si', + /** Slovak */ + Sk = 'sk', + /** Slovenian */ + Sl = 'sl', + /** Somali */ + So = 'so', + /** Southern Sotho */ + St = 'st', + /** Spanish */ + Es = 'es', + /** European Spanish */ + EsEs = 'es_ES', + /** Mexican Spanish */ + EsMx = 'es_MX', + /** Sundanese */ + Su = 'su', + /** Swahili */ + Sw = 'sw', + /** Congo Swahili */ + SwCd = 'sw_CD', + /** Swedish */ + Sv = 'sv', + /** Tajik */ + Tg = 'tg', + /** Tamil */ + Ta = 'ta', + /** Tatar */ + Tt = 'tt', + /** Telugu */ + Te = 'te', + /** Thai */ + Th = 'th', + /** Tibetan */ + Bo = 'bo', + /** Tigrinya */ + Ti = 'ti', + /** Tongan */ + To = 'to', + /** Turkish */ + Tr = 'tr', + /** Turkmen */ + Tk = 'tk', + /** Ukrainian */ + Uk = 'uk', + /** Urdu */ + Ur = 'ur', + /** Uyghur */ + Ug = 'ug', + /** Uzbek */ + Uz = 'uz', + /** Vietnamese */ + Vi = 'vi', + /** Volapük */ + Vo = 'vo', + /** Welsh */ + Cy = 'cy', + /** Western Frisian */ + Fy = 'fy', + /** Wolof */ + Wo = 'wo', + /** Xhosa */ + Xh = 'xh', + /** Yiddish */ + Yi = 'yi', + /** Yoruba */ + Yo = 'yo', + /** Zulu */ + Zu = 'zu', +} + +export type Order = Node & { + __typename?: 'Order' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + /** + * The date & time that the Order was placed, i.e. the Customer + * completed the checkout and the Order is no longer "active" + */ + orderPlacedAt?: Maybe<Scalars['DateTime']> + /** A unique code for the Order */ + code: Scalars['String'] + state: Scalars['String'] + /** An order is active as long as the payment process has not been completed */ + active: Scalars['Boolean'] + customer?: Maybe<Customer> + shippingAddress?: Maybe<OrderAddress> + billingAddress?: Maybe<OrderAddress> + lines: Array<OrderLine> + /** + * Surcharges are arbitrary modifications to the Order total which are neither + * ProductVariants nor discounts resulting from applied Promotions. For example, + * one-off discounts based on customer interaction, or surcharges based on payment + * methods. + */ + surcharges: Array<Surcharge> + /** + * Order-level adjustments to the order total, such as discounts from promotions + * @deprecated Use `discounts` instead + */ + adjustments: Array<Adjustment> + discounts: Array<Adjustment> + /** An array of all coupon codes applied to the Order */ + couponCodes: Array<Scalars['String']> + /** Promotions applied to the order. Only gets populated after the payment process has completed. */ + promotions: Array<Promotion> + payments?: Maybe<Array<Payment>> + fulfillments?: Maybe<Array<Fulfillment>> + totalQuantity: Scalars['Int'] + /** + * The subTotal is the total of all OrderLines in the Order. This figure also includes any Order-level + * discounts which have been prorated (proportionally distributed) amongst the OrderItems. + * To get a total of all OrderLines which does not account for prorated discounts, use the + * sum of `OrderLine.discountedLinePrice` values. + */ + subTotal: Scalars['Int'] + /** Same as subTotal, but inclusive of tax */ + subTotalWithTax: Scalars['Int'] + currencyCode: CurrencyCode + shippingLines: Array<ShippingLine> + shipping: Scalars['Int'] + shippingWithTax: Scalars['Int'] + /** Equal to subTotal plus shipping */ + total: Scalars['Int'] + /** The final payable amount. Equal to subTotalWithTax plus shippingWithTax */ + totalWithTax: Scalars['Int'] + /** A summary of the taxes being applied to this Order */ + taxSummary: Array<OrderTaxSummary> + history: HistoryEntryList + customFields?: Maybe<Scalars['JSON']> +} + +export type OrderHistoryArgs = { + options?: Maybe<HistoryEntryListOptions> +} + +/** + * A summary of the taxes being applied to this order, grouped + * by taxRate. + */ +export type OrderTaxSummary = { + __typename?: 'OrderTaxSummary' + /** A description of this tax */ + description: Scalars['String'] + /** The taxRate as a percentage */ + taxRate: Scalars['Float'] + /** The total net price or OrderItems to which this taxRate applies */ + taxBase: Scalars['Int'] + /** The total tax being applied to the Order at this taxRate */ + taxTotal: Scalars['Int'] +} + +export type OrderAddress = { + __typename?: 'OrderAddress' + fullName?: Maybe<Scalars['String']> + company?: Maybe<Scalars['String']> + streetLine1?: Maybe<Scalars['String']> + streetLine2?: Maybe<Scalars['String']> + city?: Maybe<Scalars['String']> + province?: Maybe<Scalars['String']> + postalCode?: Maybe<Scalars['String']> + country?: Maybe<Scalars['String']> + countryCode?: Maybe<Scalars['String']> + phoneNumber?: Maybe<Scalars['String']> + customFields?: Maybe<Scalars['JSON']> +} + +export type OrderList = PaginatedList & { + __typename?: 'OrderList' + items: Array<Order> + 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 = { + __typename?: 'ShippingLine' + shippingMethod: ShippingMethod + price: Scalars['Int'] + priceWithTax: Scalars['Int'] + discountedPrice: Scalars['Int'] + discountedPriceWithTax: Scalars['Int'] + discounts: Array<Adjustment> +} + +export type OrderItem = Node & { + __typename?: 'OrderItem' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + cancelled: Scalars['Boolean'] + /** The price of a single unit, excluding tax and discounts */ + unitPrice: Scalars['Int'] + /** The price of a single unit, including tax but excluding discounts */ + unitPriceWithTax: Scalars['Int'] + /** + * The price of a single unit including discounts, excluding tax. + * + * If Order-level discounts have been applied, this will not be the + * actual taxable unit price (see `proratedUnitPrice`), but is generally the + * correct price to display to customers to avoid confusion + * about the internal handling of distributed Order-level discounts. + */ + discountedUnitPrice: Scalars['Int'] + /** The price of a single unit including discounts and tax */ + discountedUnitPriceWithTax: Scalars['Int'] + /** + * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed) + * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax + * and refund calculations. + */ + proratedUnitPrice: Scalars['Int'] + /** The proratedUnitPrice including tax */ + proratedUnitPriceWithTax: Scalars['Int'] + unitTax: Scalars['Int'] + /** @deprecated `unitPrice` is now always without tax */ + unitPriceIncludesTax: Scalars['Boolean'] + taxRate: Scalars['Float'] + adjustments: Array<Adjustment> + taxLines: Array<TaxLine> + fulfillment?: Maybe<Fulfillment> + refundId?: Maybe<Scalars['ID']> +} + +export type OrderLine = Node & { + __typename?: 'OrderLine' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + productVariant: ProductVariant + featuredAsset?: Maybe<Asset> + /** The price of a single unit, excluding tax and discounts */ + unitPrice: Scalars['Int'] + /** The price of a single unit, including tax but excluding discounts */ + unitPriceWithTax: Scalars['Int'] + /** + * The price of a single unit including discounts, excluding tax. + * + * If Order-level discounts have been applied, this will not be the + * actual taxable unit price (see `proratedUnitPrice`), but is generally the + * correct price to display to customers to avoid confusion + * about the internal handling of distributed Order-level discounts. + */ + discountedUnitPrice: Scalars['Int'] + /** The price of a single unit including discounts and tax */ + discountedUnitPriceWithTax: Scalars['Int'] + /** + * The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed) + * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax + * and refund calculations. + */ + proratedUnitPrice: Scalars['Int'] + /** The proratedUnitPrice including tax */ + proratedUnitPriceWithTax: Scalars['Int'] + quantity: Scalars['Int'] + items: Array<OrderItem> + /** @deprecated Use `linePriceWithTax` instead */ + totalPrice: Scalars['Int'] + taxRate: Scalars['Float'] + /** The total price of the line excluding tax and discounts. */ + linePrice: Scalars['Int'] + /** The total price of the line including tax bit excluding discounts. */ + linePriceWithTax: Scalars['Int'] + /** The price of the line including discounts, excluding tax */ + discountedLinePrice: Scalars['Int'] + /** The price of the line including discounts and tax */ + discountedLinePriceWithTax: Scalars['Int'] + /** + * The actual line price, taking into account both item discounts _and_ prorated (proportially-distributed) + * Order-level discounts. This value is the true economic value of the OrderLine, and is used in tax + * and refund calculations. + */ + proratedLinePrice: Scalars['Int'] + /** The proratedLinePrice including tax */ + proratedLinePriceWithTax: Scalars['Int'] + /** The total tax on this line */ + lineTax: Scalars['Int'] + /** @deprecated Use `discounts` instead */ + adjustments: Array<Adjustment> + discounts: Array<Adjustment> + taxLines: Array<TaxLine> + order: Order + customFields?: Maybe<Scalars['JSON']> +} + +export type Payment = Node & { + __typename?: 'Payment' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + method: Scalars['String'] + amount: Scalars['Int'] + state: Scalars['String'] + transactionId?: Maybe<Scalars['String']> + errorMessage?: Maybe<Scalars['String']> + refunds: Array<Refund> + metadata?: Maybe<Scalars['JSON']> +} + +export type Refund = Node & { + __typename?: 'Refund' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + items: Scalars['Int'] + shipping: Scalars['Int'] + adjustment: Scalars['Int'] + total: Scalars['Int'] + method?: Maybe<Scalars['String']> + state: Scalars['String'] + transactionId?: Maybe<Scalars['String']> + reason?: Maybe<Scalars['String']> + orderItems: Array<OrderItem> + paymentId: Scalars['ID'] + metadata?: Maybe<Scalars['JSON']> +} + +export type Fulfillment = Node & { + __typename?: 'Fulfillment' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + orderItems: Array<OrderItem> + state: Scalars['String'] + method: Scalars['String'] + trackingCode?: Maybe<Scalars['String']> + customFields?: Maybe<Scalars['JSON']> +} + +export type Surcharge = Node & { + __typename?: 'Surcharge' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + description: Scalars['String'] + sku?: Maybe<Scalars['String']> + taxLines: Array<TaxLine> + price: Scalars['Int'] + priceWithTax: Scalars['Int'] + taxRate: Scalars['Float'] +} + +export type ProductOptionGroup = Node & { + __typename?: 'ProductOptionGroup' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + code: Scalars['String'] + name: Scalars['String'] + options: Array<ProductOption> + translations: Array<ProductOptionGroupTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type ProductOptionGroupTranslation = { + __typename?: 'ProductOptionGroupTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type ProductOption = Node & { + __typename?: 'ProductOption' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + code: Scalars['String'] + name: Scalars['String'] + groupId: Scalars['ID'] + group: ProductOptionGroup + translations: Array<ProductOptionTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type ProductOptionTranslation = { + __typename?: 'ProductOptionTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type SearchReindexResponse = { + __typename?: 'SearchReindexResponse' + success: Scalars['Boolean'] +} + +export type SearchResponse = { + __typename?: 'SearchResponse' + items: Array<SearchResult> + totalItems: Scalars['Int'] + facetValues: Array<FacetValueResult> +} + +/** + * Which FacetValues are present in the products returned + * by the search, and in what quantity. + */ +export type FacetValueResult = { + __typename?: 'FacetValueResult' + facetValue: FacetValue + count: Scalars['Int'] +} + +export type SearchResultAsset = { + __typename?: 'SearchResultAsset' + id: Scalars['ID'] + preview: Scalars['String'] + focalPoint?: Maybe<Coordinate> +} + +export type SearchResult = { + __typename?: 'SearchResult' + sku: Scalars['String'] + slug: Scalars['String'] + productId: Scalars['ID'] + productName: Scalars['String'] + /** @deprecated Use `productAsset.preview` instead */ + productPreview: Scalars['String'] + productAsset?: Maybe<SearchResultAsset> + productVariantId: Scalars['ID'] + productVariantName: Scalars['String'] + /** @deprecated Use `productVariantAsset.preview` instead */ + productVariantPreview: Scalars['String'] + productVariantAsset?: Maybe<SearchResultAsset> + price: SearchResultPrice + priceWithTax: SearchResultPrice + currencyCode: CurrencyCode + description: Scalars['String'] + facetIds: Array<Scalars['ID']> + facetValueIds: Array<Scalars['ID']> + /** An array of ids of the Collections in which this result appears */ + collectionIds: Array<Scalars['ID']> + /** A relevence score for the result. Differs between database implementations */ + score: Scalars['Float'] +} + +/** The price of a search result product, either as a range or as a single price */ +export type SearchResultPrice = PriceRange | SinglePrice + +/** The price value where the result has a single price */ +export type SinglePrice = { + __typename?: 'SinglePrice' + value: Scalars['Int'] +} + +/** The price range where the result has more than one price */ +export type PriceRange = { + __typename?: 'PriceRange' + min: Scalars['Int'] + max: Scalars['Int'] +} + +export type Product = Node & { + __typename?: 'Product' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] + slug: Scalars['String'] + description: Scalars['String'] + featuredAsset?: Maybe<Asset> + assets: Array<Asset> + variants: Array<ProductVariant> + optionGroups: Array<ProductOptionGroup> + facetValues: Array<FacetValue> + translations: Array<ProductTranslation> + collections: Array<Collection> + customFields?: Maybe<Scalars['JSON']> +} + +export type ProductTranslation = { + __typename?: 'ProductTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] + slug: Scalars['String'] + description: Scalars['String'] +} + +export type ProductList = PaginatedList & { + __typename?: 'ProductList' + items: Array<Product> + totalItems: Scalars['Int'] +} + +export type ProductVariant = Node & { + __typename?: 'ProductVariant' + id: Scalars['ID'] + product: Product + productId: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + sku: Scalars['String'] + name: Scalars['String'] + featuredAsset?: Maybe<Asset> + assets: Array<Asset> + price: Scalars['Int'] + currencyCode: CurrencyCode + /** @deprecated price now always excludes tax */ + priceIncludesTax: Scalars['Boolean'] + priceWithTax: Scalars['Int'] + taxRateApplied: TaxRate + taxCategory: TaxCategory + options: Array<ProductOption> + facetValues: Array<FacetValue> + translations: Array<ProductVariantTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type ProductVariantTranslation = { + __typename?: 'ProductVariantTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] +} + +export type Promotion = Node & { + __typename?: 'Promotion' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + startsAt?: Maybe<Scalars['DateTime']> + endsAt?: Maybe<Scalars['DateTime']> + couponCode?: Maybe<Scalars['String']> + perCustomerUsageLimit?: Maybe<Scalars['Int']> + name: Scalars['String'] + enabled: Scalars['Boolean'] + conditions: Array<ConfigurableOperation> + actions: Array<ConfigurableOperation> +} + +export type PromotionList = PaginatedList & { + __typename?: 'PromotionList' + items: Array<Promotion> + totalItems: Scalars['Int'] +} + +export type Role = Node & { + __typename?: 'Role' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + code: Scalars['String'] + description: Scalars['String'] + permissions: Array<Permission> + channels: Array<Channel> +} + +export type RoleList = PaginatedList & { + __typename?: 'RoleList' + items: Array<Role> + totalItems: Scalars['Int'] +} + +export type ShippingMethod = Node & { + __typename?: 'ShippingMethod' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + code: Scalars['String'] + name: Scalars['String'] + description: Scalars['String'] + fulfillmentHandlerCode: Scalars['String'] + checker: ConfigurableOperation + calculator: ConfigurableOperation + translations: Array<ShippingMethodTranslation> + customFields?: Maybe<Scalars['JSON']> +} + +export type ShippingMethodTranslation = { + __typename?: 'ShippingMethodTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + name: Scalars['String'] + description: Scalars['String'] +} + +export type ShippingMethodList = PaginatedList & { + __typename?: 'ShippingMethodList' + items: Array<ShippingMethod> + totalItems: Scalars['Int'] +} + +export type Tag = Node & { + __typename?: 'Tag' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + value: Scalars['String'] +} + +export type TagList = PaginatedList & { + __typename?: 'TagList' + items: Array<Tag> + totalItems: Scalars['Int'] +} + +export type TaxCategory = Node & { + __typename?: 'TaxCategory' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + name: Scalars['String'] +} + +export type TaxRate = Node & { + __typename?: 'TaxRate' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + name: Scalars['String'] + enabled: Scalars['Boolean'] + value: Scalars['Float'] + category: TaxCategory + zone: Zone + customerGroup?: Maybe<CustomerGroup> +} + +export type TaxRateList = PaginatedList & { + __typename?: 'TaxRateList' + items: Array<TaxRate> + totalItems: Scalars['Int'] +} + +export type User = Node & { + __typename?: 'User' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + identifier: Scalars['String'] + verified: Scalars['Boolean'] + roles: Array<Role> + lastLogin?: Maybe<Scalars['DateTime']> + authenticationMethods: Array<AuthenticationMethod> + customFields?: Maybe<Scalars['JSON']> +} + +export type AuthenticationMethod = Node & { + __typename?: 'AuthenticationMethod' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + strategy: Scalars['String'] +} + +export type Zone = Node & { + __typename?: 'Zone' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + name: Scalars['String'] + members: Array<Country> +} + +/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */ +export type OrderModificationError = ErrorResult & { + __typename?: 'OrderModificationError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Returned when attempting to set a ShippingMethod for which the order is not eligible */ +export type IneligibleShippingMethodError = ErrorResult & { + __typename?: 'IneligibleShippingMethodError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Returned when attempting to add a Payment to an Order that is not in the `ArrangingPayment` state. */ +export type OrderPaymentStateError = ErrorResult & { + __typename?: 'OrderPaymentStateError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Returned when a Payment fails due to an error. */ +export type PaymentFailedError = ErrorResult & { + __typename?: 'PaymentFailedError' + errorCode: ErrorCode + message: Scalars['String'] + paymentErrorMessage: Scalars['String'] +} + +/** Returned when a Payment is declined by the payment provider. */ +export type PaymentDeclinedError = ErrorResult & { + __typename?: 'PaymentDeclinedError' + errorCode: ErrorCode + message: Scalars['String'] + paymentErrorMessage: Scalars['String'] +} + +/** Returned if the provided coupon code is invalid */ +export type CouponCodeInvalidError = ErrorResult & { + __typename?: 'CouponCodeInvalidError' + errorCode: ErrorCode + message: Scalars['String'] + couponCode: Scalars['String'] +} + +/** Returned if the provided coupon code is invalid */ +export type CouponCodeExpiredError = ErrorResult & { + __typename?: 'CouponCodeExpiredError' + errorCode: ErrorCode + message: Scalars['String'] + couponCode: Scalars['String'] +} + +/** Returned if the provided coupon code is invalid */ +export type CouponCodeLimitError = ErrorResult & { + __typename?: 'CouponCodeLimitError' + errorCode: ErrorCode + message: Scalars['String'] + couponCode: Scalars['String'] + limit: Scalars['Int'] +} + +/** Retured when attemting to set the Customer for an Order when already logged in. */ +export type AlreadyLoggedInError = ErrorResult & { + __typename?: 'AlreadyLoggedInError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Retured when attemting to register or verify a customer account without a password, when one is required. */ +export type MissingPasswordError = ErrorResult & { + __typename?: 'MissingPasswordError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** Retured when attemting to verify a customer account with a password, when a password has already been set. */ +export type PasswordAlreadySetError = ErrorResult & { + __typename?: 'PasswordAlreadySetError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Retured if the verification token (used to verify a Customer's email address) is either + * invalid or does not match any expected tokens. + */ +export type VerificationTokenInvalidError = ErrorResult & { + __typename?: 'VerificationTokenInvalidError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Returned if the verification token (used to verify a Customer's email address) is valid, but has + * expired according to the `verificationTokenDuration` setting in the AuthOptions. + */ +export type VerificationTokenExpiredError = ErrorResult & { + __typename?: 'VerificationTokenExpiredError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Retured if the token used to change a Customer's email address is either + * invalid or does not match any expected tokens. + */ +export type IdentifierChangeTokenInvalidError = ErrorResult & { + __typename?: 'IdentifierChangeTokenInvalidError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Retured if the token used to change a Customer's email address is valid, but has + * expired according to the `verificationTokenDuration` setting in the AuthOptions. + */ +export type IdentifierChangeTokenExpiredError = ErrorResult & { + __typename?: 'IdentifierChangeTokenExpiredError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Retured if the token used to reset a Customer's password is either + * invalid or does not match any expected tokens. + */ +export type PasswordResetTokenInvalidError = ErrorResult & { + __typename?: 'PasswordResetTokenInvalidError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Retured if the token used to reset a Customer's password is valid, but has + * expired according to the `verificationTokenDuration` setting in the AuthOptions. + */ +export type PasswordResetTokenExpiredError = ErrorResult & { + __typename?: 'PasswordResetTokenExpiredError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Returned if `authOptions.requireVerification` is set to `true` (which is the default) + * and an unverified user attempts to authenticate. + */ +export type NotVerifiedError = ErrorResult & { + __typename?: 'NotVerifiedError' + errorCode: ErrorCode + message: Scalars['String'] +} + +/** + * Returned when invoking a mutation which depends on there being an active Order on the + * current session. + */ +export type NoActiveOrderError = ErrorResult & { + __typename?: 'NoActiveOrderError' + errorCode: ErrorCode + message: Scalars['String'] +} + +export type RegisterCustomerInput = { + emailAddress: Scalars['String'] + title?: Maybe<Scalars['String']> + firstName?: Maybe<Scalars['String']> + lastName?: Maybe<Scalars['String']> + phoneNumber?: Maybe<Scalars['String']> + password?: Maybe<Scalars['String']> +} + +export type UpdateCustomerInput = { + title?: Maybe<Scalars['String']> + firstName?: Maybe<Scalars['String']> + lastName?: Maybe<Scalars['String']> + phoneNumber?: Maybe<Scalars['String']> + customFields?: Maybe<Scalars['JSON']> +} + +/** Passed as input to the `addPaymentToOrder` mutation. */ +export type PaymentInput = { + /** This field should correspond to the `code` property of a PaymentMethodHandler. */ + method: Scalars['String'] + /** + * This field should contain arbitrary data passed to the specified PaymentMethodHandler's `createPayment()` method + * as the "metadata" argument. For example, it could contain an ID for the payment and other + * data generated by the payment provider. + */ + metadata: Scalars['JSON'] +} + +export type UpdateOrderItemsResult = + | Order + | OrderModificationError + | OrderLimitError + | NegativeQuantityError + | InsufficientStockError + +export type RemoveOrderItemsResult = Order | OrderModificationError + +export type SetOrderShippingMethodResult = + | Order + | OrderModificationError + | IneligibleShippingMethodError + | NoActiveOrderError + +export type ApplyCouponCodeResult = + | Order + | CouponCodeExpiredError + | CouponCodeInvalidError + | CouponCodeLimitError + +export type AddPaymentToOrderResult = + | Order + | OrderPaymentStateError + | PaymentFailedError + | PaymentDeclinedError + | OrderStateTransitionError + | NoActiveOrderError + +export type TransitionOrderToStateResult = Order | OrderStateTransitionError + +export type SetCustomerForOrderResult = + | Order + | AlreadyLoggedInError + | EmailAddressConflictError + | NoActiveOrderError + +export type RegisterCustomerAccountResult = + | Success + | MissingPasswordError + | NativeAuthStrategyError + +export type RefreshCustomerVerificationResult = + | Success + | NativeAuthStrategyError + +export type VerifyCustomerAccountResult = + | CurrentUser + | VerificationTokenInvalidError + | VerificationTokenExpiredError + | MissingPasswordError + | PasswordAlreadySetError + | NativeAuthStrategyError + +export type UpdateCustomerPasswordResult = + | Success + | InvalidCredentialsError + | NativeAuthStrategyError + +export type RequestUpdateCustomerEmailAddressResult = + | Success + | InvalidCredentialsError + | EmailAddressConflictError + | NativeAuthStrategyError + +export type UpdateCustomerEmailAddressResult = + | Success + | IdentifierChangeTokenInvalidError + | IdentifierChangeTokenExpiredError + | NativeAuthStrategyError + +export type RequestPasswordResetResult = Success | NativeAuthStrategyError + +export type ResetPasswordResult = + | CurrentUser + | PasswordResetTokenInvalidError + | PasswordResetTokenExpiredError + | NativeAuthStrategyError + +export type NativeAuthenticationResult = + | CurrentUser + | InvalidCredentialsError + | NotVerifiedError + | NativeAuthStrategyError + +export type AuthenticationResult = + | CurrentUser + | InvalidCredentialsError + | NotVerifiedError + +export type ActiveOrderResult = Order | NoActiveOrderError + +export type CollectionListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<CollectionSortParameter> + filter?: Maybe<CollectionFilterParameter> +} + +export type ProductListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<ProductSortParameter> + filter?: Maybe<ProductFilterParameter> +} + +export type ProductVariantListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<ProductVariantSortParameter> + filter?: Maybe<ProductVariantFilterParameter> +} + +export type CustomerListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<CustomerSortParameter> + filter?: Maybe<CustomerFilterParameter> +} + +export type OrderListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<OrderSortParameter> + filter?: Maybe<OrderFilterParameter> +} + +export type HistoryEntryListOptions = { + skip?: Maybe<Scalars['Int']> + take?: Maybe<Scalars['Int']> + sort?: Maybe<HistoryEntrySortParameter> + filter?: Maybe<HistoryEntryFilterParameter> +} + +export type CollectionFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + languageCode?: Maybe<StringOperators> + name?: Maybe<StringOperators> + slug?: Maybe<StringOperators> + position?: Maybe<NumberOperators> + description?: Maybe<StringOperators> +} + +export type CollectionSortParameter = { + id?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> + name?: Maybe<SortOrder> + slug?: Maybe<SortOrder> + position?: Maybe<SortOrder> + description?: Maybe<SortOrder> +} + +export type ProductFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + languageCode?: Maybe<StringOperators> + name?: Maybe<StringOperators> + slug?: Maybe<StringOperators> + description?: Maybe<StringOperators> +} + +export type ProductSortParameter = { + id?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> + name?: Maybe<SortOrder> + slug?: Maybe<SortOrder> + description?: Maybe<SortOrder> +} + +export type ProductVariantFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + languageCode?: Maybe<StringOperators> + sku?: Maybe<StringOperators> + name?: Maybe<StringOperators> + price?: Maybe<NumberOperators> + currencyCode?: Maybe<StringOperators> + priceIncludesTax?: Maybe<BooleanOperators> + priceWithTax?: Maybe<NumberOperators> +} + +export type ProductVariantSortParameter = { + id?: Maybe<SortOrder> + productId?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> + sku?: Maybe<SortOrder> + name?: Maybe<SortOrder> + price?: Maybe<SortOrder> + priceWithTax?: Maybe<SortOrder> +} + +export type CustomerFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + title?: Maybe<StringOperators> + firstName?: Maybe<StringOperators> + lastName?: Maybe<StringOperators> + phoneNumber?: Maybe<StringOperators> + emailAddress?: Maybe<StringOperators> +} + +export type CustomerSortParameter = { + id?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> + title?: Maybe<SortOrder> + firstName?: Maybe<SortOrder> + lastName?: Maybe<SortOrder> + phoneNumber?: Maybe<SortOrder> + emailAddress?: Maybe<SortOrder> +} + +export type OrderFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + orderPlacedAt?: Maybe<DateOperators> + code?: Maybe<StringOperators> + state?: Maybe<StringOperators> + active?: Maybe<BooleanOperators> + totalQuantity?: Maybe<NumberOperators> + subTotal?: Maybe<NumberOperators> + subTotalWithTax?: Maybe<NumberOperators> + currencyCode?: Maybe<StringOperators> + shipping?: Maybe<NumberOperators> + shippingWithTax?: Maybe<NumberOperators> + total?: Maybe<NumberOperators> + totalWithTax?: Maybe<NumberOperators> +} + +export type OrderSortParameter = { + id?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> + orderPlacedAt?: Maybe<SortOrder> + code?: Maybe<SortOrder> + state?: Maybe<SortOrder> + totalQuantity?: Maybe<SortOrder> + subTotal?: Maybe<SortOrder> + subTotalWithTax?: Maybe<SortOrder> + shipping?: Maybe<SortOrder> + shippingWithTax?: Maybe<SortOrder> + total?: Maybe<SortOrder> + totalWithTax?: Maybe<SortOrder> +} + +export type HistoryEntryFilterParameter = { + createdAt?: Maybe<DateOperators> + updatedAt?: Maybe<DateOperators> + type?: Maybe<StringOperators> +} + +export type HistoryEntrySortParameter = { + id?: Maybe<SortOrder> + createdAt?: Maybe<SortOrder> + updatedAt?: Maybe<SortOrder> +} + +export type UpdateOrderInput = { + customFields?: Maybe<Scalars['JSON']> +} + +export type AuthenticationInput = { + native?: Maybe<NativeAuthInput> +} + +export type NativeAuthInput = { + username: Scalars['String'] + password: Scalars['String'] +} diff --git a/framework/vendure/schema.graphql b/framework/vendure/schema.graphql new file mode 100644 index 000000000..6d02bb154 --- /dev/null +++ b/framework/vendure/schema.graphql @@ -0,0 +1,3811 @@ +type Query { + """ + The active Channel + """ + activeChannel: Channel! + + """ + The active Customer + """ + activeCustomer: Customer + + """ + The active Order. Will be `null` until an Order is created via `addItemToOrder`. Once an Order reaches the + state of `PaymentApproved` or `PaymentSettled`, then that Order is no longer considered "active" and this + query will once again return `null`. + """ + activeOrder: Order + + """ + An array of supported Countries + """ + availableCountries: [Country!]! + + """ + A list of Collections available to the shop + """ + collections(options: CollectionListOptions): CollectionList! + + """ + Returns a Collection either by its id or slug. If neither 'id' nor 'slug' is speicified, an error will result. + """ + collection(id: ID, slug: String): Collection + + """ + Returns a list of eligible shipping methods based on the current active Order + """ + eligibleShippingMethods: [ShippingMethodQuote!]! + + """ + Returns information about the current authenticated User + """ + me: CurrentUser + + """ + Returns the possible next states that the activeOrder can transition to + """ + nextOrderStates: [String!]! + + """ + Returns an Order based on the id. Note that in the Shop API, only orders belonging to the + currently-authenticated User may be queried. + """ + order(id: ID!): Order + + """ + Returns an Order based on the order `code`. For guest Orders (i.e. Orders placed by non-authenticated Customers) + this query will only return the Order within 2 hours of the Order being placed. This allows an Order confirmation + screen to be shown immediately after completion of a guest checkout, yet prevents security risks of allowing + general anonymous access to Order data. + """ + orderByCode(code: String!): Order + + """ + Get a Product either by id or slug. If neither 'id' nor 'slug' is speicified, an error will result. + """ + product(id: ID, slug: String): Product + + """ + Get a list of Products + """ + products(options: ProductListOptions): ProductList! + + """ + Search Products based on the criteria set by the `SearchInput` + """ + search(input: SearchInput!): SearchResponse! +} + +type Mutation { + """ + Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. + """ + addItemToOrder(productVariantId: ID!, quantity: Int!): UpdateOrderItemsResult! + + """ + Remove an OrderLine from the Order + """ + removeOrderLine(orderLineId: ID!): RemoveOrderItemsResult! + + """ + Remove all OrderLine from the Order + """ + removeAllOrderLines: RemoveOrderItemsResult! + + """ + Adjusts an OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. + """ + adjustOrderLine(orderLineId: ID!, quantity: Int!): UpdateOrderItemsResult! + + """ + Applies the given coupon code to the active Order + """ + applyCouponCode(couponCode: String!): ApplyCouponCodeResult! + + """ + Removes the given coupon code from the active Order + """ + removeCouponCode(couponCode: String!): Order + + """ + Transitions an Order to a new state. Valid next states can be found by querying `nextOrderStates` + """ + transitionOrderToState(state: String!): TransitionOrderToStateResult + + """ + Sets the shipping address for this order + """ + setOrderShippingAddress(input: CreateAddressInput!): ActiveOrderResult! + + """ + Sets the billing address for this order + """ + setOrderBillingAddress(input: CreateAddressInput!): ActiveOrderResult! + + """ + Allows any custom fields to be set for the active order + """ + setOrderCustomFields(input: UpdateOrderInput!): ActiveOrderResult! + + """ + Sets the shipping method by id, which can be obtained with the `eligibleShippingMethods` query + """ + setOrderShippingMethod(shippingMethodId: ID!): SetOrderShippingMethodResult! + + """ + Add a Payment to the Order + """ + addPaymentToOrder(input: PaymentInput!): AddPaymentToOrderResult! + + """ + Set the Customer for the Order. Required only if the Customer is not currently logged in + """ + setCustomerForOrder(input: CreateCustomerInput!): SetCustomerForOrderResult! + + """ + Authenticates the user using the native authentication strategy. This mutation is an alias for `authenticate({ native: { ... }})` + """ + login( + username: String! + password: String! + rememberMe: Boolean + ): NativeAuthenticationResult! + + """ + Authenticates the user using a named authentication strategy + """ + authenticate( + input: AuthenticationInput! + rememberMe: Boolean + ): AuthenticationResult! + + """ + End the current authenticated session + """ + logout: Success! + + """ + Register a Customer account with the given credentials. There are three possible registration flows: + + _If `authOptions.requireVerification` is set to `true`:_ + + 1. **The Customer is registered _with_ a password**. A verificationToken will be created (and typically emailed to the Customer). That + verificationToken would then be passed to the `verifyCustomerAccount` mutation _without_ a password. The Customer is then + verified and authenticated in one step. + 2. **The Customer is registered _without_ a password**. A verificationToken will be created (and typically emailed to the Customer). That + verificationToken would then be passed to the `verifyCustomerAccount` mutation _with_ the chosed password of the Customer. The Customer is then + verified and authenticated in one step. + + _If `authOptions.requireVerification` is set to `false`:_ + + 3. The Customer _must_ be registered _with_ a password. No further action is needed - the Customer is able to authenticate immediately. + """ + registerCustomerAccount( + input: RegisterCustomerInput! + ): RegisterCustomerAccountResult! + + """ + Regenerate and send a verification token for a new Customer registration. Only applicable if `authOptions.requireVerification` is set to true. + """ + refreshCustomerVerification( + emailAddress: String! + ): RefreshCustomerVerificationResult! + + """ + Update an existing Customer + """ + updateCustomer(input: UpdateCustomerInput!): Customer! + + """ + Create a new Customer Address + """ + createCustomerAddress(input: CreateAddressInput!): Address! + + """ + Update an existing Address + """ + updateCustomerAddress(input: UpdateAddressInput!): Address! + + """ + Delete an existing Address + """ + deleteCustomerAddress(id: ID!): Success! + + """ + Verify a Customer email address with the token sent to that address. Only applicable if `authOptions.requireVerification` is set to true. + + If the Customer was not registered with a password in the `registerCustomerAccount` mutation, the a password _must_ be + provided here. + """ + verifyCustomerAccount( + token: String! + password: String + ): VerifyCustomerAccountResult! + + """ + Update the password of the active Customer + """ + updateCustomerPassword( + currentPassword: String! + newPassword: String! + ): UpdateCustomerPasswordResult! + + """ + Request to update the emailAddress of the active Customer. If `authOptions.requireVerification` is enabled + (as is the default), then the `identifierChangeToken` will be assigned to the current User and + a IdentifierChangeRequestEvent will be raised. This can then be used e.g. by the EmailPlugin to email + that verification token to the Customer, which is then used to verify the change of email address. + """ + requestUpdateCustomerEmailAddress( + password: String! + newEmailAddress: String! + ): RequestUpdateCustomerEmailAddressResult! + + """ + Confirm the update of the emailAddress with the provided token, which has been generated by the + `requestUpdateCustomerEmailAddress` mutation. + """ + updateCustomerEmailAddress(token: String!): UpdateCustomerEmailAddressResult! + + """ + Requests a password reset email to be sent + """ + requestPasswordReset(emailAddress: String!): RequestPasswordResetResult + + """ + Resets a Customer's password based on the provided token + """ + resetPassword(token: String!, password: String!): ResetPasswordResult! +} + +type Address implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + fullName: String + company: String + streetLine1: String! + streetLine2: String + city: String + province: String + postalCode: String + country: Country! + phoneNumber: String + defaultShippingAddress: Boolean + defaultBillingAddress: Boolean + customFields: JSON +} + +type Asset implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! + type: AssetType! + fileSize: Int! + mimeType: String! + width: Int! + height: Int! + source: String! + preview: String! + focalPoint: Coordinate +} + +type Coordinate { + x: Float! + y: Float! +} + +type AssetList implements PaginatedList { + items: [Asset!]! + totalItems: Int! +} + +enum AssetType { + IMAGE + VIDEO + BINARY +} + +type CurrentUser { + id: ID! + identifier: String! + channels: [CurrentUserChannel!]! +} + +type CurrentUserChannel { + id: ID! + token: String! + code: String! + permissions: [Permission!]! +} + +type Channel implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + code: String! + token: String! + defaultTaxZone: Zone + defaultShippingZone: Zone + defaultLanguageCode: LanguageCode! + currencyCode: CurrencyCode! + pricesIncludeTax: Boolean! +} + +type Collection implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode + name: String! + slug: String! + breadcrumbs: [CollectionBreadcrumb!]! + position: Int! + description: String! + featuredAsset: Asset + assets: [Asset!]! + parent: Collection + children: [Collection!] + filters: [ConfigurableOperation!]! + translations: [CollectionTranslation!]! + productVariants(options: ProductVariantListOptions): ProductVariantList! + customFields: JSON +} + +type CollectionBreadcrumb { + id: ID! + name: String! + slug: String! +} + +type CollectionTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! + slug: String! + description: String! +} + +type CollectionList implements PaginatedList { + items: [Collection!]! + totalItems: Int! +} + +type ProductVariantList implements PaginatedList { + items: [ProductVariant!]! + totalItems: Int! +} + +enum GlobalFlag { + TRUE + FALSE + INHERIT +} + +enum AdjustmentType { + PROMOTION + DISTRIBUTED_ORDER_PROMOTION +} + +enum DeletionResult { + """ + The entity was successfully deleted + """ + DELETED + + """ + Deletion did not take place, reason given in message + """ + NOT_DELETED +} + +""" +@description +Permissions for administrators and customers. Used to control access to +GraphQL resolvers via the {@link Allow} decorator. + +@docsCategory common +""" +enum Permission { + """ + Authenticated means simply that the user is logged in + """ + Authenticated + + """ + SuperAdmin has unrestricted access to all operations + """ + SuperAdmin + + """ + Owner means the user owns this entity, e.g. a Customer's own Order + """ + Owner + + """ + Public means any unauthenticated user may perform the operation + """ + Public + + """ + Grants permission to create Catalog + """ + CreateCatalog + + """ + Grants permission to read Catalog + """ + ReadCatalog + + """ + Grants permission to update Catalog + """ + UpdateCatalog + + """ + Grants permission to delete Catalog + """ + DeleteCatalog + + """ + Grants permission to create Customer + """ + CreateCustomer + + """ + Grants permission to read Customer + """ + ReadCustomer + + """ + Grants permission to update Customer + """ + UpdateCustomer + + """ + Grants permission to delete Customer + """ + DeleteCustomer + + """ + Grants permission to create Administrator + """ + CreateAdministrator + + """ + Grants permission to read Administrator + """ + ReadAdministrator + + """ + Grants permission to update Administrator + """ + UpdateAdministrator + + """ + Grants permission to delete Administrator + """ + DeleteAdministrator + + """ + Grants permission to create Order + """ + CreateOrder + + """ + Grants permission to read Order + """ + ReadOrder + + """ + Grants permission to update Order + """ + UpdateOrder + + """ + Grants permission to delete Order + """ + DeleteOrder + + """ + Grants permission to create Promotion + """ + CreatePromotion + + """ + Grants permission to read Promotion + """ + ReadPromotion + + """ + Grants permission to update Promotion + """ + UpdatePromotion + + """ + Grants permission to delete Promotion + """ + DeletePromotion + + """ + Grants permission to create Settings + """ + CreateSettings + + """ + Grants permission to read Settings + """ + ReadSettings + + """ + Grants permission to update Settings + """ + UpdateSettings + + """ + Grants permission to delete Settings + """ + DeleteSettings +} + +enum SortOrder { + ASC + DESC +} + +enum ErrorCode { + UNKNOWN_ERROR + NATIVE_AUTH_STRATEGY_ERROR + INVALID_CREDENTIALS_ERROR + ORDER_STATE_TRANSITION_ERROR + EMAIL_ADDRESS_CONFLICT_ERROR + ORDER_LIMIT_ERROR + NEGATIVE_QUANTITY_ERROR + INSUFFICIENT_STOCK_ERROR + ORDER_MODIFICATION_ERROR + INELIGIBLE_SHIPPING_METHOD_ERROR + ORDER_PAYMENT_STATE_ERROR + PAYMENT_FAILED_ERROR + PAYMENT_DECLINED_ERROR + COUPON_CODE_INVALID_ERROR + COUPON_CODE_EXPIRED_ERROR + COUPON_CODE_LIMIT_ERROR + ALREADY_LOGGED_IN_ERROR + MISSING_PASSWORD_ERROR + PASSWORD_ALREADY_SET_ERROR + VERIFICATION_TOKEN_INVALID_ERROR + VERIFICATION_TOKEN_EXPIRED_ERROR + IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR + IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR + PASSWORD_RESET_TOKEN_INVALID_ERROR + PASSWORD_RESET_TOKEN_EXPIRED_ERROR + NOT_VERIFIED_ERROR + NO_ACTIVE_ORDER_ERROR +} + +enum LogicalOperator { + AND + OR +} + +""" +Retured when attempting an operation that relies on the NativeAuthStrategy, if that strategy is not configured. +""" +type NativeAuthStrategyError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned if the user authentication credentials are not valid +""" +type InvalidCredentialsError implements ErrorResult { + errorCode: ErrorCode! + message: String! + authenticationError: String! +} + +""" +Returned if there is an error in transitioning the Order state +""" +type OrderStateTransitionError implements ErrorResult { + errorCode: ErrorCode! + message: String! + transitionError: String! + fromState: String! + toState: String! +} + +""" +Retured when attemting to create a Customer with an email address already registered to an existing User. +""" +type EmailAddressConflictError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured when the maximum order size limit has been reached. +""" +type OrderLimitError implements ErrorResult { + errorCode: ErrorCode! + message: String! + maxItems: Int! +} + +""" +Retured when attemting to set a negative OrderLine quantity. +""" +type NegativeQuantityError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned when attempting to add more items to the Order than are available +""" +type InsufficientStockError implements ErrorResult { + errorCode: ErrorCode! + message: String! + quantityAvailable: Int! + order: Order! +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +""" +A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. +""" +scalar DateTime + +""" +The `Upload` scalar type represents a file upload. +""" +scalar Upload + +interface PaginatedList { + items: [Node!]! + totalItems: Int! +} + +interface Node { + id: ID! +} + +interface ErrorResult { + errorCode: ErrorCode! + message: String! +} + +type Adjustment { + adjustmentSource: String! + type: AdjustmentType! + description: String! + amount: Int! +} + +type TaxLine { + description: String! + taxRate: Float! +} + +type ConfigArg { + name: String! + value: String! +} + +type ConfigArgDefinition { + name: String! + type: String! + list: Boolean! + label: String + description: String + ui: JSON +} + +type ConfigurableOperation { + code: String! + args: [ConfigArg!]! +} + +type ConfigurableOperationDefinition { + code: String! + args: [ConfigArgDefinition!]! + description: String! +} + +type DeletionResponse { + result: DeletionResult! + message: String +} + +input ConfigArgInput { + name: String! + value: String! +} + +input ConfigurableOperationInput { + code: String! + arguments: [ConfigArgInput!]! +} + +input StringOperators { + eq: String + notEq: String + contains: String + notContains: String + in: [String!] + notIn: [String!] + regex: String +} + +input BooleanOperators { + eq: Boolean +} + +input NumberRange { + start: Float! + end: Float! +} + +input NumberOperators { + eq: Float + lt: Float + lte: Float + gt: Float + gte: Float + between: NumberRange +} + +input DateRange { + start: DateTime! + end: DateTime! +} + +input DateOperators { + eq: DateTime + before: DateTime + after: DateTime + between: DateRange +} + +input SearchInput { + term: String + facetValueIds: [ID!] + facetValueOperator: LogicalOperator + collectionId: ID + collectionSlug: String + groupByProduct: Boolean + take: Int + skip: Int + sort: SearchResultSortParameter +} + +input SearchResultSortParameter { + name: SortOrder + price: SortOrder +} + +input CreateCustomerInput { + title: String + firstName: String! + lastName: String! + phoneNumber: String + emailAddress: String! + customFields: JSON +} + +input CreateAddressInput { + fullName: String + company: String + streetLine1: String! + streetLine2: String + city: String + province: String + postalCode: String + countryCode: String! + phoneNumber: String + defaultShippingAddress: Boolean + defaultBillingAddress: Boolean + customFields: JSON +} + +input UpdateAddressInput { + id: ID! + fullName: String + company: String + streetLine1: String + streetLine2: String + city: String + province: String + postalCode: String + countryCode: String + phoneNumber: String + defaultShippingAddress: Boolean + defaultBillingAddress: Boolean + customFields: JSON +} + +""" +Indicates that an operation succeeded, where we do not want to return any more specific information. +""" +type Success { + success: Boolean! +} + +type Country implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + code: String! + name: String! + enabled: Boolean! + translations: [CountryTranslation!]! +} + +type CountryTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type CountryList implements PaginatedList { + items: [Country!]! + totalItems: Int! +} + +""" +@description +ISO 4217 currency code + +@docsCategory common +""" +enum CurrencyCode { + """ + United Arab Emirates dirham + """ + AED + + """ + Afghan afghani + """ + AFN + + """ + Albanian lek + """ + ALL + + """ + Armenian dram + """ + AMD + + """ + Netherlands Antillean guilder + """ + ANG + + """ + Angolan kwanza + """ + AOA + + """ + Argentine peso + """ + ARS + + """ + Australian dollar + """ + AUD + + """ + Aruban florin + """ + AWG + + """ + Azerbaijani manat + """ + AZN + + """ + Bosnia and Herzegovina convertible mark + """ + BAM + + """ + Barbados dollar + """ + BBD + + """ + Bangladeshi taka + """ + BDT + + """ + Bulgarian lev + """ + BGN + + """ + Bahraini dinar + """ + BHD + + """ + Burundian franc + """ + BIF + + """ + Bermudian dollar + """ + BMD + + """ + Brunei dollar + """ + BND + + """ + Boliviano + """ + BOB + + """ + Brazilian real + """ + BRL + + """ + Bahamian dollar + """ + BSD + + """ + Bhutanese ngultrum + """ + BTN + + """ + Botswana pula + """ + BWP + + """ + Belarusian ruble + """ + BYN + + """ + Belize dollar + """ + BZD + + """ + Canadian dollar + """ + CAD + + """ + Congolese franc + """ + CDF + + """ + Swiss franc + """ + CHF + + """ + Chilean peso + """ + CLP + + """ + Renminbi (Chinese) yuan + """ + CNY + + """ + Colombian peso + """ + COP + + """ + Costa Rican colon + """ + CRC + + """ + Cuban convertible peso + """ + CUC + + """ + Cuban peso + """ + CUP + + """ + Cape Verde escudo + """ + CVE + + """ + Czech koruna + """ + CZK + + """ + Djiboutian franc + """ + DJF + + """ + Danish krone + """ + DKK + + """ + Dominican peso + """ + DOP + + """ + Algerian dinar + """ + DZD + + """ + Egyptian pound + """ + EGP + + """ + Eritrean nakfa + """ + ERN + + """ + Ethiopian birr + """ + ETB + + """ + Euro + """ + EUR + + """ + Fiji dollar + """ + FJD + + """ + Falkland Islands pound + """ + FKP + + """ + Pound sterling + """ + GBP + + """ + Georgian lari + """ + GEL + + """ + Ghanaian cedi + """ + GHS + + """ + Gibraltar pound + """ + GIP + + """ + Gambian dalasi + """ + GMD + + """ + Guinean franc + """ + GNF + + """ + Guatemalan quetzal + """ + GTQ + + """ + Guyanese dollar + """ + GYD + + """ + Hong Kong dollar + """ + HKD + + """ + Honduran lempira + """ + HNL + + """ + Croatian kuna + """ + HRK + + """ + Haitian gourde + """ + HTG + + """ + Hungarian forint + """ + HUF + + """ + Indonesian rupiah + """ + IDR + + """ + Israeli new shekel + """ + ILS + + """ + Indian rupee + """ + INR + + """ + Iraqi dinar + """ + IQD + + """ + Iranian rial + """ + IRR + + """ + Icelandic króna + """ + ISK + + """ + Jamaican dollar + """ + JMD + + """ + Jordanian dinar + """ + JOD + + """ + Japanese yen + """ + JPY + + """ + Kenyan shilling + """ + KES + + """ + Kyrgyzstani som + """ + KGS + + """ + Cambodian riel + """ + KHR + + """ + Comoro franc + """ + KMF + + """ + North Korean won + """ + KPW + + """ + South Korean won + """ + KRW + + """ + Kuwaiti dinar + """ + KWD + + """ + Cayman Islands dollar + """ + KYD + + """ + Kazakhstani tenge + """ + KZT + + """ + Lao kip + """ + LAK + + """ + Lebanese pound + """ + LBP + + """ + Sri Lankan rupee + """ + LKR + + """ + Liberian dollar + """ + LRD + + """ + Lesotho loti + """ + LSL + + """ + Libyan dinar + """ + LYD + + """ + Moroccan dirham + """ + MAD + + """ + Moldovan leu + """ + MDL + + """ + Malagasy ariary + """ + MGA + + """ + Macedonian denar + """ + MKD + + """ + Myanmar kyat + """ + MMK + + """ + Mongolian tögrög + """ + MNT + + """ + Macanese pataca + """ + MOP + + """ + Mauritanian ouguiya + """ + MRU + + """ + Mauritian rupee + """ + MUR + + """ + Maldivian rufiyaa + """ + MVR + + """ + Malawian kwacha + """ + MWK + + """ + Mexican peso + """ + MXN + + """ + Malaysian ringgit + """ + MYR + + """ + Mozambican metical + """ + MZN + + """ + Namibian dollar + """ + NAD + + """ + Nigerian naira + """ + NGN + + """ + Nicaraguan córdoba + """ + NIO + + """ + Norwegian krone + """ + NOK + + """ + Nepalese rupee + """ + NPR + + """ + New Zealand dollar + """ + NZD + + """ + Omani rial + """ + OMR + + """ + Panamanian balboa + """ + PAB + + """ + Peruvian sol + """ + PEN + + """ + Papua New Guinean kina + """ + PGK + + """ + Philippine peso + """ + PHP + + """ + Pakistani rupee + """ + PKR + + """ + Polish złoty + """ + PLN + + """ + Paraguayan guaraní + """ + PYG + + """ + Qatari riyal + """ + QAR + + """ + Romanian leu + """ + RON + + """ + Serbian dinar + """ + RSD + + """ + Russian ruble + """ + RUB + + """ + Rwandan franc + """ + RWF + + """ + Saudi riyal + """ + SAR + + """ + Solomon Islands dollar + """ + SBD + + """ + Seychelles rupee + """ + SCR + + """ + Sudanese pound + """ + SDG + + """ + Swedish krona/kronor + """ + SEK + + """ + Singapore dollar + """ + SGD + + """ + Saint Helena pound + """ + SHP + + """ + Sierra Leonean leone + """ + SLL + + """ + Somali shilling + """ + SOS + + """ + Surinamese dollar + """ + SRD + + """ + South Sudanese pound + """ + SSP + + """ + São Tomé and Príncipe dobra + """ + STN + + """ + Salvadoran colón + """ + SVC + + """ + Syrian pound + """ + SYP + + """ + Swazi lilangeni + """ + SZL + + """ + Thai baht + """ + THB + + """ + Tajikistani somoni + """ + TJS + + """ + Turkmenistan manat + """ + TMT + + """ + Tunisian dinar + """ + TND + + """ + Tongan paʻanga + """ + TOP + + """ + Turkish lira + """ + TRY + + """ + Trinidad and Tobago dollar + """ + TTD + + """ + New Taiwan dollar + """ + TWD + + """ + Tanzanian shilling + """ + TZS + + """ + Ukrainian hryvnia + """ + UAH + + """ + Ugandan shilling + """ + UGX + + """ + United States dollar + """ + USD + + """ + Uruguayan peso + """ + UYU + + """ + Uzbekistan som + """ + UZS + + """ + Venezuelan bolívar soberano + """ + VES + + """ + Vietnamese đồng + """ + VND + + """ + Vanuatu vatu + """ + VUV + + """ + Samoan tala + """ + WST + + """ + CFA franc BEAC + """ + XAF + + """ + East Caribbean dollar + """ + XCD + + """ + CFA franc BCEAO + """ + XOF + + """ + CFP franc (franc Pacifique) + """ + XPF + + """ + Yemeni rial + """ + YER + + """ + South African rand + """ + ZAR + + """ + Zambian kwacha + """ + ZMW + + """ + Zimbabwean dollar + """ + ZWL +} + +interface CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean +} + +type StringCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + length: Int + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + pattern: String + options: [StringFieldOption!] +} + +type StringFieldOption { + value: String! + label: [LocalizedString!] +} + +type LocaleStringCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + length: Int + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + pattern: String +} + +type IntCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + min: Int + max: Int + step: Int +} + +type FloatCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + min: Float + max: Float + step: Float +} + +type BooleanCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean +} + +""" +Expects the same validation formats as the `<input type="datetime-local">` HTML element. +See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes +""" +type DateTimeCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + min: String + max: String + step: Int +} + +type RelationCustomFieldConfig implements CustomField { + name: String! + type: String! + list: Boolean! + label: [LocalizedString!] + description: [LocalizedString!] + readonly: Boolean + internal: Boolean + entity: String! + scalarFields: [String!]! +} + +type LocalizedString { + languageCode: LanguageCode! + value: String! +} + +union CustomFieldConfig = + StringCustomFieldConfig + | LocaleStringCustomFieldConfig + | IntCustomFieldConfig + | FloatCustomFieldConfig + | BooleanCustomFieldConfig + | DateTimeCustomFieldConfig + | RelationCustomFieldConfig + +type CustomerGroup implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! + customers(options: CustomerListOptions): CustomerList! +} + +type Customer implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + title: String + firstName: String! + lastName: String! + phoneNumber: String + emailAddress: String! + addresses: [Address!] + orders(options: OrderListOptions): OrderList! + user: User + customFields: JSON +} + +type CustomerList implements PaginatedList { + items: [Customer!]! + totalItems: Int! +} + +type FacetValue implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + facet: Facet! + name: String! + code: String! + translations: [FacetValueTranslation!]! + customFields: JSON +} + +type FacetValueTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type Facet implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! + code: String! + values: [FacetValue!]! + translations: [FacetTranslation!]! + customFields: JSON +} + +type FacetTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type FacetList implements PaginatedList { + items: [Facet!]! + totalItems: Int! +} + +type HistoryEntry implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + type: HistoryEntryType! + data: JSON! +} + +enum HistoryEntryType { + CUSTOMER_REGISTERED + CUSTOMER_VERIFIED + CUSTOMER_DETAIL_UPDATED + CUSTOMER_ADDED_TO_GROUP + CUSTOMER_REMOVED_FROM_GROUP + CUSTOMER_ADDRESS_CREATED + CUSTOMER_ADDRESS_UPDATED + CUSTOMER_ADDRESS_DELETED + CUSTOMER_PASSWORD_UPDATED + CUSTOMER_PASSWORD_RESET_REQUESTED + CUSTOMER_PASSWORD_RESET_VERIFIED + CUSTOMER_EMAIL_UPDATE_REQUESTED + CUSTOMER_EMAIL_UPDATE_VERIFIED + CUSTOMER_NOTE + ORDER_STATE_TRANSITION + ORDER_PAYMENT_TRANSITION + ORDER_FULFILLMENT + ORDER_CANCELLATION + ORDER_REFUND_TRANSITION + ORDER_FULFILLMENT_TRANSITION + ORDER_NOTE + ORDER_COUPON_APPLIED + ORDER_COUPON_REMOVED + ORDER_MODIFIED +} + +type HistoryEntryList implements PaginatedList { + items: [HistoryEntry!]! + totalItems: Int! +} + +""" +@description +Languages in the form of a ISO 639-1 language code with optional +region or script modifier (e.g. de_AT). The selection available is based +on the [Unicode CLDR summary list](https://unicode-org.github.io/cldr-staging/charts/37/summary/root.html) +and includes the major spoken languages of the world and any widely-used variants. + +@docsCategory common +""" +enum LanguageCode { + """ + Afrikaans + """ + af + + """ + Akan + """ + ak + + """ + Albanian + """ + sq + + """ + Amharic + """ + am + + """ + Arabic + """ + ar + + """ + Armenian + """ + hy + + """ + Assamese + """ + as + + """ + Azerbaijani + """ + az + + """ + Bambara + """ + bm + + """ + Bangla + """ + bn + + """ + Basque + """ + eu + + """ + Belarusian + """ + be + + """ + Bosnian + """ + bs + + """ + Breton + """ + br + + """ + Bulgarian + """ + bg + + """ + Burmese + """ + my + + """ + Catalan + """ + ca + + """ + Chechen + """ + ce + + """ + Chinese + """ + zh + + """ + Simplified Chinese + """ + zh_Hans + + """ + Traditional Chinese + """ + zh_Hant + + """ + Church Slavic + """ + cu + + """ + Cornish + """ + kw + + """ + Corsican + """ + co + + """ + Croatian + """ + hr + + """ + Czech + """ + cs + + """ + Danish + """ + da + + """ + Dutch + """ + nl + + """ + Flemish + """ + nl_BE + + """ + Dzongkha + """ + dz + + """ + English + """ + en + + """ + Australian English + """ + en_AU + + """ + Canadian English + """ + en_CA + + """ + British English + """ + en_GB + + """ + American English + """ + en_US + + """ + Esperanto + """ + eo + + """ + Estonian + """ + et + + """ + Ewe + """ + ee + + """ + Faroese + """ + fo + + """ + Finnish + """ + fi + + """ + French + """ + fr + + """ + Canadian French + """ + fr_CA + + """ + Swiss French + """ + fr_CH + + """ + Fulah + """ + ff + + """ + Galician + """ + gl + + """ + Ganda + """ + lg + + """ + Georgian + """ + ka + + """ + German + """ + de + + """ + Austrian German + """ + de_AT + + """ + Swiss High German + """ + de_CH + + """ + Greek + """ + el + + """ + Gujarati + """ + gu + + """ + Haitian Creole + """ + ht + + """ + Hausa + """ + ha + + """ + Hebrew + """ + he + + """ + Hindi + """ + hi + + """ + Hungarian + """ + hu + + """ + Icelandic + """ + is + + """ + Igbo + """ + ig + + """ + Indonesian + """ + id + + """ + Interlingua + """ + ia + + """ + Irish + """ + ga + + """ + Italian + """ + it + + """ + Japanese + """ + ja + + """ + Javanese + """ + jv + + """ + Kalaallisut + """ + kl + + """ + Kannada + """ + kn + + """ + Kashmiri + """ + ks + + """ + Kazakh + """ + kk + + """ + Khmer + """ + km + + """ + Kikuyu + """ + ki + + """ + Kinyarwanda + """ + rw + + """ + Korean + """ + ko + + """ + Kurdish + """ + ku + + """ + Kyrgyz + """ + ky + + """ + Lao + """ + lo + + """ + Latin + """ + la + + """ + Latvian + """ + lv + + """ + Lingala + """ + ln + + """ + Lithuanian + """ + lt + + """ + Luba-Katanga + """ + lu + + """ + Luxembourgish + """ + lb + + """ + Macedonian + """ + mk + + """ + Malagasy + """ + mg + + """ + Malay + """ + ms + + """ + Malayalam + """ + ml + + """ + Maltese + """ + mt + + """ + Manx + """ + gv + + """ + Maori + """ + mi + + """ + Marathi + """ + mr + + """ + Mongolian + """ + mn + + """ + Nepali + """ + ne + + """ + North Ndebele + """ + nd + + """ + Northern Sami + """ + se + + """ + Norwegian Bokmål + """ + nb + + """ + Norwegian Nynorsk + """ + nn + + """ + Nyanja + """ + ny + + """ + Odia + """ + or + + """ + Oromo + """ + om + + """ + Ossetic + """ + os + + """ + Pashto + """ + ps + + """ + Persian + """ + fa + + """ + Dari + """ + fa_AF + + """ + Polish + """ + pl + + """ + Portuguese + """ + pt + + """ + Brazilian Portuguese + """ + pt_BR + + """ + European Portuguese + """ + pt_PT + + """ + Punjabi + """ + pa + + """ + Quechua + """ + qu + + """ + Romanian + """ + ro + + """ + Moldavian + """ + ro_MD + + """ + Romansh + """ + rm + + """ + Rundi + """ + rn + + """ + Russian + """ + ru + + """ + Samoan + """ + sm + + """ + Sango + """ + sg + + """ + Sanskrit + """ + sa + + """ + Scottish Gaelic + """ + gd + + """ + Serbian + """ + sr + + """ + Shona + """ + sn + + """ + Sichuan Yi + """ + ii + + """ + Sindhi + """ + sd + + """ + Sinhala + """ + si + + """ + Slovak + """ + sk + + """ + Slovenian + """ + sl + + """ + Somali + """ + so + + """ + Southern Sotho + """ + st + + """ + Spanish + """ + es + + """ + European Spanish + """ + es_ES + + """ + Mexican Spanish + """ + es_MX + + """ + Sundanese + """ + su + + """ + Swahili + """ + sw + + """ + Congo Swahili + """ + sw_CD + + """ + Swedish + """ + sv + + """ + Tajik + """ + tg + + """ + Tamil + """ + ta + + """ + Tatar + """ + tt + + """ + Telugu + """ + te + + """ + Thai + """ + th + + """ + Tibetan + """ + bo + + """ + Tigrinya + """ + ti + + """ + Tongan + """ + to + + """ + Turkish + """ + tr + + """ + Turkmen + """ + tk + + """ + Ukrainian + """ + uk + + """ + Urdu + """ + ur + + """ + Uyghur + """ + ug + + """ + Uzbek + """ + uz + + """ + Vietnamese + """ + vi + + """ + Volapük + """ + vo + + """ + Welsh + """ + cy + + """ + Western Frisian + """ + fy + + """ + Wolof + """ + wo + + """ + Xhosa + """ + xh + + """ + Yiddish + """ + yi + + """ + Yoruba + """ + yo + + """ + Zulu + """ + zu +} + +type Order implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + + """ + The date & time that the Order was placed, i.e. the Customer + completed the checkout and the Order is no longer "active" + """ + orderPlacedAt: DateTime + + """ + A unique code for the Order + """ + code: String! + state: String! + + """ + An order is active as long as the payment process has not been completed + """ + active: Boolean! + customer: Customer + shippingAddress: OrderAddress + billingAddress: OrderAddress + lines: [OrderLine!]! + + """ + Surcharges are arbitrary modifications to the Order total which are neither + ProductVariants nor discounts resulting from applied Promotions. For example, + one-off discounts based on customer interaction, or surcharges based on payment + methods. + """ + surcharges: [Surcharge!]! + + """ + Order-level adjustments to the order total, such as discounts from promotions + """ + adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead") + discounts: [Adjustment!]! + + """ + An array of all coupon codes applied to the Order + """ + couponCodes: [String!]! + + """ + Promotions applied to the order. Only gets populated after the payment process has completed. + """ + promotions: [Promotion!]! + payments: [Payment!] + fulfillments: [Fulfillment!] + totalQuantity: Int! + + """ + The subTotal is the total of all OrderLines in the Order. This figure also includes any Order-level + discounts which have been prorated (proportionally distributed) amongst the OrderItems. + To get a total of all OrderLines which does not account for prorated discounts, use the + sum of `OrderLine.discountedLinePrice` values. + """ + subTotal: Int! + + """ + Same as subTotal, but inclusive of tax + """ + subTotalWithTax: Int! + currencyCode: CurrencyCode! + shippingLines: [ShippingLine!]! + shipping: Int! + shippingWithTax: Int! + + """ + Equal to subTotal plus shipping + """ + total: Int! + + """ + The final payable amount. Equal to subTotalWithTax plus shippingWithTax + """ + totalWithTax: Int! + + """ + A summary of the taxes being applied to this Order + """ + taxSummary: [OrderTaxSummary!]! + history(options: HistoryEntryListOptions): HistoryEntryList! + customFields: JSON +} + +""" +A summary of the taxes being applied to this order, grouped +by taxRate. +""" +type OrderTaxSummary { + """ + A description of this tax + """ + description: String! + + """ + The taxRate as a percentage + """ + taxRate: Float! + + """ + The total net price or OrderItems to which this taxRate applies + """ + taxBase: Int! + + """ + The total tax being applied to the Order at this taxRate + """ + taxTotal: Int! +} + +type OrderAddress { + fullName: String + company: String + streetLine1: String + streetLine2: String + city: String + province: String + postalCode: String + country: String + countryCode: String + phoneNumber: String + customFields: JSON +} + +type OrderList implements PaginatedList { + items: [Order!]! + totalItems: Int! +} + +type ShippingMethodQuote { + id: ID! + price: Int! + priceWithTax: Int! + name: String! + description: String! + metadata: JSON +} + +type ShippingLine { + shippingMethod: ShippingMethod! + price: Int! + priceWithTax: Int! + discountedPrice: Int! + discountedPriceWithTax: Int! + discounts: [Adjustment!]! +} + +type OrderItem implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + cancelled: Boolean! + + """ + The price of a single unit, excluding tax and discounts + """ + unitPrice: Int! + + """ + The price of a single unit, including tax but excluding discounts + """ + unitPriceWithTax: Int! + + """ + The price of a single unit including discounts, excluding tax. + + If Order-level discounts have been applied, this will not be the + actual taxable unit price (see `proratedUnitPrice`), but is generally the + correct price to display to customers to avoid confusion + about the internal handling of distributed Order-level discounts. + """ + discountedUnitPrice: Int! + + """ + The price of a single unit including discounts and tax + """ + discountedUnitPriceWithTax: Int! + + """ + The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed) + Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax + and refund calculations. + """ + proratedUnitPrice: Int! + + """ + The proratedUnitPrice including tax + """ + proratedUnitPriceWithTax: Int! + unitTax: Int! + unitPriceIncludesTax: Boolean! + @deprecated(reason: "`unitPrice` is now always without tax") + taxRate: Float! + adjustments: [Adjustment!]! + taxLines: [TaxLine!]! + fulfillment: Fulfillment + refundId: ID +} + +type OrderLine implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + productVariant: ProductVariant! + featuredAsset: Asset + + """ + The price of a single unit, excluding tax and discounts + """ + unitPrice: Int! + + """ + The price of a single unit, including tax but excluding discounts + """ + unitPriceWithTax: Int! + + """ + The price of a single unit including discounts, excluding tax. + + If Order-level discounts have been applied, this will not be the + actual taxable unit price (see `proratedUnitPrice`), but is generally the + correct price to display to customers to avoid confusion + about the internal handling of distributed Order-level discounts. + """ + discountedUnitPrice: Int! + + """ + The price of a single unit including discounts and tax + """ + discountedUnitPriceWithTax: Int! + + """ + The actual unit price, taking into account both item discounts _and_ prorated (proportially-distributed) + Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax + and refund calculations. + """ + proratedUnitPrice: Int! + + """ + The proratedUnitPrice including tax + """ + proratedUnitPriceWithTax: Int! + quantity: Int! + items: [OrderItem!]! + totalPrice: Int! @deprecated(reason: "Use `linePriceWithTax` instead") + taxRate: Float! + + """ + The total price of the line excluding tax and discounts. + """ + linePrice: Int! + + """ + The total price of the line including tax bit excluding discounts. + """ + linePriceWithTax: Int! + + """ + The price of the line including discounts, excluding tax + """ + discountedLinePrice: Int! + + """ + The price of the line including discounts and tax + """ + discountedLinePriceWithTax: Int! + + """ + The actual line price, taking into account both item discounts _and_ prorated (proportially-distributed) + Order-level discounts. This value is the true economic value of the OrderLine, and is used in tax + and refund calculations. + """ + proratedLinePrice: Int! + + """ + The proratedLinePrice including tax + """ + proratedLinePriceWithTax: Int! + + """ + The total tax on this line + """ + lineTax: Int! + adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead") + discounts: [Adjustment!]! + taxLines: [TaxLine!]! + order: Order! + customFields: JSON +} + +type Payment implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + method: String! + amount: Int! + state: String! + transactionId: String + errorMessage: String + refunds: [Refund!]! + metadata: JSON +} + +type Refund implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + items: Int! + shipping: Int! + adjustment: Int! + total: Int! + method: String + state: String! + transactionId: String + reason: String + orderItems: [OrderItem!]! + paymentId: ID! + metadata: JSON +} + +type Fulfillment implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + orderItems: [OrderItem!]! + state: String! + method: String! + trackingCode: String + customFields: JSON +} + +type Surcharge implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + description: String! + sku: String + taxLines: [TaxLine!]! + price: Int! + priceWithTax: Int! + taxRate: Float! +} + +type ProductOptionGroup implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + code: String! + name: String! + options: [ProductOption!]! + translations: [ProductOptionGroupTranslation!]! + customFields: JSON +} + +type ProductOptionGroupTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type ProductOption implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + code: String! + name: String! + groupId: ID! + group: ProductOptionGroup! + translations: [ProductOptionTranslation!]! + customFields: JSON +} + +type ProductOptionTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type SearchReindexResponse { + success: Boolean! +} + +type SearchResponse { + items: [SearchResult!]! + totalItems: Int! + facetValues: [FacetValueResult!]! +} + +""" +Which FacetValues are present in the products returned +by the search, and in what quantity. +""" +type FacetValueResult { + facetValue: FacetValue! + count: Int! +} + +type SearchResultAsset { + id: ID! + preview: String! + focalPoint: Coordinate +} + +type SearchResult { + sku: String! + slug: String! + productId: ID! + productName: String! + productPreview: String! + @deprecated(reason: "Use `productAsset.preview` instead") + productAsset: SearchResultAsset + productVariantId: ID! + productVariantName: String! + productVariantPreview: String! + @deprecated(reason: "Use `productVariantAsset.preview` instead") + productVariantAsset: SearchResultAsset + price: SearchResultPrice! + priceWithTax: SearchResultPrice! + currencyCode: CurrencyCode! + description: String! + facetIds: [ID!]! + facetValueIds: [ID!]! + + """ + An array of ids of the Collections in which this result appears + """ + collectionIds: [ID!]! + + """ + A relevence score for the result. Differs between database implementations + """ + score: Float! +} + +""" +The price of a search result product, either as a range or as a single price +""" +union SearchResultPrice = PriceRange | SinglePrice + +""" +The price value where the result has a single price +""" +type SinglePrice { + value: Int! +} + +""" +The price range where the result has more than one price +""" +type PriceRange { + min: Int! + max: Int! +} + +type Product implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! + slug: String! + description: String! + featuredAsset: Asset + assets: [Asset!]! + variants: [ProductVariant!]! + optionGroups: [ProductOptionGroup!]! + facetValues: [FacetValue!]! + translations: [ProductTranslation!]! + collections: [Collection!]! + customFields: JSON +} + +type ProductTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! + slug: String! + description: String! +} + +type ProductList implements PaginatedList { + items: [Product!]! + totalItems: Int! +} + +type ProductVariant implements Node { + id: ID! + product: Product! + productId: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + sku: String! + name: String! + featuredAsset: Asset + assets: [Asset!]! + price: Int! + currencyCode: CurrencyCode! + priceIncludesTax: Boolean! + @deprecated(reason: "price now always excludes tax") + priceWithTax: Int! + taxRateApplied: TaxRate! + taxCategory: TaxCategory! + options: [ProductOption!]! + facetValues: [FacetValue!]! + translations: [ProductVariantTranslation!]! + customFields: JSON +} + +type ProductVariantTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! +} + +type Promotion implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + startsAt: DateTime + endsAt: DateTime + couponCode: String + perCustomerUsageLimit: Int + name: String! + enabled: Boolean! + conditions: [ConfigurableOperation!]! + actions: [ConfigurableOperation!]! +} + +type PromotionList implements PaginatedList { + items: [Promotion!]! + totalItems: Int! +} + +type Role implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + code: String! + description: String! + permissions: [Permission!]! + channels: [Channel!]! +} + +type RoleList implements PaginatedList { + items: [Role!]! + totalItems: Int! +} + +type ShippingMethod implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + code: String! + name: String! + description: String! + fulfillmentHandlerCode: String! + checker: ConfigurableOperation! + calculator: ConfigurableOperation! + translations: [ShippingMethodTranslation!]! + customFields: JSON +} + +type ShippingMethodTranslation { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + languageCode: LanguageCode! + name: String! + description: String! +} + +type ShippingMethodList implements PaginatedList { + items: [ShippingMethod!]! + totalItems: Int! +} + +type Tag implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + value: String! +} + +type TagList implements PaginatedList { + items: [Tag!]! + totalItems: Int! +} + +type TaxCategory implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! +} + +type TaxRate implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! + enabled: Boolean! + value: Float! + category: TaxCategory! + zone: Zone! + customerGroup: CustomerGroup +} + +type TaxRateList implements PaginatedList { + items: [TaxRate!]! + totalItems: Int! +} + +type User implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + identifier: String! + verified: Boolean! + roles: [Role!]! + lastLogin: DateTime + authenticationMethods: [AuthenticationMethod!]! + customFields: JSON +} + +type AuthenticationMethod implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + strategy: String! +} + +type Zone implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! + members: [Country!]! +} + +""" +Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. +""" +type OrderModificationError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned when attempting to set a ShippingMethod for which the order is not eligible +""" +type IneligibleShippingMethodError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned when attempting to add a Payment to an Order that is not in the `ArrangingPayment` state. +""" +type OrderPaymentStateError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned when a Payment fails due to an error. +""" +type PaymentFailedError implements ErrorResult { + errorCode: ErrorCode! + message: String! + paymentErrorMessage: String! +} + +""" +Returned when a Payment is declined by the payment provider. +""" +type PaymentDeclinedError implements ErrorResult { + errorCode: ErrorCode! + message: String! + paymentErrorMessage: String! +} + +""" +Returned if the provided coupon code is invalid +""" +type CouponCodeInvalidError implements ErrorResult { + errorCode: ErrorCode! + message: String! + couponCode: String! +} + +""" +Returned if the provided coupon code is invalid +""" +type CouponCodeExpiredError implements ErrorResult { + errorCode: ErrorCode! + message: String! + couponCode: String! +} + +""" +Returned if the provided coupon code is invalid +""" +type CouponCodeLimitError implements ErrorResult { + errorCode: ErrorCode! + message: String! + couponCode: String! + limit: Int! +} + +""" +Retured when attemting to set the Customer for an Order when already logged in. +""" +type AlreadyLoggedInError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured when attemting to register or verify a customer account without a password, when one is required. +""" +type MissingPasswordError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured when attemting to verify a customer account with a password, when a password has already been set. +""" +type PasswordAlreadySetError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured if the verification token (used to verify a Customer's email address) is either +invalid or does not match any expected tokens. +""" +type VerificationTokenInvalidError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned if the verification token (used to verify a Customer's email address) is valid, but has +expired according to the `verificationTokenDuration` setting in the AuthOptions. +""" +type VerificationTokenExpiredError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured if the token used to change a Customer's email address is either +invalid or does not match any expected tokens. +""" +type IdentifierChangeTokenInvalidError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured if the token used to change a Customer's email address is valid, but has +expired according to the `verificationTokenDuration` setting in the AuthOptions. +""" +type IdentifierChangeTokenExpiredError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured if the token used to reset a Customer's password is either +invalid or does not match any expected tokens. +""" +type PasswordResetTokenInvalidError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Retured if the token used to reset a Customer's password is valid, but has +expired according to the `verificationTokenDuration` setting in the AuthOptions. +""" +type PasswordResetTokenExpiredError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned if `authOptions.requireVerification` is set to `true` (which is the default) +and an unverified user attempts to authenticate. +""" +type NotVerifiedError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +""" +Returned when invoking a mutation which depends on there being an active Order on the +current session. +""" +type NoActiveOrderError implements ErrorResult { + errorCode: ErrorCode! + message: String! +} + +input RegisterCustomerInput { + emailAddress: String! + title: String + firstName: String + lastName: String + phoneNumber: String + password: String +} + +input UpdateCustomerInput { + title: String + firstName: String + lastName: String + phoneNumber: String + customFields: JSON +} + +""" +Passed as input to the `addPaymentToOrder` mutation. +""" +input PaymentInput { + """ + This field should correspond to the `code` property of a PaymentMethodHandler. + """ + method: String! + + """ + This field should contain arbitrary data passed to the specified PaymentMethodHandler's `createPayment()` method + as the "metadata" argument. For example, it could contain an ID for the payment and other + data generated by the payment provider. + """ + metadata: JSON! +} + +union UpdateOrderItemsResult = + Order + | OrderModificationError + | OrderLimitError + | NegativeQuantityError + | InsufficientStockError + +union RemoveOrderItemsResult = Order | OrderModificationError + +union SetOrderShippingMethodResult = + Order + | OrderModificationError + | IneligibleShippingMethodError + | NoActiveOrderError + +union ApplyCouponCodeResult = + Order + | CouponCodeExpiredError + | CouponCodeInvalidError + | CouponCodeLimitError + +union AddPaymentToOrderResult = + Order + | OrderPaymentStateError + | PaymentFailedError + | PaymentDeclinedError + | OrderStateTransitionError + | NoActiveOrderError + +union TransitionOrderToStateResult = Order | OrderStateTransitionError + +union SetCustomerForOrderResult = + Order + | AlreadyLoggedInError + | EmailAddressConflictError + | NoActiveOrderError + +union RegisterCustomerAccountResult = + Success + | MissingPasswordError + | NativeAuthStrategyError + +union RefreshCustomerVerificationResult = Success | NativeAuthStrategyError + +union VerifyCustomerAccountResult = + CurrentUser + | VerificationTokenInvalidError + | VerificationTokenExpiredError + | MissingPasswordError + | PasswordAlreadySetError + | NativeAuthStrategyError + +union UpdateCustomerPasswordResult = + Success + | InvalidCredentialsError + | NativeAuthStrategyError + +union RequestUpdateCustomerEmailAddressResult = + Success + | InvalidCredentialsError + | EmailAddressConflictError + | NativeAuthStrategyError + +union UpdateCustomerEmailAddressResult = + Success + | IdentifierChangeTokenInvalidError + | IdentifierChangeTokenExpiredError + | NativeAuthStrategyError + +union RequestPasswordResetResult = Success | NativeAuthStrategyError + +union ResetPasswordResult = + CurrentUser + | PasswordResetTokenInvalidError + | PasswordResetTokenExpiredError + | NativeAuthStrategyError + +union NativeAuthenticationResult = + CurrentUser + | InvalidCredentialsError + | NotVerifiedError + | NativeAuthStrategyError + +union AuthenticationResult = + CurrentUser + | InvalidCredentialsError + | NotVerifiedError + +union ActiveOrderResult = Order | NoActiveOrderError + +input CollectionListOptions { + skip: Int + take: Int + sort: CollectionSortParameter + filter: CollectionFilterParameter +} + +input ProductListOptions { + skip: Int + take: Int + sort: ProductSortParameter + filter: ProductFilterParameter +} + +input ProductVariantListOptions { + skip: Int + take: Int + sort: ProductVariantSortParameter + filter: ProductVariantFilterParameter +} + +input CustomerListOptions { + skip: Int + take: Int + sort: CustomerSortParameter + filter: CustomerFilterParameter +} + +input OrderListOptions { + skip: Int + take: Int + sort: OrderSortParameter + filter: OrderFilterParameter +} + +input HistoryEntryListOptions { + skip: Int + take: Int + sort: HistoryEntrySortParameter + filter: HistoryEntryFilterParameter +} + +input CollectionFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + languageCode: StringOperators + name: StringOperators + slug: StringOperators + position: NumberOperators + description: StringOperators +} + +input CollectionSortParameter { + id: SortOrder + createdAt: SortOrder + updatedAt: SortOrder + name: SortOrder + slug: SortOrder + position: SortOrder + description: SortOrder +} + +input ProductFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + languageCode: StringOperators + name: StringOperators + slug: StringOperators + description: StringOperators +} + +input ProductSortParameter { + id: SortOrder + createdAt: SortOrder + updatedAt: SortOrder + name: SortOrder + slug: SortOrder + description: SortOrder +} + +input ProductVariantFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + languageCode: StringOperators + sku: StringOperators + name: StringOperators + price: NumberOperators + currencyCode: StringOperators + priceIncludesTax: BooleanOperators + priceWithTax: NumberOperators +} + +input ProductVariantSortParameter { + id: SortOrder + productId: SortOrder + createdAt: SortOrder + updatedAt: SortOrder + sku: SortOrder + name: SortOrder + price: SortOrder + priceWithTax: SortOrder +} + +input CustomerFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + title: StringOperators + firstName: StringOperators + lastName: StringOperators + phoneNumber: StringOperators + emailAddress: StringOperators +} + +input CustomerSortParameter { + id: SortOrder + createdAt: SortOrder + updatedAt: SortOrder + title: SortOrder + firstName: SortOrder + lastName: SortOrder + phoneNumber: SortOrder + emailAddress: SortOrder +} + +input OrderFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + orderPlacedAt: DateOperators + code: StringOperators + state: StringOperators + active: BooleanOperators + totalQuantity: NumberOperators + subTotal: NumberOperators + subTotalWithTax: NumberOperators + currencyCode: StringOperators + shipping: NumberOperators + shippingWithTax: NumberOperators + total: NumberOperators + totalWithTax: NumberOperators +} + +input OrderSortParameter { + id: SortOrder + createdAt: SortOrder + updatedAt: SortOrder + orderPlacedAt: SortOrder + code: SortOrder + state: SortOrder + totalQuantity: SortOrder + subTotal: SortOrder + subTotalWithTax: SortOrder + shipping: SortOrder + shippingWithTax: SortOrder + total: SortOrder + totalWithTax: SortOrder +} + +input HistoryEntryFilterParameter { + createdAt: DateOperators + updatedAt: DateOperators + type: StringOperators +} + +input HistoryEntrySortParameter { + id: SortOrder + createdAt: SortOrder + updatedAt: SortOrder +} + +input UpdateOrderInput { + customFields: JSON +} + +input AuthenticationInput { + native: NativeAuthInput +} + +input NativeAuthInput { + username: String! + password: String! +} diff --git a/framework/vendure/scripts/generate-definitions.js b/framework/vendure/scripts/generate-definitions.js new file mode 100644 index 000000000..bcae741c8 --- /dev/null +++ b/framework/vendure/scripts/generate-definitions.js @@ -0,0 +1,49 @@ +/** + * Generates definitions for REST API endpoints that are being + * used by ../api using https://github.com/drwpow/swagger-to-ts + */ +const { readFileSync, promises } = require('fs') +const path = require('path') +const fetch = require('node-fetch') +const swaggerToTS = require('@manifoldco/swagger-to-ts').default + +async function getSchema(filename) { + const url = `http://next-api.stoplight.io/projects/8433/files/${filename}` + const res = await fetch(url) + + if (!res.ok) { + throw new Error(`Request failed with ${res.status}: ${res.statusText}`) + } + + return res.json() +} + +const schemas = Object.entries({ + '../api/definitions/catalog.ts': + 'BigCommerce_Catalog_API.oas2.yml?ref=version%2F20.930', + '../api/definitions/store-content.ts': + 'BigCommerce_Store_Content_API.oas2.yml?ref=version%2F20.930', + '../api/definitions/wishlist.ts': + 'BigCommerce_Wishlist_API.oas2.yml?ref=version%2F20.930', + // swagger-to-ts is not working for the schema of the cart API + // '../api/definitions/cart.ts': + // 'BigCommerce_Server_to_Server_Cart_API.oas2.yml', +}) + +async function writeDefinitions() { + const ops = schemas.map(async ([dest, filename]) => { + const destination = path.join(__dirname, dest) + const schema = await getSchema(filename) + const definition = swaggerToTS(schema.content, { + prettierConfig: 'package.json', + }) + + await promises.writeFile(destination, definition) + + console.log(`✔️ Added definitions for: ${dest}`) + }) + + await Promise.all(ops) +} + +writeDefinitions() diff --git a/framework/vendure/wishlist/index.ts b/framework/vendure/wishlist/index.ts new file mode 100644 index 000000000..9ea28291c --- /dev/null +++ b/framework/vendure/wishlist/index.ts @@ -0,0 +1,4 @@ +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' diff --git a/framework/vendure/wishlist/use-add-item.tsx b/framework/vendure/wishlist/use-add-item.tsx new file mode 100644 index 000000000..6e7d9de41 --- /dev/null +++ b/framework/vendure/wishlist/use-add-item.tsx @@ -0,0 +1,57 @@ +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 = { + url: '/api/bigcommerce/wishlist', + method: 'POST', +} + +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 useAddItem +} + +export default extendHook(fetcher) diff --git a/framework/vendure/wishlist/use-remove-item.tsx b/framework/vendure/wishlist/use-remove-item.tsx new file mode 100644 index 000000000..86614a21a --- /dev/null +++ b/framework/vendure/wishlist/use-remove-item.tsx @@ -0,0 +1,61 @@ +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 = { + url: '/api/bigcommerce/wishlist', + method: 'DELETE', +} + +export type RemoveItemInput = { + id: string | number +} + +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 useRemoveItem +} + +export default extendHook(fetcher) diff --git a/framework/vendure/wishlist/use-wishlist-actions.tsx b/framework/vendure/wishlist/use-wishlist-actions.tsx new file mode 100644 index 000000000..711d00516 --- /dev/null +++ b/framework/vendure/wishlist/use-wishlist-actions.tsx @@ -0,0 +1,11 @@ +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 } +} diff --git a/framework/vendure/wishlist/use-wishlist.tsx b/framework/vendure/wishlist/use-wishlist.tsx new file mode 100644 index 000000000..870b90019 --- /dev/null +++ b/framework/vendure/wishlist/use-wishlist.tsx @@ -0,0 +1,79 @@ +import { HookFetcher } from '@commerce/utils/types' +import { SwrOptions } from '@commerce/utils/use-data' +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 = { + url: '/api/bigcommerce/wishlist', + method: 'GET', +} + +export type { Wishlist } + +export interface UseWishlistOptions { + includeProducts?: boolean +} + +export interface UseWishlistInput extends UseWishlistOptions { + customerId?: number +} + +export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = ( + options, + { 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( + customFetcher: typeof fetcher, + swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput> +) { + const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => { + const { data: customer } = useCustomer() + 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 + + return useWishlist +} + +export default extendHook(fetcher) diff --git a/next.config.js b/next.config.js index e732ef78a..a0e833b8d 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,6 @@ module.exports = { images: { - domains: ['cdn11.bigcommerce.com'], + domains: ['cdn11.bigcommerce.com', 'localhost'], }, i18n: { locales: ['en-US', 'es'], diff --git a/package.json b/package.json index ff492a35e..42a1e9619 100644 --- a/package.json +++ b/package.json @@ -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" }, "license": "MIT", diff --git a/pages/index.tsx b/pages/index.tsx index 811472674..1088bf3f9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -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, } diff --git a/tsconfig.json b/tsconfig.json index 0992a8cd6..782157c3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,8 +22,8 @@ "@utils/*": ["utils/*"], "@commerce/*": ["framework/commerce/*"], "@commerce": ["framework/commerce"], - "@framework/*": ["framework/bigcommerce/*"], - "@framework": ["framework/bigcommerce"] + "@framework/*": ["framework/vendure/*"], + "@framework": ["framework/vendure"] } }, "include": [