4
0
forked from crowetic/commerce
This commit is contained in:
Luis Alvarez 2020-10-13 07:07:35 -05:00
parent e7e89e4c4c
commit ee6b65725f
9 changed files with 74 additions and 47 deletions

View File

@ -24,14 +24,21 @@ const Searchbar: FC<Props> = ({ className }) => {
<input <input
className={s.input} className={s.input}
placeholder="Search for products..." placeholder="Search for products..."
defaultValue={router.query.q}
onKeyUp={(e) => { onKeyUp={(e) => {
e.preventDefault() e.preventDefault()
if (e.key === 'Enter') { if (e.key === 'Enter') {
router.push({ const q = e.currentTarget.value
pathname: `/search`,
query: { q: e.currentTarget.value }, router.push(
}) {
pathname: `/search`,
query: q ? { q } : {},
},
undefined,
{ shallow: true }
)
} }
}} }}
/> />

View File

@ -1,3 +1,4 @@
import getAllProducts from '../../operations/get-all-products'
import type { ProductsHandlers } from '../products' import type { ProductsHandlers } from '../products'
// Return current cart info // Return current cart info
@ -11,9 +12,18 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
if (search) url.searchParams.set('keyword', search) if (search) url.searchParams.set('keyword', search)
const { data } = await config.storeApiFetch(url.pathname + url.search) // We only want the id of each product
url.searchParams.set('include_fields', 'id')
res.status(200).json({ data }) const { data } = await config.storeApiFetch<{ data: { id: number }[] }>(
url.pathname + url.search
)
const entityIds = data.map((p) => p.id)
const found = entityIds.length > 0
// We want the GraphQL version of each product
const { products } = await getAllProducts({ variables: { entityIds } })
res.status(200).json({ data: { products, found } })
} }
export default getProducts export default getProducts

View File

