forked from crowetic/commerce
Merge branch 'master' of github.com:okbel/e-comm-example
This commit is contained in:
commit
eb5fdebcf5
@ -3,9 +3,14 @@ import { UserNav } from '@components/core'
|
|||||||
import { Button } from '@components/ui'
|
import { Button } from '@components/ui'
|
||||||
import { Trash, Cross } from '@components/icon'
|
import { Trash, Cross } from '@components/icon'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
|
import { useCart } from '@lib/bigcommerce/cart'
|
||||||
|
|
||||||
const CartSidebarView: FC = () => {
|
const CartSidebarView: FC = () => {
|
||||||
|
const { data, isEmpty } = useCart()
|
||||||
const { closeSidebar } = useUI()
|
const { closeSidebar } = useUI()
|
||||||
|
|
||||||
|
console.log('CART', data, isEmpty)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className="px-4 py-6 sm:px-6 border-b border-gray-200">
|
<header className="px-4 py-6 sm:px-6 border-b border-gray-200">
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import s from './Layout.module.css'
|
import s from './Layout.module.css'
|
||||||
|
import { CommerceProvider } from '@lib/bigcommerce'
|
||||||
|
import { CartProvider } from '@lib/bigcommerce/cart'
|
||||||
import { Navbar, Featurebar } from '@components/core'
|
import { Navbar, Featurebar } from '@components/core'
|
||||||
import { Container, Sidebar } from '@components/ui'
|
import { Container, Sidebar } from '@components/ui'
|
||||||
import { CartSidebarView } from '@components/cart'
|
import { CartSidebarView } from '@components/cart'
|
||||||
@ -35,9 +37,13 @@ const CoreLayout: FC<Props> = ({ className, children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Layout: FC<Props> = (props) => (
|
const Layout: FC<Props> = (props) => (
|
||||||
<UIProvider>
|
<CommerceProvider locale="en-us">
|
||||||
<CoreLayout {...props} />
|
<CartProvider>
|
||||||
</UIProvider>
|
<UIProvider>
|
||||||
|
<CoreLayout {...props} />
|
||||||
|
</UIProvider>
|
||||||
|
</CartProvider>
|
||||||
|
</CommerceProvider>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default Layout
|
export default Layout
|
||||||
|
@ -4,6 +4,9 @@ import s from './ProductView.module.css'
|
|||||||
import { Button } from '@components/ui'
|
import { Button } from '@components/ui'
|
||||||
import { Swatch } from '@components/product'
|
import { Swatch } from '@components/product'
|
||||||
import { Colors } from '@components/ui/types'
|
import { Colors } from '@components/ui/types'
|
||||||
|
import type { Product } from '@lib/bigcommerce/api/operations/get-product'
|
||||||
|
import useAddItem from '@lib/bigcommerce/cart/use-add-item'
|
||||||
|
|
||||||
interface ProductData {
|
interface ProductData {
|
||||||
name: string
|
name: string
|
||||||
images: any
|
images: any
|
||||||
@ -12,17 +15,30 @@ interface ProductData {
|
|||||||
colors?: any[]
|
colors?: any[]
|
||||||
sizes?: any[]
|
sizes?: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
children?: any
|
children?: any
|
||||||
productData: ProductData
|
productData: ProductData
|
||||||
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductView: FC<Props> = ({ productData, className }) => {
|
const COLORS: Colors[] = ['pink', 'black', 'white']
|
||||||
const rootClassName = cn(s.root, className)
|
|
||||||
const colors: Colors[] = ['pink', 'black', 'white']
|
const ProductView: FC<Props> = ({ product, productData, className }) => {
|
||||||
|
const addItem = useAddItem()
|
||||||
|
const addToCart = () => {
|
||||||
|
// TODO: loading state by awating the promise
|
||||||
|
addItem({
|
||||||
|
item: {
|
||||||
|
productId: product.entityId,
|
||||||
|
variantId: product.variants.edges?.[0]?.node.entityId!,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={rootClassName}>
|
<div className={cn(s.root, className)}>
|
||||||
<div className="absolute">
|
<div className="absolute">
|
||||||
<h1 className="px-8 py-2 bg-violet text-white font-bold text-3xl">
|
<h1 className="px-8 py-2 bg-violet text-white font-bold text-3xl">
|
||||||
{productData.name}
|
{productData.name}
|
||||||
@ -38,8 +54,8 @@ const ProductView: FC<Props> = ({ productData, className }) => {
|
|||||||
<section className="pb-4">
|
<section className="pb-4">
|
||||||
<h2 className="uppercase font-medium">Color</h2>
|
<h2 className="uppercase font-medium">Color</h2>
|
||||||
<div className="flex flex-row py-4">
|
<div className="flex flex-row py-4">
|
||||||
{colors.map((c) => (
|
{COLORS.map((c) => (
|
||||||
<Swatch color={c} />
|
<Swatch key={c} color={c} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -57,7 +73,9 @@ const ProductView: FC<Props> = ({ productData, className }) => {
|
|||||||
<p>{productData.description}</p>
|
<p>{productData.description}</p>
|
||||||
</section>
|
</section>
|
||||||
<section className="pb-4">
|
<section className="pb-4">
|
||||||
<Button className={s.button}>Add to Cart</Button>
|
<Button type="button" className={s.button} onClick={addToCart}>
|
||||||
|
Add to Cart
|
||||||
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@ const M: FC<Props> = ({
|
|||||||
items,
|
items,
|
||||||
wrapper: Component = DefaultWrapper,
|
wrapper: Component = DefaultWrapper,
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
min = 'none',
|
// min = 'none',
|
||||||
}) => {
|
}) => {
|
||||||
const rootClassName = cn(
|
const rootClassName = cn(
|
||||||
s.root,
|
s.root,
|
||||||
|
@ -5,11 +5,36 @@ import createApiHandler, {
|
|||||||
} from './utils/create-api-handler'
|
} from './utils/create-api-handler'
|
||||||
import { BigcommerceApiError } from './utils/errors'
|
import { BigcommerceApiError } from './utils/errors'
|
||||||
|
|
||||||
type Cart = any
|
export type Item = {
|
||||||
|
productId: number
|
||||||
|
variantId: number
|
||||||
|
quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this type should match:
|
||||||
|
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
|
||||||
|
export type Cart = {
|
||||||
|
id: string
|
||||||
|
parent_id?: string
|
||||||
|
customer_id: number
|
||||||
|
email: string
|
||||||
|
currency: { code: string }
|
||||||
|
tax_included: boolean
|
||||||
|
base_amount: number
|
||||||
|
discount_amount: number
|
||||||
|
cart_amount: number
|
||||||
|
line_items: {
|
||||||
|
custom_items: any[]
|
||||||
|
digital_items: any[]
|
||||||
|
gift_certificates: any[]
|
||||||
|
psysical_items: any[]
|
||||||
|
}
|
||||||
|
// TODO: add missing fields
|
||||||
|
}
|
||||||
|
|
||||||
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
|
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
|
||||||
const cartApi: BigcommerceApiHandler = async (req, res, config) => {
|
const cartApi: BigcommerceApiHandler<Cart> = async (req, res, config) => {
|
||||||
if (!isAllowedMethod(req, res, METHODS)) return
|
if (!isAllowedMethod(req, res, METHODS)) return
|
||||||
|
|
||||||
const { cookies } = req
|
const { cookies } = req
|
||||||
@ -27,21 +52,22 @@ const cartApi: BigcommerceApiHandler = async (req, res, config) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof BigcommerceApiError && error.status === 404) {
|
if (error instanceof BigcommerceApiError && error.status === 404) {
|
||||||
// Remove the cookie if it exists but the cart wasn't found
|
// Remove the cookie if it exists but the cart wasn't found
|
||||||
res.setHeader('Set-Cookie', getCartCookie(name))
|
res.setHeader('Set-Cookie', getCartCookie(config.cartCookie))
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({ cart: result.data ?? null })
|
return res.status(200).json({ data: result.data ?? null })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or add a product to the cart
|
// Create or add a product to the cart
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { product } = req.body
|
const item: Item | undefined = req.body?.item
|
||||||
|
|
||||||
if (!product) {
|
if (!item) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
errors: [{ message: 'Missing product' }],
|
errors: [{ message: 'Missing product' }],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -49,30 +75,32 @@ const cartApi: BigcommerceApiHandler = async (req, res, config) => {
|
|||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
line_items: [parseProduct(product)],
|
line_items: [parseItem(item)],
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
const { data } = cartId
|
const { data } = cartId
|
||||||
? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options)
|
? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options)
|
||||||
: await config.storeApiFetch('/v3/carts', options)
|
: await config.storeApiFetch('/v3/carts', options)
|
||||||
|
|
||||||
|
console.log('API DATA', data)
|
||||||
|
|
||||||
// Create or update the cart cookie
|
// Create or update the cart cookie
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Set-Cookie',
|
'Set-Cookie',
|
||||||
getCartCookie(name, data.id, config.cartCookieMaxAge)
|
getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge)
|
||||||
)
|
)
|
||||||
|
|
||||||
// There's no need to send any additional data here, the UI can use this response to display a
|
return res.status(200).json({ data })
|
||||||
// "success" for the operation and revalidate the GET request for this same endpoint right after.
|
|
||||||
return res.status(200).json({ done: true })
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
error instanceof BigcommerceApiError
|
error instanceof BigcommerceApiError
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
? 'An unexpected error ocurred with the Bigcommerce API'
|
||||||
: 'An unexpected error ocurred'
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
res.status(500).json({ errors: [{ message }] })
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +110,6 @@ function getCartCookie(name: string, cartId?: string, maxAge?: number) {
|
|||||||
? {
|
? {
|
||||||
maxAge,
|
maxAge,
|
||||||
expires: new Date(Date.now() + maxAge * 1000),
|
expires: new Date(Date.now() + maxAge * 1000),
|
||||||
httpOnly: true,
|
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: process.env.NODE_ENV === 'production',
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
@ -92,10 +119,10 @@ function getCartCookie(name: string, cartId?: string, maxAge?: number) {
|
|||||||
return serialize(name, cartId || '', options)
|
return serialize(name, cartId || '', options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseProduct = (product: any) => ({
|
const parseItem = (item: Item) => ({
|
||||||
quantity: product.quantity,
|
quantity: item.quantity || 1,
|
||||||
product_id: product.productId,
|
product_id: item.productId,
|
||||||
variant_id: product.variantId,
|
variant_id: item.variantId,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default createApiHandler(cartApi)
|
export default createApiHandler(cartApi)
|
||||||
|
@ -16,17 +16,22 @@ export const getAllProductPathsQuery = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface GetAllProductPathsResult<T> {
|
export type ProductPaths = NonNullable<
|
||||||
products: T extends GetAllProductPathsQuery
|
GetAllProductPathsQuery['site']['products']['edges']
|
||||||
? NonNullable<T['site']['products']['edges']>
|
>
|
||||||
: unknown
|
|
||||||
}
|
export type GetAllProductPathsResult<
|
||||||
|
T extends { products: any[] } = { products: ProductPaths }
|
||||||
|
> = T
|
||||||
|
|
||||||
async function getAllProductPaths(opts?: {
|
async function getAllProductPaths(opts?: {
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetAllProductPathsResult<GetAllProductPathsQuery>>
|
}): Promise<GetAllProductPathsResult>
|
||||||
|
|
||||||
async function getAllProductPaths<T, V = any>(opts: {
|
async function getAllProductPaths<
|
||||||
|
T extends { products: any[] },
|
||||||
|
V = any
|
||||||
|
>(opts: {
|
||||||
query: string
|
query: string
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetAllProductPathsResult<T>>
|
}): Promise<GetAllProductPathsResult<T>>
|
||||||
@ -37,7 +42,7 @@ async function getAllProductPaths({
|
|||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
} = {}): Promise<GetAllProductPathsResult<GetAllProductPathsQuery>> {
|
} = {}): Promise<GetAllProductPathsResult> {
|
||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||||
// required in case there's a custom `query`
|
// required in case there's a custom `query`
|
||||||
|
@ -37,11 +37,13 @@ export const getAllProductsQuery = /* GraphQL */ `
|
|||||||
${productInfoFragment}
|
${productInfoFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface GetAllProductsResult<T> {
|
export type Products = NonNullable<
|
||||||
products: T extends GetAllProductsQuery
|
GetAllProductsQuery['site']['products']['edges']
|
||||||
? NonNullable<T['site']['products']['edges']>
|
>
|
||||||
: unknown
|
|
||||||
}
|
export type GetAllProductsResult<
|
||||||
|
T extends { products: any[] } = { products: Products }
|
||||||
|
> = T
|
||||||
|
|
||||||
export type ProductVariables = Images &
|
export type ProductVariables = Images &
|
||||||
Omit<GetAllProductsQueryVariables, keyof ProductImageVariables>
|
Omit<GetAllProductsQueryVariables, keyof ProductImageVariables>
|
||||||
@ -49,9 +51,9 @@ export type ProductVariables = Images &
|
|||||||
async function getAllProducts(opts?: {
|
async function getAllProducts(opts?: {
|
||||||
variables?: ProductVariables
|
variables?: ProductVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetAllProductsResult<GetAllProductsQuery>>
|
}): Promise<GetAllProductsResult>
|
||||||
|
|
||||||
async function getAllProducts<T, V = any>(opts: {
|
async function getAllProducts<T extends { products: any[] }, V = any>(opts: {
|
||||||
query: string
|
query: string
|
||||||
variables?: V
|
variables?: V
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
@ -65,7 +67,7 @@ async function getAllProducts({
|
|||||||
query?: string
|
query?: string
|
||||||
variables?: ProductVariables
|
variables?: ProductVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
} = {}): Promise<GetAllProductsResult<GetAllProductsQuery>> {
|
} = {}): Promise<GetAllProductsResult> {
|
||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
const variables: GetAllProductsQueryVariables = {
|
const variables: GetAllProductsQueryVariables = {
|
||||||
...config.imageVariables,
|
...config.imageVariables,
|
||||||
|
@ -33,11 +33,14 @@ export const getProductQuery = /* GraphQL */ `
|
|||||||
${productInfoFragment}
|
${productInfoFragment}
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface GetProductResult<T> {
|
export type Product = Extract<
|
||||||
product?: T extends GetProductQuery
|
GetProductQuery['site']['route']['node'],
|
||||||
? Extract<T['site']['route']['node'], { __typename: 'Product' }>
|
{ __typename: 'Product' }
|
||||||
: unknown
|
>
|
||||||
}
|
|
||||||
|
export type GetProductResult<
|
||||||
|
T extends { product?: any } = { product?: Product }
|
||||||
|
> = T
|
||||||
|
|
||||||
export type ProductVariables = Images &
|
export type ProductVariables = Images &
|
||||||
({ path: string; slug?: never } | { path?: never; slug: string })
|
({ path: string; slug?: never } | { path?: never; slug: string })
|
||||||
@ -45,9 +48,9 @@ export type ProductVariables = Images &
|
|||||||
async function getProduct(opts: {
|
async function getProduct(opts: {
|
||||||
variables: ProductVariables
|
variables: ProductVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetProductResult<GetProductQuery>>
|
}): Promise<GetProductResult>
|
||||||
|
|
||||||
async function getProduct<T, V = any>(opts: {
|
async function getProduct<T extends { product?: any }, V = any>(opts: {
|
||||||
query: string
|
query: string
|
||||||
variables: V
|
variables: V
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
@ -61,7 +64,7 @@ async function getProduct({
|
|||||||
query?: string
|
query?: string
|
||||||
variables: ProductVariables
|
variables: ProductVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetProductResult<GetProductQuery>> {
|
}): Promise<GetProductResult> {
|
||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
const variables: GetProductQueryVariables = {
|
const variables: GetProductQueryVariables = {
|
||||||
...config.imageVariables,
|
...config.imageVariables,
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { BigcommerceConfig, getConfig } from '..'
|
import { BigcommerceConfig, getConfig } from '..'
|
||||||
|
|
||||||
export type BigcommerceApiHandler = (
|
export type BigcommerceApiHandler<T = any> = (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse<BigcommerceApiResponse<T>>,
|
||||||
config: BigcommerceConfig
|
config: BigcommerceConfig
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
|
|
||||||
|
export type BigcommerceApiResponse<T> = {
|
||||||
|
data: T | null
|
||||||
|
errors?: { message: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
export default function createApiHandler(handler: BigcommerceApiHandler) {
|
export default function createApiHandler(handler: BigcommerceApiHandler) {
|
||||||
return function getApiHandler({
|
return function getApiHandler({
|
||||||
config,
|
config,
|
||||||
|
@ -30,7 +30,7 @@ export default async function fetchStoreApi<T>(
|
|||||||
|
|
||||||
const contentType = res.headers.get('Content-Type')
|
const contentType = res.headers.get('Content-Type')
|
||||||
|
|
||||||
if (contentType?.includes('application/json')) {
|
if (!contentType?.includes('application/json')) {
|
||||||
throw new BigcommerceApiError(
|
throw new BigcommerceApiError(
|
||||||
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
|
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
|
||||||
res
|
res
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { FC } from 'react'
|
|
||||||
import {
|
|
||||||
CartProvider as CommerceCartProvider,
|
|
||||||
useCart as useCommerceCart,
|
|
||||||
} from 'lib/commerce/cart'
|
|
||||||
|
|
||||||
export type Cart = any
|
|
||||||
|
|
||||||
export const CartProvider: FC = ({ children }) => {
|
|
||||||
return <CommerceCartProvider url="/api/cart">{children}</CommerceCartProvider>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCart() {
|
|
||||||
const cart = useCommerceCart<Cart>()
|
|
||||||
|
|
||||||
// TODO: Do something to make this prop work
|
|
||||||
cart.isEmpty = true
|
|
||||||
|
|
||||||
return cart
|
|
||||||
}
|
|
31
lib/bigcommerce/cart/index.tsx
Normal file
31
lib/bigcommerce/cart/index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import {
|
||||||
|
CartProvider as CommerceCartProvider,
|
||||||
|
useCart as useCommerceCart,
|
||||||
|
} from '@lib/commerce/cart'
|
||||||
|
import type { Cart } from '../api/cart'
|
||||||
|
|
||||||
|
export type { Cart }
|
||||||
|
|
||||||
|
export const CartProvider: FC = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<CommerceCartProvider url="/api/bigcommerce/cart">
|
||||||
|
{children}
|
||||||
|
</CommerceCartProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCart() {
|
||||||
|
const cart = useCommerceCart<Cart>()
|
||||||
|
|
||||||
|
Object.defineProperty(cart, 'isEmpty', {
|
||||||
|
get() {
|
||||||
|
return Object.values(cart.data?.line_items ?? {}).every(
|
||||||
|
(items) => !items.length
|
||||||
|
)
|
||||||
|
},
|
||||||
|
set: (x) => x,
|
||||||
|
})
|
||||||
|
|
||||||
|
return cart
|
||||||
|
}
|
31
lib/bigcommerce/cart/use-add-item.tsx
Normal file
31
lib/bigcommerce/cart/use-add-item.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { Fetcher } from '@lib/commerce'
|
||||||
|
import { default as useCartAddItem } from '@lib/commerce/cart/use-add-item'
|
||||||
|
import type { Item } from '../api/cart'
|
||||||
|
import { Cart, useCart } from '.'
|
||||||
|
|
||||||
|
export type { Item }
|
||||||
|
|
||||||
|
function fetcher(fetch: Fetcher<Cart>, { item }: { item: Item }) {
|
||||||
|
if (
|
||||||
|
item.quantity &&
|
||||||
|
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'The item quantity has to be a valid integer greater than 0'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch({ url: '/api/bigcommerce/cart', method: 'POST', body: { item } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useAddItem() {
|
||||||
|
const { mutate } = useCart()
|
||||||
|
const fn = useCartAddItem<Cart, { item: Item }>(fetcher)
|
||||||
|
const addItem: typeof fn = async (input) => {
|
||||||
|
const data = await fn(input)
|
||||||
|
mutate(data)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return addItem
|
||||||
|
}
|
@ -21,31 +21,36 @@ async function getError(res: Response) {
|
|||||||
return { message: await getText(res) }
|
return { message: await getText(res) }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetcher(url: string, query: string) {
|
|
||||||
const res = await fetch(url)
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
throw await getError(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const bigcommerceConfig: CommerceConfig = {
|
export const bigcommerceConfig: CommerceConfig = {
|
||||||
locale: 'en-us',
|
locale: 'en-us',
|
||||||
fetcher,
|
cartCookie: 'bc_cartId',
|
||||||
|
async fetcher({ url, method = 'GET', variables, body: bodyObj }) {
|
||||||
|
const hasBody = Boolean(variables || bodyObj)
|
||||||
|
const body = hasBody
|
||||||
|
? JSON.stringify(variables ? { variables } : bodyObj)
|
||||||
|
: undefined
|
||||||
|
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
||||||
|
const res = await fetch(url!, { method, body, headers })
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const { data } = await res.json()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
throw await getError(res)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BigcommerceConfig = Partial<CommerceConfig>
|
export type BigcommerceConfig = Partial<CommerceConfig>
|
||||||
|
|
||||||
export type BigcommerceProps = {
|
export type BigcommerceProps = {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
config: BigcommerceConfig
|
locale: string
|
||||||
}
|
} & BigcommerceConfig
|
||||||
|
|
||||||
export function CommerceProvider({ children, config }: BigcommerceProps) {
|
export function CommerceProvider({ children, ...config }: BigcommerceProps) {
|
||||||
return (
|
return (
|
||||||
<CoreCommerceProvider config={{ ...config, ...bigcommerceConfig }}>
|
<CoreCommerceProvider config={{ ...bigcommerceConfig, ...config }}>
|
||||||
{children}
|
{children}
|
||||||
</CoreCommerceProvider>
|
</CoreCommerceProvider>
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createContext, useContext, FC } from 'react'
|
import { createContext, useContext, FC, useCallback } from 'react'
|
||||||
import useSWR, { responseInterface } from 'swr'
|
import useSWR, { responseInterface } from 'swr'
|
||||||
import { useCommerce } from '.'
|
import Cookies from 'js-cookie'
|
||||||
|
import { useCommerce } from '..'
|
||||||
|
|
||||||
export type CartResponse<C> = responseInterface<C, Error> & {
|
export type CartResponse<C> = responseInterface<C, Error> & {
|
||||||
isEmpty: boolean
|
isEmpty: boolean
|
||||||
@ -12,18 +13,17 @@ export type CartProviderProps =
|
|||||||
|
|
||||||
const CartContext = createContext<CartResponse<any> | null>(null)
|
const CartContext = createContext<CartResponse<any> | null>(null)
|
||||||
|
|
||||||
function getCartCookie() {
|
|
||||||
// TODO: Figure how the cart should be persisted
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const CartProvider: FC<CartProviderProps> = ({ children, query, url }) => {
|
const CartProvider: FC<CartProviderProps> = ({ children, query, url }) => {
|
||||||
const { fetcher } = useCommerce()
|
const { fetcher: fetch, cartCookie } = useCommerce()
|
||||||
const cartId = getCartCookie()
|
const fetcher = (url?: string, query?: string) => fetch({ url, query })
|
||||||
const response = useSWR(() => (cartId ? [url, query] : null), fetcher)
|
const cartId = Cookies.get(cartCookie)
|
||||||
|
const response = useSWR(() => (cartId ? [url, query] : null), fetcher, {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CartContext.Provider value={{ ...response, isEmpty: true }}>
|
// Avoid destructuring in `response` so we don't trigger the getters early
|
||||||
|
<CartContext.Provider value={Object.assign(response, { isEmpty: true })}>
|
||||||
{children}
|
{children}
|
||||||
</CartContext.Provider>
|
</CartContext.Provider>
|
||||||
)
|
)
|
11
lib/commerce/cart/use-add-item.tsx
Normal file
11
lib/commerce/cart/use-add-item.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Fetcher, useCommerce } from '..'
|
||||||
|
|
||||||
|
export default function useAddItem<T, Input>(
|
||||||
|
fetcher: (fetch: Fetcher<T>, input: Input) => T | Promise<T>
|
||||||
|
) {
|
||||||
|
const { fetcher: fetch } = useCommerce()
|
||||||
|
|
||||||
|
return async function addItem(input: Input) {
|
||||||
|
return fetcher(fetch, input)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,11 @@
|
|||||||
import { createContext, ReactNode, useContext } from 'react'
|
import {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
} from 'react'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
const Commerce = createContext<CommerceConfig | null>(null)
|
const Commerce = createContext<CommerceConfig | null>(null)
|
||||||
|
|
||||||
@ -10,9 +17,18 @@ export type CommerceProps = {
|
|||||||
export type CommerceConfig = {
|
export type CommerceConfig = {
|
||||||
fetcher: Fetcher<any>
|
fetcher: Fetcher<any>
|
||||||
locale: string
|
locale: string
|
||||||
|
cartCookie: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Fetcher<T> = (...args: any) => T | Promise<T>
|
export type Fetcher<T> = (options: FetcherOptions) => T | Promise<T>
|
||||||
|
|
||||||
|
export type FetcherOptions = {
|
||||||
|
url?: string
|
||||||
|
query?: string
|
||||||
|
method?: string
|
||||||
|
variables?: any
|
||||||
|
body?: any
|
||||||
|
}
|
||||||
|
|
||||||
export function CommerceProvider({ children, config }: CommerceProps) {
|
export function CommerceProvider({ children, config }: CommerceProps) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
|
"js-cookie": "^2.2.1",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"next": "^9.5.4-canary.23",
|
"next": "^9.5.4-canary.23",
|
||||||
"postcss-nesting": "^7.0.1",
|
"postcss-nesting": "^7.0.1",
|
||||||
@ -36,6 +37,7 @@
|
|||||||
"@graphql-codegen/typescript": "^1.17.10",
|
"@graphql-codegen/typescript": "^1.17.10",
|
||||||
"@graphql-codegen/typescript-operations": "^1.17.8",
|
"@graphql-codegen/typescript-operations": "^1.17.8",
|
||||||
"@types/cookie": "^0.4.0",
|
"@types/cookie": "^0.4.0",
|
||||||
|
"@types/js-cookie": "^2.2.6",
|
||||||
"@types/node": "^14.11.2",
|
"@types/node": "^14.11.2",
|
||||||
"@types/react": "^16.9.49",
|
"@types/react": "^16.9.49",
|
||||||
"graphql": "^15.3.0",
|
"graphql": "^15.3.0",
|
||||||
|
@ -9,6 +9,11 @@ export async function getStaticProps({
|
|||||||
params,
|
params,
|
||||||
}: GetStaticPropsContext<{ slug: string }>) {
|
}: GetStaticPropsContext<{ slug: string }>) {
|
||||||
const { product } = await getProduct({ variables: { slug: params!.slug } })
|
const { product } = await getProduct({ variables: { slug: params!.slug } })
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
throw new Error(`Product with slug '${params!.slug}' not found`)
|
||||||
|
}
|
||||||
|
|
||||||
const productData = {
|
const productData = {
|
||||||
name: 'T-Shirt',
|
name: 'T-Shirt',
|
||||||
description: `
|
description: `
|
||||||
@ -51,7 +56,7 @@ export default function Slug({
|
|||||||
return router.isFallback ? (
|
return router.isFallback ? (
|
||||||
<h1>Loading...</h1>
|
<h1>Loading...</h1>
|
||||||
) : (
|
) : (
|
||||||
<ProductView productData={productData} />
|
<ProductView product={product} productData={productData} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
90
yarn.lock
90
yarn.lock
@ -1540,6 +1540,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/js-cookie@^2.2.6":
|
||||||
|
version "2.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f"
|
||||||
|
integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==
|
||||||
|
|
||||||
"@types/js-yaml@^3.12.5":
|
"@types/js-yaml@^3.12.5":
|
||||||
version "3.12.5"
|
version "3.12.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb"
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb"
|
||||||
@ -3072,11 +3077,6 @@ des.js@^1.0.0:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
|
|
||||||
desandro-matches-selector@^2.0.0:
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1"
|
|
||||||
integrity sha1-cXvu1NwT59jzdi9wem1YpndCGOE=
|
|
||||||
|
|
||||||
detect-indent@^6.0.0:
|
detect-indent@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
|
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
|
||||||
@ -3390,11 +3390,6 @@ esutils@^2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||||
|
|
||||||
ev-emitter@^1.0.0, ev-emitter@^1.0.1, ev-emitter@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a"
|
|
||||||
integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==
|
|
||||||
|
|
||||||
event-target-shim@^5.0.0:
|
event-target-shim@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||||
@ -3612,30 +3607,11 @@ find-up@^4.0.0, find-up@^4.1.0:
|
|||||||
locate-path "^5.0.0"
|
locate-path "^5.0.0"
|
||||||
path-exists "^4.0.0"
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
fizzy-ui-utils@^2.0.7:
|
|
||||||
version "2.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz#7df45dcc4eb374a08b65d39bb9a4beedf7330505"
|
|
||||||
integrity sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==
|
|
||||||
dependencies:
|
|
||||||
desandro-matches-selector "^2.0.0"
|
|
||||||
|
|
||||||
flatten@^1.0.2:
|
flatten@^1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||||
|
|
||||||
flickity@^2.2.1:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/flickity/-/flickity-2.2.1.tgz#81126e3d656cb54577358a5f959ffdbda088e670"
|
|
||||||
integrity sha512-fCZJGNqabgDrIhaUBqt2ydE8c5V6iiB3KQAf6dH3Z45MoDUm7g6+uZmteN0aLV9pzVItNqCbfOJQjsJM/rHuSA==
|
|
||||||
dependencies:
|
|
||||||
desandro-matches-selector "^2.0.0"
|
|
||||||
ev-emitter "^1.1.1"
|
|
||||||
fizzy-ui-utils "^2.0.7"
|
|
||||||
get-size "^2.0.3"
|
|
||||||
unidragger "^2.3.0"
|
|
||||||
unipointer "^2.3.0"
|
|
||||||
|
|
||||||
for-in@^1.0.2:
|
for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
@ -3730,11 +3706,6 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
|
|
||||||
get-size@^2.0.3:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/get-size/-/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef"
|
|
||||||
integrity sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==
|
|
||||||
|
|
||||||
get-stream@^4.1.0:
|
get-stream@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
|
||||||
@ -4053,13 +4024,6 @@ ignore@^5.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
|
||||||
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
||||||
|
|
||||||
imagesloaded@^4.1.4:
|
|
||||||
version "4.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7"
|
|
||||||
integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA==
|
|
||||||
dependencies:
|
|
||||||
ev-emitter "^1.0.0"
|
|
||||||
|
|
||||||
immutable@~3.7.6:
|
immutable@~3.7.6:
|
||||||
version "3.7.6"
|
version "3.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
|
||||||
@ -4439,6 +4403,11 @@ jest-worker@^26.3.0:
|
|||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^7.0.0"
|
supports-color "^7.0.0"
|
||||||
|
|
||||||
|
js-cookie@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||||
|
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
@ -5774,13 +5743,6 @@ postcss-nested@^4.1.1:
|
|||||||
postcss "^7.0.32"
|
postcss "^7.0.32"
|
||||||
postcss-selector-parser "^6.0.2"
|
postcss-selector-parser "^6.0.2"
|
||||||
|
|
||||||
postcss-nested@^5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.1.tgz#e7a77f7a806a09c8de0f2c163d8e3d09f00f3139"
|
|
||||||
integrity sha512-ZHNSAoHrMtbEzjq+Qs4R0gHijpXc6F1YUv4TGmGaz7rtfMvVJBbu5hMOH+CrhEaljQpEmx5N/P8i1pXTkbVAmg==
|
|
||||||
dependencies:
|
|
||||||
postcss-selector-parser "^6.0.4"
|
|
||||||
|
|
||||||
postcss-nesting@^7.0.0, postcss-nesting@^7.0.1:
|
postcss-nesting@^7.0.0, postcss-nesting@^7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052"
|
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052"
|
||||||
@ -5900,7 +5862,7 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4:
|
|||||||
indexes-of "^1.0.1"
|
indexes-of "^1.0.1"
|
||||||
uniq "^1.0.1"
|
uniq "^1.0.1"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
|
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||||
version "6.0.4"
|
version "6.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
|
||||||
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
|
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
|
||||||
@ -6002,7 +5964,7 @@ promise@^7.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
asap "~2.0.3"
|
asap "~2.0.3"
|
||||||
|
|
||||||
prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@15.7.2, prop-types@^15.6.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||||
@ -6096,25 +6058,11 @@ react-dom@^16.13.1:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.19.1"
|
scheduler "^0.19.1"
|
||||||
|
|
||||||
react-flickity-component@^3.5.0:
|
|
||||||
version "3.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-flickity-component/-/react-flickity-component-3.5.0.tgz#cc4d5ae2dcd8a37c3b95775946d7f4ae7843ea1a"
|
|
||||||
integrity sha512-79REAm9HRT7R+ksLA1kqzPqlntrzD7JBortIAKRoC36/BgXBfzOOF99tCGvptZvew0bMrHTkEzsFv9iSfW6wbA==
|
|
||||||
dependencies:
|
|
||||||
fbjs "^1.0.0"
|
|
||||||
imagesloaded "^4.1.4"
|
|
||||||
prop-types "^15.7.2"
|
|
||||||
|
|
||||||
react-is@16.13.1, react-is@^16.8.1:
|
react-is@16.13.1, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
react-marquee-slider@^1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-marquee-slider/-/react-marquee-slider-1.1.2.tgz#a3df0201d17ee7b20627944c7efd8af78522bc6d"
|
|
||||||
integrity sha512-Fjkwphr+vYqR4yJ9adv0rJgFsKeb5/kx35lA5gVdPFiBDno6r/nHVRg/gdGVLp/SF4dHwoJwZBwa4mKTOpHnqQ==
|
|
||||||
|
|
||||||
react-refresh@0.8.3:
|
react-refresh@0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||||
@ -7169,13 +7117,6 @@ unicode-property-aliases-ecmascript@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
|
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
|
||||||
integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
|
integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
|
||||||
|
|
||||||
unidragger@^2.3.0:
|
|
||||||
version "2.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/unidragger/-/unidragger-2.3.1.tgz#2e8c34feff61affa96dc895234ddfc1ea4ec7515"
|
|
||||||
integrity sha512-u+IgG7AG0MXJTKcdzAIYxCm+W5FcnA9M28203Awl6jIcE3/+9OtEyUX4Wv64y7XNKEVRKPot52IV4V6x7FlF5Q==
|
|
||||||
dependencies:
|
|
||||||
unipointer "^2.3.0"
|
|
||||||
|
|
||||||
union-value@^1.0.0:
|
union-value@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
|
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
|
||||||
@ -7186,13 +7127,6 @@ union-value@^1.0.0:
|
|||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^2.0.1"
|
set-value "^2.0.1"
|
||||||
|
|
||||||
unipointer@^2.3.0:
|
|
||||||
version "2.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/unipointer/-/unipointer-2.3.0.tgz#ba0dc462ce31c2a88e80810e19c3bae0ce47ed9f"
|
|
||||||
integrity sha512-m85sAoELCZhogI1owtJV3Dva7GxkHk2lI7A0otw3o0OwCuC/Q9gi7ehddigEYIAYbhkqNdri+dU1QQkrcBvirQ==
|
|
||||||
dependencies:
|
|
||||||
ev-emitter "^1.0.1"
|
|
||||||
|
|
||||||
uniq@^1.0.1:
|
uniq@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user