mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
(feat): create aquilacms provider
(style): replace all bigcommerce name by aquilacms
This commit is contained in:
parent
f770ad7a91
commit
94861a922a
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
6
framework/aquilacms/.env.template
Normal file
6
framework/aquilacms/.env.template
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
BIGCOMMERCE_STOREFRONT_API_URL=
|
||||||
|
BIGCOMMERCE_STOREFRONT_API_TOKEN=
|
||||||
|
BIGCOMMERCE_STORE_API_URL=
|
||||||
|
BIGCOMMERCE_STORE_API_TOKEN=
|
||||||
|
BIGCOMMERCE_STORE_API_CLIENT_ID=
|
||||||
|
BIGCOMMERCE_CHANNEL_ID=
|
380
framework/aquilacms/README.md
Normal file
380
framework/aquilacms/README.md
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
# 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: <b>set your environment variables</b> 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=<>
|
||||||
|
BIGCOMMERCE_CHANNEL_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 (
|
||||||
|
<CommerceProvider locale={locale}>
|
||||||
|
{children}
|
||||||
|
</CommerceProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
{children}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### useLogout
|
||||||
|
|
||||||
|
Hook to logout user.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
...
|
||||||
|
import useLogout from '@bigcommerce/storefront-data-hooks/use-logout'
|
||||||
|
|
||||||
|
const LogoutLink = () => {
|
||||||
|
const logout = useLogout()
|
||||||
|
return (
|
||||||
|
<a onClick={() => logout()}>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<div>Hello, {data.firstName}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<form onSubmit={handleSignup}>
|
||||||
|
{children}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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: LineItem) => count + item.quantity
|
||||||
|
|
||||||
|
const CartNumber = () => {
|
||||||
|
const { data } = useCart()
|
||||||
|
const itemsCount = data?.lineItems.reduce(countItem, 0) ?? 0
|
||||||
|
|
||||||
|
return itemsCount > 0 ? <span>{itemsCount}</span> : 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 <button onClick={addToCart}>Add To Cart</button>
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
max={99}
|
||||||
|
min={0}
|
||||||
|
value={quantity}
|
||||||
|
onChange={updateQuantity}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <button onClick={handleRemove}>Remove</button>
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<button onClick={handleWishlistChange}>
|
||||||
|
<Heart fill={itemInWishlist ? 'var(--pink)' : 'none'} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<Grid layout="normal">
|
||||||
|
{data.products.map(({ node }) => (
|
||||||
|
<ProductCard key={node.path} product={node} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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))
|
45
framework/aquilacms/api/cart/handlers/add-item.ts
Normal file
45
framework/aquilacms/api/cart/handlers/add-item.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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?include=line_items.physical_items.options`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
: await config.storeApiFetch(
|
||||||
|
'/v3/carts?include=line_items.physical_items.options',
|
||||||
|
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
|
32
framework/aquilacms/api/cart/handlers/get-cart.ts
Normal file
32
framework/aquilacms/api/cart/handlers/get-cart.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { AquilacmsCart } from '../../../types'
|
||||||
|
import { AquilacmsApiError } from '../../utils/errors'
|
||||||
|
import getCartCookie from '../../utils/get-cart-cookie'
|
||||||
|
import type { CartHandlers } from '..'
|
||||||
|
|
||||||
|
// Return current cart info
|
||||||
|
const getCart: CartHandlers['getCart'] = async ({
|
||||||
|
res,
|
||||||
|
body: { cartId },
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
let result: { data?: AquilacmsCart } = {}
|
||||||
|
|
||||||
|
if (cartId) {
|
||||||
|
try {
|
||||||
|
result = await config.storeApiFetch(
|
||||||
|
`/v3/carts/${cartId}?include=line_items.physical_items.options`
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AquilacmsApiError && 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
|
33
framework/aquilacms/api/cart/handlers/remove-item.ts
Normal file
33
framework/aquilacms/api/cart/handlers/remove-item.ts
Normal file
@ -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}?include=line_items.physical_items.options`,
|
||||||
|
{ 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
|
35
framework/aquilacms/api/cart/handlers/update-item.ts
Normal file
35
framework/aquilacms/api/cart/handlers/update-item.ts
Normal file
@ -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}?include=line_items.physical_items.options`,
|
||||||
|
{
|
||||||
|
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
|
78
framework/aquilacms/api/cart/index.ts
Normal file
78
framework/aquilacms/api/cart/index.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import { AquilacmsApiError } 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'
|
||||||
|
import type {
|
||||||
|
AquilacmsCart,
|
||||||
|
GetCartHandlerBody,
|
||||||
|
AddCartItemHandlerBody,
|
||||||
|
UpdateCartItemHandlerBody,
|
||||||
|
RemoveCartItemHandlerBody,
|
||||||
|
} from '../../types'
|
||||||
|
|
||||||
|
export type CartHandlers = {
|
||||||
|
getCart: AquilacmsHandler<AquilacmsCart, GetCartHandlerBody>
|
||||||
|
addItem: AquilacmsHandler<AquilacmsCart, AddCartItemHandlerBody>
|
||||||
|
updateItem: AquilacmsHandler<AquilacmsCart, UpdateCartItemHandlerBody>
|
||||||
|
removeItem: AquilacmsHandler<AquilacmsCart, RemoveCartItemHandlerBody>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
|
||||||
|
// TODO: a complete implementation should have schema validation for `req.body`
|
||||||
|
const cartApi: AquilacmsApiHandler<AquilacmsCart, CartHandlers> = 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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms 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, {})
|
79
framework/aquilacms/api/catalog/handlers/get-products.ts
Normal file
79
framework/aquilacms/api/catalog/handlers/get-products.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Product } from '@commerce/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[Number(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
|
48
framework/aquilacms/api/catalog/products.ts
Normal file
48
framework/aquilacms/api/catalog/products.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { Product } from '@commerce/types'
|
||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import { AquilacmsApiError } from '../utils/errors'
|
||||||
|
import getProducts from './handlers/get-products'
|
||||||
|
|
||||||
|
export type SearchProductsData = {
|
||||||
|
products: Product[]
|
||||||
|
found: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductsHandlers = {
|
||||||
|
getProducts: AquilacmsHandler<
|
||||||
|
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: AquilacmsApiHandler<
|
||||||
|
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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handlers = { getProducts }
|
||||||
|
|
||||||
|
export default createApiHandler(productsApi, handlers, {})
|
77
framework/aquilacms/api/checkout.ts
Normal file
77
framework/aquilacms/api/checkout.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import isAllowedMethod from './utils/is-allowed-method'
|
||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
} from './utils/create-api-handler'
|
||||||
|
import { AquilacmsApiError } from './utils/errors'
|
||||||
|
|
||||||
|
const METHODS = ['GET']
|
||||||
|
const fullCheckout = true
|
||||||
|
|
||||||
|
// TODO: a complete implementation should have schema validation for `req.body`
|
||||||
|
const checkoutApi: AquilacmsApiHandler<any> = 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 = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Checkout</title>
|
||||||
|
<script src="https://checkout-sdk.bigcommerce.com/v1/loader.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
checkoutKitLoader.load('checkout-sdk').then(function (service) {
|
||||||
|
service.embedCheckout({
|
||||||
|
containerId: 'checkout',
|
||||||
|
url: '${data.embedded_checkout_url}'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="checkout"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
res.status(200)
|
||||||
|
res.setHeader('Content-Type', 'text/html')
|
||||||
|
res.write(html)
|
||||||
|
res.end()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createApiHandler(checkoutApi, {}, {})
|
@ -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<GetLoggedInCustomerQuery['customer']>
|
||||||
|
|
||||||
|
const getLoggedInCustomer: CustomersHandlers['getLoggedInCustomer'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
const token = req.cookies[config.customerCookie]
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const { data } = await config.fetch<GetLoggedInCustomerQuery>(
|
||||||
|
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
|
49
framework/aquilacms/api/customers/handlers/login.ts
Normal file
49
framework/aquilacms/api/customers/handlers/login.ts
Normal file
@ -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
|
23
framework/aquilacms/api/customers/handlers/logout.ts
Normal file
23
framework/aquilacms/api/customers/handlers/logout.ts
Normal file
@ -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
|
62
framework/aquilacms/api/customers/handlers/signup.ts
Normal file
62
framework/aquilacms/api/customers/handlers/signup.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { AquilacmsApiError } 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 AquilacmsApiError && 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
|
46
framework/aquilacms/api/customers/index.ts
Normal file
46
framework/aquilacms/api/customers/index.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import { AquilacmsApiError } from '../utils/errors'
|
||||||
|
import getLoggedInCustomer, {
|
||||||
|
Customer,
|
||||||
|
} from './handlers/get-logged-in-customer'
|
||||||
|
|
||||||
|
export type { Customer }
|
||||||
|
|
||||||
|
export type CustomerData = {
|
||||||
|
customer: Customer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CustomersHandlers = {
|
||||||
|
getLoggedInCustomer: AquilacmsHandler<CustomerData>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['GET']
|
||||||
|
|
||||||
|
const customersApi: AquilacmsApiHandler<
|
||||||
|
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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = { getLoggedInCustomer }
|
||||||
|
|
||||||
|
export default createApiHandler(customersApi, handlers, {})
|
45
framework/aquilacms/api/customers/login.ts
Normal file
45
framework/aquilacms/api/customers/login.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import { AquilacmsApiError } from '../utils/errors'
|
||||||
|
import login from './handlers/login'
|
||||||
|
|
||||||
|
export type LoginBody = {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginHandlers = {
|
||||||
|
login: AquilacmsHandler<null, Partial<LoginBody>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['POST']
|
||||||
|
|
||||||
|
const loginApi: AquilacmsApiHandler<null, LoginHandlers> = 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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = { login }
|
||||||
|
|
||||||
|
export default createApiHandler(loginApi, handlers, {})
|
42
framework/aquilacms/api/customers/logout.ts
Normal file
42
framework/aquilacms/api/customers/logout.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import { AquilacmsApiError } from '../utils/errors'
|
||||||
|
import logout from './handlers/logout'
|
||||||
|
|
||||||
|
export type LogoutHandlers = {
|
||||||
|
logout: AquilacmsHandler<null, { redirectTo?: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['GET']
|
||||||
|
|
||||||
|
const logoutApi: AquilacmsApiHandler<null, LogoutHandlers> = 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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = { logout }
|
||||||
|
|
||||||
|
export default createApiHandler(logoutApi, handlers, {})
|
50
framework/aquilacms/api/customers/signup.ts
Normal file
50
framework/aquilacms/api/customers/signup.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import { AquilacmsApiError } from '../utils/errors'
|
||||||
|
import signup from './handlers/signup'
|
||||||
|
|
||||||
|
export type SignupBody = {
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SignupHandlers = {
|
||||||
|
signup: AquilacmsHandler<null, { cartId?: string } & Partial<SignupBody>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['POST']
|
||||||
|
|
||||||
|
const signupApi: AquilacmsApiHandler<null, SignupHandlers> = 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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = { signup }
|
||||||
|
|
||||||
|
export default createApiHandler(signupApi, handlers, {})
|
2993
framework/aquilacms/api/definitions/catalog.ts
Normal file
2993
framework/aquilacms/api/definitions/catalog.ts
Normal file
File diff suppressed because it is too large
Load Diff
329
framework/aquilacms/api/definitions/store-content.ts
Normal file
329
framework/aquilacms/api/definitions/store-content.ts
Normal file
@ -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: <Continent-name>/<City-name>.
|
||||||
|
*/
|
||||||
|
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 `<meta/>` element.
|
||||||
|
*/
|
||||||
|
meta_description?: string
|
||||||
|
/**
|
||||||
|
* Keywords for this blog post’s `<meta/>` 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 `<meta/>` element.
|
||||||
|
*/
|
||||||
|
meta_description?: string
|
||||||
|
/**
|
||||||
|
* HTML or variable that populates this page’s `<body>` 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 `<title>` 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'
|
||||||
|
}
|
||||||
|
}
|
142
framework/aquilacms/api/definitions/wishlist.ts
Normal file
142
framework/aquilacms/api/definitions/wishlist.ts
Normal file
@ -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'] }
|
||||||
|
}
|
9
framework/aquilacms/api/fragments/category-tree.ts
Normal file
9
framework/aquilacms/api/fragments/category-tree.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const categoryTreeItemFragment = /* GraphQL */ `
|
||||||
|
fragment categoryTreeItem on CategoryTreeItem {
|
||||||
|
entityId
|
||||||
|
name
|
||||||
|
path
|
||||||
|
description
|
||||||
|
productCount
|
||||||
|
}
|
||||||
|
`
|
113
framework/aquilacms/api/fragments/product.ts
Normal file
113
framework/aquilacms/api/fragments/product.ts
Normal file
@ -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}
|
||||||
|
`
|
71
framework/aquilacms/api/index.ts
Normal file
71
framework/aquilacms/api/index.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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 AquilacmsConfig extends CommerceAPIConfig {
|
||||||
|
// Indicates if the returned metadata with translations should be applied to the
|
||||||
|
// data or returned as it is
|
||||||
|
applyLocale?: boolean
|
||||||
|
storeApiUrl: string
|
||||||
|
storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
const STORE_URL = process.env.AQUILACMS_URL
|
||||||
|
const STORE_API_URL = process.env.AQUILACMS_API_URL
|
||||||
|
|
||||||
|
if (!STORE_URL) {
|
||||||
|
throw new Error(
|
||||||
|
`The environment variable AQUILACMS_URL is missing and it's required to access your store`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!STORE_API_URL) {
|
||||||
|
throw new Error(
|
||||||
|
`The environment variable AQUILACMS_API_URL is missing and it's required to access your store`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Config {
|
||||||
|
private config: AquilacmsConfig
|
||||||
|
|
||||||
|
constructor(config: Omit<AquilacmsConfig, 'customerCookie'>) {
|
||||||
|
this.config = {
|
||||||
|
...config,
|
||||||
|
// The customerCookie is not customizable for now, BC sets the cookie and it's
|
||||||
|
// not important to rename it
|
||||||
|
customerCookie: 'SHOP_TOKEN',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(userConfig: Partial<AquilacmsConfig> = {}) {
|
||||||
|
return Object.entries(userConfig).reduce<AquilacmsConfig>(
|
||||||
|
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||||
|
{ ...this.config }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(newConfig: Partial<AquilacmsConfig>) {
|
||||||
|
Object.assign(this.config, newConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ONE_DAY = 60 * 60 * 24
|
||||||
|
const config = new Config({
|
||||||
|
commerceUrl: STORE_URL,
|
||||||
|
apiToken: '',
|
||||||
|
cartCookie: process.env.AQUILACMS_CART_COOKIE ?? 'aquilacms_cartId',
|
||||||
|
cartCookieMaxAge: ONE_DAY * 30,
|
||||||
|
fetch: fetchGraphqlApi,
|
||||||
|
applyLocale: true,
|
||||||
|
storeApiUrl: STORE_API_URL,
|
||||||
|
storeApiFetch: fetchStoreApi,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function getConfig(userConfig?: Partial<AquilacmsConfig>) {
|
||||||
|
return config.getConfig(userConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setConfig(newConfig: Partial<AquilacmsConfig>) {
|
||||||
|
return config.setConfig(newConfig)
|
||||||
|
}
|
14
framework/aquilacms/api/utils/concat-cookie.ts
Normal file
14
framework/aquilacms/api/utils/concat-cookie.ts
Normal file
@ -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)]
|
||||||
|
}
|
58
framework/aquilacms/api/utils/create-api-handler.ts
Normal file
58
framework/aquilacms/api/utils/create-api-handler.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { AquilacmsConfig, getConfig } from '..'
|
||||||
|
|
||||||
|
export type AquilacmsApiHandler<
|
||||||
|
T = any,
|
||||||
|
H extends AquilacmsHandlers = {},
|
||||||
|
Options extends {} = {}
|
||||||
|
> = (
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<AquilacmsApiResponse<T>>,
|
||||||
|
config: AquilacmsConfig,
|
||||||
|
handlers: H,
|
||||||
|
// Custom configs that may be used by a particular handler
|
||||||
|
options: Options
|
||||||
|
) => void | Promise<void>
|
||||||
|
|
||||||
|
export type AquilacmsHandler<T = any, Body = null> = (options: {
|
||||||
|
req: NextApiRequest
|
||||||
|
res: NextApiResponse<AquilacmsApiResponse<T>>
|
||||||
|
config: AquilacmsConfig
|
||||||
|
body: Body
|
||||||
|
}) => void | Promise<void>
|
||||||
|
|
||||||
|
export type AquilacmsHandlers<T = any> = {
|
||||||
|
[k: string]: AquilacmsHandler<T, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AquilacmsApiResponse<T> = {
|
||||||
|
data: T | null
|
||||||
|
errors?: { message: string; code?: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createApiHandler<
|
||||||
|
T = any,
|
||||||
|
H extends AquilacmsHandlers = {},
|
||||||
|
Options extends {} = {}
|
||||||
|
>(
|
||||||
|
handler: AquilacmsApiHandler<T, H, Options>,
|
||||||
|
handlers: H,
|
||||||
|
defaultOptions: Options
|
||||||
|
) {
|
||||||
|
return function getApiHandler({
|
||||||
|
config,
|
||||||
|
operations,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
framework/aquilacms/api/utils/errors.ts
Normal file
25
framework/aquilacms/api/utils/errors.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { Response } from '@vercel/fetch'
|
||||||
|
|
||||||
|
// Used for GraphQL errors
|
||||||
|
export class AquilacmsGraphQLError extends Error {}
|
||||||
|
|
||||||
|
export class AquilacmsApiError extends Error {
|
||||||
|
status: number
|
||||||
|
res: Response
|
||||||
|
data: any
|
||||||
|
|
||||||
|
constructor(msg: string, res: Response, data?: any) {
|
||||||
|
super(msg)
|
||||||
|
this.name = 'AquilacmsApiError'
|
||||||
|
this.status = res.status
|
||||||
|
this.res = res
|
||||||
|
this.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AquilacmsNetworkError extends Error {
|
||||||
|
constructor(msg: string) {
|
||||||
|
super(msg)
|
||||||
|
this.name = 'AquilacmsNetworkError'
|
||||||
|
}
|
||||||
|
}
|
38
framework/aquilacms/api/utils/fetch-graphql-api.ts
Normal file
38
framework/aquilacms/api/utils/fetch-graphql-api.ts
Normal file
@ -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 + (preview ? '/preview' : ''), {
|
||||||
|
...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 Aquilacms API' }],
|
||||||
|
status: res.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: json.data, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchGraphqlApi
|
71
framework/aquilacms/api/utils/fetch-store-api.ts
Normal file
71
framework/aquilacms/api/utils/fetch-store-api.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type { RequestInit, Response } from '@vercel/fetch'
|
||||||
|
import { getConfig } from '..'
|
||||||
|
import { AquilacmsApiError, AquilacmsNetworkError } 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 AquilacmsNetworkError(
|
||||||
|
`Fetch to Aquilacms 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 AquilacmsApiError(msg, res, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status !== 204 && !isJSON) {
|
||||||
|
throw new AquilacmsApiError(
|
||||||
|
`Fetch to Aquilacms 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
|
||||||
|
}
|
||||||
|
}
|
3
framework/aquilacms/api/utils/fetch.ts
Normal file
3
framework/aquilacms/api/utils/fetch.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import zeitFetch from '@vercel/fetch'
|
||||||
|
|
||||||
|
export default zeitFetch()
|
5
framework/aquilacms/api/utils/filter-edges.ts
Normal file
5
framework/aquilacms/api/utils/filter-edges.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function filterEdges<T>(
|
||||||
|
edges: (T | null | undefined)[] | null | undefined
|
||||||
|
) {
|
||||||
|
return edges?.filter((edge): edge is T => !!edge) ?? []
|
||||||
|
}
|
20
framework/aquilacms/api/utils/get-cart-cookie.ts
Normal file
20
framework/aquilacms/api/utils/get-cart-cookie.ts
Normal file
@ -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)
|
||||||
|
}
|
28
framework/aquilacms/api/utils/is-allowed-method.ts
Normal file
28
framework/aquilacms/api/utils/is-allowed-method.ts
Normal file
@ -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
|
||||||
|
}
|
28
framework/aquilacms/api/utils/parse-item.ts
Normal file
28
framework/aquilacms/api/utils/parse-item.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { ItemBody as WishlistItemBody } from '../wishlist'
|
||||||
|
import type { CartItemBody, OptionSelections } from '../../types'
|
||||||
|
|
||||||
|
type BCWishlistItemBody = {
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type BCCartItemBody = {
|
||||||
|
product_id: number
|
||||||
|
variant_id: number
|
||||||
|
quantity?: number
|
||||||
|
option_selections?: OptionSelections
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseWishlistItem = (
|
||||||
|
item: WishlistItemBody
|
||||||
|
): BCWishlistItemBody => ({
|
||||||
|
product_id: Number(item.productId),
|
||||||
|
variant_id: Number(item.variantId),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
|
||||||
|
quantity: item.quantity,
|
||||||
|
product_id: Number(item.productId),
|
||||||
|
variant_id: Number(item.variantId),
|
||||||
|
option_selections: item.optionSelections,
|
||||||
|
})
|
21
framework/aquilacms/api/utils/set-product-locale-meta.ts
Normal file
21
framework/aquilacms/api/utils/set-product-locale-meta.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
framework/aquilacms/api/utils/types.ts
Normal file
7
framework/aquilacms/api/utils/types.ts
Normal file
@ -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]>
|
||||||
|
}
|
56
framework/aquilacms/api/wishlist/handlers/add-item.ts
Normal file
56
framework/aquilacms/api/wishlist/handlers/add-item.ts
Normal file
@ -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
|
37
framework/aquilacms/api/wishlist/handlers/get-wishlist.ts
Normal file
37
framework/aquilacms/api/wishlist/handlers/get-wishlist.ts
Normal file
@ -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
|
39
framework/aquilacms/api/wishlist/handlers/remove-item.ts
Normal file
39
framework/aquilacms/api/wishlist/handlers/remove-item.ts
Normal file
@ -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
|
104
framework/aquilacms/api/wishlist/index.ts
Normal file
104
framework/aquilacms/api/wishlist/index.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import isAllowedMethod from '../utils/is-allowed-method'
|
||||||
|
import createApiHandler, {
|
||||||
|
AquilacmsApiHandler,
|
||||||
|
AquilacmsHandler,
|
||||||
|
} from '../utils/create-api-handler'
|
||||||
|
import { AquilacmsApiError } 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'
|
||||||
|
import type { Product, ProductVariant, Customer } from '@commerce/types'
|
||||||
|
|
||||||
|
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['entityId']
|
||||||
|
is_public: number
|
||||||
|
name: string
|
||||||
|
items: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddWishlistBody = { wishlist: WishlistBody }
|
||||||
|
|
||||||
|
export type WishlistHandlers = {
|
||||||
|
getWishlist: AquilacmsHandler<
|
||||||
|
Wishlist,
|
||||||
|
{ customerToken?: string; includeProducts?: boolean }
|
||||||
|
>
|
||||||
|
addItem: AquilacmsHandler<
|
||||||
|
Wishlist,
|
||||||
|
{ customerToken?: string } & Partial<AddItemBody>
|
||||||
|
>
|
||||||
|
removeItem: AquilacmsHandler<
|
||||||
|
Wishlist,
|
||||||
|
{ customerToken?: string } & Partial<RemoveItemBody>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS = ['GET', 'POST', 'DELETE']
|
||||||
|
|
||||||
|
// TODO: a complete implementation should have schema validation for `req.body`
|
||||||
|
const wishlistApi: AquilacmsApiHandler<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 AquilacmsApiError
|
||||||
|
? 'An unexpected error ocurred with the Aquilacms API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handlers = {
|
||||||
|
getWishlist,
|
||||||
|
addItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createApiHandler(wishlistApi, handlers, {})
|
3
framework/aquilacms/auth/index.ts
Normal file
3
framework/aquilacms/auth/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as useLogin } from './use-login'
|
||||||
|
export { default as useLogout } from './use-logout'
|
||||||
|
export { default as useSignup } from './use-signup'
|
73
framework/aquilacms/auth/login.ts
Normal file
73
framework/aquilacms/auth/login.ts
Normal file
@ -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 { AquilacmsConfig, 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?: AquilacmsConfig
|
||||||
|
res: ServerResponse
|
||||||
|
}): Promise<LoginResult>
|
||||||
|
|
||||||
|
async function login<T extends { result?: any }, V = any>(opts: {
|
||||||
|
query: string
|
||||||
|
variables: V
|
||||||
|
res: ServerResponse
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
}): Promise<LoginResult<T>>
|
||||||
|
|
||||||
|
async function login({
|
||||||
|
query = loginMutation,
|
||||||
|
variables,
|
||||||
|
res: response,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables: LoginVariables
|
||||||
|
res: ServerResponse
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
}): 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
|
40
framework/aquilacms/auth/use-login.tsx
Normal file
40
framework/aquilacms/auth/use-login.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
|
import type { LoginBody } from '../api/customers/login'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
|
export default useLogin as UseLogin<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<null, {}, LoginBody> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/customers/login',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
|
if (!(email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch({
|
||||||
|
...options,
|
||||||
|
body: { email, password },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function login(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
25
framework/aquilacms/auth/use-logout.tsx
Normal file
25
framework/aquilacms/auth/use-logout.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
|
export default useLogout as UseLogout<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<null> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/customers/logout',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
|
const { mutate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function logout() {
|
||||||
|
const data = await fetch()
|
||||||
|
await mutate(null, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
44
framework/aquilacms/auth/use-signup.tsx
Normal file
44
framework/aquilacms/auth/use-signup.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
|
import type { SignupBody } from '../api/customers/signup'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<null, {}, SignupBody, SignupBody> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/customers/signup',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { firstName, lastName, email, password },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}) {
|
||||||
|
if (!(firstName && lastName && email && password)) {
|
||||||
|
throw new CommerceError({
|
||||||
|
message:
|
||||||
|
'A first name, last name, email and password are required to signup',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch({
|
||||||
|
...options,
|
||||||
|
body: { firstName, lastName, email, password },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
|
const { revalidate } = useCustomer()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function signup(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
4
framework/aquilacms/cart/index.ts
Normal file
4
framework/aquilacms/cart/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as useCart } from './use-cart'
|
||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
||||||
|
export { default as useUpdateItem } from './use-update-item'
|
50
framework/aquilacms/cart/use-add-item.tsx
Normal file
50
framework/aquilacms/cart/use-add-item.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
import type {
|
||||||
|
Cart,
|
||||||
|
AquilacmsCart,
|
||||||
|
CartItemBody,
|
||||||
|
AddCartItemBody,
|
||||||
|
} from '../types'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/cart',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
async fetcher({ input: item, options, 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',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch<AquilacmsCart, AddCartItemBody>({
|
||||||
|
...options,
|
||||||
|
body: { item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return normalizeCart(data)
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
41
framework/aquilacms/cart/use-cart.tsx
Normal file
41
framework/aquilacms/cart/use-cart.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart'
|
||||||
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
import type { Cart } from '../types'
|
||||||
|
|
||||||
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<
|
||||||
|
Cart | null,
|
||||||
|
{},
|
||||||
|
FetchCartInput,
|
||||||
|
{ isEmpty?: boolean }
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/cart',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { cartId }, options, fetch }) {
|
||||||
|
const data = cartId ? await fetch(options) : null
|
||||||
|
return data && normalizeCart(data)
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) => (input) => {
|
||||||
|
const response = useData({
|
||||||
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.lineItems.length ?? 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
71
framework/aquilacms/cart/use-remove-item.tsx
Normal file
71
framework/aquilacms/cart/use-remove-item.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useRemoveItem, {
|
||||||
|
RemoveItemInput as RemoveItemInputBase,
|
||||||
|
UseRemoveItem,
|
||||||
|
} from '@commerce/cart/use-remove-item'
|
||||||
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
import type {
|
||||||
|
RemoveCartItemBody,
|
||||||
|
Cart,
|
||||||
|
AquilacmsCart,
|
||||||
|
LineItem,
|
||||||
|
} from '../types'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export type RemoveItemFn<T = any> = T extends LineItem
|
||||||
|
? (input?: RemoveItemInput<T>) => Promise<Cart | null>
|
||||||
|
: (input: RemoveItemInput<T>) => Promise<Cart | null>
|
||||||
|
|
||||||
|
export type RemoveItemInput<T = any> = T extends LineItem
|
||||||
|
? Partial<RemoveItemInputBase>
|
||||||
|
: RemoveItemInputBase
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/cart',
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<RemoveCartItemBody>) {
|
||||||
|
const data = await fetch<AquilacmsCart>({
|
||||||
|
...options,
|
||||||
|
body: { itemId },
|
||||||
|
})
|
||||||
|
return normalizeCart(data)
|
||||||
|
},
|
||||||
|
useHook: ({
|
||||||
|
fetch,
|
||||||
|
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => <
|
||||||
|
T extends LineItem | undefined = undefined
|
||||||
|
>(
|
||||||
|
ctx: { item?: T } = {}
|
||||||
|
) => {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart()
|
||||||
|
const removeItem: RemoveItemFn<LineItem> = async (input) => {
|
||||||
|
const itemId = input?.id ?? item?.id
|
||||||
|
|
||||||
|
if (!itemId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId } })
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||||
|
},
|
||||||
|
}
|
97
framework/aquilacms/cart/use-update-item.tsx
Normal file
97
framework/aquilacms/cart/use-update-item.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
import type {
|
||||||
|
MutationHookContext,
|
||||||
|
HookFetcherContext,
|
||||||
|
} from '@commerce/utils/types'
|
||||||
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
|
import useUpdateItem, {
|
||||||
|
UpdateItemInput as UpdateItemInputBase,
|
||||||
|
UseUpdateItem,
|
||||||
|
} from '@commerce/cart/use-update-item'
|
||||||
|
import { normalizeCart } from '../lib/normalize'
|
||||||
|
import type {
|
||||||
|
UpdateCartItemBody,
|
||||||
|
Cart,
|
||||||
|
AquilacmsCart,
|
||||||
|
LineItem,
|
||||||
|
} from '../types'
|
||||||
|
import { handler as removeItemHandler } from './use-remove-item'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
|
||||||
|
export type UpdateItemInput<T = any> = T extends LineItem
|
||||||
|
? Partial<UpdateItemInputBase<LineItem>>
|
||||||
|
: UpdateItemInputBase<LineItem>
|
||||||
|
|
||||||
|
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/cart',
|
||||||
|
method: 'PUT',
|
||||||
|
},
|
||||||
|
async fetcher({
|
||||||
|
input: { itemId, item },
|
||||||
|
options,
|
||||||
|
fetch,
|
||||||
|
}: HookFetcherContext<UpdateCartItemBody>) {
|
||||||
|
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 removeItemHandler.fetcher({
|
||||||
|
options: removeItemHandler.fetchOptions,
|
||||||
|
input: { itemId },
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (item.quantity) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'The item quantity has to be a valid integer',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch<AquilacmsCart, UpdateCartItemBody>({
|
||||||
|
...options,
|
||||||
|
body: { itemId, item },
|
||||||
|
})
|
||||||
|
|
||||||
|
return normalizeCart(data)
|
||||||
|
},
|
||||||
|
useHook: ({
|
||||||
|
fetch,
|
||||||
|
}: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
|
||||||
|
T extends LineItem | undefined = undefined
|
||||||
|
>(
|
||||||
|
ctx: {
|
||||||
|
item?: T
|
||||||
|
wait?: number
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
const { item } = ctx
|
||||||
|
const { mutate } = useCart() as any
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
debounce(async (input: UpdateItemInput<T>) => {
|
||||||
|
const itemId = input.id ?? item?.id
|
||||||
|
const productId = input.productId ?? item?.productId
|
||||||
|
const variantId = input.productId ?? item?.variantId
|
||||||
|
|
||||||
|
if (!itemId || !productId || !variantId) {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: 'Invalid input used for this operation',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({
|
||||||
|
input: {
|
||||||
|
itemId,
|
||||||
|
item: { productId, variantId, quantity: input.quantity },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate(data, false)
|
||||||
|
return data
|
||||||
|
}, ctx.wait ?? 500),
|
||||||
|
[fetch, mutate]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
6
framework/aquilacms/commerce.config.json
Normal file
6
framework/aquilacms/commerce.config.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"provider": "aquilacms",
|
||||||
|
"features": {
|
||||||
|
"wishlist": false
|
||||||
|
}
|
||||||
|
}
|
43
framework/aquilacms/common/get-all-pages.ts
Normal file
43
framework/aquilacms/common/get-all-pages.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
||||||
|
import { AquilacmsConfig, 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?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetAllPagesResult>
|
||||||
|
|
||||||
|
async function getAllPages<T extends { pages: any[] }>(opts: {
|
||||||
|
url: string
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetAllPagesResult<T>>
|
||||||
|
|
||||||
|
async function getAllPages({
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<GetAllPagesResult> {
|
||||||
|
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[] }>
|
||||||
|
>('/v3/content/pages')
|
||||||
|
const pages = (data as RecursiveRequired<typeof data>) ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages: preview ? pages : pages.filter((p) => p.is_visible),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllPages
|
53
framework/aquilacms/common/get-page.ts
Normal file
53
framework/aquilacms/common/get-page.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
||||||
|
import { AquilacmsConfig, 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?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetPageResult>
|
||||||
|
|
||||||
|
async function getPage<T extends { page?: any }, V = any>(opts: {
|
||||||
|
url: string
|
||||||
|
variables: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetPageResult<T>>
|
||||||
|
|
||||||
|
async function getPage({
|
||||||
|
url,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
variables: PageVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
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
|
106
framework/aquilacms/common/get-site-info.ts
Normal file
106
framework/aquilacms/common/get-site-info.ts
Normal file
@ -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 { AquilacmsConfig, 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?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetSiteInfoResult>
|
||||||
|
|
||||||
|
async function getSiteInfo<
|
||||||
|
T extends { categories: any[]; brands: any[] },
|
||||||
|
V = any
|
||||||
|
>(opts: {
|
||||||
|
query: string
|
||||||
|
variables?: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetSiteInfoResult<T>>
|
||||||
|
|
||||||
|
async function getSiteInfo({
|
||||||
|
query = getSiteInfoQuery,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: GetSiteInfoQueryVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
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
|
34
framework/aquilacms/customer/get-customer-id.ts
Normal file
34
framework/aquilacms/customer/get-customer-id.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { GetCustomerIdQuery } from '../schema'
|
||||||
|
import { AquilacmsConfig, getConfig } from '../api'
|
||||||
|
|
||||||
|
export const getCustomerIdQuery = /* GraphQL */ `
|
||||||
|
query getCustomerId {
|
||||||
|
customer {
|
||||||
|
entityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
async function getCustomerId({
|
||||||
|
customerToken,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
customerToken: string
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
}): 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
|
88
framework/aquilacms/customer/get-customer-wishlist.ts
Normal file
88
framework/aquilacms/customer/get-customer-wishlist.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
||||||
|
import { definitions } from '../api/definitions/wishlist'
|
||||||
|
import { AquilacmsConfig, 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?: AquilacmsConfig
|
||||||
|
includeProducts?: boolean
|
||||||
|
}): Promise<GetCustomerWishlistResult>
|
||||||
|
|
||||||
|
async function getCustomerWishlist<
|
||||||
|
T extends { wishlist?: any },
|
||||||
|
V = any
|
||||||
|
>(opts: {
|
||||||
|
url: string
|
||||||
|
variables: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
includeProducts?: boolean
|
||||||
|
}): Promise<GetCustomerWishlistResult<T>>
|
||||||
|
|
||||||
|
async function getCustomerWishlist({
|
||||||
|
config,
|
||||||
|
variables,
|
||||||
|
includeProducts,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
variables: GetCustomerWishlistVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
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[Number(p.id)] = p as any
|
||||||
|
return prods
|
||||||
|
}, {})
|
||||||
|
// Populate the wishlist items with the graphql products
|
||||||
|
wishlist.items.forEach((item) => {
|
||||||
|
const product = item && productsById[item.product_id!]
|
||||||
|
if (item && product) {
|
||||||
|
// @ts-ignore Fix this type when the wishlist type is properly defined
|
||||||
|
item.product = product
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { wishlist: wishlist as RecursiveRequired<typeof wishlist> }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCustomerWishlist
|
1
framework/aquilacms/customer/index.ts
Normal file
1
framework/aquilacms/customer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as useCustomer } from './use-customer'
|
24
framework/aquilacms/customer/use-customer.tsx
Normal file
24
framework/aquilacms/customer/use-customer.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
|
import type { Customer, CustomerData } from '../api/customers'
|
||||||
|
|
||||||
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<Customer | null> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/customers',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
async fetcher({ options, fetch }) {
|
||||||
|
const data = await fetch<CustomerData | null>(options)
|
||||||
|
return data?.customer ?? null
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) => (input) => {
|
||||||
|
return useData({
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input?.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
41
framework/aquilacms/fetcher.ts
Normal file
41
framework/aquilacms/fetcher.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
import type { Fetcher } from '@commerce/utils/types'
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetcher: Fetcher = async ({
|
||||||
|
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 default fetcher
|
35
framework/aquilacms/index.tsx
Normal file
35
framework/aquilacms/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import {
|
||||||
|
CommerceConfig,
|
||||||
|
CommerceProvider as CoreCommerceProvider,
|
||||||
|
useCommerce as useCoreCommerce,
|
||||||
|
} from '@commerce'
|
||||||
|
import { aquilacmsProvider, AquilacmsProvider } from './provider'
|
||||||
|
|
||||||
|
export { aquilacmsProvider }
|
||||||
|
export type { AquilacmsProvider }
|
||||||
|
|
||||||
|
export const aquilacmsConfig: CommerceConfig = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: 'aquilacms_cartId',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AquilacmsConfig = Partial<CommerceConfig>
|
||||||
|
|
||||||
|
export type AquilacmsProps = {
|
||||||
|
children?: ReactNode
|
||||||
|
locale: string
|
||||||
|
} & AquilacmsConfig
|
||||||
|
|
||||||
|
export function CommerceProvider({ children, ...config }: AquilacmsProps) {
|
||||||
|
return (
|
||||||
|
<CoreCommerceProvider
|
||||||
|
provider={aquilacmsProvider}
|
||||||
|
config={{ ...aquilacmsConfig, ...config }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CoreCommerceProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCommerce = () => useCoreCommerce<AquilacmsProvider>()
|
13
framework/aquilacms/lib/immutability.ts
Normal file
13
framework/aquilacms/lib/immutability.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
113
framework/aquilacms/lib/normalize.ts
Normal file
113
framework/aquilacms/lib/normalize.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { Product } from '@commerce/types'
|
||||||
|
import type { Cart, AquilacmsCart, LineItem } from '../types'
|
||||||
|
import update from './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: AquilacmsCart): Cart {
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
customerId: String(data.customer_id),
|
||||||
|
email: data.email,
|
||||||
|
createdAt: data.created_time,
|
||||||
|
currency: data.currency,
|
||||||
|
taxesIncluded: data.tax_included,
|
||||||
|
lineItems: data.line_items.physical_items.map(normalizeLineItem),
|
||||||
|
lineItemsSubtotalPrice: data.base_amount,
|
||||||
|
subtotalPrice: data.base_amount + data.discount_amount,
|
||||||
|
totalPrice: data.cart_amount,
|
||||||
|
discounts: data.discounts?.map((discount) => ({
|
||||||
|
value: discount.discounted_amount,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLineItem(item: any): LineItem {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
variantId: String(item.variant_id),
|
||||||
|
productId: String(item.product_id),
|
||||||
|
name: item.name,
|
||||||
|
quantity: item.quantity,
|
||||||
|
variant: {
|
||||||
|
id: String(item.variant_id),
|
||||||
|
sku: item.sku,
|
||||||
|
name: item.name,
|
||||||
|
image: {
|
||||||
|
url: item.image_url,
|
||||||
|
},
|
||||||
|
requiresShipping: item.is_require_shipping,
|
||||||
|
price: item.sale_price,
|
||||||
|
listPrice: item.list_price,
|
||||||
|
},
|
||||||
|
path: item.url.split('/')[3],
|
||||||
|
discounts: item.discounts.map((discount: any) => ({
|
||||||
|
value: discount.discounted_amount,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
8
framework/aquilacms/next.config.js
Normal file
8
framework/aquilacms/next.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const commerce = require('./commerce.config.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
commerce,
|
||||||
|
images: {
|
||||||
|
domains: ['cdn11.bigcommerce.com'],
|
||||||
|
},
|
||||||
|
}
|
71
framework/aquilacms/product/get-all-product-paths.ts
Normal file
71
framework/aquilacms/product/get-all-product-paths.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type {
|
||||||
|
GetAllProductPathsQuery,
|
||||||
|
GetAllProductPathsQueryVariables,
|
||||||
|
} from '../schema'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
||||||
|
import filterEdges from '../api/utils/filter-edges'
|
||||||
|
import { AquilacmsConfig, getConfig } from '../api'
|
||||||
|
|
||||||
|
export const getAllProductPathsQuery = /* GraphQL */ `
|
||||||
|
query getAllProductPaths($first: Int = 100) {
|
||||||
|
site {
|
||||||
|
products(first: $first) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
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?: AquilacmsConfig
|
||||||
|
}): Promise<GetAllProductPathsResult>
|
||||||
|
|
||||||
|
async function getAllProductPaths<
|
||||||
|
T extends { products: any[] },
|
||||||
|
V = any
|
||||||
|
>(opts: {
|
||||||
|
query: string
|
||||||
|
variables?: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
}): Promise<GetAllProductPathsResult<T>>
|
||||||
|
|
||||||
|
async function getAllProductPaths({
|
||||||
|
query = getAllProductPathsQuery,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: GetAllProductPathsQueryVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
} = {}): 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.site?.products?.edges
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: filterEdges(products as RecursiveRequired<typeof products>),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProductPaths
|
135
framework/aquilacms/product/get-all-products.ts
Normal file
135
framework/aquilacms/product/get-all-products.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import type {
|
||||||
|
GetAllProductsQuery,
|
||||||
|
GetAllProductsQueryVariables,
|
||||||
|
} from '../schema'
|
||||||
|
import type { Product } from '@commerce/types'
|
||||||
|
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 { AquilacmsConfig, getConfig } from '../api'
|
||||||
|
import { normalizeProduct } from '../lib/normalize'
|
||||||
|
|
||||||
|
export const getAllProductsQuery = /* GraphQL */ `
|
||||||
|
query getAllProducts(
|
||||||
|
$hasLocale: Boolean = false
|
||||||
|
$locale: String = "null"
|
||||||
|
$entityIds: [Int!]
|
||||||
|
$first: Int = 10
|
||||||
|
$products: Boolean = false
|
||||||
|
$featuredProducts: Boolean = false
|
||||||
|
$bestSellingProducts: Boolean = false
|
||||||
|
$newestProducts: Boolean = false
|
||||||
|
) {
|
||||||
|
site {
|
||||||
|
products(first: $first, entityIds: $entityIds) @include(if: $products) {
|
||||||
|
...productConnnection
|
||||||
|
}
|
||||||
|
featuredProducts(first: $first) @include(if: $featuredProducts) {
|
||||||
|
...productConnnection
|
||||||
|
}
|
||||||
|
bestSellingProducts(first: $first) @include(if: $bestSellingProducts) {
|
||||||
|
...productConnnection
|
||||||
|
}
|
||||||
|
newestProducts(first: $first) @include(if: $newestProducts) {
|
||||||
|
...productConnnection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${productConnectionFragment}
|
||||||
|
`
|
||||||
|
|
||||||
|
export type ProductEdge = NonNullable<
|
||||||
|
NonNullable<GetAllProductsQuery['site']['products']['edges']>[0]
|
||||||
|
>
|
||||||
|
|
||||||
|
export type ProductNode = ProductEdge['node']
|
||||||
|
|
||||||
|
export type GetAllProductsResult<
|
||||||
|
T extends Record<keyof GetAllProductsResult, any[]> = {
|
||||||
|
products: ProductEdge[]
|
||||||
|
}
|
||||||
|
> = T
|
||||||
|
|
||||||
|
const FIELDS = [
|
||||||
|
'products',
|
||||||
|
'featuredProducts',
|
||||||
|
'bestSellingProducts',
|
||||||
|
'newestProducts',
|
||||||
|
]
|
||||||
|
|
||||||
|
export type ProductTypes =
|
||||||
|
| 'products'
|
||||||
|
| 'featuredProducts'
|
||||||
|
| 'bestSellingProducts'
|
||||||
|
| 'newestProducts'
|
||||||
|
|
||||||
|
export type ProductVariables = { field?: ProductTypes } & Omit<
|
||||||
|
GetAllProductsQueryVariables,
|
||||||
|
ProductTypes | 'hasLocale'
|
||||||
|
>
|
||||||
|
|
||||||
|
async function getAllProducts(opts?: {
|
||||||
|
variables?: ProductVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<{ products: Product[] }>
|
||||||
|
|
||||||
|
async function getAllProducts<
|
||||||
|
T extends Record<keyof GetAllProductsResult, any[]>,
|
||||||
|
V = any
|
||||||
|
>(opts: {
|
||||||
|
query: string
|
||||||
|
variables?: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetAllProductsResult<T>>
|
||||||
|
|
||||||
|
async function getAllProducts({
|
||||||
|
query = getAllProductsQuery,
|
||||||
|
variables: { field = 'products', ...vars } = {},
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: ProductVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
// TODO: fix the product type here
|
||||||
|
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const locale = vars.locale || config.locale
|
||||||
|
const variables: GetAllProductsQueryVariables = {
|
||||||
|
...vars,
|
||||||
|
locale,
|
||||||
|
hasLocale: !!locale,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FIELDS.includes(field)) {
|
||||||
|
throw new Error(
|
||||||
|
`The field variable has to match one of ${FIELDS.join(', ')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
variables[field] = true
|
||||||
|
|
||||||
|
// 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<GetAllProductsQuery>>(
|
||||||
|
query,
|
||||||
|
{ variables }
|
||||||
|
)
|
||||||
|
const edges = data.site?.[field]?.edges
|
||||||
|
const products = filterEdges(edges as RecursiveRequired<typeof edges>)
|
||||||
|
|
||||||
|
if (locale && config.applyLocale) {
|
||||||
|
products.forEach((product: RecursivePartial<ProductEdge>) => {
|
||||||
|
if (product.node) setProductLocaleMeta(product.node)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { products: products.map(({ node }) => normalizeProduct(node as any)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getAllProducts
|
121
framework/aquilacms/product/get-product.ts
Normal file
121
framework/aquilacms/product/get-product.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import type { GetProductQuery, GetProductQueryVariables } from '../schema'
|
||||||
|
import setProductLocaleMeta from '../api/utils/set-product-locale-meta'
|
||||||
|
import { productInfoFragment } from '../api/fragments/product'
|
||||||
|
import { AquilacmsConfig, getConfig } from '../api'
|
||||||
|
import { normalizeProduct } from '../lib/normalize'
|
||||||
|
import type { Product } from '@commerce/types'
|
||||||
|
|
||||||
|
export const getProductQuery = /* GraphQL */ `
|
||||||
|
query getProduct(
|
||||||
|
$hasLocale: Boolean = false
|
||||||
|
$locale: String = "null"
|
||||||
|
$path: String!
|
||||||
|
) {
|
||||||
|
site {
|
||||||
|
route(path: $path) {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
... on Product {
|
||||||
|
...productInfo
|
||||||
|
variants {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
entityId
|
||||||
|
defaultImage {
|
||||||
|
urlOriginal
|
||||||
|
altText
|
||||||
|
isDefault
|
||||||
|
}
|
||||||
|
prices {
|
||||||
|
...productPrices
|
||||||
|
}
|
||||||
|
inventory {
|
||||||
|
aggregated {
|
||||||
|
availableToSell
|
||||||
|
warningLevel
|
||||||
|
}
|
||||||
|
isInStock
|
||||||
|
}
|
||||||
|
productOptions {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
entityId
|
||||||
|
displayName
|
||||||
|
...multipleChoiceOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${productInfoFragment}
|
||||||
|
`
|
||||||
|
|
||||||
|
export type ProductNode = Extract<
|
||||||
|
GetProductQuery['site']['route']['node'],
|
||||||
|
{ __typename: 'Product' }
|
||||||
|
>
|
||||||
|
|
||||||
|
export type GetProductResult<
|
||||||
|
T extends { product?: any } = { product?: ProductNode }
|
||||||
|
> = T
|
||||||
|
|
||||||
|
export type ProductVariables = { locale?: string } & (
|
||||||
|
| { path: string; slug?: never }
|
||||||
|
| { path?: never; slug: string }
|
||||||
|
)
|
||||||
|
|
||||||
|
async function getProduct(opts: {
|
||||||
|
variables: ProductVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetProductResult>
|
||||||
|
|
||||||
|
async function getProduct<T extends { product?: any }, V = any>(opts: {
|
||||||
|
query: string
|
||||||
|
variables: V
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<GetProductResult<T>>
|
||||||
|
|
||||||
|
async function getProduct({
|
||||||
|
query = getProductQuery,
|
||||||
|
variables: { slug, ...vars },
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables: ProductVariables
|
||||||
|
config?: AquilacmsConfig
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<Product | {} | any> {
|
||||||
|
config = getConfig(config)
|
||||||
|
|
||||||
|
const locale = vars.locale || config.locale
|
||||||
|
const variables: GetProductQueryVariables = {
|
||||||
|
...vars,
|
||||||
|
locale,
|
||||||
|
hasLocale: !!locale,
|
||||||
|
path: slug ? `/${slug}/` : vars.path!,
|
||||||
|
}
|
||||||
|
const { data } = await config.fetch<GetProductQuery>(query, { variables })
|
||||||
|
const product = data.site?.route?.node
|
||||||
|
|
||||||
|
if (product?.__typename === 'Product') {
|
||||||
|
if (locale && config.applyLocale) {
|
||||||
|
setProductLocaleMeta(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { product: normalizeProduct(product as any) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getProduct
|
4
framework/aquilacms/product/index.ts
Normal file
4
framework/aquilacms/product/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as usePrice } from './use-price'
|
||||||
|
export { default as useSearch } from './use-search'
|
||||||
|
export { default as getProduct } from './get-product'
|
||||||
|
export { default as getAllProducts } from './get-all-products'
|
2
framework/aquilacms/product/use-price.tsx
Normal file
2
framework/aquilacms/product/use-price.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from '@commerce/product/use-price'
|
||||||
|
export { default } from '@commerce/product/use-price'
|
53
framework/aquilacms/product/use-search.tsx
Normal file
53
framework/aquilacms/product/use-search.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
|
import type { SearchProductsData } from '../api/catalog/products'
|
||||||
|
|
||||||
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
|
export type SearchProductsInput = {
|
||||||
|
search?: string
|
||||||
|
categoryId?: number
|
||||||
|
brandId?: number
|
||||||
|
sort?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: SWRHook<
|
||||||
|
SearchProductsData,
|
||||||
|
SearchProductsInput,
|
||||||
|
SearchProductsInput
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/catalog/products',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||||
|
// Use a dummy base as we only care about the relative path
|
||||||
|
const url = new URL(options.url!, 'http://a')
|
||||||
|
|
||||||
|
if (search) url.searchParams.set('search', search)
|
||||||
|
if (Number.isInteger(categoryId))
|
||||||
|
url.searchParams.set('category', String(categoryId))
|
||||||
|
if (Number.isInteger(brandId))
|
||||||
|
url.searchParams.set('brand', String(brandId))
|
||||||
|
if (sort) url.searchParams.set('sort', sort)
|
||||||
|
|
||||||
|
return fetch({
|
||||||
|
url: url.pathname + url.search,
|
||||||
|
method: options.method,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) => (input = {}) => {
|
||||||
|
return useData({
|
||||||
|
input: [
|
||||||
|
['search', input.search],
|
||||||
|
['categoryId', input.categoryId],
|
||||||
|
['brandId', input.brandId],
|
||||||
|
['sort', input.sort],
|
||||||
|
],
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
34
framework/aquilacms/provider.ts
Normal file
34
framework/aquilacms/provider.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { handler as useCart } from './cart/use-cart'
|
||||||
|
import { handler as useAddItem } from './cart/use-add-item'
|
||||||
|
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||||
|
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||||
|
|
||||||
|
import { handler as useWishlist } from './wishlist/use-wishlist'
|
||||||
|
import { handler as useWishlistAddItem } from './wishlist/use-add-item'
|
||||||
|
import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item'
|
||||||
|
|
||||||
|
import { handler as useCustomer } from './customer/use-customer'
|
||||||
|
import { handler as useSearch } from './product/use-search'
|
||||||
|
|
||||||
|
import { handler as useLogin } from './auth/use-login'
|
||||||
|
import { handler as useLogout } from './auth/use-logout'
|
||||||
|
import { handler as useSignup } from './auth/use-signup'
|
||||||
|
|
||||||
|
import fetcher from './fetcher'
|
||||||
|
|
||||||
|
export const aquilacmsProvider = {
|
||||||
|
locale: 'en-us',
|
||||||
|
cartCookie: 'aquilacms_cartId',
|
||||||
|
fetcher,
|
||||||
|
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||||
|
wishlist: {
|
||||||
|
useWishlist,
|
||||||
|
useAddItem: useWishlistAddItem,
|
||||||
|
useRemoveItem: useWishlistRemoveItem,
|
||||||
|
},
|
||||||
|
customer: { useCustomer },
|
||||||
|
products: { useSearch },
|
||||||
|
auth: { useLogin, useLogout, useSignup },
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AquilacmsProvider = typeof aquilacmsProvider
|
2064
framework/aquilacms/schema.d.ts
vendored
Normal file
2064
framework/aquilacms/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
49
framework/aquilacms/scripts/generate-definitions.js
Normal file
49
framework/aquilacms/scripts/generate-definitions.js
Normal file
@ -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 = `https://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()
|
56
framework/aquilacms/types.ts
Normal file
56
framework/aquilacms/types.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import * as Core from '@commerce/types'
|
||||||
|
|
||||||
|
export type AquilacmsCart = {
|
||||||
|
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[]
|
||||||
|
}
|
||||||
|
created_time: string
|
||||||
|
discounts?: { id: number; discounted_amount: number }[]
|
||||||
|
// TODO: add missing fields
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Cart = Core.Cart & {
|
||||||
|
lineItems: LineItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LineItem = Core.LineItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cart mutations
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type OptionSelections = {
|
||||||
|
option_id: number
|
||||||
|
option_value: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartItemBody = Core.CartItemBody & {
|
||||||
|
productId: string // The product id is always required for BC
|
||||||
|
optionSelections?: OptionSelections
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCartHandlerBody = Core.GetCartHandlerBody
|
||||||
|
|
||||||
|
export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
|
||||||
|
|
||||||
|
export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
|
||||||
|
|
||||||
|
export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
|
||||||
|
|
||||||
|
export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody<CartItemBody>
|
||||||
|
|
||||||
|
export type RemoveCartItemBody = Core.RemoveCartItemBody
|
||||||
|
|
||||||
|
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody
|
3
framework/aquilacms/wishlist/index.ts
Normal file
3
framework/aquilacms/wishlist/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as useAddItem } from './use-add-item'
|
||||||
|
export { default as useWishlist } from './use-wishlist'
|
||||||
|
export { default as useRemoveItem } from './use-remove-item'
|
37
framework/aquilacms/wishlist/use-add-item.tsx
Normal file
37
framework/aquilacms/wishlist/use-add-item.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useAddItem, { UseAddItem } from '@commerce/wishlist/use-add-item'
|
||||||
|
import type { ItemBody, AddItemBody } from '../api/wishlist'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import useWishlist from './use-wishlist'
|
||||||
|
|
||||||
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<any, {}, ItemBody, AddItemBody> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/wishlist',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => () => {
|
||||||
|
const { data: customer } = useCustomer()
|
||||||
|
const { revalidate } = useWishlist()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function addItem(item) {
|
||||||
|
if (!customer) {
|
||||||
|
// A signed customer is required in order to have a wishlist
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'Signed customer not found',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add validations before doing the fetch
|
||||||
|
const data = await fetch({ input: { item } })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate, customer]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
44
framework/aquilacms/wishlist/use-remove-item.tsx
Normal file
44
framework/aquilacms/wishlist/use-remove-item.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
|
import useRemoveItem, {
|
||||||
|
RemoveItemInput,
|
||||||
|
UseRemoveItem,
|
||||||
|
} from '@commerce/wishlist/use-remove-item'
|
||||||
|
import type { RemoveItemBody, Wishlist } from '../api/wishlist'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import useWishlist, { UseWishlistInput } from './use-wishlist'
|
||||||
|
|
||||||
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
|
export const handler: MutationHook<
|
||||||
|
Wishlist | null,
|
||||||
|
{ wishlist?: UseWishlistInput },
|
||||||
|
RemoveItemInput,
|
||||||
|
RemoveItemBody
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/wishlist',
|
||||||
|
method: 'DELETE',
|
||||||
|
},
|
||||||
|
useHook: ({ fetch }) => ({ wishlist } = {}) => {
|
||||||
|
const { data: customer } = useCustomer()
|
||||||
|
const { revalidate } = useWishlist(wishlist)
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async function removeItem(input) {
|
||||||
|
if (!customer) {
|
||||||
|
// A signed customer is required in order to have a wishlist
|
||||||
|
throw new CommerceError({
|
||||||
|
message: 'Signed customer not found',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fetch({ input: { itemId: String(input.id) } })
|
||||||
|
await revalidate()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
[fetch, revalidate, customer]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
60
framework/aquilacms/wishlist/use-wishlist.tsx
Normal file
60
framework/aquilacms/wishlist/use-wishlist.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
|
import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist'
|
||||||
|
import type { Wishlist } from '../api/wishlist'
|
||||||
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
|
export type UseWishlistInput = { includeProducts?: boolean }
|
||||||
|
|
||||||
|
export default useWishlist as UseWishlist<typeof handler>
|
||||||
|
|
||||||
|
export const handler: SWRHook<
|
||||||
|
Wishlist | null,
|
||||||
|
UseWishlistInput,
|
||||||
|
{ customerId?: number } & UseWishlistInput,
|
||||||
|
{ isEmpty?: boolean }
|
||||||
|
> = {
|
||||||
|
fetchOptions: {
|
||||||
|
url: '/api/bigcommerce/wishlist',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||||
|
if (!customerId) return null
|
||||||
|
|
||||||
|
// Use a dummy base as we only care about the relative path
|
||||||
|
const url = new URL(options.url!, 'http://a')
|
||||||
|
|
||||||
|
if (includeProducts) url.searchParams.set('products', '1')
|
||||||
|
|
||||||
|
return fetch({
|
||||||
|
url: url.pathname + url.search,
|
||||||
|
method: options.method,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useHook: ({ useData }) => (input) => {
|
||||||
|
const { data: customer } = useCustomer()
|
||||||
|
const response = useData({
|
||||||
|
input: [
|
||||||
|
['customerId', customer?.entityId],
|
||||||
|
['includeProducts', input?.includeProducts],
|
||||||
|
],
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
...input?.swrOptions,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
Object.create(response, {
|
||||||
|
isEmpty: {
|
||||||
|
get() {
|
||||||
|
return (response.data?.items?.length || 0) <= 0
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[response]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
41
framework/commerce/with-config.js
Normal file
41
framework/commerce/with-config.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* This file is expected to be used in next.config.js only
|
||||||
|
*/
|
||||||
|
|
||||||
|
const merge = require('deepmerge')
|
||||||
|
|
||||||
|
const PROVIDERS = ['bigcommerce', 'shopify', 'aquilacms']
|
||||||
|
|
||||||
|
function getProviderName() {
|
||||||
|
// TODO: OSOT.
|
||||||
|
return process.env.BIGCOMMERCE_STOREFRONT_API_URL ? 'bigcommerce' : null
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (nextConfig = {}) => {
|
||||||
|
const commerce = nextConfig.commerce || {}
|
||||||
|
const name = commerce.provider || getProviderName()
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new Error(
|
||||||
|
`The commerce provider is missing, please add a valid provider name or its environment variables`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!PROVIDERS.includes(name)) {
|
||||||
|
throw new Error(
|
||||||
|
`The commerce provider "${name}" can't be found, please use one of "${PROVIDERS.join(
|
||||||
|
', '
|
||||||
|
)}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const commerceNextConfig = require(`../${name}/next.config`)
|
||||||
|
const config = merge(commerceNextConfig, nextConfig)
|
||||||
|
|
||||||
|
config.env = config.env || {}
|
||||||
|
|
||||||
|
Object.entries(config.commerce.features).forEach(([k, v]) => {
|
||||||
|
if (v) config.env[`COMMERCE_${k.toUpperCase()}_ENABLED`] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
@ -7,6 +7,7 @@ const {
|
|||||||
const provider = commerce.provider || getProviderName()
|
const provider = commerce.provider || getProviderName()
|
||||||
const isBC = provider === 'bigcommerce'
|
const isBC = provider === 'bigcommerce'
|
||||||
const isShopify = provider === 'shopify'
|
const isShopify = provider === 'shopify'
|
||||||
|
const isAquilacms = provider === 'aquilacms'
|
||||||
|
|
||||||
module.exports = withCommerceConfig({
|
module.exports = withCommerceConfig({
|
||||||
commerce,
|
commerce,
|
||||||
|
361
yarn.lock
361
yarn.lock
@ -993,32 +993,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108"
|
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108"
|
||||||
integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==
|
integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==
|
||||||
|
|
||||||
"@types/eslint-scope@^3.7.0":
|
|
||||||
version "3.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
|
|
||||||
integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==
|
|
||||||
dependencies:
|
|
||||||
"@types/eslint" "*"
|
|
||||||
"@types/estree" "*"
|
|
||||||
|
|
||||||
"@types/eslint@*":
|
|
||||||
version "7.2.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c"
|
|
||||||
integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw==
|
|
||||||
dependencies:
|
|
||||||
"@types/estree" "*"
|
|
||||||
"@types/json-schema" "*"
|
|
||||||
|
|
||||||
"@types/estree@*":
|
|
||||||
version "0.0.46"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe"
|
|
||||||
integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==
|
|
||||||
|
|
||||||
"@types/estree@^0.0.45":
|
|
||||||
version "0.0.45"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
|
|
||||||
integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
|
|
||||||
|
|
||||||
"@types/http-proxy-agent@^2.0.2":
|
"@types/http-proxy-agent@^2.0.2":
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1"
|
resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1"
|
||||||
@ -1036,11 +1010,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.6.tgz#7f10c926aa41e189a2755c4c7fcf8e4573bd7ac1"
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.6.tgz#7f10c926aa41e189a2755c4c7fcf8e4573bd7ac1"
|
||||||
integrity sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ==
|
integrity sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ==
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.6":
|
|
||||||
version "7.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
|
||||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
|
||||||
|
|
||||||
"@types/json-stable-stringify@^1.0.32":
|
"@types/json-stable-stringify@^1.0.32":
|
||||||
version "1.0.32"
|
version "1.0.32"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e"
|
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e"
|
||||||
@ -1186,161 +1155,6 @@
|
|||||||
agentkeepalive "3.4.1"
|
agentkeepalive "3.4.1"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.1.tgz#76c6937716d68bf1484c15139f5ed30b9abc8bb4"
|
|
||||||
integrity sha512-uMu1nCWn2Wxyy126LlGqRVlhdTOsO/bsBRI4dNq3+6SiSuRKRQX6ejjKgh82LoGAPSq72lDUiQ4FWVaf0PecYw==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/helper-module-context" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode" "1.9.1"
|
|
||||||
"@webassemblyjs/wast-parser" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/floating-point-hex-parser@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.1.tgz#9eb0ff90a1cdeef51f36ba533ed9f06b5cdadd09"
|
|
||||||
integrity sha512-5VEKu024RySmLKTTBl9q1eO/2K5jk9ZS+2HXDBLA9s9p5IjkaXxWiDb/+b7wSQp6FRdLaH1IVGIfOex58Na2pg==
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-api-error@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.1.tgz#ad89015c4246cd7f5ed0556700237f8b9c2c752f"
|
|
||||||
integrity sha512-y1lGmfm38djrScwpeL37rRR9f1D6sM8RhMpvM7CYLzOlHVboouZokXK/G88BpzW0NQBSvCCOnW5BFhten4FPfA==
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-buffer@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.1.tgz#186e67ac25f9546ea7939759413987f157524133"
|
|
||||||
integrity sha512-uS6VSgieHbk/m4GSkMU5cqe/5TekdCzQso4revCIEQ3vpGZgqSSExi4jWpTWwDpAHOIAb1Jfrs0gUB9AA4n71w==
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-code-frame@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.1.tgz#aab177b7cc87a318a8f8664ad68e2c3828ebc42b"
|
|
||||||
integrity sha512-ZQ2ZT6Evk4DPIfD+92AraGYaFIqGm4U20e7FpXwl7WUo2Pn1mZ1v8VGH8i+Y++IQpxPbQo/UyG0Khs7eInskzA==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/wast-printer" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-fsm@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.1.tgz#527e91628e84d13d3573884b3dc4c53a81dcb911"
|
|
||||||
integrity sha512-J32HGpveEqqcKFS0YbgicB0zAlpfIxJa5MjxDxhu3i5ltPcVfY5EPvKQ1suRguFPehxiUs+/hfkwPEXom/l0lw==
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-module-context@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.1.tgz#778670b3d471f7cf093d1e7c0dde431b54310e16"
|
|
||||||
integrity sha512-IEH2cMmEQKt7fqelLWB5e/cMdZXf2rST1JIrzWmf4XBt3QTxGdnnLvV4DYoN8pJjOx0VYXsWg+yF16MmJtolZg==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.1.tgz#563f59bcf409ccf469edde168b9426961ffbf6df"
|
|
||||||
integrity sha512-i2rGTBqFUcSXxyjt2K4vm/3kkHwyzG6o427iCjcIKjOqpWH8SEem+xe82jUk1iydJO250/CvE5o7hzNAMZf0dQ==
|
|
||||||
|
|
||||||
"@webassemblyjs/helper-wasm-section@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.1.tgz#f7988f94c12b01b99a16120cb01dc099b00e4798"
|
|
||||||
integrity sha512-FetqzjtXZr2d57IECK+aId3D0IcGweeM0CbAnJHkYJkcRTHP+YcMb7Wmc0j21h5UWBpwYGb9dSkK/93SRCTrGg==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-buffer" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-gen" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/ieee754@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.1.tgz#3b715871ca7d75784717cf9ceca9d7b81374b8af"
|
|
||||||
integrity sha512-EvTG9M78zP1MmkBpUjGQHZc26DzPGZSLIPxYHCjQsBMo60Qy2W34qf8z0exRDtxBbRIoiKa5dFyWer/7r1aaSQ==
|
|
||||||
dependencies:
|
|
||||||
"@xtuc/ieee754" "^1.2.0"
|
|
||||||
|
|
||||||
"@webassemblyjs/leb128@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.1.tgz#b2ecaa39f9e8277cc9c707c1ca8b2aa7b27d0b72"
|
|
||||||
integrity sha512-Oc04ub0vFfLnF+2/+ki3AE+anmW4sv9uNBqb+79fgTaPv6xJsOT0dhphNfL3FrME84CbX/D1T9XT8tjFo0IIiw==
|
|
||||||
dependencies:
|
|
||||||
"@xtuc/long" "4.2.2"
|
|
||||||
|
|
||||||
"@webassemblyjs/utf8@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.1.tgz#d02d9daab85cda3211e43caf31dca74c260a73b0"
|
|
||||||
integrity sha512-llkYtppagjCodFjo0alWOUhAkfOiQPQDIc5oA6C9sFAXz7vC9QhZf/f8ijQIX+A9ToM3c9Pq85X0EX7nx9gVhg==
|
|
||||||
|
|
||||||
"@webassemblyjs/wasm-edit@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.1.tgz#e27a6bdbf78e5c72fa812a2fc3cbaad7c3e37578"
|
|
||||||
integrity sha512-S2IaD6+x9B2Xi8BCT0eGsrXXd8UxAh2LVJpg1ZMtHXnrDcsTtIX2bDjHi40Hio6Lc62dWHmKdvksI+MClCYbbw==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-buffer" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-section" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-gen" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-opt" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-parser" "1.9.1"
|
|
||||||
"@webassemblyjs/wast-printer" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/wasm-gen@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.1.tgz#56a0787d1fa7994fdc7bea59004e5bec7189c5fc"
|
|
||||||
integrity sha512-bqWI0S4lBQsEN5FTZ35vYzfKUJvtjNnBobB1agCALH30xNk1LToZ7Z8eiaR/Z5iVECTlBndoRQV3F6mbEqE/fg==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode" "1.9.1"
|
|
||||||
"@webassemblyjs/ieee754" "1.9.1"
|
|
||||||
"@webassemblyjs/leb128" "1.9.1"
|
|
||||||
"@webassemblyjs/utf8" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/wasm-opt@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.1.tgz#fbdf8943a825e6dcc4cd69c3e092289fa4aec96c"
|
|
||||||
integrity sha512-gSf7I7YWVXZ5c6XqTEqkZjVs8K1kc1k57vsB6KBQscSagDNbAdxt6MwuJoMjsE1yWY1tsuL+pga268A6u+Fdkg==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-buffer" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-gen" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-parser" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/wasm-parser@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.1.tgz#5e8352a246d3f605312c8e414f7990de55aaedfa"
|
|
||||||
integrity sha512-ImM4N2T1MEIond0MyE3rXvStVxEmivQrDKf/ggfh5pP6EHu3lL/YTAoSrR7shrbKNPpeKpGesW1LIK/L4kqduw==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-api-error" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-wasm-bytecode" "1.9.1"
|
|
||||||
"@webassemblyjs/ieee754" "1.9.1"
|
|
||||||
"@webassemblyjs/leb128" "1.9.1"
|
|
||||||
"@webassemblyjs/utf8" "1.9.1"
|
|
||||||
|
|
||||||
"@webassemblyjs/wast-parser@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.1.tgz#e25ef13585c060073c1db0d6bd94340fdeee7596"
|
|
||||||
integrity sha512-2xVxejXSvj3ls/o2TR/zI6p28qsGupjHhnHL6URULQRcXmryn3w7G83jQMcT7PHqUfyle65fZtWLukfdLdE7qw==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/floating-point-hex-parser" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-api-error" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-code-frame" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-fsm" "1.9.1"
|
|
||||||
"@xtuc/long" "4.2.2"
|
|
||||||
|
|
||||||
"@webassemblyjs/wast-printer@1.9.1":
|
|
||||||
version "1.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.1.tgz#b9f38e93652037d4f3f9c91584635af4191ed7c1"
|
|
||||||
integrity sha512-tDV8V15wm7mmbAH6XvQRU1X+oPGmeOzYsd6h7hlRLz6QpV4Ec/KKxM8OpLtFmQPLCreGxTp+HuxtH4pRIZyL9w==
|
|
||||||
dependencies:
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/wast-parser" "1.9.1"
|
|
||||||
"@xtuc/long" "4.2.2"
|
|
||||||
|
|
||||||
"@xtuc/ieee754@^1.2.0":
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
|
||||||
integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
|
|
||||||
|
|
||||||
"@xtuc/long@4.2.2":
|
|
||||||
version "4.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
|
||||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
|
||||||
|
|
||||||
"@zeit/dns-cached-resolve@2.1.0":
|
"@zeit/dns-cached-resolve@2.1.0":
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zeit/dns-cached-resolve/-/dns-cached-resolve-2.1.0.tgz#78583010df1683fdb7b05949b75593c9a8641bc1"
|
resolved "https://registry.yarnpkg.com/@zeit/dns-cached-resolve/-/dns-cached-resolve-2.1.0.tgz#78583010df1683fdb7b05949b75593c9a8641bc1"
|
||||||
@ -1403,12 +1217,7 @@ aggregate-error@^3.0.0:
|
|||||||
clean-stack "^2.0.0"
|
clean-stack "^2.0.0"
|
||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
|
|
||||||
ajv-keywords@^3.5.2:
|
ajv@^6.12.6:
|
||||||
version "3.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
|
||||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
|
||||||
|
|
||||||
ajv@^6.12.5, ajv@^6.12.6:
|
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||||
@ -1812,7 +1621,7 @@ browserslist@4.16.1:
|
|||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
node-releases "^1.1.69"
|
node-releases "^1.1.69"
|
||||||
|
|
||||||
browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.1, browserslist@^4.6.4:
|
browserslist@^4.12.0, browserslist@^4.16.1, browserslist@^4.6.4:
|
||||||
version "4.16.3"
|
version "4.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
|
||||||
integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
|
integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
|
||||||
@ -2009,13 +1818,6 @@ chokidar@3.5.1, chokidar@^3.4.3:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.1"
|
fsevents "~2.3.1"
|
||||||
|
|
||||||
chrome-trace-event@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
|
|
||||||
integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
|
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.0"
|
|
||||||
|
|
||||||
ci-info@^2.0.0:
|
ci-info@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||||
@ -2166,7 +1968,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
commander@^2.13.0, commander@^2.16.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1:
|
commander@^2.13.0, commander@^2.16.0, commander@^2.20.3, commander@^2.8.1:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
@ -2801,14 +2603,6 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
|
|||||||
memory-fs "^0.5.0"
|
memory-fs "^0.5.0"
|
||||||
tapable "^1.0.0"
|
tapable "^1.0.0"
|
||||||
|
|
||||||
enhanced-resolve@^5.3.1:
|
|
||||||
version "5.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c"
|
|
||||||
integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.2.4"
|
|
||||||
tapable "^2.2.0"
|
|
||||||
|
|
||||||
enquirer@^2.3.6:
|
enquirer@^2.3.6:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||||
@ -2881,14 +2675,6 @@ escodegen@^1.8.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-scope@^5.1.1:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
|
||||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
|
||||||
dependencies:
|
|
||||||
esrecurse "^4.3.0"
|
|
||||||
estraverse "^4.1.1"
|
|
||||||
|
|
||||||
eslint-visitor-keys@^1.1.0:
|
eslint-visitor-keys@^1.1.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
|
||||||
@ -2899,23 +2685,11 @@ esprima@4.0.1, esprima@^4.0.0, esprima@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
|
||||||
esrecurse@^4.3.0:
|
estraverse@^4.2.0:
|
||||||
version "4.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
|
||||||
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
|
|
||||||
dependencies:
|
|
||||||
estraverse "^5.2.0"
|
|
||||||
|
|
||||||
estraverse@^4.1.1, estraverse@^4.2.0:
|
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||||
|
|
||||||
estraverse@^5.2.0:
|
|
||||||
version "5.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
|
|
||||||
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
|
|
||||||
|
|
||||||
esutils@^2.0.2:
|
esutils@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
@ -2926,7 +2700,7 @@ etag@1.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
events@^3.0.0, events@^3.2.0:
|
events@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
|
||||||
integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
|
integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
|
||||||
@ -3302,7 +3076,7 @@ got@^9.6.0:
|
|||||||
to-readable-stream "^1.0.0"
|
to-readable-stream "^1.0.0"
|
||||||
url-parse-lax "^3.0.0"
|
url-parse-lax "^3.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||||
version "4.2.6"
|
version "4.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
|
||||||
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
||||||
@ -3852,15 +3626,6 @@ jest-worker@24.9.0:
|
|||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^6.1.0"
|
supports-color "^6.1.0"
|
||||||
|
|
||||||
jest-worker@^26.6.2:
|
|
||||||
version "26.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
|
|
||||||
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
merge-stream "^2.0.0"
|
|
||||||
supports-color "^7.0.0"
|
|
||||||
|
|
||||||
js-cookie@^2.2.1:
|
js-cookie@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||||
@ -3889,11 +3654,6 @@ json-buffer@3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||||
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
|
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
|
||||||
|
|
||||||
json-parse-better-errors@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
|
||||||
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
|
|
||||||
|
|
||||||
json-parse-even-better-errors@^2.3.0:
|
json-parse-even-better-errors@^2.3.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
|
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
|
||||||
@ -4105,11 +3865,6 @@ listr@^0.14.3:
|
|||||||
p-map "^2.0.0"
|
p-map "^2.0.0"
|
||||||
rxjs "^6.3.3"
|
rxjs "^6.3.3"
|
||||||
|
|
||||||
loader-runner@^4.1.0:
|
|
||||||
version "4.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
|
|
||||||
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
|
|
||||||
|
|
||||||
loader-utils@1.2.3:
|
loader-utils@1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||||
@ -4438,7 +4193,7 @@ mime-db@1.45.0:
|
|||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
|
||||||
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
|
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
|
||||||
|
|
||||||
mime-types@^2.1.12, mime-types@^2.1.27:
|
mime-types@^2.1.12:
|
||||||
version "2.1.28"
|
version "2.1.28"
|
||||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
|
||||||
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
|
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
|
||||||
@ -4563,11 +4318,6 @@ native-url@0.3.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
querystring "^0.2.0"
|
querystring "^0.2.0"
|
||||||
|
|
||||||
neo-async@^2.6.2:
|
|
||||||
version "2.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
|
||||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
|
||||||
|
|
||||||
next-seo@^4.11.0:
|
next-seo@^4.11.0:
|
||||||
version "4.19.0"
|
version "4.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.19.0.tgz#b3be79a544e420dbbf1e4fcff98dad9790f15e36"
|
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.19.0.tgz#b3be79a544e420dbbf1e4fcff98dad9790f15e36"
|
||||||
@ -4877,7 +4627,7 @@ p-limit@3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-try "^2.0.0"
|
p-try "^2.0.0"
|
||||||
|
|
||||||
p-limit@3.1.0, p-limit@^3.0.2, p-limit@^3.1.0:
|
p-limit@3.1.0, p-limit@^3.0.2:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
|
||||||
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
|
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
|
||||||
@ -5639,7 +5389,7 @@ quick-lru@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
|
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
|
||||||
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
|
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
|
||||||
|
|
||||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||||
@ -5986,15 +5736,6 @@ scheduler@^0.20.1:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
schema-utils@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
|
|
||||||
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
|
|
||||||
dependencies:
|
|
||||||
"@types/json-schema" "^7.0.6"
|
|
||||||
ajv "^6.12.5"
|
|
||||||
ajv-keywords "^3.5.2"
|
|
||||||
|
|
||||||
scuid@^1.1.0:
|
scuid@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab"
|
resolved "https://registry.yarnpkg.com/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab"
|
||||||
@ -6027,13 +5768,6 @@ semver@^7.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
serialize-javascript@^5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
|
||||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
|
||||||
dependencies:
|
|
||||||
randombytes "^2.1.0"
|
|
||||||
|
|
||||||
set-blocking@^2.0.0:
|
set-blocking@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
@ -6138,12 +5872,7 @@ slice-ansi@^4.0.0:
|
|||||||
astral-regex "^2.0.0"
|
astral-regex "^2.0.0"
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
|
||||||
source-list-map@^2.0.1:
|
source-map-support@^0.5.17:
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
|
||||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
|
||||||
|
|
||||||
source-map-support@^0.5.17, source-map-support@~0.5.19:
|
|
||||||
version "0.5.19"
|
version "0.5.19"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||||
@ -6151,7 +5880,7 @@ source-map-support@^0.5.17, source-map-support@~0.5.19:
|
|||||||
buffer-from "^1.0.0"
|
buffer-from "^1.0.0"
|
||||||
source-map "^0.6.0"
|
source-map "^0.6.0"
|
||||||
|
|
||||||
source-map@0.7.3, source-map@~0.7.2:
|
source-map@0.7.3:
|
||||||
version "0.7.3"
|
version "0.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||||
@ -6429,7 +6158,7 @@ supports-color@^6.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^3.0.0"
|
has-flag "^3.0.0"
|
||||||
|
|
||||||
supports-color@^7.0.0, supports-color@^7.1.0:
|
supports-color@^7.1.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||||
@ -6492,37 +6221,11 @@ tapable@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||||
|
|
||||||
tapable@^2.1.1, tapable@^2.2.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
|
|
||||||
integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==
|
|
||||||
|
|
||||||
temp@~0.4.0:
|
temp@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60"
|
resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60"
|
||||||
integrity sha1-ZxrWPVe+D+nXKUZks/xABjZnimA=
|
integrity sha1-ZxrWPVe+D+nXKUZks/xABjZnimA=
|
||||||
|
|
||||||
terser-webpack-plugin@^5.0.3:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673"
|
|
||||||
integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==
|
|
||||||
dependencies:
|
|
||||||
jest-worker "^26.6.2"
|
|
||||||
p-limit "^3.1.0"
|
|
||||||
schema-utils "^3.0.0"
|
|
||||||
serialize-javascript "^5.0.1"
|
|
||||||
source-map "^0.6.1"
|
|
||||||
terser "^5.5.1"
|
|
||||||
|
|
||||||
terser@^5.5.1:
|
|
||||||
version "5.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2"
|
|
||||||
integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==
|
|
||||||
dependencies:
|
|
||||||
commander "^2.20.0"
|
|
||||||
source-map "~0.7.2"
|
|
||||||
source-map-support "~0.5.19"
|
|
||||||
|
|
||||||
through@^2.3.6, through@^2.3.8:
|
through@^2.3.6, through@^2.3.8:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
@ -6825,7 +6528,7 @@ warning@^4.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
watchpack@2.1.1, watchpack@^2.0.0:
|
watchpack@2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"
|
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"
|
||||||
integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==
|
integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==
|
||||||
@ -6860,44 +6563,6 @@ webpack-bundle-analyzer@4.3.0:
|
|||||||
sirv "^1.0.7"
|
sirv "^1.0.7"
|
||||||
ws "^7.3.1"
|
ws "^7.3.1"
|
||||||
|
|
||||||
webpack-sources@^2.1.1:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac"
|
|
||||||
integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==
|
|
||||||
dependencies:
|
|
||||||
source-list-map "^2.0.1"
|
|
||||||
source-map "^0.6.1"
|
|
||||||
|
|
||||||
webpack@5.11.1:
|
|
||||||
version "5.11.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.11.1.tgz#39b2b9daeb5c6c620e03b7556ec674eaed4016b4"
|
|
||||||
integrity sha512-tNUIdAmYJv+nupRs/U/gqmADm6fgrf5xE+rSlSsf2PgsGO7j2WG7ccU6AWNlOJlHFl+HnmXlBmHIkiLf+XA9mQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/eslint-scope" "^3.7.0"
|
|
||||||
"@types/estree" "^0.0.45"
|
|
||||||
"@webassemblyjs/ast" "1.9.1"
|
|
||||||
"@webassemblyjs/helper-module-context" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-edit" "1.9.1"
|
|
||||||
"@webassemblyjs/wasm-parser" "1.9.1"
|
|
||||||
acorn "^8.0.4"
|
|
||||||
browserslist "^4.14.5"
|
|
||||||
chrome-trace-event "^1.0.2"
|
|
||||||
enhanced-resolve "^5.3.1"
|
|
||||||
eslint-scope "^5.1.1"
|
|
||||||
events "^3.2.0"
|
|
||||||
glob-to-regexp "^0.4.1"
|
|
||||||
graceful-fs "^4.2.4"
|
|
||||||
json-parse-better-errors "^1.0.2"
|
|
||||||
loader-runner "^4.1.0"
|
|
||||||
mime-types "^2.1.27"
|
|
||||||
neo-async "^2.6.2"
|
|
||||||
pkg-dir "^5.0.0"
|
|
||||||
schema-utils "^3.0.0"
|
|
||||||
tapable "^2.1.1"
|
|
||||||
terser-webpack-plugin "^5.0.3"
|
|
||||||
watchpack "^2.0.0"
|
|
||||||
webpack-sources "^2.1.1"
|
|
||||||
|
|
||||||
whatwg-fetch@^3.4.1:
|
whatwg-fetch@^3.4.1:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
|
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user