Added useSearch hook

This commit is contained in:
Luis Alvarez 2020-10-13 03:49:24 -05:00
parent 1629f718b0
commit beed2d23e0
13 changed files with 3195 additions and 31 deletions

View File

@ -1,4 +1,3 @@
import cn from 'classnames'
import { FC } from 'react'
import s from './Navbar.module.css'
import { Logo, Container } from '@components/ui'

View File

@ -1,14 +1,19 @@
import { FC, useEffect } from 'react'
import cn from 'classnames'
import { FC } from 'react'
import s from './Searchbar.module.css'
import { useRouter } from 'next/router'
interface Props {
className?: string
children?: any
}
const Searchbar: FC<Props> = ({ className }) => {
const router = useRouter()
useEffect(() => {
router.prefetch('/search')
}, [])
return (
<div
className={cn(

View File

@ -0,0 +1,19 @@
import type { ProductsHandlers } from '../products'
// Return current cart info
const getProducts: ProductsHandlers['getProducts'] = async ({
res,
body: { search },
config,
}) => {
// Use a dummy base as we only care about the relative path
const url = new URL('/v3/catalog/products', 'http://a')
if (search) url.searchParams.set('keyword', search)
const { data } = await config.storeApiFetch(url.pathname + url.search)
res.status(200).json({ data })
}
export default getProducts

View File

@ -0,0 +1,47 @@
import type { definitions } from '../definitions/catalog'
import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, {
BigcommerceApiHandler,
BigcommerceHandler,
} from '../utils/create-api-handler'
import { BigcommerceApiError } from '../utils/errors'
import getProducts from './handlers/get-products'
export type Product = definitions['product_Full']
export type ProductsHandlers = {
getProducts: BigcommerceHandler<Product[], { search?: 'string' }>
}
const METHODS = ['GET']
// TODO: a complete implementation should have schema validation for `req.body`
const cartApi: BigcommerceApiHandler<Product[], ProductsHandlers> = async (
req,
res,
config,
handlers
) => {
if (!isAllowedMethod(req, res, METHODS)) return
try {
// Return current cart info
if (req.method === 'GET') {
const body = req.query
return await handlers['getProducts']({ req, res, config, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof BigcommerceApiError
? 'An unexpected error ocurred with the Bigcommerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
}
export const handlers = { getProducts }
export default createApiHandler(cartApi, handlers)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
import { ConfigInterface } from 'swr'
import { HookFetcher, HookDeps } from '@lib/commerce/utils/types'
import useCommerceCart from '@lib/commerce/cart/use-cart'
import type { Cart } from '../api/cart'
@ -8,24 +9,22 @@ const defaultOpts = {
export type { Cart }
export const fetcher: HookFetcher<Cart | null, HookDeps[]> = (
options,
_,
fetch
) => {
export const fetcher: HookFetcher<Cart | null, {}> = (options, _, fetch) => {
return fetch({
url: options?.url,
query: options?.query,
})
}
export function extendHook(customFetcher: typeof fetcher) {
export function extendHook(
customFetcher: typeof fetcher,
swrOptions?: ConfigInterface
) {
const useCart = () => {
const cart = useCommerceCart<Cart | null>(
[defaultOpts.url, undefined],
customFetcher,
{ revalidateOnFocus: false }
)
const cart = useCommerceCart<Cart | null>(defaultOpts, [], customFetcher, {
revalidateOnFocus: false,
...swrOptions,
})
// Uses a getter to only calculate the prop when required
// cart.data is also a getter and it's better to not trigger it early

View File

@ -0,0 +1,52 @@
import useSWR, { ConfigInterface } from 'swr'
import { HookDeps, HookFetcher } from '@lib/commerce/utils/types'
import useCommerceSearch from '@lib/commerce/products/use-search'
import type { Product } from '../api/catalog/products'
import { useCommerce } from '..'
const defaultOpts = {
url: '/api/bigcommerce/catalog/products',
method: 'GET',
}
export type SearchProductsInput = {
search?: string
}
export const fetcher: HookFetcher<Product[], SearchProductsInput> = (
options,
{ search },
fetch
) => {
// Use a dummy base as we only care about the relative path
const url = new URL(options?.url ?? defaultOpts.url, 'http://a')
if (search) url.searchParams.set('search', search)
return fetch({
url: url.pathname + url.search,
method: options?.method ?? defaultOpts.method,
})
}
export function extendHook(
customFetcher: typeof fetcher,
swrOptions?: ConfigInterface
) {
const useSearch = (input: SearchProductsInput = {}) => {
const response = useCommerceSearch<Product[]>(
defaultOpts,
[['search', input.search]],
customFetcher,
{ revalidateOnFocus: false, ...swrOptions }
)
return response
}
useSearch.extend = extendHook
return useSearch
}
export default extendHook(fetcher)

View File

@ -1,6 +1,6 @@
import useSWR, { responseInterface, ConfigInterface } from 'swr'
import Cookies from 'js-cookie'
import { HookDeps, HookFetcher } from '../utils/types'
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
import { useCommerce } from '..'
export type CartResponse<C> = responseInterface<C, Error> & {
@ -8,16 +8,28 @@ export type CartResponse<C> = responseInterface<C, Error> & {
}
export default function useCart<T>(
deps: [string | undefined, string | undefined, ...HookDeps[]],
fetcherFn: HookFetcher<T, HookDeps[]>,
options: HookFetcherOptions,
input: HookInput,
fetcherFn: HookFetcher<T, any>,
swrOptions?: ConfigInterface<T | null>
) {
const { fetcherRef, cartCookie } = useCommerce()
const fetcher = (url?: string, query?: string, ...args: HookDeps[]) =>
const fetcher = (url?: string, query?: string, ...args: any[]) =>
Cookies.get(cartCookie)
? fetcherFn({ url, query }, args, fetcherRef.current)
? fetcherFn(
{ url, query },
args.reduce((obj, val, i) => {
obj[input[i][1]!] = val
return obj
}, {}),
fetcherRef.current
)
: null
const response = useSWR(deps, fetcher, swrOptions)
const response = useSWR(
[options.url, options.query, ...input.map((e) => e[1])],
fetcher,
swrOptions
)
return Object.assign(response, { isEmpty: true }) as CartResponse<T>
}

View File

@ -0,0 +1,28 @@
import useSWR, { ConfigInterface } from 'swr'
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
import { useCommerce } from '..'
export default function useSearch<T>(
options: HookFetcherOptions,
input: HookInput,
fetcherFn: HookFetcher<T, any>,
swrOptions?: ConfigInterface<T | null>
) {
const { fetcherRef } = useCommerce()
const fetcher = (url?: string, query?: string, ...args: any[]) =>
fetcherFn(
{ url, query },
args.reduce((obj, val, i) => {
obj[input[i][1]!] = val
return obj
}, {}),
fetcherRef.current
)
const response = useSWR(
[options.url, options.query, ...input.map((e) => e[1])],
fetcher,
swrOptions
)
return response
}

View File

@ -21,4 +21,6 @@ export type HookFetcherOptions = {
method?: string
}
export type HookInput = [string, string | number | undefined][]
export type HookDeps = string | number | undefined[]

View File

@ -0,0 +1,3 @@
import catalogProductsApi from '@lib/bigcommerce/api/catalog/products'
export default catalogProductsApi()

View File

@ -4,8 +4,6 @@ import { Layout } from '@components/core'
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export async function getStaticProps({ preview }: GetStaticPropsContext) {
const { products } = await getAllProducts()
@ -21,12 +19,6 @@ export default function Home({
categories,
brands,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter()
useEffect(() => {
router.prefetch('/search')
}, [])
return (
<div className="mt-3">
<Grid items={products.slice(0, 3)} wrapper={ProductCard} />

View File

@ -1,9 +1,10 @@
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import getAllProducts from '@lib/bigcommerce/api/operations/get-all-products'
import { Layout } from '@components/core'
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
import useSearch from '@lib/bigcommerce/products/use-search'
import { Layout } from '@components/core'
import { Grid } from '@components/ui'
import { ProductCard } from '@components/product'
import { useRouter } from 'next/router'
export async function getStaticProps({ preview }: GetStaticPropsContext) {
@ -21,6 +22,10 @@ export default function Home({
brands,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter()
const search = useSearch({ search: router.query.search as string })
console.log('SEARCH', search)
return (
<div className="grid grid-cols-12 gap-8 mt-3 mb-20">
<div className="col-span-2">