4
0
forked from crowetic/commerce

Moved auth & cart hooks + several fixes

This commit is contained in:
cond0r 2021-02-22 14:06:34 +02:00
parent 005fe9d6c9
commit 528d7556a8
53 changed files with 447 additions and 331 deletions

View File

@ -3,7 +3,6 @@
--primary-2: #f1f3f5; --primary-2: #f1f3f5;
--secondary: #000000; --secondary: #000000;
--secondary-2: #111; --secondary-2: #111;
--selection: var(--cyan); --selection: var(--cyan);
--text-base: #000000; --text-base: #000000;
@ -13,18 +12,14 @@
--hover: rgba(0, 0, 0, 0.075); --hover: rgba(0, 0, 0, 0.075);
--hover-1: rgba(0, 0, 0, 0.15); --hover-1: rgba(0, 0, 0, 0.15);
--hover-2: rgba(0, 0, 0, 0.25); --hover-2: rgba(0, 0, 0, 0.25);
--cyan: #22b8cf; --cyan: #22b8cf;
--green: #37b679; --green: #37b679;
--red: #da3c3c; --red: #da3c3c;
--pink: #e64980; --pink: #e64980;
--purple: #f81ce5; --purple: #f81ce5;
--blue: #0070f3; --blue: #0070f3;
--violet-light: #7048e8;
--violet: #5f3dc4; --violet: #5f3dc4;
--violet-light: #7048e8;
--accents-0: #f8f9fa; --accents-0: #f8f9fa;
--accents-1: #f1f3f5; --accents-1: #f1f3f5;
--accents-2: #e9ecef; --accents-2: #e9ecef;
@ -132,3 +127,4 @@ a {
opacity: 1; opacity: 1;
} }
} }

View File

@ -1,3 +1,3 @@
.fit { .fit {
min-height: calc(100vh - 88px); min-height: calc(100vh - 88px);
} }

View File

