4
0
forked from crowetic/commerce

Merge branch 'master' into arzafran/tweak-banner

This commit is contained in:
Franco Arza 2020-10-19 19:29:34 -03:00
commit 0a270e7d23
46 changed files with 912 additions and 173 deletions

View File

@ -22,16 +22,16 @@
--violet: #7928ca; --violet: #7928ca;
--blue: #0070f3; --blue: #0070f3;
--accents-0: #fff; --accents-0: #f8f9fa;
--accents-1: #fafafa; --accents-1: #f1f3f5;
--accents-2: #eaeaea; --accents-2: #e9ecef;
--accents-3: #999999; --accents-3: #dee2e6;
--accents-4: #888888; --accents-4: #ced4da;
--accents-5: #666666; --accents-5: #adb5bd;
--accents-6: #444444; --accents-6: #868e96;
--accents-7: #333333; --accents-7: #495057;
--accents-8: #111111; --accents-8: #343a40;
--accents-9: #000; --accents-9: #212529;
} }
[data-theme='dark'] { [data-theme='dark'] {
@ -46,16 +46,16 @@
--text-primary: white; --text-primary: white;
--text-secondary: black; --text-secondary: black;
--accents-0: #000; --accents-0: #212529;
--accents-1: #111111; --accents-1: #343a40;
--accents-2: #333333; --accents-2: #495057;
--accents-3: #444444; --accents-3: #868e96;
--accents-4: #666666; --accents-4: #adb5bd;
--accents-5: #888888; --accents-5: #ced4da;
--accents-6: #999999; --accents-6: #dee2e6;
--accents-7: #eaeaea; --accents-7: #e9ecef;
--accents-8: #fafafa; --accents-8: #f1f3f5;
--accents-9: #fff; --accents-9: #f8f9fa;
} }
.fit { .fit {

View File

@ -1,5 +1,5 @@
.input { .input {
@apply bg-transparent px-3 py-2 appearance-none w-full transition duration-150 ease-in-out rounded-lg placeholder-accents-4 pr-10; @apply bg-transparent px-3 py-2 appearance-none w-full transition duration-150 ease-in-out placeholder-accents-5 pr-10;
min-width: 300px; min-width: 300px;
} }

View File

@ -17,7 +17,7 @@ const Searchbar: FC<Props> = ({ className }) => {
return ( return (
<div <div
className={cn( className={cn(
'relative rounded-lg text-sm bg-accents-1 text-base w-full border border-accents-2', 'relative text-sm bg-accents-1 text-base w-full',
className className
)} )}
> >

View File

@ -2,12 +2,11 @@
} }
.list { .list {
@apply flex flex-row items-center; @apply flex flex-row items-center h-full;
} }
.item { .item {
@apply mr-6 cursor-pointer relative transition ease-in-out duration-100 text-base; @apply mr-6 cursor-pointer relative transition ease-in-out duration-100 text-base flex items-center;
&:hover { &:hover {
@apply text-accents-8; @apply text-accents-8;
} }

View File

@ -10,15 +10,15 @@ const ArrowLeft = ({ ...props }) => {
> >
<path <path
d="M19 12H5" d="M19 12H5"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
<path <path
d="M12 19L5 12L12 5" d="M12 19L5 12L12 5"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
</svg> </svg>
) )

View File

@ -10,9 +10,9 @@ const Check = ({ ...props }) => {
> >
<path <path
d="M20 6L9 17L4 12" d="M20 6L9 17L4 12"
stroke-width="2" strokeWidth="2"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
</svg> </svg>
) )

View File

@ -4,9 +4,9 @@ const Minus = ({ ...props }) => {
<path <path
d="M5 12H19" d="M5 12H19"
stroke="currentColor" stroke="currentColor"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
</svg> </svg>
) )

View File

@ -4,16 +4,16 @@ const Plus = ({ ...props }) => {
<path <path
d="M12 5V19" d="M12 5V19"
stroke="currentColor" stroke="currentColor"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
<path <path
d="M5 12H19" d="M5 12H19"
stroke="currentColor" stroke="currentColor"
stroke-width="1.5" strokeWidth="1.5"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
/> />
</svg> </svg>
) )

View File

@ -55,7 +55,7 @@
} }
.squareBg, .squareBg,
.productTitle, .productTitle > span,
.productPrice, .productPrice,
.wishlistButton { .wishlistButton {
@apply transition ease-in-out duration-300; @apply transition ease-in-out duration-300;
@ -65,9 +65,13 @@
@apply transform absolute inset-0 z-0 bg-secondary; @apply transform absolute inset-0 z-0 bg-secondary;
} }
.squareBg.gray {
@apply bg-gray-300 !important;
}
.productTitle { .productTitle {
line-height: 51px; line-height: 40px;
width: 200px; width: 18vw;
& span { & span {
@apply inline text-2xl leading-6 p-4 bg-primary text-primary font-bold; @apply inline text-2xl leading-6 p-4 bg-primary text-primary font-bold;

View File

@ -8,7 +8,7 @@ interface Props {
className?: string className?: string
children?: ReactNode[] | Component[] | any[] children?: ReactNode[] | Component[] | any[]
node: ProductData node: ProductData
variant?: 'slim' variant?: 'slim' | 'simple'
} }
interface ProductData { interface ProductData {
@ -23,7 +23,7 @@ const ProductCard: FC<Props> = ({ className, node: p, variant }) => {
return ( return (
<div className="relative overflow-hidden box-border"> <div className="relative overflow-hidden box-border">
<img <img
className="object-scale-down h-24" className="object-scale-down h-48"
src={p.images.edges[0].node.urlSmall} src={p.images.edges[0].node.urlSmall}
/> />
<div className="absolute inset-0 flex items-center justify-end mr-8"> <div className="absolute inset-0 flex items-center justify-end mr-8">
@ -44,12 +44,12 @@ const ProductCard: FC<Props> = ({ className, node: p, variant }) => {
src={p.images.edges[0].node.urlXL} src={p.images.edges[0].node.urlXL}
/> />
</div> </div>
<div className={s.squareBg} /> <div className={cn(s.squareBg, { [s.gray]: variant === 'simple' })} />
<div className="flex flex-row justify-between box-border w-full z-10 relative"> <div className="flex flex-row justify-between box-border w-full z-10 relative">
<div className=""> <div className="">
<div className={s.productTitle}> <p className={s.productTitle}>
<span>{p.name}</span> <span>{p.name}</span>
</div> </p>
<span className={s.productPrice}>${p.prices.price.value}</span> <span className={s.productPrice}>${p.prices.price.value}</span>
</div> </div>
<div className={s.wishlistButton}> <div className={s.wishlistButton}>

View File

@ -1,7 +1,6 @@
import { NextSeo } from 'next-seo' import { NextSeo } from 'next-seo'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import s from './ProductView.module.css' import s from './ProductView.module.css'
import { Colors } from '@components/ui/types'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import { Button, Container } from '@components/ui' import { Button, Container } from '@components/ui'
import { Swatch, ProductSlider } from '@components/product' import { Swatch, ProductSlider } from '@components/product'
@ -15,19 +14,12 @@ interface Props {
product: Product product: Product
} }
interface Choices {
size?: string | null
color?: string | null
}
const ProductView: FC<Props> = ({ product, className }) => { const ProductView: FC<Props> = ({ product, className }) => {
const options = getProductOptions(product)
// console.log(options)
const addItem = useAddItem() const addItem = useAddItem()
const { openSidebar } = useUI() const { openSidebar } = useUI()
const options = getProductOptions(product)
const [choices, setChoices] = useState<Choices>({ const [choices, setChoices] = useState<Record<string, any>>({
size: null, size: null,
color: null, color: null,
}) })
@ -48,9 +40,6 @@ const ProductView: FC<Props> = ({ product, className }) => {
} }
} }
const activeSize = choices.size
const activeColor = choices.color
return ( return (
<Container> <Container>
<NextSeo <NextSeo
@ -88,6 +77,7 @@ const ProductView: FC<Props> = ({ product, className }) => {
{/** TODO: Change with Image Component */} {/** TODO: Change with Image Component */}
{product.images.edges?.map((image, i) => ( {product.images.edges?.map((image, i) => (
<img <img
key={image?.node.urlSmall}
className="w-full object-cover" className="w-full object-cover"
src={image?.node.urlXL} src={image?.node.urlXL}
loading={i === 0 ? 'eager' : 'lazy'} loading={i === 0 ? 'eager' : 'lazy'}
@ -104,25 +94,28 @@ const ProductView: FC<Props> = ({ product, className }) => {
<div className="flex-1 flex flex-col pt-24"> <div className="flex-1 flex flex-col pt-24">
<section> <section>
{options?.map((opt: any) => ( {options?.map((opt: any) => (
<div className="pb-4"> <div className="pb-4" key={opt.displayName}>
<h2 className="uppercase font-medium">{opt.displayName}</h2> <h2 className="uppercase font-medium">{opt.displayName}</h2>
<div className="flex flex-row py-4"> <div className="flex flex-row py-4">
{opt.values.map((v: any) => { {opt.values.map((v: any) => {
const active = choices[opt.displayName]
return ( return (
<Swatch <Swatch
key={v.entityId} key={v.entityId}
active={v.label === activeColor} active={v.label === active}
variant={opt.displayName} variant={opt.displayName}
color={v.hexColors ? v.hexColors[0] : ''} color={v.hexColors ? v.hexColors[0] : ''}
label={v.label} label={v.label}
onClick={() => onClick={() => {
setChoices((choices) => { setChoices((choices) => {
console.log(choices)
return { return {
...choices, ...choices,
[opt.displayName]: v.label, [opt.displayName]: v.label,
} }
}) })
} }}
/> />
) )
})} })}

View File

@ -1,11 +1,11 @@
.root { .root {
@apply h-12 w-12 bg-primary text-primary rounded-full mr-3 inline-flex @apply h-12 w-12 bg-primary text-primary rounded-full mr-3 inline-flex
items-center justify-center cursor-pointer transition duration-75 ease-in-out items-center justify-center cursor-pointer transition duration-100 ease-in-out
p-0 shadow-none border-gray-200 border box-border; p-0 shadow-none border-gray-200 border box-border text-black;
} }
.active.size { .active.size {
@apply border-accents-2 border-2; @apply border-accents-9 border-2;
} }
.root:hover { .root:hover {

View File

@ -1,10 +1,9 @@
import cn from 'classnames' import cn from 'classnames'
import { FC } from 'react' import { FC } from 'react'
import s from './Swatch.module.css' import s from './Swatch.module.css'
import { Colors } from '@components/ui/types'
import { Check } from '@components/icon' import { Check } from '@components/icon'
import Button, { ButtonProps } from '@components/ui/Button' import Button, { ButtonProps } from '@components/ui/Button'
import { isDark } from '@lib/colors'
interface Props { interface Props {
active?: boolean active?: boolean
children?: any children?: any
@ -24,7 +23,8 @@ const Swatch: FC<Props & ButtonProps> = ({
}) => { }) => {
variant = variant?.toLowerCase() variant = variant?.toLowerCase()
label = label?.toLowerCase() label = label?.toLowerCase()
// console.log(variant) const isDarkBg = isDark(color)
const rootClassName = cn( const rootClassName = cn(
s.root, s.root,
{ {
@ -38,12 +38,12 @@ const Swatch: FC<Props & ButtonProps> = ({
<Button <Button
className={rootClassName} className={rootClassName}
style={color ? { backgroundColor: color } : {}} style={color ? { backgroundColor: color } : {}}
{...props}
> >
{variant === 'color' && active && ( {variant === 'color' && active && (
<span <span
className={cn('absolute', { className={cn('absolute', {
'text-white': label !== 'white', 'text-white': isDarkBg,
'text-black': label === 'white',
})} })}
> >
<Check /> <Check />

View File

@ -20,3 +20,7 @@
.loading { .loading {
@apply bg-accents-1 text-accents-3 border-accents-2 cursor-not-allowed; @apply bg-accents-1 text-accents-3 border-accents-2 cursor-not-allowed;
} }
.slim {
@apply py-2 transform-none normal-case;
}

View File

@ -13,7 +13,7 @@ import { LoadingDots } from '@components/ui'
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
href?: string href?: string
className?: string className?: string
variant?: 'filled' | 'outlined' | 'flat' | 'none' variant?: 'flat' | 'slim'
active?: boolean active?: boolean
type?: 'submit' | 'reset' | 'button' type?: 'submit' | 'reset' | 'button'
Component?: string | JSXElementConstructor<any> Component?: string | JSXElementConstructor<any>
@ -24,7 +24,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => { const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
const { const {
className, className,
variant = 'filled', variant = 'flat',
children, children,
active, active,
onClick, onClick,
@ -50,6 +50,7 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
const rootClassName = cn( const rootClassName = cn(
s.root, s.root,
{ {
[s.slim]: variant === 'slim',
[s.loading]: loading, [s.loading]: loading,
}, },
className className
@ -57,16 +58,16 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
return ( return (
<Component <Component
className={rootClassName}
aria-pressed={active} aria-pressed={active}
data-variant={variant} data-variant={variant}
ref={mergeRefs([ref, buttonRef])} ref={mergeRefs([ref, buttonRef])}
{...buttonProps} {...buttonProps}
data-active={isPressed ? '' : undefined}
className={rootClassName}
style={{ style={{
width, width,
...style, ...style,
}} }}
data-active={isPressed ? '' : undefined}
> >
{children} {children}
{loading && ( {loading && (

View File

@ -1,12 +1,14 @@
const Logo = () => ( const Logo = ({ className = '', ...props }) => (
<svg <svg
width="32" width="32"
height="32" height="32"
viewBox="0 0 32 32" viewBox="0 0 32 32"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={className}
{...props}
> >
<rect width="32" height="32" rx="16" fill="var(--secondary)" /> <rect width="100%" height="100%" rx="16" fill="var(--secondary)" />
<path <path
fillRule="evenodd" fillRule="evenodd"
clipRule="evenodd" clipRule="evenodd"

View File

@ -8,7 +8,7 @@
& > * { & > * {
@apply flex-1 px-16 py-4; @apply flex-1 px-16 py-4;
width: 400px; width: 430px;
} }
} }

View File

@ -0,0 +1,8 @@
.root {
@apply fixed bg-black flex items-center inset-0 z-50 justify-center;
background-color: rgba(0, 0, 0, 0.35);
}
.modal {
@apply bg-white p-12;
}

View File

@ -0,0 +1,52 @@
import cn from 'classnames'
import { FC, useRef } from 'react'
import s from './Modal.module.css'
import { useDialog } from '@react-aria/dialog'
import {
useOverlay,
usePreventScroll,
useModal,
OverlayProvider,
OverlayContainer,
} from '@react-aria/overlays'
import { FocusScope } from '@react-aria/focus'
interface Props {
className?: string
children?: any
show?: boolean
close: () => void
}
const Modal: FC<Props> = ({
className,
children,
show = true,
close,
...props
}) => {
const rootClassName = cn(s.root, className)
let ref = useRef() as React.MutableRefObject<HTMLInputElement>
usePreventScroll()
let { modalProps } = useModal()
let { overlayProps } = useOverlay(props, ref)
let { dialogProps } = useDialog(props, ref)
return (
<div className={rootClassName}>
<FocusScope contain restoreFocus autoFocus>
<div
{...overlayProps}
{...dialogProps}
{...modalProps}
ref={ref}
className={s.modal}
>
{children}
</div>
</FocusScope>
</div>
)
}
export default Modal

View File

@ -0,0 +1 @@
export { default } from './Modal'

View File

@ -7,3 +7,4 @@ export { default as Marquee } from './Marquee'
export { default as Container } from './Container' export { default as Container } from './Container'
export { default as LoadingDots } from './LoadingDots' export { default as LoadingDots } from './LoadingDots'
export { default as Skeleton } from './Skeleton' export { default as Skeleton } from './Skeleton'
export { default as Modal } from './Modal'

View File

@ -22,7 +22,7 @@ export type ProductsHandlers = {
const METHODS = ['GET'] const METHODS = ['GET']
// TODO: a complete implementation should have schema validation for `req.body` // TODO: a complete implementation should have schema validation for `req.body`
const cartApi: BigcommerceApiHandler< const productApi: BigcommerceApiHandler<
SearchProductsData, SearchProductsData,
ProductsHandlers ProductsHandlers
> = async (req, res, config, handlers) => { > = async (req, res, config, handlers) => {
@ -45,4 +45,4 @@ const cartApi: BigcommerceApiHandler<
export const handlers = { getProducts } export const handlers = { getProducts }
export default createApiHandler(cartApi, handlers, {}) export default createApiHandler(productApi, handlers, {})

View File

@ -9,12 +9,15 @@ export const responsiveImageFragment = /* GraphQL */ `
isDefault isDefault
} }
` `
export const multipleChoiceFragment = /* GraphQL */ `
export const swatchOptionFragment = /* GraphQL */ `
fragment swatchOption on SwatchOptionValue { fragment swatchOption on SwatchOptionValue {
isDefault isDefault
hexColors hexColors
} }
`
export const multipleChoiceOptionFragment = /* GraphQL */ `
fragment multipleChoiceOption on MultipleChoiceOption { fragment multipleChoiceOption on MultipleChoiceOption {
entityId entityId
values { values {
@ -26,6 +29,8 @@ export const multipleChoiceFragment = /* GraphQL */ `
} }
} }
} }
${swatchOptionFragment}
` `
export const productInfoFragment = /* GraphQL */ ` export const productInfoFragment = /* GraphQL */ `
@ -76,5 +81,22 @@ export const productInfoFragment = /* GraphQL */ `
} }
${responsiveImageFragment} ${responsiveImageFragment}
${multipleChoiceFragment} ${multipleChoiceOptionFragment}
`
export const productConnectionFragment = /* GraphQL */ `
fragment productConnnection on ProductConnection {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
...productInfo
}
}
}
${productInfoFragment}
` `

View File

@ -4,7 +4,7 @@ import type {
} from 'lib/bigcommerce/schema' } from 'lib/bigcommerce/schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges' import filterEdges from '../utils/filter-edges'
import { productInfoFragment } from '../fragments/product' import { productConnectionFragment } from '../fragments/product'
import { BigcommerceConfig, getConfig, Images, ProductImageVariables } from '..' import { BigcommerceConfig, getConfig, Images, ProductImageVariables } from '..'
export const getAllProductsQuery = /* GraphQL */ ` export const getAllProductsQuery = /* GraphQL */ `
@ -19,24 +19,28 @@ export const getAllProductsQuery = /* GraphQL */ `
$imgLargeHeight: Int $imgLargeHeight: Int
$imgXLWidth: Int = 1280 $imgXLWidth: Int = 1280
$imgXLHeight: Int $imgXLHeight: Int
$products: Boolean = false
$featuredProducts: Boolean = false
$bestSellingProducts: Boolean = false
$newestProducts: Boolean = false
) { ) {
site { site {
products(first: $first, entityIds: $entityIds) { products(first: $first, entityIds: $entityIds) @include(if: $products) {
pageInfo { ...productConnnection
startCursor }
endCursor featuredProducts(first: $first) @include(if: $featuredProducts) {
} ...productConnnection
edges { }
cursor bestSellingProducts(first: $first) @include(if: $bestSellingProducts) {
node { ...productConnnection
...productInfo }
} newestProducts(first: $first) @include(if: $newestProducts) {
} ...productConnnection
} }
} }
} }
${productInfoFragment} ${productConnectionFragment}
` `
export type Product = NonNullable< export type Product = NonNullable<
@ -46,18 +50,34 @@ export type Product = NonNullable<
export type Products = Product[] export type Products = Product[]
export type GetAllProductsResult< export type GetAllProductsResult<
T extends { products: any[] } = { products: Products } T extends Record<keyof GetAllProductsResult, any[]> = { products: Products }
> = T > = T
export type ProductVariables = Images & const FIELDS = [
Omit<GetAllProductsQueryVariables, keyof ProductImageVariables> 'products',
'featuredProducts',
'bestSellingProducts',
'newestProducts',
]
export type ProductTypes =
| 'products'
| 'featuredProducts'
| 'bestSellingProducts'
| 'newestProducts'
export type ProductVariables = { field?: ProductTypes } & Images &
Omit<GetAllProductsQueryVariables, ProductTypes | keyof ProductImageVariables>
async function getAllProducts(opts?: { async function getAllProducts(opts?: {
variables?: ProductVariables variables?: ProductVariables
config?: BigcommerceConfig config?: BigcommerceConfig
}): Promise<GetAllProductsResult> }): Promise<GetAllProductsResult>
async function getAllProducts<T extends { products: any[] }, V = any>(opts: { async function getAllProducts<
T extends Record<keyof GetAllProductsResult, any[]>,
V = any
>(opts: {
query: string query: string
variables?: V variables?: V
config?: BigcommerceConfig config?: BigcommerceConfig
@ -65,7 +85,7 @@ async function getAllProducts<T extends { products: any[] }, V = any>(opts: {
async function getAllProducts({ async function getAllProducts({
query = getAllProductsQuery, query = getAllProductsQuery,
variables: vars, variables: { field = 'products', ...vars } = {},
config, config,
}: { }: {
query?: string query?: string
@ -73,17 +93,27 @@ async function getAllProducts({
config?: BigcommerceConfig config?: BigcommerceConfig
} = {}): Promise<GetAllProductsResult> { } = {}): Promise<GetAllProductsResult> {
config = getConfig(config) config = getConfig(config)
const variables: GetAllProductsQueryVariables = { const variables: GetAllProductsQueryVariables = {
...config.imageVariables, ...config.imageVariables,
...vars, ...vars,
} }
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 // 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`
const data = await config.fetch<RecursivePartial<GetAllProductsQuery>>( const data = await config.fetch<RecursivePartial<GetAllProductsQuery>>(
query, query,
{ variables } { variables }
) )
const products = data.site?.products?.edges const products = data.site?.[field]?.edges
return { return {
products: filterEdges(products as RecursiveRequired<typeof products>), products: filterEdges(products as RecursiveRequired<typeof products>),

View File

@ -0,0 +1,30 @@
import type { WishlistHandlers } from '..'
// Return current wishlist info
const addItem: WishlistHandlers['addItem'] = async ({
res,
body: { wishlistId, item },
config,
}) => {
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item' }],
})
}
const options = {
method: 'POST',
body: JSON.stringify({
items: [item],
}),
}
const { data } = await config.storeApiFetch(
`/v3/wishlists/${wishlistId}/items`,
options
)
res.status(200).json({ data })
}
export default addItem

View File

@ -0,0 +1,25 @@
import type { WishlistHandlers } from '..'
// Return current wishlist info
const addWishlist: WishlistHandlers['addWishlist'] = async ({
res,
body: { wishlist },
config,
}) => {
if (!wishlist) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing wishlist data' }],
})
}
const options = {
method: 'POST',
body: JSON.stringify(wishlist),
}
const { data } = await config.storeApiFetch(`/v3/wishlists/`, options)
res.status(200).json({ data })
}
export default addWishlist

View File

@ -0,0 +1,22 @@
import { BigcommerceApiError } from '../../utils/errors'
import type { WishlistList, WishlistHandlers } from '..'
// Return all wishlists
const getAllWishlists: WishlistHandlers['getAllWishlists'] = async ({
res,
body: { customerId },
config,
}) => {
let result: { data?: WishlistList } = {}
try {
result = await config.storeApiFetch(`/v3/wishlists/customer_id=${customerId}`)
} catch (error) {
throw error
}
const data = (result.data ?? []) as any
res.status(200).json({ data })
}
export default getAllWishlists

View File

@ -0,0 +1,20 @@
import type { Wishlist, WishlistHandlers } from '..'
// Return wishlist info
const getWishlist: WishlistHandlers['getWishlist'] = async ({
res,
body: { wishlistId },
config,
}) => {
let result: { data?: Wishlist } = {}
try {
result = await config.storeApiFetch(`/v3/wishlists/${wishlistId}`)
} catch (error) {
throw error
}
res.status(200).json({ data: result.data ?? null })
}
export default getWishlist

View File

@ -0,0 +1,25 @@
import type { WishlistHandlers } from '..'
// Return current wishlist info
const removeItem: WishlistHandlers['removeItem'] = async ({
res,
body: { wishlistId, itemId },
config,
}) => {
if (!wishlistId || !itemId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const result = await config.storeApiFetch<{ data: any } | null>(
`/v3/wishlists/${wishlistId}/items/${itemId}`,
{ method: 'DELETE' }
)
const data = result?.data ?? null
res.status(200).json({ data })
}
export default removeItem

View File

@ -0,0 +1,25 @@
import type { WishlistHandlers } from '..'
// Return current wishlist info
const removeWishlist: WishlistHandlers['removeWishlist'] = async ({
res,
body: { wishlistId },
config,
}) => {
if (!wishlistId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const result = await config.storeApiFetch<{ data: any } | null>(
`/v3/wishlists/${wishlistId}/`,
{ method: 'DELETE' }
)
const data = result?.data ?? null
res.status(200).json({ data })
}
export default removeWishlist

View File

@ -0,0 +1,27 @@
import type { WishlistHandlers } from '..'
// Update wish info
const updateWishlist: WishlistHandlers['updateWishlist'] = async ({
res,
body: { wishlistId, wishlist },
config,
}) => {
if (!wishlistId || !wishlist) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const { data } = await config.storeApiFetch(
`/v3/wishlists/${wishlistId}/`,
{
method: 'PUT',
body: JSON.stringify(wishlist),
}
)
res.status(200).json({ data })
}
export default updateWishlist

View File

@ -0,0 +1,152 @@
import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, {
BigcommerceApiHandler,
BigcommerceHandler,
} from '../utils/create-api-handler'
import { BigcommerceApiError } from '../utils/errors'
import getWishlist from './handlers/get-wishlist'
import getAllWishlists from './handlers/get-all-wishlists'
import addItem from './handlers/add-item'
import removeItem from './handlers/remove-item'
import updateWishlist from './handlers/update-wishlist'
import removeWishlist from './handlers/remove-wishlist'
import addWishlist from './handlers/add-wishlist'
type Body<T> = Partial<T> | undefined
export type ItemBody = {
product_id: number
variant_id: number
}
export type AddItemBody = { wishlistId: string; item: ItemBody }
export type RemoveItemBody = { wishlistId: string; itemId: string }
export type WishlistBody = {
customer_id: number
is_public: number
name: string
items: any[]
}
export type AddWishlistBody = { wishlist: WishlistBody }
// TODO: this type should match:
// https://developer.bigcommerce.com/api-reference/store-management/wishlists/wishlists/wishlistsbyidget
export type Wishlist = {
id: string
customer_id: number
name: string
is_public: boolean
token: string
items: any[]
// TODO: add missing fields
}
export type WishlistList = Wishlist[]
export type WishlistHandlers = {
getAllWishlists: BigcommerceHandler<WishlistList, { customerId?: string }>
getWishlist: BigcommerceHandler<Wishlist, { wishlistId?: string }>
addWishlist: BigcommerceHandler<
Wishlist,
{ wishlistId: string } & Body<AddWishlistBody>
>
updateWishlist: BigcommerceHandler<
Wishlist,
{ wishlistId: string } & Body<AddWishlistBody>
>
addItem: BigcommerceHandler<Wishlist, { wishlistId: string } & Body<AddItemBody>>
removeItem: BigcommerceHandler<
Wishlist,
{ wishlistId: string } & Body<RemoveItemBody>
>
removeWishlist: BigcommerceHandler<Wishlist, { wishlistId: string }>
}
const METHODS = ['GET', 'POST', 'PUT', '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
try {
const { wishlistId, itemId, customerId } = req.body
// Return current wishlist info
if (req.method === 'GET' && wishlistId) {
const body = { wishlistId: wishlistId as string }
return await handlers['getWishlist']({ req, res, config, body })
}
// Add an item to the wishlist
if (req.method === 'POST' && wishlistId) {
const body = { wishlistId, ...req.body }
return await handlers['addItem']({ req, res, config, body })
}
// Update a wishlist
if (req.method === 'PUT' && wishlistId) {
const body = { wishlistId, ...req.body }
return await handlers['updateWishlist']({ req, res, config, body })
}
// Remove an item from the wishlist
if (req.method === 'DELETE' && wishlistId && itemId) {
const body = {
wishlistId: wishlistId as string,
itemId: itemId as string,
}
return await handlers['removeItem']({ req, res, config, body })
}
// Remove the wishlist
if (req.method === 'DELETE' && wishlistId && !itemId) {
const body = { wishlistId: wishlistId as string }
return await handlers['removeWishlist']({ req, res, config, body })
}
// Get all the wishlists
if (req.method === 'GET' && !wishlistId) {
const body = { customerId: customerId as string }
return await handlers['getAllWishlists']({
req,
res: res as any,
config,
body,
})
}
// Create a wishlist
if (req.method === 'POST' && !wishlistId) {
const { body } = req
return await handlers['addWishlist']({ 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,
updateWishlist,
removeItem,
removeWishlist,
getAllWishlists,
addWishlist,
}
export default createApiHandler(wishlistApi, handlers, {})

View File

@ -1785,6 +1785,24 @@ export type ProductInfoFragment = { __typename?: 'Product' } & Pick<
} }
} }
export type ProductConnnectionFragment = {
__typename?: 'ProductConnection'
} & {
pageInfo: { __typename?: 'PageInfo' } & Pick<
PageInfo,
'startCursor' | 'endCursor'
>
edges?: Maybe<
Array<
Maybe<
{ __typename?: 'ProductEdge' } & Pick<ProductEdge, 'cursor'> & {
node: { __typename?: 'Product' } & ProductInfoFragment
}
>
>
>
}
export type GetAllProductPathsQueryVariables = Exact<{ [key: string]: never }> export type GetAllProductPathsQueryVariables = Exact<{ [key: string]: never }>
export type GetAllProductPathsQuery = { __typename?: 'Query' } & { export type GetAllProductPathsQuery = { __typename?: 'Query' } & {
@ -1814,25 +1832,24 @@ export type GetAllProductsQueryVariables = Exact<{
imgLargeHeight?: Maybe<Scalars['Int']> imgLargeHeight?: Maybe<Scalars['Int']>
imgXLWidth?: Maybe<Scalars['Int']> imgXLWidth?: Maybe<Scalars['Int']>
imgXLHeight?: Maybe<Scalars['Int']> imgXLHeight?: Maybe<Scalars['Int']>
products?: Maybe<Scalars['Boolean']>
featuredProducts?: Maybe<Scalars['Boolean']>
bestSellingProducts?: Maybe<Scalars['Boolean']>
newestProducts?: Maybe<Scalars['Boolean']>
}> }>
export type GetAllProductsQuery = { __typename?: 'Query' } & { export type GetAllProductsQuery = { __typename?: 'Query' } & {
site: { __typename?: 'Site' } & { site: { __typename?: 'Site' } & {
products: { __typename?: 'ProductConnection' } & { products: { __typename?: 'ProductConnection' } & ProductConnnectionFragment
pageInfo: { __typename?: 'PageInfo' } & Pick< featuredProducts: {
PageInfo, __typename?: 'ProductConnection'
'startCursor' | 'endCursor' } & ProductConnnectionFragment
> bestSellingProducts: {
edges?: Maybe< __typename?: 'ProductConnection'
Array< } & ProductConnnectionFragment
Maybe< newestProducts: {
{ __typename?: 'ProductEdge' } & Pick<ProductEdge, 'cursor'> & { __typename?: 'ProductConnection'
node: { __typename?: 'Product' } & ProductInfoFragment } & ProductConnnectionFragment
}
>
>
>
}
} }
} }

View File

@ -0,0 +1,46 @@
import { useCallback } from 'react'
import { HookFetcher } from '@lib/commerce/utils/types'
import useAction from '@lib/commerce/utils/use-action'
import type { ItemBody, AddItemBody } from '../api/wishlist'
import useWishlist, { Wishlist } from './use-wishlist'
const defaultOpts = {
url: '/api/bigcommerce/wishlist',
method: 'POST',
}
export type AddItemInput = ItemBody
export const fetcher: HookFetcher<Wishlist, AddItemBody> = (
options,
{ wishlistId, item },
fetch
) => {
return fetch({
url: options?.url ?? defaultOpts.url,
method: options?.method ?? defaultOpts.method,
body: { wishlistId, item },
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useAddItem = (wishlistId: string) => {
const { mutate } = useWishlist(wishlistId)
const fn = useAction<Wishlist, AddItemBody>(defaultOpts, customFetcher)
return useCallback(
async function addItem(input: AddItemInput) {
const data = await fn({ wishlistId, item: input })
await mutate(data, false)
return data
},
[fn, mutate]
)
}
useAddItem.extend = extendHook
return useAddItem
}
export default extendHook(fetcher)

View File

@ -0,0 +1,51 @@
import { useCallback } from 'react'
import { HookFetcher } from '@lib/commerce/utils/types'
import useAction from '@lib/commerce/utils/use-action'
import type { RemoveItemBody } from '../api/wishlist'
import useWishlist, { Wishlist } from './use-wishlist'
const defaultOpts = {
url: '/api/bigcommerce/wishlists',
method: 'DELETE',
}
export type RemoveItemInput = {
id: string
}
export const fetcher: HookFetcher<Wishlist | null, RemoveItemBody> = (
options,
{ wishlistId, itemId },
fetch
) => {
return fetch({
url: options?.url ?? defaultOpts.url,
method: options?.method ?? defaultOpts.method,
body: { wishlistId, itemId },
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useRemoveItem = (wishlistId: string, item?: any) => {
const { mutate } = useWishlist(wishlistId)
const fn = useAction<Wishlist | null, RemoveItemBody>(
defaultOpts,
customFetcher
)
return useCallback(
async function removeItem(input: RemoveItemInput) {
const data = await fn({ wishlistId, itemId: input.id ?? item?.id })
await mutate(data, false)
return data
},
[fn, mutate]
)
}
useRemoveItem.extend = extendHook
return useRemoveItem
}
export default extendHook(fetcher)

View File

@ -0,0 +1,11 @@
import useAddItem from './use-add-item'
import useRemoveItem from './use-remove-item'
// This hook is probably not going to be used, but it's here
// to show how a commerce should be structuring it
export default function useWishlistActions(wishlistId: string) {
const addItem = useAddItem(wishlistId)
const removeItem = useRemoveItem(wishlistId)
return { addItem, removeItem }
}

View File

@ -0,0 +1,41 @@
import { HookFetcher } from '@lib/commerce/utils/types'
import useData from '@lib/commerce/utils/use-data'
import type { Wishlist } from '../api/wishlist'
const defaultOpts = {
url: '/api/bigcommerce/wishlists',
}
export type { Wishlist }
export type WishlistInput = {
wishlistId: string | undefined
}
export const fetcher: HookFetcher<Wishlist | null, WishlistInput> = (
options,
{ wishlistId },
fetch
) => {
return fetch({
url: options?.url,
body: { wishlistId },
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useWishlists = (wishlistId: string) => {
const fetchFn: typeof fetcher = (options, input, fetch) => {
return customFetcher(options, input, fetch)
}
const response = useData(defaultOpts, [['wishlistId', wishlistId]], fetchFn)
return response
}
useWishlists.extend = extendHook
return useWishlists
}
export default extendHook(fetcher)

View File

@ -14,3 +14,37 @@ export function getRandomPairOfColors() {
// Returns a pair of colors // Returns a pair of colors
return [colors[idx], colors[idx2]] return [colors[idx], colors[idx2]]
} }
function hexToRgb(hex: string = '') {
// @ts-ignore
const match = hex.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i)
if (!match) {
return [0, 0, 0]
}
let colorString = match[0]
if (match[0].length === 3) {
colorString = colorString
.split('')
.map((char: string) => {
return char + char
})
.join('')
}
const integer = parseInt(colorString, 16)
const r = (integer >> 16) & 0xff
const g = (integer >> 8) & 0xff
const b = integer & 0xff
return [r, g, b]
}
export function isDark(color = '') {
// Equation from http://24ways.org/2010/calculating-color-contrast
const rgb = hexToRgb(color)
const res = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
return res < 128
}

View File

@ -19,10 +19,12 @@ export default function useCart<T>(
swrOptions?: ConfigInterface<T | null> swrOptions?: ConfigInterface<T | null>
) { ) {
const { cartCookie } = useCommerce() const { cartCookie } = useCommerce()
const fetcher: typeof fetcherFn = (options, input, fetch) => { const fetcher: typeof fetcherFn = (options, input, fetch) => {
input.cartId = Cookies.get(cartCookie) input.cartId = Cookies.get(cartCookie)
return fetcherFn(options, input, fetch) return fetcherFn(options, input, fetch)
} }
const response = useData(options, input, fetcher, swrOptions) const response = useData(options, input, fetcher, swrOptions)
return Object.assign(response, { isEmpty: true }) as CartResponse<T> return Object.assign(response, { isEmpty: true }) as CartResponse<T>

View File

@ -8,7 +8,7 @@ import {
} from 'react' } from 'react'
import { Fetcher } from './utils/types' import { Fetcher } from './utils/types'
const Commerce = createContext<CommerceContextValue | null>(null) const Commerce = createContext<CommerceContextValue | {}>({})
export type CommerceProps = { export type CommerceProps = {
children?: ReactNode children?: ReactNode

View File

@ -1,5 +1,18 @@
import bunyan from 'bunyan' import bunyan from 'bunyan'
import PrettyStream from 'bunyan-prettystream'
const log = bunyan.createLogger({ name: 'Next.js - Commerce' }) const prettyStdOut = new PrettyStream()
const log = bunyan.createLogger({
name: 'Next.js - Commerce',
level: 'debug',
streams: [
{
level: 'debug',
type: 'raw',
stream: prettyStdOut,
},
],
})
export default log export default log

View File

@ -22,15 +22,17 @@
"@headlessui/react": "^0.2.0", "@headlessui/react": "^0.2.0",
"@tailwindcss/ui": "^0.6.2", "@tailwindcss/ui": "^0.6.2",
"@types/bunyan": "^1.8.6", "@types/bunyan": "^1.8.6",
"@types/bunyan-prettystream": "^0.1.31",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"@types/react-swipeable-views": "^0.13.0", "@types/react-swipeable-views": "^0.13.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bunyan": "^1.8.14", "bunyan": "^1.8.14",
"bunyan-prettystream": "^0.1.3",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"next": "^9.5.4", "next": "^9.5.6-canary.4",
"next-seo": "^4.11.0", "next-seo": "^4.11.0",
"next-themes": "^0.0.4", "next-themes": "^0.0.4",
"nextjs-progressbar": "^0.0.6", "nextjs-progressbar": "^0.0.6",

View File

@ -9,22 +9,26 @@ import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages'
export async function getStaticProps({ preview }: GetStaticPropsContext) { export async function getStaticProps({ preview }: GetStaticPropsContext) {
const { pages } = await getAllPages() const { pages } = await getAllPages()
const { products } = await getAllProducts() const { products } = await getAllProducts()
const { products: featuredProducts } = await getAllProducts({
variables: { field: 'featuredProducts', first: 3 },
})
const { categories, brands } = await getSiteInfo() const { categories, brands } = await getSiteInfo()
return { return {
props: { pages, products, categories, brands }, props: { pages, products, featuredProducts, categories, brands },
} }
} }
export default function Home({ export default function Home({
products, products,
featuredProducts,
categories, categories,
brands, brands,
}: InferGetStaticPropsType<typeof getStaticProps>) { }: InferGetStaticPropsType<typeof getStaticProps>) {
return ( return (
<div className="mt-3"> <div className="mt-3">
<Grid> <Grid>
{products.slice(0, 3).map((p: any) => ( {featuredProducts.slice(0, 3).map((p: any) => (
<ProductCard key={p.id} {...p} /> <ProductCard key={p.id} {...p} />
))} ))}
</Grid> </Grid>
@ -44,42 +48,44 @@ export default function Home({
Natural." Natural."
/> />
<Grid layout="B"> <Grid layout="B">
{products.slice(3, 6).map((p: any) => ( {featuredProducts.slice(3, 6).map((p: any) => (
<ProductCard key={p.id} {...p} /> <ProductCard key={p.id} {...p} />
))} ))}
</Grid> </Grid>
<Marquee> <Marquee>
{products.slice(0, 3).map((p: any) => ( {products.slice(3, 6).map((p: any) => (
<ProductCard key={p.id} {...p} variant="slim" /> <ProductCard key={p.id} {...p} variant="slim" />
))} ))}
</Marquee> </Marquee>
<div className="py-12 flex flex-row w-full px-12"> <div className="py-12 flex flex-row w-full px-12">
<div className="pr-3 w-48"> <div className="pr-3 w-48 relative">
<ul className="mb-10"> <div className="sticky top-2">
<li className="py-1 text-base font-bold tracking-wide"> <ul className="mb-10">
All Categories <li className="py-1 text-base font-bold tracking-wide">
</li> All Categories
{categories.map((cat) => (
<li key={cat.path} className="py-1 text-accents-8">
<a href="#">{cat.name}</a>
</li> </li>
))} {categories.map((cat) => (
</ul> <li key={cat.path} className="py-1 text-accents-8">
<ul className=""> <a href="#">{cat.name}</a>
<li className="py-1 text-base font-bold tracking-wide"> </li>
All Designers ))}
</li> </ul>
{brands.flatMap(({ node }) => ( <ul className="">
<li key={node.path} className="py-1 text-accents-8"> <li className="py-1 text-base font-bold tracking-wide">
<a href="#">{node.name}</a> All Designers
</li> </li>
))} {brands.flatMap(({ node }) => (
</ul> <li key={node.path} className="py-1 text-accents-8">
<a href="#">{node.name}</a>
</li>
))}
</ul>
</div>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<Grid layout="normal"> <Grid layout="normal">
{products.map((p: any) => ( {products.map((p: any) => (
<ProductCard key={p.id} {...p} /> <ProductCard key={p.id} {...p} variant="simple" />
))} ))}
</Grid> </Grid>
</div> </div>

40
pages/login.tsx Normal file
View File

@ -0,0 +1,40 @@
import { Layout } from '@components/core'
import { Logo, Modal, Button } from '@components/ui'
export default function Login() {
return (
<div className="pb-20">
<Modal close={() => {}}>
<div className="h-80 w-80 flex flex-col justify-between py-3 px-3">
<div className="flex justify-center pb-12">
<Logo width="64px" height="64px" />
</div>
<div className="flex flex-col space-y-3">
<div className="border border-accents-3 text-accents-6">
<input
placeholder="Email"
className="focus:outline-none focus:shadow-outline-gray border-none py-2 px-6 w-full appearance-none transition duration-150 ease-in-out placeholder-accents-5 pr-10"
/>
</div>
<div className="border border-accents-3 text-accents-6">
<input
placeholder="Password"
className="focus:outline-none focus:shadow-outline-gray border-none py-2 px-6 w-full appearance-none transition duration-150 ease-in-out placeholder-accents-5 pr-10"
/>
</div>
<Button variant="slim">Log In</Button>
<span className="pt-3 text-center text-sm">
<span className="text-accents-7">Don't have an account?</span>
{` `}
<a className="text-accent-9 font-bold hover:underline cursor-pointer">
Sign Up
</a>
</span>
</div>
</div>
</Modal>
</div>
)
}
Login.Layout = Layout

View File

@ -149,7 +149,7 @@ export default function Search({
))} ))}
</Grid> </Grid>
) : ( ) : (
<Grid> <Grid layout="normal">
{range(12).map(() => ( {range(12).map(() => (
<Skeleton <Skeleton
className="w-full animate__animated animate__fadeIn" className="w-full animate__animated animate__fadeIn"

View File

@ -1397,6 +1397,26 @@
is-promise "4.0.0" is-promise "4.0.0"
tslib "~2.0.1" tslib "~2.0.1"
"@hapi/accept@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10"
integrity sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q==
dependencies:
"@hapi/boom" "9.x.x"
"@hapi/hoek" "9.x.x"
"@hapi/boom@9.x.x":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.0.tgz#0d9517657a56ff1e0b42d0aca9da1b37706fec56"
integrity sha512-4nZmpp4tXbm162LaZT45P7F7sgiem8dwAh2vHWT6XX24dozNjGMg6BvKCRvtCUcmcXqeMIUqWN8Rc5X8yKuROQ==
dependencies:
"@hapi/hoek" "9.x.x"
"@hapi/hoek@9.x.x":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6"
integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==
"@headlessui/react@^0.2.0": "@headlessui/react@^0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-0.2.0.tgz#a31f90892d736243ba91c1474f534b3256d0c538" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-0.2.0.tgz#a31f90892d736243ba91c1474f534b3256d0c538"
@ -1412,20 +1432,20 @@
meow "^7.0.0" meow "^7.0.0"
prettier "^2.0.5" prettier "^2.0.5"
"@next/env@9.5.4": "@next/env@9.5.6-canary.4":
version "9.5.4" version "9.5.6-canary.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-9.5.4.tgz#950f3370151a940ecac6e7e19cf125e6113e101e" resolved "https://registry.yarnpkg.com/@next/env/-/env-9.5.6-canary.4.tgz#785956d1e2d26e25377a2dcafbd3fe785cd88c35"
integrity sha512-uGnUO68/u9C8bqHj5obIvyGRDqe/jh1dFSLx03mJmlESjcCmV4umXYJOnt3XzU1VhVntSE+jUZtnS5bjYmmLfQ== integrity sha512-c+JrotEtdgcaF6m4xSZ6D7eY4NWSDBsqcaLwkKAXh+pSEHfH2eKnZv7cRIaR3s7Ll37gE/RNfXGVC2l43vCcuw==
"@next/polyfill-module@9.5.4": "@next/polyfill-module@9.5.6-canary.4":
version "9.5.4" version "9.5.6-canary.4"
resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-9.5.4.tgz#35ea31ce5f6bbf0ac31aac483b60d4ba17a79861" resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-9.5.6-canary.4.tgz#6144ee81ae42aa44905f6c2b864ff5be19c67c38"
integrity sha512-GA2sW7gs33s7RGPFqkMiT9asYpaV/Hhw9+XM9/UlPrkNdTaxZWaPa2iHgmqJ7k6OHiOmy+CBLFrUBgzqKNhs3Q== integrity sha512-r+kAXpF8AjVZGanJBwveCR5GCqnNIjA0WBsotat4MP+KKQwrptocRYsP+eZZo7gyhHEwSMdMag2NWahqjW+Vlg==
"@next/react-dev-overlay@9.5.4": "@next/react-dev-overlay@9.5.6-canary.4":
version "9.5.4" version "9.5.6-canary.4"
resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.4.tgz#7d88a710d23021020cca213bc77106df18950b2b" resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.6-canary.4.tgz#2218f9a9d5874e838d24178f657ec6dd8e1aa961"
integrity sha512-tYvNmOQ0inykSvcimkTiONMv4ZyFB2G2clsy9FKLLRZ2OA+Jiov6T7Pq6YpKbBwTLu/BQGVc7Qn4BZ5CDHR8ig== integrity sha512-zYKkkdWi2rSyWYnOn7BEAQeZwOP2O6wHC4A/nB2YdRBDvD9p3qLawmribuiQ7vspPmAIgv2JgQ8DauC5YQU7DA==
dependencies: dependencies:
"@babel/code-frame" "7.10.4" "@babel/code-frame" "7.10.4"
ally.js "1.4.1" ally.js "1.4.1"
@ -1438,10 +1458,10 @@
stacktrace-parser "0.1.10" stacktrace-parser "0.1.10"
strip-ansi "6.0.0" strip-ansi "6.0.0"
"@next/react-refresh-utils@9.5.4": "@next/react-refresh-utils@9.5.6-canary.4":
version "9.5.4" version "9.5.6-canary.4"
resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.4.tgz#3bfe067f0cfc717f079482d956211708c9e81126" resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-9.5.6-canary.4.tgz#098a55714d906248ef46e9a1c077a1dc6e231331"
integrity sha512-TPhEiYxK5YlEuzVuTzgZiDN7SDh4drvUAqsO9Yccd8WLcfYqOLRN2fCALremW5mNLAZQZW3iFgW8PW8Gckq4EQ== integrity sha512-hxSF3/lWX/a+6vPLViklLK6DNDzRzd8T/EUJpkUgLdwdHnABKFB6p9m1qvdGuqBQug2RNDcLQy7oKucy42tpVQ==
"@nodelib/fs.scandir@2.1.3": "@nodelib/fs.scandir@2.1.3":
version "2.1.3" version "2.1.3"
@ -2095,6 +2115,13 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@types/bunyan-prettystream@^0.1.31":
version "0.1.31"
resolved "https://registry.yarnpkg.com/@types/bunyan-prettystream/-/bunyan-prettystream-0.1.31.tgz#3864836abb907ab151f7edf7c64c323c9609e1d1"
integrity sha512-NE7fq2ZcX7OSMK+VhTNJkVEHlo+hm0uVXpuLeH1ifGm52Qwuo/kLD2GHo7UcEXMFu3duKver/AFo8C4TME93zw==
dependencies:
"@types/node" "*"
"@types/bunyan@^1.8.6": "@types/bunyan@^1.8.6":
version "1.8.6" version "1.8.6"
resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582" resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582"
@ -2977,6 +3004,11 @@ bufferutil@^4.0.1:
dependencies: dependencies:
node-gyp-build "~3.7.0" node-gyp-build "~3.7.0"
bunyan-prettystream@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/bunyan-prettystream/-/bunyan-prettystream-0.1.3.tgz#6c3b713266f6ad32007c7b6ab1e998a245349d98"
integrity sha1-bDtxMmb2rTIAfHtqsemYokU0nZg=
bunyan@^1.8.14: bunyan@^1.8.14:
version "1.8.14" version "1.8.14"
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.14.tgz#3d8c1afea7de158a5238c7cb8a66ab6b38dd45b4" resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.14.tgz#3d8c1afea7de158a5238c7cb8a66ab6b38dd45b4"
@ -5887,10 +5919,10 @@ next-tick@~1.0.0:
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
next@^9.5.4: next@^9.5.6-canary.4:
version "9.5.4" version "9.5.6-canary.4"
resolved "https://registry.yarnpkg.com/next/-/next-9.5.4.tgz#3c6aa3fd38ff1711e956ea2b6833475e0262ec35" resolved "https://registry.yarnpkg.com/next/-/next-9.5.6-canary.4.tgz#af3ed55845f6005ac155f7c5d9d21b7493be7141"
integrity sha512-dicsJSxiUFcRjeZ/rNMAO3HS5ttFFuRHhdAn5g7lHnWUZ3MnEX4ggBIihaoUr6qu2So9KoqUPXpS91MuSXUmBw== integrity sha512-sd29wpQer1Ha9EWpvrBvN6XXmLtJnR5JWAyYnlBmrA835BolN0cQw6++sOGcdTJZV5+ZWbhTEzBVOUTKsSLd5w==
dependencies: dependencies:
"@ampproject/toolbox-optimizer" "2.6.0" "@ampproject/toolbox-optimizer" "2.6.0"
"@babel/code-frame" "7.10.4" "@babel/code-frame" "7.10.4"
@ -5910,10 +5942,11 @@ next@^9.5.4:
"@babel/preset-typescript" "7.10.4" "@babel/preset-typescript" "7.10.4"
"@babel/runtime" "7.11.2" "@babel/runtime" "7.11.2"
"@babel/types" "7.11.5" "@babel/types" "7.11.5"
"@next/env" "9.5.4" "@hapi/accept" "5.0.1"
"@next/polyfill-module" "9.5.4" "@next/env" "9.5.6-canary.4"
"@next/react-dev-overlay" "9.5.4" "@next/polyfill-module" "9.5.6-canary.4"
"@next/react-refresh-utils" "9.5.4" "@next/react-dev-overlay" "9.5.6-canary.4"
"@next/react-refresh-utils" "9.5.6-canary.4"
ast-types "0.13.2" ast-types "0.13.2"
babel-plugin-transform-define "2.0.0" babel-plugin-transform-define "2.0.0"
babel-plugin-transform-react-remove-prop-types "0.4.24" babel-plugin-transform-react-remove-prop-types "0.4.24"