@ -1,4 +1,3 @@
import type { definitions } from '../definitions/catalog'
import isAllowedMethod from '../utils/is-allowed-method' import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, { import createApiHandler, {
BigcommerceApiHandler, BigcommerceApiHandler,
@ -6,22 +5,24 @@ import createApiHandler, {
} from '../utils/create-api-handler' } from '../utils/create-api-handler'
import { BigcommerceApiError } from '../utils/errors' import { BigcommerceApiError } from '../utils/errors'
import getProducts from './handlers/get-products' import getProducts from './handlers/get-products'
import { Products } from '../operations/get-all-products'
export type Product = definitions['product_Full'] export type SearchProductsData = {
products: Products
found: boolean
}
export type ProductsHandlers = { export type ProductsHandlers = {
getProducts: BigcommerceHandler<Product[], { search?: 'string' }> getProducts: BigcommerceHandler<SearchProductsData, { search?: 'string' }>
} }
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<Product[], ProductsHandlers> = async ( const cartApi: BigcommerceApiHandler<
req, SearchProductsData,
res, ProductsHandlers
config, > = async (req, res, config, handlers) => {
handlers
) => {
if (!isAllowedMethod(req, res, METHODS)) return if (!isAllowedMethod(req, res, METHODS)) return
try { try {

View File

@ -17,6 +17,7 @@ export const productInfoFragment = /* GraphQL */ `
path path
brand { brand {
name name
entityId
} }
description description
prices { prices {

View File

@ -9,6 +9,7 @@ import { BigcommerceConfig, getConfig, Images, ProductImageVariables } from '..'
export const getAllProductsQuery = /* GraphQL */ ` export const getAllProductsQuery = /* GraphQL */ `
query getAllProducts( query getAllProducts(
$entityIds: [Int!]
$first: Int = 10 $first: Int = 10
$imgSmallWidth: Int = 320 $imgSmallWidth: Int = 320
$imgSmallHeight: Int $imgSmallHeight: Int
@ -20,7 +21,7 @@ export const getAllProductsQuery = /* GraphQL */ `
$imgXLHeight: Int $imgXLHeight: Int
) { ) {
site { site {
products(first: $first) { products(first: $first, entityIds: $entityIds) {
pageInfo { pageInfo {
startCursor startCursor
endCursor endCursor

View File

@ -1,8 +1,7 @@
import useSWR, { ConfigInterface } from 'swr' import { ConfigInterface } from 'swr'
import { HookDeps, HookFetcher } from '@lib/commerce/utils/types' import { HookFetcher } from '@lib/commerce/utils/types'
import useCommerceSearch from '@lib/commerce/products/use-search' import useCommerceSearch from '@lib/commerce/products/use-search'
import type { Product } from '../api/catalog/products' import type { SearchProductsData } from '../api/catalog/products'
import { useCommerce } from '..'
const defaultOpts = { const defaultOpts = {
url: '/api/bigcommerce/catalog/products', url: '/api/bigcommerce/catalog/products',
@ -13,7 +12,7 @@ export type SearchProductsInput = {
search?: string search?: string
} }
export const fetcher: HookFetcher<Product[], SearchProductsInput> = ( export const fetcher: HookFetcher<SearchProductsData, SearchProductsInput> = (
options, options,
{ search }, { search },
fetch fetch
@ -34,7 +33,7 @@ export function extendHook(
swrOptions?: ConfigInterface swrOptions?: ConfigInterface
) { ) {
const useSearch = (input: SearchProductsInput = {}) => { const useSearch = (input: SearchProductsInput = {}) => {
const response = useCommerceSearch<Product[]>( const response = useCommerceSearch<SearchProductsData>(
defaultOpts, defaultOpts,
[['search', input.search]], [['search', input.search]],
customFetcher, customFetcher,

View File

@ -1674,7 +1674,7 @@ export type ProductInfoFragment = { __typename?: 'Product' } & Pick<
Product, Product,
'entityId' | 'name' | 'path' | 'description' 'entityId' | 'name' | 'path' | 'description'
> & { > & {
brand?: Maybe<{ __typename?: 'Brand' } & Pick<Brand, 'name'>> brand?: Maybe<{ __typename?: 'Brand' } & Pick<Brand, 'name' | 'entityId'>>
prices?: Maybe< prices?: Maybe<
{ __typename?: 'Prices' } & { { __typename?: 'Prices' } & {
price: { __typename?: 'Money' } & Pick<Money, 'value' | 'currencyCode'> price: { __typename?: 'Money' } & Pick<Money, 'value' | 'currencyCode'>
@ -1759,6 +1759,7 @@ export type GetAllProductPathsQuery = { __typename?: 'Query' } & {
} }
export type GetAllProductsQueryVariables = Exact<{ export type GetAllProductsQueryVariables = Exact<{
entityIds?: Maybe<Array<Scalars['Int']>>
first?: Maybe<Scalars['Int']> first?: Maybe<Scalars['Int']>
imgSmallWidth?: Maybe<Scalars['Int']> imgSmallWidth?: Maybe<Scalars['Int']>
imgSmallHeight?: Maybe<Scalars['Int']> imgSmallHeight?: Maybe<Scalars['Int']>

View File

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

View File

@ -1,5 +1,4 @@
import { GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import getAllProducts from '@lib/bigcommerce/api/operations/get-all-products'
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 useSearch from '@lib/bigcommerce/products/use-search'
import { Layout } from '@components/core' import { Layout } from '@components/core'
@ -8,23 +7,22 @@ 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) {
const { products } = await getAllProducts()
const { categories, brands } = await getSiteInfo() const { categories, brands } = await getSiteInfo()
return { return {
props: { products, categories, brands }, props: { categories, brands },
} }
} }
export default function Home({ export default function Home({
products,
categories, categories,
brands, brands,
}: InferGetStaticPropsType<typeof getStaticProps>) { }: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter() const router = useRouter()
const search = useSearch({ search: router.query.search as string }) const { q } = router.query
const { data } = useSearch({
console.log('SEARCH', search) search: typeof q === 'string' ? q : '',
})
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">
@ -51,18 +49,23 @@ export default function Home({
</ul> </ul>
</div> </div>
<div className="col-span-8"> <div className="col-span-8">
<div className="mb-12"> {data ? (
Showing 8 results for "<strong>{router.query.q}</strong>" <>
</div> {q && (
<Grid <div className="mb-12">
items={[ {data.found ? (
...products.slice(6), <>Showing {data.products.length} results for</>
...products.slice(6), ) : (
...products.slice(6), <>There are no products that match</>
]} )}{' '}
layout="normal" "<strong>{q}</strong>"
wrapper={ProductCard} </div>
/> )}
<Grid items={data.products} layout="normal" wrapper={ProductCard} />
</>
) : (
<div>Searching...</div>
)}
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<ul> <ul>