mirror of
https://github.com/vercel/commerce.git
synced 2025-03-31 09:15:53 +00:00
[WIP] Node.js provider for the API (#252)
* Adding multiple initial files * Updated the default cart endpoint * Fixes * Updated CommerceAPI class for better usage * Adding more migration changes * Taking multiple steps into better API types * Adding more experimental types * Removed many testing types * Adding types, fixes and other updates * Updated commerce types * Updated types for hooks now using the API * Updated mutation types * Simplified cart types for the provider * Updated cart hooks * Remove normalizers from the hooks * Updated cart endpoint * Removed cart handlers * bug fixes * Improve quantity input behavior in cart item * Removed endpoints folder * Making progress on api operations * Moved method * Moved types * Changed the way ops are created * Added customer endpoint * Login endpoint * Added logout endpoint * Add missing logout files * Added signup endpoint * Removed customers old endpoints * Moved endpoints to nested folder * Removed old customer endpoint builders * Updated login operation * Updated login operation * Added getAllPages operation * Renamed endpoint operations to handlers * Changed import * Renamed operations to handlers in usage * Moved getAllPages everywhere * Moved getPage * Updated getPage usage * Moved getSiteInfo * Added def types for product * Updated type * moved products catalog endpoint * removed old catalog endpoint * Moved wishlist * Removed commerce.endpoint * Replaced references to commerce.endpoint * Updated catalog products * Moved checkout api * Added the get customer wishlist operation * Removed old wishlist stuff * Added getAllProductPaths operation * updated reference to operation * Moved getAllProducts * Updated getProduct operation * Removed old getConfig and references * Removed is-allowed-method from BC * Updated types for auth hooks * Updated useCustomer and core types * Updated useData and util hooks * Updated useSearch hook * Updated types for useWishlist * Added index for types * Fixes * Updated urls to the API * Renamed fetchInput to fetcherInput * Updated fetch type * Fixes in search hook * Updated Shopify Provider Structure (#340) * Add codegen, update fragments & schemas * Update checkout-create.ts * Update checkout-create.ts * Update README.md * Update product mutations & queries * Uptate customer fetch types * Update schemas * Start updates * Moved Page, AllPages & Site Info * Moved product, all products (paths) * Add translations, update operations & fixes * Update api endpoints, types & fixes * Add api checkout endpoint * Updates * Fixes * Update commerce.config.json Co-authored-by: B <curciobelen@gmail.com> * Added category type and normalizer * updated init script to exclude other providers * Excluded swell and venture temporarily * Fix category & color normalization * Fixed category normalizer in shopify * Don't use getSlug for category on /search * Update colors.ts Co-authored-by: cond0r <pinte_catalin@yahoo.com> Co-authored-by: B <curciobelen@gmail.com>
This commit is contained in:
parent
0792eabd4c
commit
a98c95d447
components
cart/CartItem
common
Footer
HomeAllProductsGrid
Layout
Navbar
UserNav
product
wishlist
framework
bigcommerce
api
cart
catalog
checkout.tscustomers
endpoints
cart
catalog/products
checkout
customer
login
logout
signup
wishlist
operations
get-all-pages.tsget-all-product-paths.tsget-all-products.tsget-customer-wishlist.tsget-page.tsget-product.tsget-site-info.tslogin.ts
utils
create-api-handler.tsfetch-graphql-api.tsfetch-store-api.tsis-allowed-method.tsparse-item.tsset-product-locale-meta.ts
wishlist
auth
cart
common
customer
lib
product
types
cart.tscheckout.tscommon.tscustomer.tsindex.tslogin.tslogout.tspage.tsproduct.tssignup.tssite.tswishlist.ts
wishlist
commerce/api/endpoints
@ -5,7 +5,7 @@ import Link from 'next/link'
|
|||||||
import s from './CartItem.module.css'
|
import s from './CartItem.module.css'
|
||||||
import { Trash, Plus, Minus } from '@components/icons'
|
import { Trash, Plus, Minus } from '@components/icons'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import type { LineItem } from '@framework/types'
|
import type { LineItem } from '@commerce/types/cart'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import useUpdateItem from '@framework/cart/use-update-item'
|
import useUpdateItem from '@framework/cart/use-update-item'
|
||||||
import useRemoveItem from '@framework/cart/use-remove-item'
|
import useRemoveItem from '@framework/cart/use-remove-item'
|
||||||
@ -35,7 +35,7 @@ const CartItem = ({
|
|||||||
|
|
||||||
const updateItem = useUpdateItem({ item })
|
const updateItem = useUpdateItem({ item })
|
||||||
const removeItem = useRemoveItem()
|
const removeItem = useRemoveItem()
|
||||||
const [quantity, setQuantity] = useState(item.quantity)
|
const [quantity, setQuantity] = useState<number | ''>(item.quantity)
|
||||||
const [removing, setRemoving] = useState(false)
|
const [removing, setRemoving] = useState(false)
|
||||||
|
|
||||||
const updateQuantity = async (val: number) => {
|
const updateQuantity = async (val: number) => {
|
||||||
@ -43,10 +43,10 @@ const CartItem = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQuantity = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleQuantity = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const val = Number(e.target.value)
|
const val = !e.target.value ? '' : Number(e.target.value)
|
||||||
|
|
||||||
if (Number.isInteger(val) && val >= 0) {
|
if (!val || (Number.isInteger(val) && val >= 0)) {
|
||||||
setQuantity(Number(e.target.value))
|
setQuantity(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
|
@ -2,7 +2,7 @@ import { FC } from 'react'
|
|||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import type { Page } from '@framework/common/get-all-pages'
|
import type { Page } from '@commerce/types/page'
|
||||||
import getSlug from '@lib/get-slug'
|
import getSlug from '@lib/get-slug'
|
||||||
import { Github, Vercel } from '@components/icons'
|
import { Github, Vercel } from '@components/icons'
|
||||||
import { Logo, Container } from '@components/ui'
|
import { Logo, Container } from '@components/ui'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types/product'
|
||||||
import { Grid } from '@components/ui'
|
import { Grid } from '@components/ui'
|
||||||
import { ProductCard } from '@components/product'
|
import { ProductCard } from '@components/product'
|
||||||
import s from './HomeAllProductsGrid.module.css'
|
import s from './HomeAllProductsGrid.module.css'
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import cn from 'classnames'
|
|
||||||
import dynamic from 'next/dynamic'
|
|
||||||
import s from './Layout.module.css'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import type { Page } from '@commerce/types/page'
|
||||||
|
import type { Category } from '@commerce/types/site'
|
||||||
|
import { CommerceProvider } from '@framework'
|
||||||
|
import { useAcceptCookies } from '@lib/hooks/useAcceptCookies'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import { Navbar, Footer } from '@components/common'
|
import { Navbar, Footer } from '@components/common'
|
||||||
import { useAcceptCookies } from '@lib/hooks/useAcceptCookies'
|
|
||||||
import { Sidebar, Button, Modal, LoadingDots } from '@components/ui'
|
import { Sidebar, Button, Modal, LoadingDots } from '@components/ui'
|
||||||
import CartSidebarView from '@components/cart/CartSidebarView'
|
import CartSidebarView from '@components/cart/CartSidebarView'
|
||||||
import type { Page, Category } from '@commerce/types'
|
|
||||||
import LoginView from '@components/auth/LoginView'
|
import LoginView from '@components/auth/LoginView'
|
||||||
import { CommerceProvider } from '@framework'
|
import s from './Layout.module.css'
|
||||||
|
|
||||||
const Loading = () => (
|
const Loading = () => (
|
||||||
<div className="w-80 h-80 flex items-center text-center justify-center p-3">
|
<div className="w-80 h-80 flex items-center text-center justify-center p-3">
|
||||||
|
@ -27,13 +27,11 @@ const Navbar: FC<NavbarProps> = ({ links }) => (
|
|||||||
<Link href="/search">
|
<Link href="/search">
|
||||||
<a className={s.link}>All</a>
|
<a className={s.link}>All</a>
|
||||||
</Link>
|
</Link>
|
||||||
{links
|
{links?.map((l) => (
|
||||||
? links.map((l) => (
|
<Link href={l.href} key={l.href}>
|
||||||
<Link href={l.href}>
|
|
||||||
<a className={s.link}>{l.label}</a>
|
<a className={s.link}>{l.label}</a>
|
||||||
</Link>
|
</Link>
|
||||||
))
|
))}
|
||||||
: null}
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import type { LineItem } from '@framework/types'
|
import type { LineItem } from '@commerce/types/cart'
|
||||||
import useCart from '@framework/cart/use-cart'
|
import useCart from '@framework/cart/use-cart'
|
||||||
import useCustomer from '@framework/customer/use-customer'
|
import useCustomer from '@framework/customer/use-customer'
|
||||||
import { Avatar } from '@components/common'
|
import { Avatar } from '@components/common'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types/product'
|
||||||
import s from './ProductCard.module.css'
|
import s from './ProductCard.module.css'
|
||||||
import Image, { ImageProps } from 'next/image'
|
import Image, { ImageProps } from 'next/image'
|
||||||
import WishlistButton from '@components/wishlist/WishlistButton'
|
import WishlistButton from '@components/wishlist/WishlistButton'
|
||||||
|
@ -5,7 +5,7 @@ import { FC, useEffect, useState } from 'react'
|
|||||||
import s from './ProductView.module.css'
|
import s from './ProductView.module.css'
|
||||||
import { Swatch, ProductSlider } from '@components/product'
|
import { Swatch, ProductSlider } from '@components/product'
|
||||||
import { Button, Container, Text, useUI } from '@components/ui'
|
import { Button, Container, Text, useUI } from '@components/ui'
|
||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types/product'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import { useAddItem } from '@framework/cart'
|
import { useAddItem } from '@framework/cart'
|
||||||
import { getVariant, SelectedOptions } from '../helpers'
|
import { getVariant, SelectedOptions } from '../helpers'
|
||||||
@ -18,6 +18,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ProductView: FC<Props> = ({ product }) => {
|
const ProductView: FC<Props> = ({ product }) => {
|
||||||
|
// TODO: fix this missing argument issue
|
||||||
|
/* @ts-ignore */
|
||||||
const addItem = useAddItem()
|
const addItem = useAddItem()
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.price.value,
|
amount: product.price.value,
|
||||||
@ -146,8 +148,11 @@ const ProductView: FC<Props> = ({ product }) => {
|
|||||||
className={s.button}
|
className={s.button}
|
||||||
onClick={addToCart}
|
onClick={addToCart}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
disabled={variant?.availableForSale === false}
|
||||||
>
|
>
|
||||||
Add to Cart
|
{variant?.availableForSale === false
|
||||||
|
? 'Not Available'
|
||||||
|
: 'Add To Cart'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,14 +44,15 @@ const Swatch: FC<Omit<ButtonProps, 'variant'> & SwatchProps> = ({
|
|||||||
className={swatchClassName}
|
className={swatchClassName}
|
||||||
style={color ? { backgroundColor: color } : {}}
|
style={color ? { backgroundColor: color } : {}}
|
||||||
aria-label="Variant Swatch"
|
aria-label="Variant Swatch"
|
||||||
|
{...(label && color && { title: label })}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{variant === 'color' && active && (
|
{color && active && (
|
||||||
<span>
|
<span>
|
||||||
<Check />
|
<Check />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{variant !== 'color' ? label : null}
|
{!color ? label : null}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types/product'
|
||||||
export type SelectedOptions = Record<string, string | null>
|
export type SelectedOptions = Record<string, string | null>
|
||||||
|
|
||||||
export function getVariant(product: Product, opts: SelectedOptions) {
|
export function getVariant(product: Product, opts: SelectedOptions) {
|
||||||
|
@ -6,7 +6,7 @@ import useAddItem from '@framework/wishlist/use-add-item'
|
|||||||
import useCustomer from '@framework/customer/use-customer'
|
import useCustomer from '@framework/customer/use-customer'
|
||||||
import useWishlist from '@framework/wishlist/use-wishlist'
|
import useWishlist from '@framework/wishlist/use-wishlist'
|
||||||
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
||||||
import type { Product, ProductVariant } from '@commerce/types'
|
import type { Product, ProductVariant } from '@commerce/types/product'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
productId: Product['id']
|
productId: Product['id']
|
||||||
|
@ -7,7 +7,7 @@ import { Trash } from '@components/icons'
|
|||||||
import { Button, Text } from '@components/ui'
|
import { Button, Text } from '@components/ui'
|
||||||
|
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '@commerce/types/product'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import useAddItem from '@framework/cart/use-add-item'
|
import useAddItem from '@framework/cart/use-add-item'
|
||||||
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
||||||
@ -20,14 +20,17 @@ const placeholderImg = '/product-img-placeholder.svg'
|
|||||||
|
|
||||||
const WishlistCard: FC<Props> = ({ product }) => {
|
const WishlistCard: FC<Props> = ({ product }) => {
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.prices?.price?.value,
|
amount: product.price?.value,
|
||||||
baseAmount: product.prices?.retailPrice?.value,
|
baseAmount: product.price?.retailPrice,
|
||||||
currencyCode: product.prices?.price?.currencyCode!,
|
currencyCode: product.price?.currencyCode!,
|
||||||
})
|
})
|
||||||
// @ts-ignore Wishlist is not always enabled
|
// @ts-ignore Wishlist is not always enabled
|
||||||
const removeItem = useRemoveItem({ wishlist: { includeProducts: true } })
|
const removeItem = useRemoveItem({ wishlist: { includeProducts: true } })
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [removing, setRemoving] = useState(false)
|
const [removing, setRemoving] = useState(false)
|
||||||
|
|
||||||
|
// TODO: fix this missing argument issue
|
||||||
|
/* @ts-ignore */
|
||||||
const addItem = useAddItem()
|
const addItem = useAddItem()
|
||||||
const { openSidebar } = useUI()
|
const { openSidebar } = useUI()
|
||||||
|
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import getCart from './handlers/get-cart'
|
|
||||||
import addItem from './handlers/add-item'
|
|
||||||
import updateItem from './handlers/update-item'
|
|
||||||
import removeItem from './handlers/remove-item'
|
|
||||||
import type {
|
|
||||||
BigcommerceCart,
|
|
||||||
GetCartHandlerBody,
|
|
||||||
AddCartItemHandlerBody,
|
|
||||||
UpdateCartItemHandlerBody,
|
|
||||||
RemoveCartItemHandlerBody,
|
|
||||||
} from '../../types'
|
|
||||||
|
|
||||||
export type CartHandlers = {
|
|
||||||
getCart: BigcommerceHandler<BigcommerceCart, GetCartHandlerBody>
|
|
||||||
addItem: BigcommerceHandler<BigcommerceCart, AddCartItemHandlerBody>
|
|
||||||
updateItem: BigcommerceHandler<BigcommerceCart, UpdateCartItemHandlerBody>
|
|
||||||
removeItem: BigcommerceHandler<BigcommerceCart, RemoveCartItemHandlerBody>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
|
|
||||||
|
|
||||||
// TODO: a complete implementation should have schema validation for `req.body`
|
|
||||||
const cartApi: BigcommerceApiHandler<BigcommerceCart, 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 BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handlers = { getCart, addItem, updateItem, removeItem }
|
|
||||||
|
|
||||||
export default createApiHandler(cartApi, handlers, {})
|
|
@ -1,48 +0,0 @@
|
|||||||
import type { Product } from '@commerce/types'
|
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import getProducts from './handlers/get-products'
|
|
||||||
|
|
||||||
export type SearchProductsData = {
|
|
||||||
products: Product[]
|
|
||||||
found: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProductsHandlers = {
|
|
||||||
getProducts: BigcommerceHandler<
|
|
||||||
SearchProductsData,
|
|
||||||
{ search?: string; category?: string; brand?: string; sort?: string }
|
|
||||||
>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['GET']
|
|
||||||
|
|
||||||
// TODO(lf): a complete implementation should have schema validation for `req.body`
|
|
||||||
const productsApi: BigcommerceApiHandler<
|
|
||||||
SearchProductsData,
|
|
||||||
ProductsHandlers
|
|
||||||
> = async (req, res, config, handlers) => {
|
|
||||||
if (!isAllowedMethod(req, res, METHODS)) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = req.query
|
|
||||||
return await handlers['getProducts']({ req, res, config, body })
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
const message =
|
|
||||||
error instanceof BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handlers = { getProducts }
|
|
||||||
|
|
||||||
export default createApiHandler(productsApi, handlers, {})
|
|
@ -1,77 +0,0 @@
|
|||||||
import isAllowedMethod from './utils/is-allowed-method'
|
|
||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
} from './utils/create-api-handler'
|
|
||||||
import { BigcommerceApiError } from './utils/errors'
|
|
||||||
|
|
||||||
const METHODS = ['GET']
|
|
||||||
const fullCheckout = true
|
|
||||||
|
|
||||||
// TODO: a complete implementation should have schema validation for `req.body`
|
|
||||||
const checkoutApi: BigcommerceApiHandler<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 BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createApiHandler(checkoutApi, {}, {})
|
|
@ -1,46 +0,0 @@
|
|||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import getLoggedInCustomer, {
|
|
||||||
Customer,
|
|
||||||
} from './handlers/get-logged-in-customer'
|
|
||||||
|
|
||||||
export type { Customer }
|
|
||||||
|
|
||||||
export type CustomerData = {
|
|
||||||
customer: Customer
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CustomersHandlers = {
|
|
||||||
getLoggedInCustomer: BigcommerceHandler<CustomerData>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['GET']
|
|
||||||
|
|
||||||
const customersApi: BigcommerceApiHandler<
|
|
||||||
CustomerData,
|
|
||||||
CustomersHandlers
|
|
||||||
> = async (req, res, config, handlers) => {
|
|
||||||
if (!isAllowedMethod(req, res, METHODS)) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = null
|
|
||||||
return await handlers['getLoggedInCustomer']({ req, res, config, body })
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
const message =
|
|
||||||
error instanceof BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers = { getLoggedInCustomer }
|
|
||||||
|
|
||||||
export default createApiHandler(customersApi, handlers, {})
|
|
@ -1,45 +0,0 @@
|
|||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import login from './handlers/login'
|
|
||||||
|
|
||||||
export type LoginBody = {
|
|
||||||
email: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LoginHandlers = {
|
|
||||||
login: BigcommerceHandler<null, Partial<LoginBody>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['POST']
|
|
||||||
|
|
||||||
const loginApi: BigcommerceApiHandler<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 BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers = { login }
|
|
||||||
|
|
||||||
export default createApiHandler(loginApi, handlers, {})
|
|
@ -1,42 +0,0 @@
|
|||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import logout from './handlers/logout'
|
|
||||||
|
|
||||||
export type LogoutHandlers = {
|
|
||||||
logout: BigcommerceHandler<null, { redirectTo?: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['GET']
|
|
||||||
|
|
||||||
const logoutApi: BigcommerceApiHandler<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 BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers = { logout }
|
|
||||||
|
|
||||||
export default createApiHandler(logoutApi, handlers, {})
|
|
@ -1,50 +0,0 @@
|
|||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import signup from './handlers/signup'
|
|
||||||
|
|
||||||
export type SignupBody = {
|
|
||||||
firstName: string
|
|
||||||
lastName: string
|
|
||||||
email: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SignupHandlers = {
|
|
||||||
signup: BigcommerceHandler<null, { cartId?: string } & Partial<SignupBody>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['POST']
|
|
||||||
|
|
||||||
const signupApi: BigcommerceApiHandler<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 BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers = { signup }
|
|
||||||
|
|
||||||
export default createApiHandler(signupApi, handlers, {})
|
|
@ -1,8 +1,9 @@
|
|||||||
|
import { normalizeCart } from '../../../lib/normalize'
|
||||||
import { parseCartItem } from '../../utils/parse-item'
|
import { parseCartItem } from '../../utils/parse-item'
|
||||||
import getCartCookie from '../../utils/get-cart-cookie'
|
import getCartCookie from '../../utils/get-cart-cookie'
|
||||||
import type { CartHandlers } from '..'
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
const addItem: CartHandlers['addItem'] = async ({
|
const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { cartId, item },
|
body: { cartId, item },
|
||||||
config,
|
config,
|
||||||
@ -39,7 +40,7 @@ const addItem: CartHandlers['addItem'] = async ({
|
|||||||
'Set-Cookie',
|
'Set-Cookie',
|
||||||
getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge)
|
getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge)
|
||||||
)
|
)
|
||||||
res.status(200).json({ data })
|
res.status(200).json({ data: normalizeCart(data) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addItem
|
export default addItem
|
@ -1,10 +1,11 @@
|
|||||||
import type { BigcommerceCart } from '../../../types'
|
import { normalizeCart } from '../../../lib/normalize'
|
||||||
import { BigcommerceApiError } from '../../utils/errors'
|
import { BigcommerceApiError } from '../../utils/errors'
|
||||||
import getCartCookie from '../../utils/get-cart-cookie'
|
import getCartCookie from '../../utils/get-cart-cookie'
|
||||||
import type { CartHandlers } from '../'
|
import type { BigcommerceCart } from '../../../types/cart'
|
||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
// Return current cart info
|
// Return current cart info
|
||||||
const getCart: CartHandlers['getCart'] = async ({
|
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { cartId },
|
body: { cartId },
|
||||||
config,
|
config,
|
||||||
@ -26,7 +27,9 @@ const getCart: CartHandlers['getCart'] = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({ data: result.data ?? null })
|
res.status(200).json({
|
||||||
|
data: result.data ? normalizeCart(result.data) : null,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getCart
|
export default getCart
|
26
framework/bigcommerce/api/endpoints/cart/index.ts
Normal file
26
framework/bigcommerce/api/endpoints/cart/index.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import cartEndpoint from '@commerce/api/endpoints/cart'
|
||||||
|
import type { CartSchema } from '../../../types/cart'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import getCart from './get-cart'
|
||||||
|
import addItem from './add-item'
|
||||||
|
import updateItem from './update-item'
|
||||||
|
import removeItem from './remove-item'
|
||||||
|
|
||||||
|
export type CartAPI = GetAPISchema<BigcommerceAPI, CartSchema>
|
||||||
|
|
||||||
|
export type CartEndpoint = CartAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CartEndpoint['handlers'] = {
|
||||||
|
getCart,
|
||||||
|
addItem,
|
||||||
|
updateItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartApi = createEndpoint<CartAPI>({
|
||||||
|
handler: cartEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default cartApi
|
@ -1,7 +1,8 @@
|
|||||||
|
import { normalizeCart } from '../../../lib/normalize'
|
||||||
import getCartCookie from '../../utils/get-cart-cookie'
|
import getCartCookie from '../../utils/get-cart-cookie'
|
||||||
import type { CartHandlers } from '..'
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
const removeItem: CartHandlers['removeItem'] = async ({
|
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { cartId, itemId },
|
body: { cartId, itemId },
|
||||||
config,
|
config,
|
||||||
@ -27,7 +28,7 @@ const removeItem: CartHandlers['removeItem'] = async ({
|
|||||||
: // Remove the cart cookie if the cart was removed (empty items)
|
: // Remove the cart cookie if the cart was removed (empty items)
|
||||||
getCartCookie(config.cartCookie)
|
getCartCookie(config.cartCookie)
|
||||||
)
|
)
|
||||||
res.status(200).json({ data })
|
res.status(200).json({ data: data && normalizeCart(data) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default removeItem
|
export default removeItem
|
@ -1,8 +1,9 @@
|
|||||||
|
import { normalizeCart } from '../../../lib/normalize'
|
||||||
import { parseCartItem } from '../../utils/parse-item'
|
import { parseCartItem } from '../../utils/parse-item'
|
||||||
import getCartCookie from '../../utils/get-cart-cookie'
|
import getCartCookie from '../../utils/get-cart-cookie'
|
||||||
import type { CartHandlers } from '..'
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
const updateItem: CartHandlers['updateItem'] = async ({
|
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { cartId, itemId, item },
|
body: { cartId, itemId, item },
|
||||||
config,
|
config,
|
||||||
@ -29,7 +30,7 @@ const updateItem: CartHandlers['updateItem'] = async ({
|
|||||||
'Set-Cookie',
|
'Set-Cookie',
|
||||||
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
|
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
|
||||||
)
|
)
|
||||||
res.status(200).json({ data })
|
res.status(200).json({ data: normalizeCart(data) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default updateItem
|
export default updateItem
|
@ -1,6 +1,5 @@
|
|||||||
import { Product } from '@commerce/types'
|
import { Product } from '@commerce/types/product'
|
||||||
import getAllProducts, { ProductEdge } from '../../../product/get-all-products'
|
import { ProductsEndpoint } from '.'
|
||||||
import type { ProductsHandlers } from '../products'
|
|
||||||
|
|
||||||
const SORT: { [key: string]: string | undefined } = {
|
const SORT: { [key: string]: string | undefined } = {
|
||||||
latest: 'id',
|
latest: 'id',
|
||||||
@ -11,10 +10,11 @@ const SORT: { [key: string]: string | undefined } = {
|
|||||||
const LIMIT = 12
|
const LIMIT = 12
|
||||||
|
|
||||||
// Return current cart info
|
// Return current cart info
|
||||||
const getProducts: ProductsHandlers['getProducts'] = async ({
|
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { search, category, brand, sort },
|
body: { search, categoryId, brandId, sort },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
// Use a dummy base as we only care about the relative path
|
// Use a dummy base as we only care about the relative path
|
||||||
const url = new URL('/v3/catalog/products', 'http://a')
|
const url = new URL('/v3/catalog/products', 'http://a')
|
||||||
@ -24,11 +24,11 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
|
|||||||
|
|
||||||
if (search) url.searchParams.set('keyword', search)
|
if (search) url.searchParams.set('keyword', search)
|
||||||
|
|
||||||
if (category && Number.isInteger(Number(category)))
|
if (categoryId && Number.isInteger(Number(categoryId)))
|
||||||
url.searchParams.set('categories:in', category)
|
url.searchParams.set('categories:in', String(categoryId))
|
||||||
|
|
||||||
if (brand && Number.isInteger(Number(brand)))
|
if (brandId && Number.isInteger(Number(brandId)))
|
||||||
url.searchParams.set('brand_id', brand)
|
url.searchParams.set('brand_id', String(brandId))
|
||||||
|
|
||||||
if (sort) {
|
if (sort) {
|
||||||
const [_sort, direction] = sort.split('-')
|
const [_sort, direction] = sort.split('-')
|
||||||
@ -47,18 +47,18 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
|
|||||||
url.pathname + url.search
|
url.pathname + url.search
|
||||||
)
|
)
|
||||||
|
|
||||||
const entityIds = data.map((p) => p.id)
|
const ids = data.map((p) => String(p.id))
|
||||||
const found = entityIds.length > 0
|
const found = ids.length > 0
|
||||||
|
|
||||||
// We want the GraphQL version of each product
|
// We want the GraphQL version of each product
|
||||||
const graphqlData = await getAllProducts({
|
const graphqlData = await commerce.getAllProducts({
|
||||||
variables: { first: LIMIT, entityIds },
|
variables: { first: LIMIT, ids },
|
||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Put the products in an object that we can use to get them by id
|
// Put the products in an object that we can use to get them by id
|
||||||
const productsById = graphqlData.products.reduce<{
|
const productsById = graphqlData.products.reduce<{
|
||||||
[k: number]: Product
|
[k: string]: Product
|
||||||
}>((prods, p) => {
|
}>((prods, p) => {
|
||||||
prods[Number(p.id)] = p
|
prods[Number(p.id)] = p
|
||||||
return prods
|
return prods
|
||||||
@ -68,7 +68,7 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
|
|||||||
|
|
||||||
// Populate the products array with the graphql products, in the order
|
// Populate the products array with the graphql products, in the order
|
||||||
// assigned by the list of entity ids
|
// assigned by the list of entity ids
|
||||||
entityIds.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
const product = productsById[id]
|
const product = productsById[id]
|
||||||
if (product) products.push(product)
|
if (product) products.push(product)
|
||||||
})
|
})
|
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import productsEndpoint from '@commerce/api/endpoints/catalog/products'
|
||||||
|
import type { ProductsSchema } from '../../../../types/product'
|
||||||
|
import type { BigcommerceAPI } from '../../..'
|
||||||
|
import getProducts from './get-products'
|
||||||
|
|
||||||
|
export type ProductsAPI = GetAPISchema<BigcommerceAPI, ProductsSchema>
|
||||||
|
|
||||||
|
export type ProductsEndpoint = ProductsAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: ProductsEndpoint['handlers'] = { getProducts }
|
||||||
|
|
||||||
|
const productsApi = createEndpoint<ProductsAPI>({
|
||||||
|
handler: productsEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default productsApi
|
62
framework/bigcommerce/api/endpoints/checkout/checkout.ts
Normal file
62
framework/bigcommerce/api/endpoints/checkout/checkout.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import type { CheckoutEndpoint } from '.'
|
||||||
|
|
||||||
|
const fullCheckout = true
|
||||||
|
|
||||||
|
const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
const { cookies } = req
|
||||||
|
const cartId = cookies[config.cartCookie]
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkout
|
18
framework/bigcommerce/api/endpoints/checkout/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/checkout/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
|
||||||
|
import type { CheckoutSchema } from '../../../types/checkout'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import checkout from './checkout'
|
||||||
|
|
||||||
|
export type CheckoutAPI = GetAPISchema<BigcommerceAPI, CheckoutSchema>
|
||||||
|
|
||||||
|
export type CheckoutEndpoint = CheckoutAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CheckoutEndpoint['handlers'] = { checkout }
|
||||||
|
|
||||||
|
const checkoutApi = createEndpoint<CheckoutAPI>({
|
||||||
|
handler: checkoutEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default checkoutApi
|
@ -1,5 +1,5 @@
|
|||||||
import type { GetLoggedInCustomerQuery } from '../../../schema'
|
import type { GetLoggedInCustomerQuery } from '../../../schema'
|
||||||
import type { CustomersHandlers } from '..'
|
import type { CustomerEndpoint } from '.'
|
||||||
|
|
||||||
export const getLoggedInCustomerQuery = /* GraphQL */ `
|
export const getLoggedInCustomerQuery = /* GraphQL */ `
|
||||||
query getLoggedInCustomer {
|
query getLoggedInCustomer {
|
||||||
@ -24,7 +24,7 @@ export const getLoggedInCustomerQuery = /* GraphQL */ `
|
|||||||
|
|
||||||
export type Customer = NonNullable<GetLoggedInCustomerQuery['customer']>
|
export type Customer = NonNullable<GetLoggedInCustomerQuery['customer']>
|
||||||
|
|
||||||
const getLoggedInCustomer: CustomersHandlers['getLoggedInCustomer'] = async ({
|
const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] = async ({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
config,
|
config,
|
18
framework/bigcommerce/api/endpoints/customer/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/customer/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import customerEndpoint from '@commerce/api/endpoints/customer'
|
||||||
|
import type { CustomerSchema } from '../../../types/customer'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import getLoggedInCustomer from './get-logged-in-customer'
|
||||||
|
|
||||||
|
export type CustomerAPI = GetAPISchema<BigcommerceAPI, CustomerSchema>
|
||||||
|
|
||||||
|
export type CustomerEndpoint = CustomerAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: CustomerEndpoint['handlers'] = { getLoggedInCustomer }
|
||||||
|
|
||||||
|
const customerApi = createEndpoint<CustomerAPI>({
|
||||||
|
handler: customerEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default customerApi
|
18
framework/bigcommerce/api/endpoints/login/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/login/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import loginEndpoint from '@commerce/api/endpoints/login'
|
||||||
|
import type { LoginSchema } from '../../../types/login'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import login from './login'
|
||||||
|
|
||||||
|
export type LoginAPI = GetAPISchema<BigcommerceAPI, LoginSchema>
|
||||||
|
|
||||||
|
export type LoginEndpoint = LoginAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: LoginEndpoint['handlers'] = { login }
|
||||||
|
|
||||||
|
const loginApi = createEndpoint<LoginAPI>({
|
||||||
|
handler: loginEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default loginApi
|
@ -1,13 +1,13 @@
|
|||||||
import { FetcherError } from '@commerce/utils/errors'
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
import login from '../../../auth/login'
|
import type { LoginEndpoint } from '.'
|
||||||
import type { LoginHandlers } from '../login'
|
|
||||||
|
|
||||||
const invalidCredentials = /invalid credentials/i
|
const invalidCredentials = /invalid credentials/i
|
||||||
|
|
||||||
const loginHandler: LoginHandlers['login'] = async ({
|
const login: LoginEndpoint['handlers']['login'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { email, password },
|
body: { email, password },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: Add proper validations with something like Ajv
|
// TODO: Add proper validations with something like Ajv
|
||||||
if (!(email && password)) {
|
if (!(email && password)) {
|
||||||
@ -21,7 +21,7 @@ const loginHandler: LoginHandlers['login'] = async ({
|
|||||||
// and numeric characters.
|
// and numeric characters.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await login({ variables: { email, password }, config, res })
|
await commerce.login({ variables: { email, password }, config, res })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Check if the email and password didn't match an existing account
|
// Check if the email and password didn't match an existing account
|
||||||
if (
|
if (
|
||||||
@ -46,4 +46,4 @@ const loginHandler: LoginHandlers['login'] = async ({
|
|||||||
res.status(200).json({ data: null })
|
res.status(200).json({ data: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default loginHandler
|
export default login
|
18
framework/bigcommerce/api/endpoints/logout/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/logout/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import logoutEndpoint from '@commerce/api/endpoints/logout'
|
||||||
|
import type { LogoutSchema } from '../../../types/logout'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import logout from './logout'
|
||||||
|
|
||||||
|
export type LogoutAPI = GetAPISchema<BigcommerceAPI, LogoutSchema>
|
||||||
|
|
||||||
|
export type LogoutEndpoint = LogoutAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: LogoutEndpoint['handlers'] = { logout }
|
||||||
|
|
||||||
|
const logoutApi = createEndpoint<LogoutAPI>({
|
||||||
|
handler: logoutEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default logoutApi
|
@ -1,7 +1,7 @@
|
|||||||
import { serialize } from 'cookie'
|
import { serialize } from 'cookie'
|
||||||
import { LogoutHandlers } from '../logout'
|
import type { LogoutEndpoint } from '.'
|
||||||
|
|
||||||
const logoutHandler: LogoutHandlers['logout'] = async ({
|
const logout: LogoutEndpoint['handlers']['logout'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { redirectTo },
|
body: { redirectTo },
|
||||||
config,
|
config,
|
||||||
@ -20,4 +20,4 @@ const logoutHandler: LogoutHandlers['logout'] = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default logoutHandler
|
export default logout
|
18
framework/bigcommerce/api/endpoints/signup/index.ts
Normal file
18
framework/bigcommerce/api/endpoints/signup/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import signupEndpoint from '@commerce/api/endpoints/signup'
|
||||||
|
import type { SignupSchema } from '../../../types/signup'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import signup from './signup'
|
||||||
|
|
||||||
|
export type SignupAPI = GetAPISchema<BigcommerceAPI, SignupSchema>
|
||||||
|
|
||||||
|
export type SignupEndpoint = SignupAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: SignupEndpoint['handlers'] = { signup }
|
||||||
|
|
||||||
|
const singupApi = createEndpoint<SignupAPI>({
|
||||||
|
handler: signupEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default singupApi
|
@ -1,11 +1,11 @@
|
|||||||
import { BigcommerceApiError } from '../../utils/errors'
|
import { BigcommerceApiError } from '../../utils/errors'
|
||||||
import login from '../../../auth/login'
|
import type { SignupEndpoint } from '.'
|
||||||
import { SignupHandlers } from '../signup'
|
|
||||||
|
|
||||||
const signup: SignupHandlers['signup'] = async ({
|
const signup: SignupEndpoint['handlers']['signup'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { firstName, lastName, email, password },
|
body: { firstName, lastName, email, password },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: Add proper validations with something like Ajv
|
// TODO: Add proper validations with something like Ajv
|
||||||
if (!(firstName && lastName && email && password)) {
|
if (!(firstName && lastName && email && password)) {
|
||||||
@ -54,7 +54,7 @@ const signup: SignupHandlers['signup'] = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login the customer right after creating it
|
// Login the customer right after creating it
|
||||||
await login({ variables: { email, password }, res, config })
|
await commerce.login({ variables: { email, password }, res, config })
|
||||||
|
|
||||||
res.status(200).json({ data: null })
|
res.status(200).json({ data: null })
|
||||||
}
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import type { WishlistHandlers } from '..'
|
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||||
import getCustomerId from '../../../customer/get-customer-id'
|
|
||||||
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
|
|
||||||
import { parseWishlistItem } from '../../utils/parse-item'
|
import { parseWishlistItem } from '../../utils/parse-item'
|
||||||
|
import getCustomerId from './utils/get-customer-id'
|
||||||
|
import type { WishlistEndpoint } from '.'
|
||||||
|
|
||||||
// Returns the wishlist of the signed customer
|
// Return wishlist info
|
||||||
const addItem: WishlistHandlers['addItem'] = async ({
|
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { customerToken, item },
|
body: { customerToken, item },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@ -26,7 +27,7 @@ const addItem: WishlistHandlers['addItem'] = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wishlist } = await getCustomerWishlist({
|
const { wishlist } = await commerce.getCustomerWishlist({
|
||||||
variables: { customerId },
|
variables: { customerId },
|
||||||
config,
|
config,
|
||||||
})
|
})
|
@ -1,12 +1,14 @@
|
|||||||
import getCustomerId from '../../../customer/get-customer-id'
|
import type { Wishlist } from '../../../types/wishlist'
|
||||||
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
|
import type { WishlistEndpoint } from '.'
|
||||||
import type { Wishlist, WishlistHandlers } from '..'
|
import getCustomerId from './utils/get-customer-id'
|
||||||
|
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||||
|
|
||||||
// Return wishlist info
|
// Return wishlist info
|
||||||
const getWishlist: WishlistHandlers['getWishlist'] = async ({
|
const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { customerToken, includeProducts },
|
body: { customerToken, includeProducts },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
let result: { data?: Wishlist } = {}
|
let result: { data?: Wishlist } = {}
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ const getWishlist: WishlistHandlers['getWishlist'] = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wishlist } = await getCustomerWishlist({
|
const { wishlist } = await commerce.getCustomerWishlist({
|
||||||
variables: { customerId },
|
variables: { customerId },
|
||||||
includeProducts,
|
includeProducts,
|
||||||
config,
|
config,
|
24
framework/bigcommerce/api/endpoints/wishlist/index.ts
Normal file
24
framework/bigcommerce/api/endpoints/wishlist/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||||
|
import wishlistEndpoint from '@commerce/api/endpoints/wishlist'
|
||||||
|
import type { WishlistSchema } from '../../../types/wishlist'
|
||||||
|
import type { BigcommerceAPI } from '../..'
|
||||||
|
import getWishlist from './get-wishlist'
|
||||||
|
import addItem from './add-item'
|
||||||
|
import removeItem from './remove-item'
|
||||||
|
|
||||||
|
export type WishlistAPI = GetAPISchema<BigcommerceAPI, WishlistSchema>
|
||||||
|
|
||||||
|
export type WishlistEndpoint = WishlistAPI['endpoint']
|
||||||
|
|
||||||
|
export const handlers: WishlistEndpoint['handlers'] = {
|
||||||
|
getWishlist,
|
||||||
|
addItem,
|
||||||
|
removeItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wishlistApi = createEndpoint<WishlistAPI>({
|
||||||
|
handler: wishlistEndpoint,
|
||||||
|
handlers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default wishlistApi
|
@ -1,20 +1,20 @@
|
|||||||
import getCustomerId from '../../../customer/get-customer-id'
|
import type { Wishlist } from '../../../types/wishlist'
|
||||||
import getCustomerWishlist, {
|
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||||
Wishlist,
|
import getCustomerId from './utils/get-customer-id'
|
||||||
} from '../../../customer/get-customer-wishlist'
|
import type { WishlistEndpoint } from '.'
|
||||||
import type { WishlistHandlers } from '..'
|
|
||||||
|
|
||||||
// Return current wishlist info
|
// Return wishlist info
|
||||||
const removeItem: WishlistHandlers['removeItem'] = async ({
|
const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
||||||
res,
|
res,
|
||||||
body: { customerToken, itemId },
|
body: { customerToken, itemId },
|
||||||
config,
|
config,
|
||||||
|
commerce,
|
||||||
}) => {
|
}) => {
|
||||||
const customerId =
|
const customerId =
|
||||||
customerToken && (await getCustomerId({ customerToken, config }))
|
customerToken && (await getCustomerId({ customerToken, config }))
|
||||||
const { wishlist } =
|
const { wishlist } =
|
||||||
(customerId &&
|
(customerId &&
|
||||||
(await getCustomerWishlist({
|
(await commerce.getCustomerWishlist({
|
||||||
variables: { customerId },
|
variables: { customerId },
|
||||||
config,
|
config,
|
||||||
}))) ||
|
}))) ||
|
@ -1,5 +1,5 @@
|
|||||||
import { GetCustomerIdQuery } from '../schema'
|
import type { GetCustomerIdQuery } from '../../../../schema'
|
||||||
import { BigcommerceConfig, getConfig } from '../api'
|
import type { BigcommerceConfig } from '../../..'
|
||||||
|
|
||||||
export const getCustomerIdQuery = /* GraphQL */ `
|
export const getCustomerIdQuery = /* GraphQL */ `
|
||||||
query getCustomerId {
|
query getCustomerId {
|
||||||
@ -14,10 +14,8 @@ async function getCustomerId({
|
|||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
customerToken: string
|
customerToken: string
|
||||||
config?: BigcommerceConfig
|
config: BigcommerceConfig
|
||||||
}): Promise<number | undefined> {
|
}): Promise<string | undefined> {
|
||||||
config = getConfig(config)
|
|
||||||
|
|
||||||
const { data } = await config.fetch<GetCustomerIdQuery>(
|
const { data } = await config.fetch<GetCustomerIdQuery>(
|
||||||
getCustomerIdQuery,
|
getCustomerIdQuery,
|
||||||
undefined,
|
undefined,
|
||||||
@ -28,7 +26,7 @@ async function getCustomerId({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return data?.customer?.entityId
|
return String(data?.customer?.entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getCustomerId
|
export default getCustomerId
|
@ -1,8 +1,29 @@
|
|||||||
import type { RequestInit } from '@vercel/fetch'
|
import type { RequestInit } from '@vercel/fetch'
|
||||||
import type { CommerceAPIConfig } from '@commerce/api'
|
import {
|
||||||
|
CommerceAPI,
|
||||||
|
CommerceAPIConfig,
|
||||||
|
getCommerceApi as commerceApi,
|
||||||
|
} from '@commerce/api'
|
||||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||||
import fetchStoreApi from './utils/fetch-store-api'
|
import fetchStoreApi from './utils/fetch-store-api'
|
||||||
|
|
||||||
|
import type { CartAPI } from './endpoints/cart'
|
||||||
|
import type { CustomerAPI } from './endpoints/customer'
|
||||||
|
import type { LoginAPI } from './endpoints/login'
|
||||||
|
import type { LogoutAPI } from './endpoints/logout'
|
||||||
|
import type { SignupAPI } from './endpoints/signup'
|
||||||
|
import type { ProductsAPI } from './endpoints/catalog/products'
|
||||||
|
import type { WishlistAPI } from './endpoints/wishlist'
|
||||||
|
|
||||||
|
import login from './operations/login'
|
||||||
|
import getAllPages from './operations/get-all-pages'
|
||||||
|
import getPage from './operations/get-page'
|
||||||
|
import getSiteInfo from './operations/get-site-info'
|
||||||
|
import getCustomerWishlist from './operations/get-customer-wishlist'
|
||||||
|
import getAllProductPaths from './operations/get-all-product-paths'
|
||||||
|
import getAllProducts from './operations/get-all-products'
|
||||||
|
import getProduct from './operations/get-product'
|
||||||
|
|
||||||
export interface BigcommerceConfig extends CommerceAPIConfig {
|
export interface BigcommerceConfig extends CommerceAPIConfig {
|
||||||
// Indicates if the returned metadata with translations should be applied to the
|
// Indicates if the returned metadata with translations should be applied to the
|
||||||
// data or returned as it is
|
// data or returned as it is
|
||||||
@ -39,34 +60,12 @@ if (!(STORE_API_URL && STORE_API_TOKEN && STORE_API_CLIENT_ID)) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Config {
|
|
||||||
private config: BigcommerceConfig
|
|
||||||
|
|
||||||
constructor(config: Omit<BigcommerceConfig, '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<BigcommerceConfig> = {}) {
|
|
||||||
return Object.entries(userConfig).reduce<BigcommerceConfig>(
|
|
||||||
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
|
||||||
{ ...this.config }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(newConfig: Partial<BigcommerceConfig>) {
|
|
||||||
Object.assign(this.config, newConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ONE_DAY = 60 * 60 * 24
|
const ONE_DAY = 60 * 60 * 24
|
||||||
const config = new Config({
|
|
||||||
|
const config: BigcommerceConfig = {
|
||||||
commerceUrl: API_URL,
|
commerceUrl: API_URL,
|
||||||
apiToken: API_TOKEN,
|
apiToken: API_TOKEN,
|
||||||
|
customerCookie: 'SHOP_TOKEN',
|
||||||
cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId',
|
cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId',
|
||||||
cartCookieMaxAge: ONE_DAY * 30,
|
cartCookieMaxAge: ONE_DAY * 30,
|
||||||
fetch: fetchGraphqlApi,
|
fetch: fetchGraphqlApi,
|
||||||
@ -77,12 +76,36 @@ const config = new Config({
|
|||||||
storeApiClientId: STORE_API_CLIENT_ID,
|
storeApiClientId: STORE_API_CLIENT_ID,
|
||||||
storeChannelId: STORE_CHANNEL_ID,
|
storeChannelId: STORE_CHANNEL_ID,
|
||||||
storeApiFetch: fetchStoreApi,
|
storeApiFetch: fetchStoreApi,
|
||||||
})
|
|
||||||
|
|
||||||
export function getConfig(userConfig?: Partial<BigcommerceConfig>) {
|
|
||||||
return config.getConfig(userConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setConfig(newConfig: Partial<BigcommerceConfig>) {
|
const operations = {
|
||||||
return config.setConfig(newConfig)
|
login,
|
||||||
|
getAllPages,
|
||||||
|
getPage,
|
||||||
|
getSiteInfo,
|
||||||
|
getCustomerWishlist,
|
||||||
|
getAllProductPaths,
|
||||||
|
getAllProducts,
|
||||||
|
getProduct,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = { config, operations }
|
||||||
|
|
||||||
|
export type Provider = typeof provider
|
||||||
|
|
||||||
|
export type APIs =
|
||||||
|
| CartAPI
|
||||||
|
| CustomerAPI
|
||||||
|
| LoginAPI
|
||||||
|
| LogoutAPI
|
||||||
|
| SignupAPI
|
||||||
|
| ProductsAPI
|
||||||
|
| WishlistAPI
|
||||||
|
|
||||||
|
export type BigcommerceAPI<P extends Provider = Provider> = CommerceAPI<P>
|
||||||
|
|
||||||
|
export function getCommerceApi<P extends Provider>(
|
||||||
|
customProvider: P = provider as any
|
||||||
|
): BigcommerceAPI<P> {
|
||||||
|
return commerceApi(customProvider)
|
||||||
}
|
}
|
||||||
|
46
framework/bigcommerce/api/operations/get-all-pages.ts
Normal file
46
framework/bigcommerce/api/operations/get-all-pages.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { Page, GetAllPagesOperation } from '../../types/page'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
|
import { BigcommerceConfig, Provider } from '..'
|
||||||
|
|
||||||
|
export default function getAllPagesOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getAllPages<T extends GetAllPagesOperation>(opts?: {
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllPages<T extends GetAllPagesOperation>(
|
||||||
|
opts: {
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllPages<T extends GetAllPagesOperation>({
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
const cfg = commerce.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 cfg.storeApiFetch<
|
||||||
|
RecursivePartial<{ data: Page[] }>
|
||||||
|
>('/v3/content/pages')
|
||||||
|
const pages = (data as RecursiveRequired<typeof data>) ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages: preview ? pages : pages.filter((p) => p.is_visible),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllPages
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { GetAllProductPathsQuery } from '../../schema'
|
||||||
|
import type { GetAllProductPathsOperation } from '../../types/product'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
|
import filterEdges from '../utils/filter-edges'
|
||||||
|
import { BigcommerceConfig, Provider } from '..'
|
||||||
|
|
||||||
|
export const getAllProductPathsQuery = /* GraphQL */ `
|
||||||
|
query getAllProductPaths($first: Int = 100) {
|
||||||
|
site {
|
||||||
|
products(first: $first) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function getAllProductPathsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getAllProductPaths<
|
||||||
|
T extends GetAllProductPathsOperation
|
||||||
|
>(opts?: {
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
|
||||||
|
opts: {
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||||
|
query = getAllProductPathsQuery,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
config = commerce.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>).map(
|
||||||
|
({ node }) => node
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAllProductPaths
|
||||||
|
}
|
135
framework/bigcommerce/api/operations/get-all-products.ts
Normal file
135
framework/bigcommerce/api/operations/get-all-products.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type {
|
||||||
|
GetAllProductsQuery,
|
||||||
|
GetAllProductsQueryVariables,
|
||||||
|
} from '../../schema'
|
||||||
|
import type { GetAllProductsOperation } from '../../types/product'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
|
import filterEdges from '../utils/filter-edges'
|
||||||
|
import setProductLocaleMeta from '../utils/set-product-locale-meta'
|
||||||
|
import { productConnectionFragment } from '../fragments/product'
|
||||||
|
import { BigcommerceConfig, Provider } from '..'
|
||||||
|
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
|
||||||
|
|
||||||
|
function getProductsType(
|
||||||
|
relevance?: GetAllProductsOperation['variables']['relevance']
|
||||||
|
) {
|
||||||
|
switch (relevance) {
|
||||||
|
case 'featured':
|
||||||
|
return 'featuredProducts'
|
||||||
|
case 'best_selling':
|
||||||
|
return 'bestSellingProducts'
|
||||||
|
case 'newest':
|
||||||
|
return 'newestProducts'
|
||||||
|
default:
|
||||||
|
return 'products'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getAllProductsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllProducts<T extends GetAllProductsOperation>(
|
||||||
|
opts: {
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getAllProducts<T extends GetAllProductsOperation>({
|
||||||
|
query = getAllProductsQuery,
|
||||||
|
variables: vars = {},
|
||||||
|
config: cfg,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables?: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
const config = commerce.getConfig(cfg)
|
||||||
|
const { locale } = config
|
||||||
|
const field = getProductsType(vars.relevance)
|
||||||
|
const variables: GetAllProductsQueryVariables = {
|
||||||
|
locale,
|
||||||
|
hasLocale: !!locale,
|
||||||
|
}
|
||||||
|
|
||||||
|
variables[field] = true
|
||||||
|
|
||||||
|
if (vars.first) variables.first = vars.first
|
||||||
|
if (vars.ids) variables.entityIds = vars.ids.map((id) => Number(id))
|
||||||
|
|
||||||
|
// 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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllProducts
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type {
|
||||||
|
GetCustomerWishlistOperation,
|
||||||
|
Wishlist,
|
||||||
|
} from '../../types/wishlist'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
|
import { BigcommerceConfig, Provider } from '..'
|
||||||
|
import getAllProducts, { ProductEdge } from './get-all-products'
|
||||||
|
|
||||||
|
export default function getCustomerWishlistOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getCustomerWishlist<
|
||||||
|
T extends GetCustomerWishlistOperation
|
||||||
|
>(opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
includeProducts?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>(
|
||||||
|
opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
includeProducts?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>({
|
||||||
|
config,
|
||||||
|
variables,
|
||||||
|
includeProducts,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
variables: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
includeProducts?: boolean
|
||||||
|
}): Promise<T['data']> {
|
||||||
|
config = commerce.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 ids = wishlist.items
|
||||||
|
?.map((item) => (item?.product_id ? String(item?.product_id) : null))
|
||||||
|
.filter((id): id is string => !!id)
|
||||||
|
|
||||||
|
if (ids?.length) {
|
||||||
|
const graphqlData = await commerce.getAllProducts({
|
||||||
|
variables: { first: 100, ids },
|
||||||
|
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> }
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCustomerWishlist
|
||||||
|
}
|
54
framework/bigcommerce/api/operations/get-page.ts
Normal file
54
framework/bigcommerce/api/operations/get-page.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { GetPageOperation, Page } from '../../types/page'
|
||||||
|
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
|
import type { BigcommerceConfig, Provider } from '..'
|
||||||
|
import { normalizePage } from '../../lib/normalize'
|
||||||
|
|
||||||
|
export default function getPageOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getPage<T extends GetPageOperation>(opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getPage<T extends GetPageOperation>(
|
||||||
|
opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getPage<T extends GetPageOperation>({
|
||||||
|
url,
|
||||||
|
variables,
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
url?: string
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']> {
|
||||||
|
const cfg = commerce.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 cfg.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: normalizePage(page as any) }
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPage
|
||||||
|
}
|
119
framework/bigcommerce/api/operations/get-product.ts
Normal file
119
framework/bigcommerce/api/operations/get-product.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { GetProductOperation } from '../../types/product'
|
||||||
|
import type { GetProductQuery, GetProductQueryVariables } from '../../schema'
|
||||||
|
import setProductLocaleMeta from '../utils/set-product-locale-meta'
|
||||||
|
import { productInfoFragment } from '../fragments/product'
|
||||||
|
import { BigcommerceConfig, Provider } from '..'
|
||||||
|
import { normalizeProduct } from '../../lib/normalize'
|
||||||
|
|
||||||
|
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}
|
||||||
|
`
|
||||||
|
|
||||||
|
// TODO: See if this type is useful for defining the Product type
|
||||||
|
// export type ProductNode = Extract<
|
||||||
|
// GetProductQuery['site']['route']['node'],
|
||||||
|
// { __typename: 'Product' }
|
||||||
|
// >
|
||||||
|
|
||||||
|
export default function getAllProductPathsOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getProduct<T extends GetProductOperation>(opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getProduct<T extends GetProductOperation>(
|
||||||
|
opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getProduct<T extends GetProductOperation>({
|
||||||
|
query = getProductQuery,
|
||||||
|
variables: { slug, ...vars },
|
||||||
|
config: cfg,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables: T['variables']
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']> {
|
||||||
|
const config = commerce.getConfig(cfg)
|
||||||
|
const { locale } = config
|
||||||
|
const variables: GetProductQueryVariables = {
|
||||||
|
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 {}
|
||||||
|
}
|
||||||
|
return getProduct
|
||||||
|
}
|
87
framework/bigcommerce/api/operations/get-site-info.ts
Normal file
87
framework/bigcommerce/api/operations/get-site-info.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { GetSiteInfoOperation } from '../../types/site'
|
||||||
|
import type { GetSiteInfoQuery } from '../../schema'
|
||||||
|
import filterEdges from '../utils/filter-edges'
|
||||||
|
import type { BigcommerceConfig, Provider } from '..'
|
||||||
|
import { categoryTreeItemFragment } from '../fragments/category-tree'
|
||||||
|
import { normalizeCategory } from '../../lib/normalize'
|
||||||
|
|
||||||
|
// 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 default function getSiteInfoOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function getSiteInfo<T extends GetSiteInfoOperation>(opts?: {
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getSiteInfo<T extends GetSiteInfoOperation>(
|
||||||
|
opts: {
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function getSiteInfo<T extends GetSiteInfoOperation>({
|
||||||
|
query = getSiteInfoQuery,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
config?: Partial<BigcommerceConfig>
|
||||||
|
preview?: boolean
|
||||||
|
} = {}): Promise<T['data']> {
|
||||||
|
const cfg = commerce.getConfig(config)
|
||||||
|
const { data } = await cfg.fetch<GetSiteInfoQuery>(query)
|
||||||
|
const categories = data.site.categoryTree.map(normalizeCategory)
|
||||||
|
const brands = data.site?.brands?.edges
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories: categories ?? [],
|
||||||
|
brands: filterEdges(brands),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSiteInfo
|
||||||
|
}
|
79
framework/bigcommerce/api/operations/login.ts
Normal file
79
framework/bigcommerce/api/operations/login.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { ServerResponse } from 'http'
|
||||||
|
import type {
|
||||||
|
OperationContext,
|
||||||
|
OperationOptions,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
import type { LoginOperation } from '../../types/login'
|
||||||
|
import type { LoginMutation } from '../../schema'
|
||||||
|
import type { RecursivePartial } from '../utils/types'
|
||||||
|
import concatHeader from '../utils/concat-cookie'
|
||||||
|
import type { BigcommerceConfig, Provider } from '..'
|
||||||
|
|
||||||
|
export const loginMutation = /* GraphQL */ `
|
||||||
|
mutation login($email: String!, $password: String!) {
|
||||||
|
login(email: $email, password: $password) {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function loginOperation({
|
||||||
|
commerce,
|
||||||
|
}: OperationContext<Provider>) {
|
||||||
|
async function login<T extends LoginOperation>(opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
res: ServerResponse
|
||||||
|
}): Promise<T['data']>
|
||||||
|
|
||||||
|
async function login<T extends LoginOperation>(
|
||||||
|
opts: {
|
||||||
|
variables: T['variables']
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
res: ServerResponse
|
||||||
|
} & OperationOptions
|
||||||
|
): Promise<T['data']>
|
||||||
|
|
||||||
|
async function login<T extends LoginOperation>({
|
||||||
|
query = loginMutation,
|
||||||
|
variables,
|
||||||
|
res: response,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables: T['variables']
|
||||||
|
res: ServerResponse
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
}): Promise<T['data']> {
|
||||||
|
config = commerce.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import { BigcommerceConfig, getConfig } from '..'
|
|
||||||
|
|
||||||
export type BigcommerceApiHandler<
|
|
||||||
T = any,
|
|
||||||
H extends BigcommerceHandlers = {},
|
|
||||||
Options extends {} = {}
|
|
||||||
> = (
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse<BigcommerceApiResponse<T>>,
|
|
||||||
config: BigcommerceConfig,
|
|
||||||
handlers: H,
|
|
||||||
// Custom configs that may be used by a particular handler
|
|
||||||
options: Options
|
|
||||||
) => void | Promise<void>
|
|
||||||
|
|
||||||
export type BigcommerceHandler<T = any, Body = null> = (options: {
|
|
||||||
req: NextApiRequest
|
|
||||||
res: NextApiResponse<BigcommerceApiResponse<T>>
|
|
||||||
config: BigcommerceConfig
|
|
||||||
body: Body
|
|
||||||
}) => void | Promise<void>
|
|
||||||
|
|
||||||
export type BigcommerceHandlers<T = any> = {
|
|
||||||
[k: string]: BigcommerceHandler<T, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BigcommerceApiResponse<T> = {
|
|
||||||
data: T | null
|
|
||||||
errors?: { message: string; code?: string }[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function createApiHandler<
|
|
||||||
T = any,
|
|
||||||
H extends BigcommerceHandlers = {},
|
|
||||||
Options extends {} = {}
|
|
||||||
>(
|
|
||||||
handler: BigcommerceApiHandler<T, H, Options>,
|
|
||||||
handlers: H,
|
|
||||||
defaultOptions: Options
|
|
||||||
) {
|
|
||||||
return function getApiHandler({
|
|
||||||
config,
|
|
||||||
operations,
|
|
||||||
options,
|
|
||||||
}: {
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import { FetcherError } from '@commerce/utils/errors'
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
import type { GraphQLFetcher } from '@commerce/api'
|
import type { GraphQLFetcher } from '@commerce/api'
|
||||||
import { getConfig } from '..'
|
import { provider } from '..'
|
||||||
import fetch from './fetch'
|
import fetch from './fetch'
|
||||||
|
|
||||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||||
@ -9,7 +9,7 @@ const fetchGraphqlApi: GraphQLFetcher = async (
|
|||||||
fetchOptions
|
fetchOptions
|
||||||
) => {
|
) => {
|
||||||
// log.warn(query)
|
// log.warn(query)
|
||||||
const config = getConfig()
|
const { config } = provider
|
||||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { RequestInit, Response } from '@vercel/fetch'
|
import type { RequestInit, Response } from '@vercel/fetch'
|
||||||
import { getConfig } from '..'
|
import { provider } from '..'
|
||||||
import { BigcommerceApiError, BigcommerceNetworkError } from './errors'
|
import { BigcommerceApiError, BigcommerceNetworkError } from './errors'
|
||||||
import fetch from './fetch'
|
import fetch from './fetch'
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ export default async function fetchStoreApi<T>(
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
options?: RequestInit
|
options?: RequestInit
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const config = getConfig()
|
const { config } = provider
|
||||||
let res: Response
|
let res: Response
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import type { ItemBody as WishlistItemBody } from '../wishlist'
|
import type { WishlistItemBody } from '../../types/wishlist'
|
||||||
import type { CartItemBody, OptionSelections } from '../../types'
|
import type { CartItemBody, OptionSelections } from '../../types/cart'
|
||||||
|
|
||||||
type BCWishlistItemBody = {
|
type BCWishlistItemBody = {
|
||||||
product_id: number
|
product_id: number
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { ProductNode } from '../../product/get-all-products'
|
import type { ProductNode } from '../operations/get-all-products'
|
||||||
import type { RecursivePartial } from './types'
|
import type { RecursivePartial } from './types'
|
||||||
|
|
||||||
export default function setProductLocaleMeta(
|
export default function setProductLocaleMeta(
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
|
||||||
import createApiHandler, {
|
|
||||||
BigcommerceApiHandler,
|
|
||||||
BigcommerceHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import type {
|
|
||||||
Wishlist,
|
|
||||||
WishlistItem,
|
|
||||||
} from '../../customer/get-customer-wishlist'
|
|
||||||
import getWishlist from './handlers/get-wishlist'
|
|
||||||
import addItem from './handlers/add-item'
|
|
||||||
import removeItem from './handlers/remove-item'
|
|
||||||
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: BigcommerceHandler<
|
|
||||||
Wishlist,
|
|
||||||
{ customerToken?: string; includeProducts?: boolean }
|
|
||||||
>
|
|
||||||
addItem: BigcommerceHandler<
|
|
||||||
Wishlist,
|
|
||||||
{ customerToken?: string } & Partial<AddItemBody>
|
|
||||||
>
|
|
||||||
removeItem: BigcommerceHandler<
|
|
||||||
Wishlist,
|
|
||||||
{ customerToken?: string } & Partial<RemoveItemBody>
|
|
||||||
>
|
|
||||||
}
|
|
||||||
|
|
||||||
const METHODS = ['GET', 'POST', 'DELETE']
|
|
||||||
|
|
||||||
// TODO: a complete implementation should have schema validation for `req.body`
|
|
||||||
const wishlistApi: BigcommerceApiHandler<Wishlist, WishlistHandlers> = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
config,
|
|
||||||
handlers
|
|
||||||
) => {
|
|
||||||
if (!isAllowedMethod(req, res, METHODS)) return
|
|
||||||
|
|
||||||
const { cookies } = req
|
|
||||||
const customerToken = cookies[config.customerCookie]
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Return current wishlist info
|
|
||||||
if (req.method === 'GET') {
|
|
||||||
const body = {
|
|
||||||
customerToken,
|
|
||||||
includeProducts: req.query.products === '1',
|
|
||||||
}
|
|
||||||
return await handlers['getWishlist']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an item to the wishlist
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const body = { ...req.body, customerToken }
|
|
||||||
return await handlers['addItem']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove an item from the wishlist
|
|
||||||
if (req.method === 'DELETE') {
|
|
||||||
const body = { ...req.body, customerToken }
|
|
||||||
return await handlers['removeItem']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
const message =
|
|
||||||
error instanceof BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handlers = {
|
|
||||||
getWishlist,
|
|
||||||
addItem,
|
|
||||||
removeItem,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createApiHandler(wishlistApi, handlers, {})
|
|
@ -1,73 +0,0 @@
|
|||||||
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 { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
res: ServerResponse
|
|
||||||
}): Promise<LoginResult>
|
|
||||||
|
|
||||||
async function login<T extends { result?: any }, V = any>(opts: {
|
|
||||||
query: string
|
|
||||||
variables: V
|
|
||||||
res: ServerResponse
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
}): Promise<LoginResult<T>>
|
|
||||||
|
|
||||||
async function login({
|
|
||||||
query = loginMutation,
|
|
||||||
variables,
|
|
||||||
res: response,
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables: LoginVariables
|
|
||||||
res: ServerResponse
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
}): 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
|
|
@ -2,14 +2,14 @@ import { useCallback } from 'react'
|
|||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
import type { LoginBody } from '../api/customers/login'
|
import type { LoginHook } from '../types/login'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
export default useLogin as UseLogin<typeof handler>
|
export default useLogin as UseLogin<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<null, {}, LoginBody> = {
|
export const handler: MutationHook<LoginHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/customers/login',
|
url: '/api/login',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
},
|
},
|
||||||
async fetcher({ input: { email, password }, options, fetch }) {
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
import useLogout, { UseLogout } from '@commerce/auth/use-logout'
|
||||||
|
import type { LogoutHook } from '../types/logout'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
export default useLogout as UseLogout<typeof handler>
|
export default useLogout as UseLogout<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<null> = {
|
export const handler: MutationHook<LogoutHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/customers/logout',
|
url: '/api/logout',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
},
|
},
|
||||||
useHook: ({ fetch }) => () => {
|
useHook: ({ fetch }) => () => {
|
||||||
|
@ -2,14 +2,14 @@ import { useCallback } from 'react'
|
|||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
import type { SignupBody } from '../api/customers/signup'
|
import type { SignupHook } from '../types/signup'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
export default useSignup as UseSignup<typeof handler>
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<null, {}, SignupBody, SignupBody> = {
|
export const handler: MutationHook<SignupHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/customers/signup',
|
url: '/api/signup',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
},
|
},
|
||||||
async fetcher({
|
async fetcher({
|
||||||
|
@ -2,20 +2,14 @@ import { useCallback } from 'react'
|
|||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
import { normalizeCart } from '../lib/normalize'
|
import type { AddItemHook } from '@commerce/types/cart'
|
||||||
import type {
|
|
||||||
Cart,
|
|
||||||
BigcommerceCart,
|
|
||||||
CartItemBody,
|
|
||||||
AddCartItemBody,
|
|
||||||
} from '../types'
|
|
||||||
import useCart from './use-cart'
|
import useCart from './use-cart'
|
||||||
|
|
||||||
export default useAddItem as UseAddItem<typeof handler>
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/cart',
|
url: '/api/cart',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
},
|
},
|
||||||
async fetcher({ input: item, options, fetch }) {
|
async fetcher({ input: item, options, fetch }) {
|
||||||
@ -28,12 +22,12 @@ export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fetch<BigcommerceCart, AddCartItemBody>({
|
const data = await fetch({
|
||||||
...options,
|
...options,
|
||||||
body: { item },
|
body: { item },
|
||||||
})
|
})
|
||||||
|
|
||||||
return normalizeCart(data)
|
return data
|
||||||
},
|
},
|
||||||
useHook: ({ fetch }) => () => {
|
useHook: ({ fetch }) => () => {
|
||||||
const { mutate } = useCart()
|
const { mutate } = useCart()
|
||||||
|
@ -1,25 +1,15 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { SWRHook } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart'
|
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
||||||
import { normalizeCart } from '../lib/normalize'
|
import type { GetCartHook } from '@commerce/types/cart'
|
||||||
import type { Cart } from '../types'
|
|
||||||
|
|
||||||
export default useCart as UseCart<typeof handler>
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
export const handler: SWRHook<
|
export const handler: SWRHook<GetCartHook> = {
|
||||||
Cart | null,
|
|
||||||
{},
|
|
||||||
FetchCartInput,
|
|
||||||
{ isEmpty?: boolean }
|
|
||||||
> = {
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/cart',
|
url: '/api/cart',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
},
|
},
|
||||||
async fetcher({ input: { cartId }, options, fetch }) {
|
|
||||||
const data = cartId ? await fetch(options) : null
|
|
||||||
return data && normalizeCart(data)
|
|
||||||
},
|
|
||||||
useHook: ({ useData }) => (input) => {
|
useHook: ({ useData }) => (input) => {
|
||||||
const response = useData({
|
const response = useData({
|
||||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||||
|
@ -4,48 +4,33 @@ import type {
|
|||||||
HookFetcherContext,
|
HookFetcherContext,
|
||||||
} from '@commerce/utils/types'
|
} from '@commerce/utils/types'
|
||||||
import { ValidationError } from '@commerce/utils/errors'
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
import useRemoveItem, {
|
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||||
RemoveItemInput as RemoveItemInputBase,
|
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
|
||||||
UseRemoveItem,
|
|
||||||
} from '@commerce/cart/use-remove-item'
|
|
||||||
import { normalizeCart } from '../lib/normalize'
|
|
||||||
import type {
|
|
||||||
RemoveCartItemBody,
|
|
||||||
Cart,
|
|
||||||
BigcommerceCart,
|
|
||||||
LineItem,
|
|
||||||
} from '../types'
|
|
||||||
import useCart from './use-cart'
|
import useCart from './use-cart'
|
||||||
|
|
||||||
export type RemoveItemFn<T = any> = T extends LineItem
|
export type RemoveItemFn<T = any> = T extends LineItem
|
||||||
? (input?: RemoveItemInput<T>) => Promise<Cart | null>
|
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||||
: (input: RemoveItemInput<T>) => Promise<Cart | null>
|
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||||
|
|
||||||
export type RemoveItemInput<T = any> = T extends LineItem
|
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||||
? Partial<RemoveItemInputBase>
|
? Partial<RemoveItemHook['actionInput']>
|
||||||
: RemoveItemInputBase
|
: RemoveItemHook['actionInput']
|
||||||
|
|
||||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
export const handler = {
|
export const handler = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/cart',
|
url: '/api/cart',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
},
|
},
|
||||||
async fetcher({
|
async fetcher({
|
||||||
input: { itemId },
|
input: { itemId },
|
||||||
options,
|
options,
|
||||||
fetch,
|
fetch,
|
||||||
}: HookFetcherContext<RemoveCartItemBody>) {
|
}: HookFetcherContext<RemoveItemHook>) {
|
||||||
const data = await fetch<BigcommerceCart>({
|
return await fetch({ ...options, body: { itemId } })
|
||||||
...options,
|
|
||||||
body: { itemId },
|
|
||||||
})
|
|
||||||
return normalizeCart(data)
|
|
||||||
},
|
},
|
||||||
useHook: ({
|
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) => <
|
||||||
fetch,
|
|
||||||
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => <
|
|
||||||
T extends LineItem | undefined = undefined
|
T extends LineItem | undefined = undefined
|
||||||
>(
|
>(
|
||||||
ctx: { item?: T } = {}
|
ctx: { item?: T } = {}
|
||||||
|
@ -5,36 +5,27 @@ import type {
|
|||||||
HookFetcherContext,
|
HookFetcherContext,
|
||||||
} from '@commerce/utils/types'
|
} from '@commerce/utils/types'
|
||||||
import { ValidationError } from '@commerce/utils/errors'
|
import { ValidationError } from '@commerce/utils/errors'
|
||||||
import useUpdateItem, {
|
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||||
UpdateItemInput as UpdateItemInputBase,
|
import type { LineItem, UpdateItemHook } from '@commerce/types/cart'
|
||||||
UseUpdateItem,
|
|
||||||
} from '@commerce/cart/use-update-item'
|
|
||||||
import { normalizeCart } from '../lib/normalize'
|
|
||||||
import type {
|
|
||||||
UpdateCartItemBody,
|
|
||||||
Cart,
|
|
||||||
BigcommerceCart,
|
|
||||||
LineItem,
|
|
||||||
} from '../types'
|
|
||||||
import { handler as removeItemHandler } from './use-remove-item'
|
import { handler as removeItemHandler } from './use-remove-item'
|
||||||
import useCart from './use-cart'
|
import useCart from './use-cart'
|
||||||
|
|
||||||
export type UpdateItemInput<T = any> = T extends LineItem
|
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||||
? Partial<UpdateItemInputBase<LineItem>>
|
? Partial<UpdateItemHook['actionInput']>
|
||||||
: UpdateItemInputBase<LineItem>
|
: UpdateItemHook['actionInput']
|
||||||
|
|
||||||
export default useUpdateItem as UseUpdateItem<typeof handler>
|
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||||
|
|
||||||
export const handler = {
|
export const handler = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/cart',
|
url: '/api/cart',
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
},
|
},
|
||||||
async fetcher({
|
async fetcher({
|
||||||
input: { itemId, item },
|
input: { itemId, item },
|
||||||
options,
|
options,
|
||||||
fetch,
|
fetch,
|
||||||
}: HookFetcherContext<UpdateCartItemBody>) {
|
}: HookFetcherContext<UpdateItemHook>) {
|
||||||
if (Number.isInteger(item.quantity)) {
|
if (Number.isInteger(item.quantity)) {
|
||||||
// Also allow the update hook to remove an item if the quantity is lower than 1
|
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||||
if (item.quantity! < 1) {
|
if (item.quantity! < 1) {
|
||||||
@ -50,16 +41,12 @@ export const handler = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fetch<BigcommerceCart, UpdateCartItemBody>({
|
return await fetch({
|
||||||
...options,
|
...options,
|
||||||
body: { itemId, item },
|
body: { itemId, item },
|
||||||
})
|
})
|
||||||
|
|
||||||
return normalizeCart(data)
|
|
||||||
},
|
},
|
||||||
useHook: ({
|
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) => <
|
||||||
fetch,
|
|
||||||
}: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
|
|
||||||
T extends LineItem | undefined = undefined
|
T extends LineItem | undefined = undefined
|
||||||
>(
|
>(
|
||||||
ctx: {
|
ctx: {
|
||||||
@ -71,7 +58,7 @@ export const handler = {
|
|||||||
const { mutate } = useCart() as any
|
const { mutate } = useCart() as any
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
debounce(async (input: UpdateItemInput<T>) => {
|
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||||
const itemId = input.id ?? item?.id
|
const itemId = input.id ?? item?.id
|
||||||
const productId = input.productId ?? item?.productId
|
const productId = input.productId ?? item?.productId
|
||||||
const variantId = input.productId ?? item?.variantId
|
const variantId = input.productId ?? item?.variantId
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
|
||||||
import { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetAllPagesResult>
|
|
||||||
|
|
||||||
async function getAllPages<T extends { pages: any[] }>(opts: {
|
|
||||||
url: string
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetAllPagesResult<T>>
|
|
||||||
|
|
||||||
async function getAllPages({
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
}: {
|
|
||||||
url?: string
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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
|
|
@ -1,53 +0,0 @@
|
|||||||
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
|
||||||
import { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetPageResult>
|
|
||||||
|
|
||||||
async function getPage<T extends { page?: any }, V = any>(opts: {
|
|
||||||
url: string
|
|
||||||
variables: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetPageResult<T>>
|
|
||||||
|
|
||||||
async function getPage({
|
|
||||||
url,
|
|
||||||
variables,
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
}: {
|
|
||||||
url?: string
|
|
||||||
variables: PageVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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
|
|
@ -1,117 +0,0 @@
|
|||||||
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../schema'
|
|
||||||
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
|
||||||
import filterEdges from '../api/utils/filter-edges'
|
|
||||||
import { BigcommerceConfig, getConfig } from '../api'
|
|
||||||
import { categoryTreeItemFragment } from '../api/fragments/category-tree'
|
|
||||||
import { Category } from '@commerce/types'
|
|
||||||
import getSlug from '@lib/get-slug'
|
|
||||||
|
|
||||||
// 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: Category[]
|
|
||||||
brands: Brands
|
|
||||||
}
|
|
||||||
> = T
|
|
||||||
|
|
||||||
async function getSiteInfo(opts?: {
|
|
||||||
variables?: GetSiteInfoQueryVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetSiteInfoResult>
|
|
||||||
|
|
||||||
async function getSiteInfo<
|
|
||||||
T extends { categories: Category[]; brands: any[] },
|
|
||||||
V = any
|
|
||||||
>(opts: {
|
|
||||||
query: string
|
|
||||||
variables?: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetSiteInfoResult<T>>
|
|
||||||
|
|
||||||
async function getSiteInfo({
|
|
||||||
query = getSiteInfoQuery,
|
|
||||||
variables,
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables?: GetSiteInfoQueryVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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 }
|
|
||||||
)
|
|
||||||
|
|
||||||
let categories = data!.site!.categoryTree?.map(
|
|
||||||
({ entityId, name, path }: any) => ({
|
|
||||||
id: `${entityId}`,
|
|
||||||
name,
|
|
||||||
slug: getSlug(path),
|
|
||||||
path,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const brands = data.site?.brands?.edges
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories: categories ?? [],
|
|
||||||
brands: filterEdges(brands as RecursiveRequired<typeof brands>),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getSiteInfo
|
|
@ -1,88 +0,0 @@
|
|||||||
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
|
||||||
import { definitions } from '../api/definitions/wishlist'
|
|
||||||
import { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
includeProducts?: boolean
|
|
||||||
}): Promise<GetCustomerWishlistResult>
|
|
||||||
|
|
||||||
async function getCustomerWishlist<
|
|
||||||
T extends { wishlist?: any },
|
|
||||||
V = any
|
|
||||||
>(opts: {
|
|
||||||
url: string
|
|
||||||
variables: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
includeProducts?: boolean
|
|
||||||
}): Promise<GetCustomerWishlistResult<T>>
|
|
||||||
|
|
||||||
async function getCustomerWishlist({
|
|
||||||
config,
|
|
||||||
variables,
|
|
||||||
includeProducts,
|
|
||||||
}: {
|
|
||||||
url?: string
|
|
||||||
variables: GetCustomerWishlistVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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,16 +1,16 @@
|
|||||||
import { SWRHook } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
|
||||||
import type { Customer, CustomerData } from '../api/customers'
|
import type { CustomerHook } from '../types/customer'
|
||||||
|
|
||||||
export default useCustomer as UseCustomer<typeof handler>
|
export default useCustomer as UseCustomer<typeof handler>
|
||||||
|
|
||||||
export const handler: SWRHook<Customer | null> = {
|
export const handler: SWRHook<CustomerHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/customers',
|
url: '/api/customer',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
},
|
},
|
||||||
async fetcher({ options, fetch }) {
|
async fetcher({ options, fetch }) {
|
||||||
const data = await fetch<CustomerData | null>(options)
|
const data = await fetch(options)
|
||||||
return data?.customer ?? null
|
return data?.customer ?? null
|
||||||
},
|
},
|
||||||
useHook: ({ useData }) => (input) => {
|
useHook: ({ useData }) => (input) => {
|
||||||
|
5
framework/bigcommerce/lib/get-slug.ts
Normal file
5
framework/bigcommerce/lib/get-slug.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Remove trailing and leading slash, usually included in nodes
|
||||||
|
// returned by the BigCommerce API
|
||||||
|
const getSlug = (path: string) => path.replace(/^\/|\/$/g, '')
|
||||||
|
|
||||||
|
export default getSlug
|
@ -1,6 +1,10 @@
|
|||||||
import type { Product } from '@commerce/types'
|
import type { Product } from '../types/product'
|
||||||
import type { Cart, BigcommerceCart, LineItem } from '../types'
|
import type { Cart, BigcommerceCart, LineItem } from '../types/cart'
|
||||||
|
import type { Page } from '../types/page'
|
||||||
|
import type { BCCategory, Category } from '../types/site'
|
||||||
|
import { definitions } from '../api/definitions/store-content'
|
||||||
import update from './immutability'
|
import update from './immutability'
|
||||||
|
import getSlug from './get-slug'
|
||||||
|
|
||||||
function normalizeProductOption(productOption: any) {
|
function normalizeProductOption(productOption: any) {
|
||||||
const {
|
const {
|
||||||
@ -69,6 +73,16 @@ export function normalizeProduct(productNode: any): Product {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizePage(page: definitions['page_Full']): Page {
|
||||||
|
return {
|
||||||
|
id: String(page.id),
|
||||||
|
name: page.name,
|
||||||
|
is_visible: page.is_visible,
|
||||||
|
sort_order: page.sort_order,
|
||||||
|
body: page.body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeCart(data: BigcommerceCart): Cart {
|
export function normalizeCart(data: BigcommerceCart): Cart {
|
||||||
return {
|
return {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
@ -111,3 +125,12 @@ function normalizeLineItem(item: any): LineItem {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeCategory(category: BCCategory): Category {
|
||||||
|
return {
|
||||||
|
id: `${category.entityId}`,
|
||||||
|
name: category.name,
|
||||||
|
slug: getSlug(category.path),
|
||||||
|
path: category.path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import type {
|
|
||||||
GetAllProductPathsQuery,
|
|
||||||
GetAllProductPathsQueryVariables,
|
|
||||||
} from '../schema'
|
|
||||||
import type { RecursivePartial, RecursiveRequired } from '../api/utils/types'
|
|
||||||
import filterEdges from '../api/utils/filter-edges'
|
|
||||||
import { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
}): Promise<GetAllProductPathsResult>
|
|
||||||
|
|
||||||
async function getAllProductPaths<
|
|
||||||
T extends { products: any[] },
|
|
||||||
V = any
|
|
||||||
>(opts: {
|
|
||||||
query: string
|
|
||||||
variables?: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
}): Promise<GetAllProductPathsResult<T>>
|
|
||||||
|
|
||||||
async function getAllProductPaths({
|
|
||||||
query = getAllProductPathsQuery,
|
|
||||||
variables,
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables?: GetAllProductPathsQueryVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
} = {}): 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
|
|
@ -1,135 +0,0 @@
|
|||||||
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 { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<{ products: Product[] }>
|
|
||||||
|
|
||||||
async function getAllProducts<
|
|
||||||
T extends Record<keyof GetAllProductsResult, any[]>,
|
|
||||||
V = any
|
|
||||||
>(opts: {
|
|
||||||
query: string
|
|
||||||
variables?: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetAllProductsResult<T>>
|
|
||||||
|
|
||||||
async function getAllProducts({
|
|
||||||
query = getAllProductsQuery,
|
|
||||||
variables: { field = 'products', ...vars } = {},
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables?: ProductVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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
|
|
@ -1,121 +0,0 @@
|
|||||||
import type { GetProductQuery, GetProductQueryVariables } from '../schema'
|
|
||||||
import setProductLocaleMeta from '../api/utils/set-product-locale-meta'
|
|
||||||
import { productInfoFragment } from '../api/fragments/product'
|
|
||||||
import { BigcommerceConfig, 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?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetProductResult>
|
|
||||||
|
|
||||||
async function getProduct<T extends { product?: any }, V = any>(opts: {
|
|
||||||
query: string
|
|
||||||
variables: V
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
preview?: boolean
|
|
||||||
}): Promise<GetProductResult<T>>
|
|
||||||
|
|
||||||
async function getProduct({
|
|
||||||
query = getProductQuery,
|
|
||||||
variables: { slug, ...vars },
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables: ProductVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
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
|
|
@ -1,4 +1,2 @@
|
|||||||
export { default as usePrice } from './use-price'
|
export { default as usePrice } from './use-price'
|
||||||
export { default as useSearch } from './use-search'
|
export { default as useSearch } from './use-search'
|
||||||
export { default as getProduct } from './get-product'
|
|
||||||
export { default as getAllProducts } from './get-all-products'
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SWRHook } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||||
import type { SearchProductsData } from '../api/catalog/products'
|
import type { SearchProductsHook } from '../types/product'
|
||||||
|
|
||||||
export default useSearch as UseSearch<typeof handler>
|
export default useSearch as UseSearch<typeof handler>
|
||||||
|
|
||||||
@ -9,15 +9,12 @@ export type SearchProductsInput = {
|
|||||||
categoryId?: number | string
|
categoryId?: number | string
|
||||||
brandId?: number
|
brandId?: number
|
||||||
sort?: string
|
sort?: string
|
||||||
|
locale?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handler: SWRHook<
|
export const handler: SWRHook<SearchProductsHook> = {
|
||||||
SearchProductsData,
|
|
||||||
SearchProductsInput,
|
|
||||||
SearchProductsInput
|
|
||||||
> = {
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/catalog/products',
|
url: '/api/catalog/products',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
},
|
},
|
||||||
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||||
@ -26,9 +23,9 @@ export const handler: SWRHook<
|
|||||||
|
|
||||||
if (search) url.searchParams.set('search', search)
|
if (search) url.searchParams.set('search', search)
|
||||||
if (Number.isInteger(categoryId))
|
if (Number.isInteger(categoryId))
|
||||||
url.searchParams.set('category', String(categoryId))
|
url.searchParams.set('categoryId', String(categoryId))
|
||||||
if (Number.isInteger(brandId))
|
if (Number.isInteger(brandId))
|
||||||
url.searchParams.set('brand', String(brandId))
|
url.searchParams.set('brandId', String(brandId))
|
||||||
if (sort) url.searchParams.set('sort', sort)
|
if (sort) url.searchParams.set('sort', sort)
|
||||||
|
|
||||||
return fetch({
|
return fetch({
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import * as Core from '@commerce/types'
|
import * as Core from '@commerce/types/cart'
|
||||||
|
|
||||||
|
export * from '@commerce/types/cart'
|
||||||
|
|
||||||
// TODO: this type should match:
|
// TODO: this type should match:
|
||||||
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
|
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
|
||||||
@ -23,16 +25,14 @@ export type BigcommerceCart = {
|
|||||||
// TODO: add missing fields
|
// TODO: add missing fields
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Cart = Core.Cart & {
|
|
||||||
lineItems: LineItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LineItem = Core.LineItem
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cart mutations
|
* Extend core cart types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type Cart = Core.Cart & {
|
||||||
|
lineItems: Core.LineItem[]
|
||||||
|
}
|
||||||
|
|
||||||
export type OptionSelections = {
|
export type OptionSelections = {
|
||||||
option_id: number
|
option_id: number
|
||||||
option_value: number | string
|
option_value: number | string
|
||||||
@ -43,16 +43,24 @@ export type CartItemBody = Core.CartItemBody & {
|
|||||||
optionSelections?: OptionSelections
|
optionSelections?: OptionSelections
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetCartHandlerBody = Core.GetCartHandlerBody
|
export type CartTypes = {
|
||||||
|
cart: Cart
|
||||||
|
item: Core.LineItem
|
||||||
|
itemBody: CartItemBody
|
||||||
|
}
|
||||||
|
|
||||||
export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
|
export type CartHooks = Core.CartHooks<CartTypes>
|
||||||
|
|
||||||
export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
|
export type GetCartHook = CartHooks['getCart']
|
||||||
|
export type AddItemHook = CartHooks['addItem']
|
||||||
|
export type UpdateItemHook = CartHooks['updateItem']
|
||||||
|
export type RemoveItemHook = CartHooks['removeItem']
|
||||||
|
|
||||||
export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
|
export type CartSchema = Core.CartSchema<CartTypes>
|
||||||
|
|
||||||
export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody<CartItemBody>
|
export type CartHandlers = Core.CartHandlers<CartTypes>
|
||||||
|
|
||||||
export type RemoveCartItemBody = Core.RemoveCartItemBody
|
export type GetCartHandler = CartHandlers['getCart']
|
||||||
|
export type AddItemHandler = CartHandlers['addItem']
|
||||||
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody
|
export type UpdateItemHandler = CartHandlers['updateItem']
|
||||||
|
export type RemoveItemHandler = CartHandlers['removeItem']
|
1
framework/bigcommerce/types/checkout.ts
Normal file
1
framework/bigcommerce/types/checkout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/checkout'
|
1
framework/bigcommerce/types/common.ts
Normal file
1
framework/bigcommerce/types/common.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/common'
|
5
framework/bigcommerce/types/customer.ts
Normal file
5
framework/bigcommerce/types/customer.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as Core from '@commerce/types/customer'
|
||||||
|
|
||||||
|
export * from '@commerce/types/customer'
|
||||||
|
|
||||||
|
export type CustomerSchema = Core.CustomerSchema
|
25
framework/bigcommerce/types/index.ts
Normal file
25
framework/bigcommerce/types/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as Cart from './cart'
|
||||||
|
import * as Checkout from './checkout'
|
||||||
|
import * as Common from './common'
|
||||||
|
import * as Customer from './customer'
|
||||||
|
import * as Login from './login'
|
||||||
|
import * as Logout from './logout'
|
||||||
|
import * as Page from './page'
|
||||||
|
import * as Product from './product'
|
||||||
|
import * as Signup from './signup'
|
||||||
|
import * as Site from './site'
|
||||||
|
import * as Wishlist from './wishlist'
|
||||||
|
|
||||||
|
export type {
|
||||||
|
Cart,
|
||||||
|
Checkout,
|
||||||
|
Common,
|
||||||
|
Customer,
|
||||||
|
Login,
|
||||||
|
Logout,
|
||||||
|
Page,
|
||||||
|
Product,
|
||||||
|
Signup,
|
||||||
|
Site,
|
||||||
|
Wishlist,
|
||||||
|
}
|
8
framework/bigcommerce/types/login.ts
Normal file
8
framework/bigcommerce/types/login.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as Core from '@commerce/types/login'
|
||||||
|
import type { LoginMutationVariables } from '../schema'
|
||||||
|
|
||||||
|
export * from '@commerce/types/login'
|
||||||
|
|
||||||
|
export type LoginOperation = Core.LoginOperation & {
|
||||||
|
variables: LoginMutationVariables
|
||||||
|
}
|
1
framework/bigcommerce/types/logout.ts
Normal file
1
framework/bigcommerce/types/logout.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/logout'
|
11
framework/bigcommerce/types/page.ts
Normal file
11
framework/bigcommerce/types/page.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as Core from '@commerce/types/page'
|
||||||
|
export * from '@commerce/types/page'
|
||||||
|
|
||||||
|
export type Page = Core.Page
|
||||||
|
|
||||||
|
export type PageTypes = {
|
||||||
|
page: Page
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
|
||||||
|
export type GetPageOperation = Core.GetPageOperation<PageTypes>
|
1
framework/bigcommerce/types/product.ts
Normal file
1
framework/bigcommerce/types/product.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/product'
|
1
framework/bigcommerce/types/signup.ts
Normal file
1
framework/bigcommerce/types/signup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from '@commerce/types/signup'
|
19
framework/bigcommerce/types/site.ts
Normal file
19
framework/bigcommerce/types/site.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as Core from '@commerce/types/site'
|
||||||
|
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../schema'
|
||||||
|
|
||||||
|
export * from '@commerce/types/site'
|
||||||
|
|
||||||
|
export type BCCategory = NonNullable<
|
||||||
|
GetSiteInfoQuery['site']['categoryTree']
|
||||||
|
>[0]
|
||||||
|
|
||||||
|
export type Brand = NonNullable<
|
||||||
|
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
|
||||||
|
>
|
||||||
|
|
||||||
|
export type SiteTypes = {
|
||||||
|
category: Core.Category
|
||||||
|
brand: Brand
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetSiteInfoOperation = Core.GetSiteInfoOperation<SiteTypes>
|
23
framework/bigcommerce/types/wishlist.ts
Normal file
23
framework/bigcommerce/types/wishlist.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as Core from '@commerce/types/wishlist'
|
||||||
|
import { definitions } from '../api/definitions/wishlist'
|
||||||
|
import type { ProductEdge } from '../api/operations/get-all-products'
|
||||||
|
|
||||||
|
export * from '@commerce/types/wishlist'
|
||||||
|
|
||||||
|
export type WishlistItem = NonNullable<
|
||||||
|
definitions['wishlist_Full']['items']
|
||||||
|
>[0] & {
|
||||||
|
product?: ProductEdge['node']
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
|
||||||
|
items?: WishlistItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WishlistTypes = {
|
||||||
|
wishlist: Wishlist
|
||||||
|
itemBody: Core.WishlistItemBody
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WishlistSchema = Core.WishlistSchema<WishlistTypes>
|
||||||
|
export type GetCustomerWishlistOperation = Core.GetCustomerWishlistOperation<WishlistTypes>
|
@ -2,15 +2,15 @@ import { useCallback } from 'react'
|
|||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useAddItem, { UseAddItem } from '@commerce/wishlist/use-add-item'
|
import useAddItem, { UseAddItem } from '@commerce/wishlist/use-add-item'
|
||||||
import type { ItemBody, AddItemBody } from '../api/wishlist'
|
import type { AddItemHook } from '../types/wishlist'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
import useWishlist from './use-wishlist'
|
import useWishlist from './use-wishlist'
|
||||||
|
|
||||||
export default useAddItem as UseAddItem<typeof handler>
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<any, {}, ItemBody, AddItemBody> = {
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/wishlist',
|
url: '/api/wishlist',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
},
|
},
|
||||||
useHook: ({ fetch }) => () => {
|
useHook: ({ fetch }) => () => {
|
||||||
|
@ -2,23 +2,17 @@ import { useCallback } from 'react'
|
|||||||
import type { MutationHook } from '@commerce/utils/types'
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useRemoveItem, {
|
import useRemoveItem, {
|
||||||
RemoveItemInput,
|
|
||||||
UseRemoveItem,
|
UseRemoveItem,
|
||||||
} from '@commerce/wishlist/use-remove-item'
|
} from '@commerce/wishlist/use-remove-item'
|
||||||
import type { RemoveItemBody, Wishlist } from '../api/wishlist'
|
import type { RemoveItemHook } from '../types/wishlist'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
import useWishlist, { UseWishlistInput } from './use-wishlist'
|
import useWishlist from './use-wishlist'
|
||||||
|
|
||||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<
|
export const handler: MutationHook<RemoveItemHook> = {
|
||||||
Wishlist | null,
|
|
||||||
{ wishlist?: UseWishlistInput },
|
|
||||||
RemoveItemInput,
|
|
||||||
RemoveItemBody
|
|
||||||
> = {
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/wishlist',
|
url: '/api/wishlist',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
},
|
},
|
||||||
useHook: ({ fetch }) => ({ wishlist } = {}) => {
|
useHook: ({ fetch }) => ({ wishlist } = {}) => {
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { SWRHook } from '@commerce/utils/types'
|
import { SWRHook } from '@commerce/utils/types'
|
||||||
import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist'
|
import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist'
|
||||||
import type { Wishlist } from '../api/wishlist'
|
import type { GetWishlistHook } from '../types/wishlist'
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
|
|
||||||
export type UseWishlistInput = { includeProducts?: boolean }
|
|
||||||
|
|
||||||
export default useWishlist as UseWishlist<typeof handler>
|
export default useWishlist as UseWishlist<typeof handler>
|
||||||
|
|
||||||
export const handler: SWRHook<
|
export const handler: SWRHook<GetWishlistHook> = {
|
||||||
Wishlist | null,
|
|
||||||
UseWishlistInput,
|
|
||||||
{ customerId?: number } & UseWishlistInput,
|
|
||||||
{ isEmpty?: boolean }
|
|
||||||
> = {
|
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/bigcommerce/wishlist',
|
url: '/api/wishlist',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
},
|
},
|
||||||
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||||
|
62
framework/commerce/api/endpoints/cart.ts
Normal file
62
framework/commerce/api/endpoints/cart.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import type { CartSchema } from '../../types/cart'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const cartEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
CartSchema<any>
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers, config } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
GET: handlers['getCart'],
|
||||||
|
POST: handlers['addItem'],
|
||||||
|
PUT: handlers['updateItem'],
|
||||||
|
DELETE: handlers['removeItem'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
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']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or add an item to the cart
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const body = { ...req.body, cartId }
|
||||||
|
return await handlers['addItem']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update item in cart
|
||||||
|
if (req.method === 'PUT') {
|
||||||
|
const body = { ...req.body, cartId }
|
||||||
|
return await handlers['updateItem']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an item from the cart
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const body = { ...req.body, cartId }
|
||||||
|
return await handlers['removeItem']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cartEndpoint
|
31
framework/commerce/api/endpoints/catalog/products.ts
Normal file
31
framework/commerce/api/endpoints/catalog/products.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { ProductsSchema } from '../../../types/product'
|
||||||
|
import { CommerceAPIError } from '../../utils/errors'
|
||||||
|
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '../..'
|
||||||
|
|
||||||
|
const productsEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
ProductsSchema
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
|
||||||
|
if (!isAllowedOperation(req, res, { GET: handlers['getProducts'] })) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = req.query
|
||||||
|
return await handlers['getProducts']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default productsEndpoint
|
35
framework/commerce/api/endpoints/checkout.ts
Normal file
35
framework/commerce/api/endpoints/checkout.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { CheckoutSchema } from '../../types/checkout'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const checkoutEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
CheckoutSchema
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
GET: handlers['checkout'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = null
|
||||||
|
return await handlers['checkout']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkoutEndpoint
|
35
framework/commerce/api/endpoints/customer.ts
Normal file
35
framework/commerce/api/endpoints/customer.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { CustomerSchema } from '../../types/customer'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const customerEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
CustomerSchema<any>
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
GET: handlers['getLoggedInCustomer'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = null
|
||||||
|
return await handlers['getLoggedInCustomer']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default customerEndpoint
|
35
framework/commerce/api/endpoints/login.ts
Normal file
35
framework/commerce/api/endpoints/login.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { LoginSchema } from '../../types/login'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const loginEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
LoginSchema<any>
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
POST: handlers['login'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = req.body ?? {}
|
||||||
|
return await handlers['login']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loginEndpoint
|
37
framework/commerce/api/endpoints/logout.ts
Normal file
37
framework/commerce/api/endpoints/logout.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type { LogoutSchema } from '../../types/logout'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const logoutEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
LogoutSchema
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
GET: handlers['logout'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const redirectTo = req.query.redirect_to
|
||||||
|
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
|
||||||
|
|
||||||
|
return await handlers['logout']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default logoutEndpoint
|
38
framework/commerce/api/endpoints/signup.ts
Normal file
38
framework/commerce/api/endpoints/signup.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { SignupSchema } from '../../types/signup'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const signupEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
SignupSchema
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers, config } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
POST: handlers['signup'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cookies } = req
|
||||||
|
const cartId = cookies[config.cartCookie]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = { ...req.body, cartId }
|
||||||
|
return await handlers['signup']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default signupEndpoint
|
58
framework/commerce/api/endpoints/wishlist.ts
Normal file
58
framework/commerce/api/endpoints/wishlist.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { WishlistSchema } from '../../types/wishlist'
|
||||||
|
import { CommerceAPIError } from '../utils/errors'
|
||||||
|
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||||
|
import type { GetAPISchema } from '..'
|
||||||
|
|
||||||
|
const wishlistEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
WishlistSchema<any>
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers, config } = ctx
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
GET: handlers['getWishlist'],
|
||||||
|
POST: handlers['addItem'],
|
||||||
|
DELETE: handlers['removeItem'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
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']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an item to the wishlist
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const body = { ...req.body, customerToken }
|
||||||
|
return await handlers['addItem']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an item from the wishlist
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const body = { ...req.body, customerToken }
|
||||||
|
return await handlers['removeItem']({ ...ctx, body })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default wishlistEndpoint
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user