@ -1,5 +1,5 @@
import { FC, useState, useMemo, useRef, useEffect } from 'react' import { FC, useRef, useEffect } from 'react'
import { getRandomPairOfColors } from '@lib/colors' import { useUserAvatar } from '@lib/hooks/useUserAvatar'
interface Props { interface Props {
className?: string className?: string
@ -7,18 +7,13 @@ interface Props {
} }
const Avatar: FC<Props> = ({}) => { const Avatar: FC<Props> = ({}) => {
const [bg] = useState(useMemo(() => getRandomPairOfColors, []))
let ref = useRef() as React.MutableRefObject<HTMLInputElement> let ref = useRef() as React.MutableRefObject<HTMLInputElement>
let { userAvatar } = useUserAvatar()
useEffect(() => {
if (ref && ref.current) {
ref.current.style.backgroundImage = `linear-gradient(140deg, ${bg[0]}, ${bg[1]} 100%)`
}
}, [bg])
return ( return (
<div <div
ref={ref} ref={ref}
style={{ backgroundImage: userAvatar }}
className="inline-block h-8 w-8 rounded-full border-2 border-primary hover:border-secondary focus:border-secondary transition linear-out duration-150" className="inline-block h-8 w-8 rounded-full border-2 border-primary hover:border-secondary focus:border-secondary transition linear-out duration-150"
> >
{/* Add an image - We're generating a gradient as placeholder <img></img> */} {/* Add an image - We're generating a gradient as placeholder <img></img> */}

View File

@ -27,7 +27,7 @@ const HomeAllProductsGrid: FC<Props> = ({
<ul className="mb-10"> <ul className="mb-10">
<li className="py-1 text-base font-bold tracking-wide"> <li className="py-1 text-base font-bold tracking-wide">
<Link href={getCategoryPath('')}> <Link href={getCategoryPath('')}>
<a>All Collections</a> <a>All Categories</a>
</Link> </Link>
</li> </li>
{categories.map((cat: any) => ( {categories.map((cat: any) => (
@ -38,6 +38,20 @@ const HomeAllProductsGrid: FC<Props> = ({
</li> </li>
))} ))}
</ul> </ul>
<ul className="">
<li className="py-1 text-base font-bold tracking-wide">
<Link href={getDesignerPath('')}>
<a>All Designers</a>
</Link>
</li>
{brands.flatMap(({ node }: any) => (
<li key={node.path} className="py-1 text-accents-8 text-base">
<Link href={getDesignerPath(node.path)}>
<a>{node.name}</a>
</Link>
</li>
))}
</ul>
</div> </div>
</div> </div>
<div className="flex-1"> <div className="flex-1">

View File

@ -58,7 +58,7 @@ const Layout: FC<Props> = ({
} = useUI() } = useUI()
const { acceptedCookies, onAcceptCookies } = useAcceptCookies() const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
const { locale = 'en-US' } = useRouter() const { locale = 'en-US' } = useRouter()
const isWishlistEnabled = commerceFeatures.wishlist const isWishlistEnabled = commerceFeatures?.wishlist
return ( return (
<CommerceProvider locale={locale}> <CommerceProvider locale={locale}>
<div className={cn(s.root)}> <div className={cn(s.root)}>

View File

@ -7,7 +7,7 @@
} }
.input:focus { .input:focus {
@apply outline-none shadow-outline-2; @apply outline-none shadow-outline-normal;
} }
.iconContainer { .iconContainer {

View File

@ -24,7 +24,11 @@
} }
.bagCount { .bagCount {
@apply border border-accents-1 bg-secondary text-secondary h-4 w-4 absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs; @apply border border-accents-1 bg-secondary text-secondary absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs;
padding-left: 2.5px;
padding-right: 2.5px;
min-width: 1.25rem;
min-height: 1.25rem;
} }
.avatarButton { .avatarButton {

View File

@ -132,5 +132,5 @@
} }
.productImage { .productImage {
@apply transform transition-transform duration-500 object-cover; @apply transform transition-transform duration-500 object-cover scale-120;
} }

View File

@ -28,8 +28,8 @@ const ProductCard: FC<Props> = ({
<a className={cn(s.root, { [s.simple]: variant === 'simple' }, className)}> <a className={cn(s.root, { [s.simple]: variant === 'simple' }, className)}>
{variant === 'slim' ? ( {variant === 'slim' ? (
<div className="relative overflow-hidden box-border"> <div className="relative overflow-hidden box-border">
<div className="absolute inset-0 flex items-start justify-end m-1 z-20"> <div className="absolute inset-0 flex items-center justify-end mr-8 z-20">
<span className="text-black inline-block p-3 font-bold text-xl break-words"> <span className="bg-black text-white inline-block p-3 font-bold text-xl break-words">
{product.name} {product.name}
</span> </span>
</div> </div>

View File

@ -15,7 +15,7 @@
.leftControl:hover, .leftControl:hover,
.rightControl:hover { .rightControl:hover {
@apply outline-none shadow-outline-blue; @apply outline-none shadow-outline-normal;
} }
.leftControl { .leftControl {
@ -70,7 +70,7 @@
} }
.positionIndicator:focus .dot { .positionIndicator:focus .dot {
@apply shadow-outline-blue; @apply shadow-outline-normal;
} }
.positionIndicatorActive .dot { .positionIndicatorActive .dot {

View File

@ -7,7 +7,7 @@
} }
.root:focus { .root:focus {
@apply shadow-outline outline-none; @apply shadow-outline-normal outline-none;
} }
.root[data-active] { .root[data-active] {

View File

@ -3,5 +3,5 @@
} }
.root:focus { .root:focus {
@apply outline-none shadow-outline-gray; @apply outline-none shadow-outline-normal;
} }

View File

@ -127,6 +127,12 @@ function uiReducer(state: State, action: Action) {
toastText: action.text, toastText: action.text,
} }
} }
case 'SET_USER_AVATAR': {
return {
...state,
userAvatar: action.value,
}
}
} }
} }

View File

@ -8,6 +8,7 @@ const fetchGraphqlApi: GraphQLFetcher = async (
{ variables, preview } = {}, { variables, preview } = {},
fetchOptions fetchOptions
) => { ) => {
// log.warn(query)
const config = getConfig() const config = getConfig()
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), { const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
...fetchOptions, ...fetchOptions,

View File

@ -2,7 +2,7 @@ import type { GetProductQuery, GetProductQueryVariables } from '../schema'
import setProductLocaleMeta from '../api/utils/set-product-locale-meta' import setProductLocaleMeta from '../api/utils/set-product-locale-meta'
import { productInfoFragment } from '../api/fragments/product' import { productInfoFragment } from '../api/fragments/product'
import { BigcommerceConfig, getConfig } from '../api' import { BigcommerceConfig, getConfig } from '../api'
import { normalizeProduct } from '@framework/utils/normalize' import { normalizeProduct } from '@framework/lib/normalize'
import type { Product } from '@commerce/types' import type { Product } from '@commerce/types'
export const getProductQuery = /* GraphQL */ ` export const getProductQuery = /* GraphQL */ `

View File

@ -18,8 +18,8 @@ const fetchAllProducts = async ({
variables: { ...variables, cursor }, variables: { ...variables, cursor },
}) })
const edges: ProductEdge[] = data?.products?.edges ?? [] const edges: ProductEdge[] = data.products?.edges ?? []
const hasNextPage = data?.products?.pageInfo?.hasNextPage const hasNextPage = data.products?.pageInfo?.hasNextPage
acc = acc.concat(edges) acc = acc.concat(edges)
if (hasNextPage) { if (hasNextPage) {

View File

@ -29,6 +29,6 @@ const fetchGraphqlApi: GraphQLFetcher = async (
throw getError(errors, status) throw getError(errors, status)
} }
return { data: data, res } return { data, res }
} }
export default fetchGraphqlApi export default fetchGraphqlApi

View File

@ -1,56 +1,80 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import { CommerceError, ValidationError } from '@commerce/utils/errors' import { CommerceError, ValidationError } from '@commerce/utils/errors'
import useCommerceLogin from '@commerce/use-login'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import createCustomerAccessTokenMutation from '../utils/mutations/customer-access-token-create' import createCustomerAccessTokenMutation from '../utils/mutations/customer-access-token-create'
import { CustomerAccessTokenCreateInput } from '@framework/schema' import {
import handleLogin from '@framework/utils/handle-login' CustomerAccessToken,
CustomerAccessTokenCreateInput,
CustomerAccessTokenCreatePayload,
CustomerUserError,
Mutation,
MutationCheckoutCreateArgs,
} from '@framework/schema'
import useLogin, { UseLogin } from '@commerce/use-login'
import { setCustomerToken } from '@framework/utils'
const defaultOpts = { export default useLogin as UseLogin<typeof handler>
query: createCustomerAccessTokenMutation,
}
export const fetcher: HookFetcher<null, CustomerAccessTokenCreateInput> = ( const getErrorMessage = ({ code, message }: CustomerUserError) => {
options, console.log(code)
input,
fetch switch (code) {
) => { case 'UNIDENTIFIED_CUSTOMER':
if (!(input.email && input.password)) { message = 'Cannot find an account that matches the provided credentials'
throw new CommerceError({ break
message:
'A first name, last name, email and password are required to login',
})
} }
return message
return fetch({
...defaultOpts,
...options,
variables: { input },
}).then(handleLogin)
} }
export function extendHook(customFetcher: typeof fetcher) { export const handler: MutationHook<null, {}, CustomerAccessTokenCreateInput> = {
const useLogin = () => { fetchOptions: {
query: createCustomerAccessTokenMutation,
},
async fetcher({ input: { email, password }, options, fetch }) {
if (!(email && password)) {
throw new CommerceError({
message:
'A first name, last name, email and password are required to login',
})
}
const { customerAccessTokenCreate } = await fetch<
Mutation,
MutationCheckoutCreateArgs
>({
...options,
variables: {
input: { email, password },
},
})
const errors = customerAccessTokenCreate?.customerUserErrors
if (errors && errors.length) {
throw new ValidationError({
message: getErrorMessage(errors[0]),
})
}
const customerAccessToken = customerAccessTokenCreate?.customerAccessToken
const accessToken = customerAccessToken?.accessToken
if (accessToken) {
setCustomerToken(accessToken)
}
return null
},
useHook: ({ fetch }) => () => {
const { revalidate } = useCustomer() const { revalidate } = useCustomer()
const fn = useCommerceLogin<null, CustomerAccessTokenCreateInput>(
defaultOpts,
customFetcher
)
return useCallback( return useCallback(
async function login(input: CustomerAccessTokenCreateInput) { async function login(input) {
const data = await fn(input) const data = await fetch({ input })
await revalidate() await revalidate()
return data return data
}, },
[fn] [fetch, revalidate]
) )
} },
useLogin.extend = extendHook
return useLogin
} }
export default extendHook(fetcher)

View File

@ -1,6 +1,6 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import useCommerceLogout from '@commerce/use-logout' import useLogout, { UseLogout } from '@commerce/use-logout'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import customerAccessTokenDeleteMutation from '@framework/utils/mutations/customer-access-token-delete' import customerAccessTokenDeleteMutation from '@framework/utils/mutations/customer-access-token-delete'
import { import {
@ -8,38 +8,32 @@ import {
setCustomerToken, setCustomerToken,
} from '@framework/utils/customer-token' } from '@framework/utils/customer-token'
const defaultOpts = { export default useLogout as UseLogout<typeof handler>
query: customerAccessTokenDeleteMutation,
}
export const fetcher: HookFetcher<null> = (options, _, fetch) => { export const handler: MutationHook<null> = {
return fetch({ fetchOptions: {
...defaultOpts, query: customerAccessTokenDeleteMutation,
...options, },
variables: { async fetcher({ options, fetch }) {
customerAccessToken: getCustomerToken(), await fetch({
}, ...options,
}).then((d) => setCustomerToken(null)) variables: {
} customerAccessToken: getCustomerToken(),
},
export function extendHook(customFetcher: typeof fetcher) { })
const useLogout = () => { setCustomerToken(null)
return null
},
useHook: ({ fetch }) => () => {
const { mutate } = useCustomer() const { mutate } = useCustomer()
const fn = useCommerceLogout<null>(defaultOpts, customFetcher)
return useCallback( return useCallback(
async function login() { async function logout() {
const data = await fn(null) const data = await fetch()
await mutate(null, false) await mutate(null, false)
return data return data
}, },
[fn] [fetch, mutate]
) )
} },
useLogout.extend = extendHook
return useLogout
} }
export default extendHook(fetcher)

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useCommerceSignup from '@commerce/use-signup' import useSignup, { UseSignup } from '@commerce/use-signup'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { CustomerCreateInput } from '@framework/schema' import { CustomerCreateInput } from '@framework/schema'
@ -11,63 +11,64 @@ import {
} from '@framework/utils/mutations' } from '@framework/utils/mutations'
import handleLogin from '@framework/utils/handle-login' import handleLogin from '@framework/utils/handle-login'
const defaultOpts = { export default useSignup as UseSignup<typeof handler>
query: customerCreateMutation,
}
export const fetcher: HookFetcher<null, CustomerCreateInput> = ( export const handler: MutationHook<
options, null,
input, {},
fetch CustomerCreateInput,
) => { CustomerCreateInput
if (!(input.firstName && input.lastName && input.email && input.password)) { > = {
throw new CommerceError({ fetchOptions: {
message: query: customerCreateMutation,
'A first name, last name, email and password are required to signup', },
async fetcher({
input: { firstName, lastName, email, password },
options,
fetch,
}) {
if (!(firstName && lastName && email && password)) {
throw new CommerceError({
message:
'A first name, last name, email and password are required to signup',
})
}
const data = await fetch({
...options,
variables: {
input: {
firstName,
lastName,
email,
password,
},
},
}) })
}
return fetch({
...defaultOpts,
...options,
variables: { input },
}).then(async (data) => {
try { try {
const loginData = await fetch({ const loginData = await fetch({
query: customerAccessTokenCreateMutation, query: customerAccessTokenCreateMutation,
variables: { variables: {
input: { input: {
email: input.email, email,
password: input.password, password,
}, },
}, },
}) })
handleLogin(loginData) handleLogin(loginData)
} catch (error) {} } catch (error) {}
return data return data
}) },
} useHook: ({ fetch }) => () => {
export function extendHook(customFetcher: typeof fetcher) {
const useSignup = () => {
const { revalidate } = useCustomer() const { revalidate } = useCustomer()
const fn = useCommerceSignup<null, CustomerCreateInput>(
defaultOpts,
customFetcher
)
return useCallback( return useCallback(
async function signup(input: CustomerCreateInput) { async function signup(input) {
const data = await fn(input) const data = await fetch({ input })
await revalidate() await revalidate()
return data return data
}, },
[fn] [fetch, revalidate]
) )
} },
useSignup.extend = extendHook
return useSignup
} }
export default extendHook(fetcher)

View File

@ -1,22 +1,20 @@
import type { MutationHandler } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import useCart from './use-cart' import useCart from './use-cart'
import { ShopifyProvider } from '..' import { Cart, CartItemBody } from '../types'
import { Cart, AddCartItemBody, CartItemBody } from '../types'
import { checkoutLineItemAddMutation, getCheckoutId } from '../utils' import { checkoutLineItemAddMutation, getCheckoutId } from '../utils'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import { Mutation } from '../schema' import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema'
import { useCallback } from 'react'
export default useAddItem as UseAddItem<ShopifyProvider, CartItemBody> export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHandler<Cart, {}, AddCartItemBody> = { export const handler: MutationHook<Cart, {}, CartItemBody> = {
fetchOptions: { fetchOptions: {
query: checkoutLineItemAddMutation, query: checkoutLineItemAddMutation,
}, },
async fetcher({ input, options, fetch }) { async fetcher({ input: item, options, fetch }) {
const item = input?.item ?? input
if ( if (
item.quantity && item.quantity &&
(!Number.isInteger(item.quantity) || item.quantity! < 1) (!Number.isInteger(item.quantity) || item.quantity! < 1)
@ -26,27 +24,34 @@ export const handler: MutationHandler<Cart, {}, AddCartItemBody> = {
}) })
} }
const { checkoutLineItemsAdd }: Mutation = await fetch<any, any>({ const { checkoutLineItemsAdd } = await fetch<
Mutation,
MutationCheckoutLineItemsAddArgs
>({
...options, ...options,
variables: { variables: {
checkoutId: getCheckoutId(),
lineItems: [ lineItems: [
{ {
variantId: item.variantId, variantId: item.variantId,
quantity: item.quantity ?? 1, quantity: item.quantity ?? 1,
}, },
], ],
checkoutId: getCheckoutId(),
}, },
}) })
return checkoutToCart(checkoutLineItemsAdd) return checkoutToCart(checkoutLineItemsAdd)
}, },
useHook() { useHook: ({ fetch }) => () => {
const { mutate } = useCart() const { mutate } = useCart()
return async function addItem({ input, fetch }) {
const data = await fetch({ input }) return useCallback(
await mutate(data, false) async function addItem(input) {
return data const data = await fetch({ input })
} await mutate(data, false)
return data
},
[fetch, mutate]
)
}, },
} }

View File

@ -7,14 +7,13 @@ import useCommerceCart, {
} from '@commerce/cart/use-cart' } from '@commerce/cart/use-cart'
import { Cart } from '@commerce/types' import { Cart } from '@commerce/types'
import { HookHandler } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import { checkoutCreate, checkoutToCart } from './utils'
import fetcher from './utils/fetcher' import getCheckoutQuery from '../utils/queries/get-checkout-query'
import getCheckoutQuery from '@framework/utils/queries/get-checkout-query'
export default useCommerceCart as UseCart<ShopifyProvider> export default useCommerceCart as UseCart<ShopifyProvider>
export const handler: HookHandler< export const handler: SWRHook<
Cart | null, Cart | null,
{}, {},
FetchCartInput, FetchCartInput,
@ -23,10 +22,27 @@ export const handler: HookHandler<
fetchOptions: { fetchOptions: {
query: getCheckoutQuery, query: getCheckoutQuery,
}, },
fetcher, async fetcher({ input: { cartId: checkoutId }, options, fetch }) {
useHook({ input, useData }) { let checkout
if (checkoutId) {
const data = await fetch({
...options,
variables: {
checkoutId,
},
})
checkout = data.node
}
if (checkout?.completedAt || !checkoutId) {
checkout = await checkoutCreate(fetch)
}
return checkoutToCart({ checkout })
},
useHook: ({ useData }) => (input) => {
const response = useData({ const response = useData({
swrOptions: { revalidateOnFocus: false, ...input.swrOptions }, swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
}) })
return useMemo( return useMemo(
() => () =>

View File

@ -1,48 +1,61 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { HookFetcher } from '@commerce/utils/types'
import type {
MutationHookContext,
HookFetcherContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useCartRemoveItem, {
RemoveItemInput as UseRemoveItemInput, import useRemoveItem, {
RemoveItemInput as RemoveItemInputBase,
UseRemoveItem,
} from '@commerce/cart/use-remove-item' } from '@commerce/cart/use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
import { checkoutLineItemRemoveMutation, getCheckoutId } from '@framework/utils'
import type { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
import { checkoutLineItemRemoveMutation } from '@framework/utils/mutations'
import getCheckoutId from '@framework/utils/get-checkout-id'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import { Cart, LineItem } from '@framework/types'
const defaultOpts = { import {
query: checkoutLineItemRemoveMutation, Mutation,
} MutationCheckoutLineItemsRemoveArgs,
} from '@framework/schema'
import { RemoveCartItemBody } from '@commerce/types'
export type RemoveItemFn<T = any> = T extends LineItem export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemInput<T>) => Promise<Cart | null> ? (input?: RemoveItemInput<T>) => Promise<Cart | null>
: (input: RemoveItemInput<T>) => Promise<Cart | null> : (input: RemoveItemInput<T>) => Promise<Cart | null>
export type RemoveItemInput<T = any> = T extends LineItem export type RemoveItemInput<T = any> = T extends LineItem
? Partial<UseRemoveItemInput> ? Partial<RemoveItemInputBase>
: UseRemoveItemInput : RemoveItemInputBase
export const fetcher: HookFetcher<Cart | null, any> = async ( export default useRemoveItem as UseRemoveItem<typeof handler>
options,
{ itemId, checkoutId },
fetch
) => {
const data = await fetch<any>({
...defaultOpts,
...options,
variables: { lineItemIds: [itemId], checkoutId },
})
return checkoutToCart(data.checkoutLineItemsRemove)
}
export function extendHook(customFetcher: typeof fetcher) { export const handler = {
const useRemoveItem = <T extends LineItem | undefined = undefined>( fetchOptions: {
item?: T query: checkoutLineItemRemoveMutation,
},
async fetcher({
input: { itemId },
options,
fetch,
}: HookFetcherContext<RemoveCartItemBody>) {
const data = await fetch<Mutation, MutationCheckoutLineItemsRemoveArgs>({
...options,
variables: { checkoutId: getCheckoutId(), lineItemIds: [itemId] },
})
return checkoutToCart(data.checkoutLineItemsRemove)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: { item?: T } = {}
) => { ) => {
const { mutate, data: cart } = useCart() const { item } = ctx
const fn = useCartRemoveItem<Cart | null, any>(defaultOpts, customFetcher) const { mutate } = useCart()
const removeItem: RemoveItemFn<LineItem> = async (input) => { const removeItem: RemoveItemFn<LineItem> = async (input) => {
const itemId = input?.id ?? item?.id const itemId = input?.id ?? item?.id
@ -52,21 +65,11 @@ export function extendHook(customFetcher: typeof fetcher) {
}) })
} }
const data = await fn({ const data = await fetch({ input: { itemId } })
checkoutId: getCheckoutId(cart?.id),
itemId,
})
await mutate(data, false) await mutate(data, false)
return data return data
} }
return useCallback(removeItem as RemoveItemFn<T>, [fn, mutate]) return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
} },
useRemoveItem.extend = extendHook
return useRemoveItem
} }
export default extendHook(fetcher)

View File

@ -1,85 +1,110 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import type { HookFetcher } from '@commerce/utils/types' import type {
HookFetcherContext,
MutationHookContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useCartUpdateItem, { import useUpdateItem, {
UpdateItemInput as UseUpdateItemInput, UpdateItemInput as UpdateItemInputBase,
UseUpdateItem,
} from '@commerce/cart/use-update-item' } from '@commerce/cart/use-update-item'
import { fetcher as removeFetcher } from './use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
import { handler as removeItemHandler } from './use-remove-item'
import type { Cart, LineItem, UpdateCartItemBody } from '@commerce/types' import type { Cart, LineItem, UpdateCartItemBody } from '../types'
import { checkoutToCart } from './utils' import { checkoutToCart } from './utils'
import checkoutLineItemUpdateMutation from '@framework/utils/mutations/checkout-line-item-update' import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils'
import getCheckoutId from '@framework/utils/get-checkout-id' import {
Mutation,
const defaultOpts = { MutationCheckoutLineItemsUpdateArgs,
query: checkoutLineItemUpdateMutation, } from '@framework/schema'
}
export type UpdateItemInput<T = any> = T extends LineItem export type UpdateItemInput<T = any> = T extends LineItem
? Partial<UseUpdateItemInput<LineItem>> ? Partial<UpdateItemInputBase<LineItem>>
: UseUpdateItemInput<LineItem> : UpdateItemInputBase<LineItem>
export const fetcher: HookFetcher<Cart | null, any> = async ( export default useUpdateItem as UseUpdateItem<typeof handler>
options,
{ item, checkoutId }, export const handler = {
fetch fetchOptions: {
) => { query: checkoutLineItemUpdateMutation,
if (Number.isInteger(item.quantity)) { },
// Also allow the update hook to remove an item if the quantity is lower than 1 async fetcher({
if (item.quantity! < 1) { input: { itemId, item },
return removeFetcher(null, { itemId: item.id, checkoutId }, fetch) options,
fetch,
}: HookFetcherContext<UpdateCartItemBody>) {
if (Number.isInteger(item.quantity)) {
// Also allow the update hook to remove an item if the quantity is lower than 1
if (item.quantity! < 1) {
return removeItemHandler.fetcher({
options: removeItemHandler.fetchOptions,
input: { itemId },
fetch,
})
}
} else if (item.quantity) {
throw new ValidationError({
message: 'The item quantity has to be a valid integer',
})
} }
} else if (item.quantity) { const { checkoutLineItemsUpdate } = await fetch<
throw new ValidationError({ Mutation,
message: 'The item quantity has to be a valid integer', MutationCheckoutLineItemsUpdateArgs
>({
...options,
variables: {
checkoutId: getCheckoutId(),
lineItems: [
{
id: itemId,
quantity: item.quantity,
},
],
},
}) })
}
const data = await fetch<any, any>({
...defaultOpts,
...options,
variables: { checkoutId, lineItems: [item] },
})
return checkoutToCart(data.checkoutLineItemsUpdate) return checkoutToCart(checkoutLineItemsUpdate)
} },
useHook: ({
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { fetch,
const useUpdateItem = <T extends LineItem | undefined = undefined>( }: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
item?: T T extends LineItem | undefined = undefined
>(
ctx: {
item?: T
wait?: number
} = {}
) => { ) => {
const { mutate, data: cart } = useCart() const { item } = ctx
const fn = useCartUpdateItem<Cart | null, any>(defaultOpts, customFetcher) const { mutate } = useCart() as any
return useCallback( return useCallback(
debounce(async (input: UpdateItemInput<T>) => { debounce(async (input: UpdateItemInput<T>) => {
const itemId = input.id ?? item?.id const itemId = input.id ?? item?.id
const productId = input.productId ?? item?.productId
const variantId = input.productId ?? item?.variantId const variantId = input.productId ?? item?.variantId
if (!itemId || !productId || !variantId) {
if (!itemId || !variantId) {
throw new ValidationError({ throw new ValidationError({
message: 'Invalid input used for this operation', message: 'Invalid input used for this operation',
}) })
} }
const data = await fn({ const data = await fetch({
item: { id: itemId, variantId, quantity: input.quantity }, input: {
checkoutId: getCheckoutId(cart?.id), item: {
productId,
variantId,
quantity: input.quantity,
},
itemId,
},
}) })
await mutate(data, false) await mutate(data, false)
return data return data
}, cfg?.wait ?? 500), }, ctx.wait ?? 500),
[fn, mutate] [fetch, mutate]
) )
} },
useUpdateItem.extend = extendHook
return useUpdateItem
} }
export default extendHook(fetcher)

View File

@ -11,7 +11,7 @@ export const checkoutCreate = async (fetch: any) => {
query: checkoutCreateMutation, query: checkoutCreateMutation,
}) })
const checkout = data?.checkoutCreate?.checkout const checkout = data.checkoutCreate?.checkout
const checkoutId = checkout?.id const checkoutId = checkout?.id
if (checkoutId) { if (checkoutId) {

View File

@ -3,6 +3,7 @@ import { CommerceError, ValidationError } from '@commerce/utils/errors'
import { import {
CheckoutLineItemsAddPayload, CheckoutLineItemsAddPayload,
CheckoutLineItemsRemovePayload,
CheckoutLineItemsUpdatePayload, CheckoutLineItemsUpdatePayload,
Maybe, Maybe,
} from '@framework/schema' } from '@framework/schema'
@ -11,9 +12,10 @@ import { normalizeCart } from '@framework/utils'
export type CheckoutPayload = export type CheckoutPayload =
| CheckoutLineItemsAddPayload | CheckoutLineItemsAddPayload
| CheckoutLineItemsUpdatePayload | CheckoutLineItemsUpdatePayload
| CheckoutLineItemsRemovePayload
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => { const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload || !checkoutPayload?.checkout) { if (!checkoutPayload) {
throw new CommerceError({ throw new CommerceError({
message: 'Invalid response from Shopify', message: 'Invalid response from Shopify',
}) })
@ -28,6 +30,12 @@ const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
}) })
} }
if (!checkout) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
return normalizeCart(checkout) return normalizeCart(checkout)
} }

