forked from crowetic/commerce
Added useSearch hook
This commit is contained in:
parent
1629f718b0
commit
beed2d23e0
@ -1,4 +1,3 @@
|
|||||||
import cn from 'classnames'
|
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import s from './Navbar.module.css'
|
import s from './Navbar.module.css'
|
||||||
import { Logo, Container } from '@components/ui'
|
import { Logo, Container } from '@components/ui'
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
|
import { FC, useEffect } from 'react'
|
||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { FC } from 'react'
|
|
||||||
import s from './Searchbar.module.css'
|
import s from './Searchbar.module.css'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
children?: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Searchbar: FC<Props> = ({ className }) => {
|
const Searchbar: FC<Props> = ({ className }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
router.prefetch('/search')
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
19
lib/bigcommerce/api/catalog/handlers/get-products.ts
Normal file
19
lib/bigcommerce/api/catalog/handlers/get-products.ts
Normal 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
|
47
lib/bigcommerce/api/catalog/products.ts
Normal file
47
lib/bigcommerce/api/catalog/products.ts
Normal 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)
|
3001
lib/bigcommerce/api/definitions/catalog.ts
Normal file
3001
lib/bigcommerce/api/definitions/catalog.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
|||||||
|
import { ConfigInterface } from 'swr'
|
||||||
import { HookFetcher, HookDeps } from '@lib/commerce/utils/types'
|
import { HookFetcher, HookDeps } from '@lib/commerce/utils/types'
|
||||||
import useCommerceCart from '@lib/commerce/cart/use-cart'
|
import useCommerceCart from '@lib/commerce/cart/use-cart'
|
||||||
import type { Cart } from '../api/cart'
|
import type { Cart } from '../api/cart'
|
||||||
@ -8,24 +9,22 @@ const defaultOpts = {
|
|||||||
|
|
||||||
export type { Cart }
|
export type { Cart }
|
||||||
|
|
||||||
export const fetcher: HookFetcher<Cart | null, HookDeps[]> = (
|
export const fetcher: HookFetcher<Cart | null, {}> = (options, _, fetch) => {
|
||||||
options,
|
|
||||||
_,
|
|
||||||
fetch
|
|
||||||
) => {
|
|
||||||
return fetch({
|
return fetch({
|
||||||
url: options?.url,
|
url: options?.url,
|
||||||
query: options?.query,
|
query: options?.query,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extendHook(customFetcher: typeof fetcher) {
|
export function extendHook(
|
||||||
|
customFetcher: typeof fetcher,
|
||||||
|
swrOptions?: ConfigInterface
|
||||||
|
) {
|
||||||
const useCart = () => {
|
const useCart = () => {
|
||||||
const cart = useCommerceCart<Cart | null>(
|
const cart = useCommerceCart<Cart | null>(defaultOpts, [], customFetcher, {
|
||||||
[defaultOpts.url, undefined],
|
revalidateOnFocus: false,
|
||||||
customFetcher,
|
...swrOptions,
|
||||||
{ revalidateOnFocus: false }
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Uses a getter to only calculate the prop when required
|
// 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
|
// cart.data is also a getter and it's better to not trigger it early
|
||||||
|
52
lib/bigcommerce/products/use-search.tsx
Normal file
52
lib/bigcommerce/products/use-search.tsx
Normal 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)
|
@ -1,6 +1,6 @@
|
|||||||
import useSWR, { responseInterface, ConfigInterface } from 'swr'
|
import useSWR, { responseInterface, ConfigInterface } from 'swr'
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import { HookDeps, HookFetcher } from '../utils/types'
|
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||||
import { useCommerce } from '..'
|
import { useCommerce } from '..'
|
||||||
|
|
||||||
export type CartResponse<C> = responseInterface<C, Error> & {
|
export type CartResponse<C> = responseInterface<C, Error> & {
|
||||||
@ -8,16 +8,28 @@ export type CartResponse<C> = responseInterface<C, Error> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function useCart<T>(
|
export default function useCart<T>(
|
||||||
deps: [string | undefined, string | undefined, ...HookDeps[]],
|
options: HookFetcherOptions,
|
||||||
fetcherFn: HookFetcher<T, HookDeps[]>,
|
input: HookInput,
|
||||||
|
fetcherFn: HookFetcher<T, any>,
|
||||||
swrOptions?: ConfigInterface<T | null>
|
swrOptions?: ConfigInterface<T | null>
|
||||||
) {
|
) {
|
||||||
const { fetcherRef, cartCookie } = useCommerce()
|
const { fetcherRef, cartCookie } = useCommerce()
|
||||||
const fetcher = (url?: string, query?: string, ...args: HookDeps[]) =>
|
const fetcher = (url?: string, query?: string, ...args: any[]) =>
|
||||||
Cookies.get(cartCookie)
|
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
|
: 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>
|
return Object.assign(response, { isEmpty: true }) as CartResponse<T>
|
||||||
}
|
}
|
||||||
|
28
lib/commerce/products/use-search.tsx
Normal file
28
lib/commerce/products/use-search.tsx
Normal 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
|
||||||
|
}
|
@ -21,4 +21,6 @@ export type HookFetcherOptions = {
|
|||||||
method?: string
|
method?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HookInput = [string, string | number | undefined][]
|
||||||
|
|
||||||
export type HookDeps = string | number | undefined[]
|
export type HookDeps = string | number | undefined[]
|
||||||
|
3
pages/api/bigcommerce/catalog/products.ts
Normal file
3
pages/api/bigcommerce/catalog/products.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import catalogProductsApi from '@lib/bigcommerce/api/catalog/products'
|
||||||
|
|
||||||
|
export default catalogProductsApi()
|
@ -4,8 +4,6 @@ import { Layout } from '@components/core'
|
|||||||
import { Grid, Marquee, Hero } from '@components/ui'
|
import { Grid, Marquee, Hero } from '@components/ui'
|
||||||
import { ProductCard } from '@components/product'
|
import { ProductCard } from '@components/product'
|
||||||
import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info'
|
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) {
|
export async function getStaticProps({ preview }: GetStaticPropsContext) {
|
||||||
const { products } = await getAllProducts()
|
const { products } = await getAllProducts()
|
||||||
@ -21,12 +19,6 @@ export default function Home({
|
|||||||
categories,
|
categories,
|
||||||
brands,
|
brands,
|
||||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
router.prefetch('/search')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Grid items={products.slice(0, 3)} wrapper={ProductCard} />
|
<Grid items={products.slice(0, 3)} wrapper={ProductCard} />
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
|
||||||
import getAllProducts from '@lib/bigcommerce/api/operations/get-all-products'
|
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 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'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export async function getStaticProps({ preview }: GetStaticPropsContext) {
|
export async function getStaticProps({ preview }: GetStaticPropsContext) {
|
||||||
@ -21,6 +22,10 @@ export default function Home({
|
|||||||
brands,
|
brands,
|
||||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const search = useSearch({ search: router.query.search as string })
|
||||||
|
|
||||||
|
console.log('SEARCH', search)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-8 mt-3 mb-20">
|
<div className="grid grid-cols-12 gap-8 mt-3 mb-20">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user