forked from crowetic/commerce
Search
This commit is contained in:
parent
e7e89e4c4c
commit
ee6b65725f
@ -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 }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -17,6 +17,7 @@ export const productInfoFragment = /* GraphQL */ `
|
|||||||
path
|
path
|
||||||
brand {
|
brand {
|
||||||
name
|
name
|
||||||
|
entityId
|
||||||
}
|
}
|
||||||
description
|
description
|
||||||
prices {
|
prices {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
3
lib/bigcommerce/schema.d.ts
vendored
3
lib/bigcommerce/schema.d.ts
vendored
@ -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']>
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user