View File

@ -17,7 +17,7 @@ const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
checkoutId, checkoutId,
}, },
}) })
checkout = data?.node checkout = data.node
} }
if (checkout?.completedAt || !checkoutId) { if (checkout?.completedAt || !checkoutId) {

View File

@ -1,3 +1,2 @@
export { default as checkoutToCart } from './checkout-to-cart' export { default as checkoutToCart } from './checkout-to-cart'
export { default as checkoutCreate } from './checkout-create' export { default as checkoutCreate } from './checkout-create'
export { default as fetcher } from './fetcher'

View File

@ -19,7 +19,7 @@ const getAllPages = async (options?: {
config = getConfig(config) config = getConfig(config)
const { data } = await config.fetch(getAllPagesQuery, { variables }) const { data } = await config.fetch(getAllPagesQuery, { variables })
const edges = data?.pages?.edges const edges = data.pages?.edges
const pages = edges?.map(({ node }: PageEdge) => ({ const pages = edges?.map(({ node }: PageEdge) => ({
...node, ...node,

View File

@ -24,7 +24,7 @@ const getPage = async (options: {
variables, variables,
}) })
const page: Page = data?.pageByHandle const page: Page = data.pageByHandle
return { return {
page: page page: page

View File

@ -0,0 +1,5 @@
{
"features": {
"wishlist": false
}
}

View File

@ -18,7 +18,7 @@ async function getCustomerId({
}, },
}) })
return data?.customer?.id return data.customer?.id
} }
export default getCustomerId export default getCustomerId

View File

@ -1,24 +1,26 @@
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer' import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
import { Customer } from '@commerce/types' import { Customer } from '@commerce/types'
import { HookHandler } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import { getCustomerQuery } from '@framework/utils' import { getCustomerQuery, getCustomerToken } from '../utils'
import type { ShopifyProvider } from '..' import type { ShopifyProvider } from '..'
export default useCustomer as UseCustomer<ShopifyProvider> export default useCustomer as UseCustomer<ShopifyProvider>
export const handler: SWRHook<Customer | null> = {
export const handler: HookHandler<Customer | null> = {
fetchOptions: { fetchOptions: {
query: getCustomerQuery, query: getCustomerQuery,
}, },
async fetcher({ options, fetch }) { async fetcher({ options, fetch }) {
const data = await fetch<any | null>(options) const data = await fetch<any | null>({
return data?.customer ?? null ...options,
variables: { customerAccessToken: getCustomerToken() },
})
return data.customer ?? null
}, },
useHook({ input, useData }) { useHook: ({ useData }) => (input) => {
return useData({ return useData({
swrOptions: { swrOptions: {
revalidateOnFocus: false, revalidateOnFocus: false,
...input.swrOptions, ...input?.swrOptions,
}, },
}) })
}, },

View File

@ -11,7 +11,7 @@ const getAllCollections = async (options?: {
config = getConfig(config) config = getConfig(config)
const { data } = await config.fetch(getAllCollectionsQuery, { variables }) const { data } = await config.fetch(getAllCollectionsQuery, { variables })
const edges = data?.collections?.edges ?? [] const edges = data.collections?.edges ?? []
const categories = edges.map( const categories = edges.map(
({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({ ({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({

View File

@ -28,7 +28,7 @@ const getAllProducts = async (options: {
{ variables } { variables }
) )
const products = data?.products?.edges?.map(({ node: p }: ProductEdge) => const products = data.products?.edges?.map(({ node: p }: ProductEdge) =>
normalizeProduct(p) normalizeProduct(p)
) )

View File

@ -27,7 +27,7 @@ const getProduct = async (options: {
variables, variables,
}) })
const product = data?.productByHandle const { productByHandle: product } = data
return { return {
product: product ? normalizeProduct(product) : null, product: product ? normalizeProduct(product) : null,

View File

@ -1,6 +1,6 @@
import useSearch, { UseSearch } from '@commerce/products/use-search' import { SWRHook } from '@commerce/utils/types'
import { SearchProductsData } from '@commerce/types' import useSearch, { UseSearch } from '@commerce/product/use-search'
import { HookHandler } from '@commerce/utils/types'
import { ProductEdge } from '@framework/schema' import { ProductEdge } from '@framework/schema'
import { import {
getAllProductsQuery, getAllProductsQuery,
@ -9,6 +9,8 @@ import {
} from '@framework/utils' } from '@framework/utils'
import type { ShopifyProvider } from '..' import type { ShopifyProvider } from '..'
import { Product } from '@commerce/types'
export default useSearch as UseSearch<ShopifyProvider> export default useSearch as UseSearch<ShopifyProvider>
export type SearchProductsInput = { export type SearchProductsInput = {
@ -18,7 +20,11 @@ export type SearchProductsInput = {
sort?: string sort?: string
} }
export const handler: HookHandler< export type SearchProductsData = {
products: Product[]
found: boolean
}
export const handler: SWRHook<
SearchProductsData, SearchProductsData,
SearchProductsInput, SearchProductsInput,
SearchProductsInput SearchProductsInput
@ -38,7 +44,7 @@ export const handler: HookHandler<
found: !!edges?.length, found: !!edges?.length,
} }
}, },
useHook({ input, useData }) { useHook: ({ useData }) => (input = {}) => {
return useData({ return useData({
input: [ input: [
['search', input.search], ['search', input.search],

View File

@ -2,8 +2,16 @@ import { SHOPIFY_CHECKOUT_ID_COOKIE, STORE_DOMAIN } from './const'
import { handler as useCart } from './cart/use-cart' import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item' import { handler as useAddItem } from './cart/use-add-item'
import { handler as useSearch } from './product/use-search' import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item'
import { handler as useCustomer } from './customer/use-customer' import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search'
import { handler as useLogin } from './auth/use-login'
import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'
import fetcher from './fetcher' import fetcher from './fetcher'
export const shopifyProvider = { export const shopifyProvider = {
@ -11,9 +19,13 @@ export const shopifyProvider = {
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
storeDomain: STORE_DOMAIN, storeDomain: STORE_DOMAIN,
fetcher, fetcher,
cart: { useCart, useAddItem }, cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer }, customer: { useCustomer },
products: { useSearch }, products: { useSearch },
auth: { useLogin, useLogout, useSignup },
features: {
wishlist: false,
},
} }
export type ShopifyProvider = typeof shopifyProvider export type ShopifyProvider = typeof shopifyProvider

View File

@ -16,7 +16,7 @@ const getCategories = async (config: ShopifyConfig): Promise<Category[]> => {
}) })
return ( return (
data?.collections?.edges?.map( data.collections?.edges?.map(
({ node: { title: name, handle } }: CollectionEdge) => ({ ({ node: { title: name, handle } }: CollectionEdge) => ({
entityId: handle, entityId: handle,
name, name,

View File

@ -17,7 +17,7 @@ const getErrorMessage = ({
} }
const handleLogin = (data: any) => { const handleLogin = (data: any) => {
const response = data?.customerAccessTokenCreate const response = data.customerAccessTokenCreate
const errors = response?.customerUserErrors const errors = response?.customerUserErrors
if (errors && errors.length) { if (errors && errors.length) {

View File

@ -4,7 +4,6 @@ export { default as getSortVariables } from './get-sort-variables'
export { default as getVendors } from './get-vendors' export { default as getVendors } from './get-vendors'
export { default as getCategories } from './get-categories' export { default as getCategories } from './get-categories'
export { default as getCheckoutId } from './get-checkout-id' export { default as getCheckoutId } from './get-checkout-id'
export * from './queries' export * from './queries'
export * from './mutations' export * from './mutations'
export * from './normalize' export * from './normalize'

View File

@ -1,10 +1,7 @@
export { default as createCustomerMutation } from './customer-create'
export { default as checkoutCreateMutation } from './checkout-create'
export { default as checkoutLineItemAddMutation } from './checkout-line-item-add'
export { default as checkoutLineItemUpdateMutation } from './checkout-create'
export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove'
export { default as customerCreateMutation } from './customer-create' export { default as customerCreateMutation } from './customer-create'
export { default as checkoutCreateMutation } from './checkout-create'
export { default as checkoutLineItemAddMutation } from './checkout-line-item-add'
export { default as checkoutLineItemUpdateMutation } from './checkout-line-item-update'
export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove'
export { default as customerAccessTokenCreateMutation } from './customer-access-token-create' export { default as customerAccessTokenCreateMutation } from './customer-access-token-create'
export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete' export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete'

View File

@ -1,4 +1,4 @@
export const checkoutDetailsFragment = /* GraphQL */ ` export const checkoutDetailsFragment = `
id id
webUrl webUrl
subtotalPrice subtotalPrice

View File

@ -7,4 +7,4 @@ export { default as getCollectionProductsQuery } from './get-collection-products
export { default as getCheckoutQuery } from './get-checkout-query' export { default as getCheckoutQuery } from './get-checkout-query'
export { default as getAllPagesQuery } from './get-all-pages-query' export { default as getAllPagesQuery } from './get-all-pages-query'
export { default as getPageQuery } from './get-page-query' export { default as getPageQuery } from './get-page-query'
export { default as getCustomerQuery } from './get-checkout-query' export { default as getCustomerQuery } from './get-customer-query'

View File

@ -0,0 +1,26 @@
import { useEffect } from 'react'
import { useUI } from '@components/ui/context'
import { getRandomPairOfColors } from '@lib/colors'
export const useUserAvatar = (name = 'userAvatar') => {
const { userAvatar, setUserAvatar } = useUI()
useEffect(() => {
if (!userAvatar && localStorage.getItem(name)) {
// Get bg from localStorage and push it to the context.
setUserAvatar(localStorage.getItem(name))
}
if (!localStorage.getItem(name)) {
// bg not set locally, generating one, setting localStorage and context to persist.
const bg = getRandomPairOfColors()
const value = `linear-gradient(140deg, ${bg[0]}, ${bg[1]} 100%)`
localStorage.setItem(name, value)
setUserAvatar(value)
}
}, [])
return {
userAvatar,
setUserAvatar,
}
}

View File

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

View File

@ -34,7 +34,4 @@ module.exports = {
}, },
] ]
}, },
typescript: {
ignoreBuildErrors: true,
},
} }

View File

@ -1,3 +1,3 @@
import customersApi from '@framework/api/customer' import customersApi from '@framework/api/customers'
export default customersApi() export default customersApi()

View File

@ -36,7 +36,7 @@ export async function getStaticProps({
wishlist: isWishlistEnabled, wishlist: isWishlistEnabled,
}, },
}, },
revalidate: 1440, revalidate: 14400,
} }
} }

View File

@ -106,7 +106,7 @@ export default function Search({
<button <button
type="button" type="button"
onClick={(e) => handleClick(e, 'categories')} onClick={(e) => handleClick(e, 'categories')}
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-normal active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
id="options-menu" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true" aria-expanded="true"
@ -205,7 +205,7 @@ export default function Search({
<button <button
type="button" type="button"
onClick={(e) => handleClick(e, 'brands')} onClick={(e) => handleClick(e, 'brands')}
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-900 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-900 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-normal active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
id="options-menu" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true" aria-expanded="true"
@ -383,7 +383,7 @@ export default function Search({
<button <button
type="button" type="button"
onClick={(e) => handleClick(e, 'sort')} onClick={(e) => handleClick(e, 'sort')}
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-normal active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
id="options-menu" id="options-menu"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="true" aria-expanded="true"

View File

@ -51,7 +51,7 @@ module.exports = {
secondary: 'var(--text-secondary)', secondary: 'var(--text-secondary)',
}, },
boxShadow: { boxShadow: {
'outline-2': '0 0 0 2px var(--accents-2)', 'outline-normal': '0 0 0 2px var(--accents-2)',
magical: magical:
'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px', 'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px',
}, },
@ -63,5 +63,4 @@ module.exports = {
}, },
}, },
}, },
plugins: [require('@tailwindcss/ui')],
} }

View File

@ -22,8 +22,8 @@
"@utils/*": ["utils/*"], "@utils/*": ["utils/*"],
"@commerce/*": ["framework/commerce/*"], "@commerce/*": ["framework/commerce/*"],
"@commerce": ["framework/commerce"], "@commerce": ["framework/commerce"],
"@framework/*": ["framework/shopify/*"], "@framework/*": ["framework/bigcommerce/*"],
"@framework": ["framework/shopify"] "@framework": ["framework/bigcommerce"]
} }
}, },
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],

View File

@ -7257,4 +7257,4 @@ yn@3.1.1:
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==