forked from crowetic/commerce
query all products for vendors & paths, improve search
This commit is contained in:
parent
1450492826
commit
8d801ce5d7
@ -7,7 +7,8 @@ import {
|
||||
SHOPIFY_CHECKOUT_ID_COOKIE,
|
||||
SHOPIFY_CHECKOUT_URL_COOKIE,
|
||||
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
} from '@framework/provider'
|
||||
} from '@framework/const'
|
||||
|
||||
import { getConfig } from '..'
|
||||
import associateCustomerWithCheckoutMutation from '@framework/utils/mutations/associate-customer-with-checkout'
|
||||
|
||||
|
41
framework/shopify/api/utils/fetch-all-products.ts
Normal file
41
framework/shopify/api/utils/fetch-all-products.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { ProductEdge } from '@framework/schema'
|
||||
import { ShopifyConfig } from '..'
|
||||
|
||||
const fetchAllProducts = async ({
|
||||
config,
|
||||
query,
|
||||
variables,
|
||||
acc = [],
|
||||
cursor,
|
||||
}: {
|
||||
config: ShopifyConfig
|
||||
query: string
|
||||
acc?: ProductEdge[]
|
||||
variables?: any
|
||||
cursor?: string
|
||||
}): Promise<ProductEdge[]> => {
|
||||
const { data } = await config.fetch(query, {
|
||||
variables: { ...variables, cursor },
|
||||
})
|
||||
|
||||
const edges: ProductEdge[] = data?.products?.edges ?? []
|
||||
const hasNextPage = data?.products?.pageInfo?.hasNextPage
|
||||
acc = acc.concat(edges)
|
||||
|
||||
if (hasNextPage) {
|
||||
const cursor = edges.pop()?.cursor
|
||||
if (cursor) {
|
||||
return fetchAllProducts({
|
||||
config,
|
||||
query,
|
||||
variables,
|
||||
acc,
|
||||
cursor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
export default fetchAllProducts
|
@ -1,7 +1,8 @@
|
||||
import {
|
||||
SHOPIFY_CHECKOUT_ID_COOKIE,
|
||||
SHOPIFY_CHECKOUT_URL_COOKIE,
|
||||
} from '@framework/provider'
|
||||
} from '@framework/const'
|
||||
|
||||
import checkoutCreateMutation from '@framework/utils/mutations/checkout-create'
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
|
@ -19,14 +19,12 @@ const getAllPages = async (options?: {
|
||||
config = getConfig(config)
|
||||
|
||||
const { data } = await config.fetch(getAllPagesQuery, { variables })
|
||||
const edges = data?.pages?.edges
|
||||
|
||||
const pages = data.pages.edges.map(({ node }: PageEdge) => {
|
||||
return {
|
||||
...node,
|
||||
name: node.handle,
|
||||
url: `${config!.locale}/${node.handle}`,
|
||||
}
|
||||
})
|
||||
const pages = edges?.map(({ node }: PageEdge) => ({
|
||||
...node,
|
||||
url: node.handle,
|
||||
}))
|
||||
|
||||
return { pages }
|
||||
}
|
||||
|
@ -1,27 +1,39 @@
|
||||
import { ShopifyConfig, getConfig } from '../api'
|
||||
import type { Page } from '../types'
|
||||
import { GraphQLFetcherResult } from '@commerce/api'
|
||||
|
||||
export type { Page }
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import getPageQuery from '@framework/utils/queries/get-page-query'
|
||||
import { Page, PageEdge } from '@framework/schema'
|
||||
|
||||
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||
|
||||
export type PageVariables = {
|
||||
id: string
|
||||
type Variables = {
|
||||
slug: string
|
||||
}
|
||||
|
||||
async function getPage({
|
||||
url,
|
||||
variables,
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: PageVariables
|
||||
config?: ShopifyConfig
|
||||
type ReturnType = {
|
||||
page: any
|
||||
}
|
||||
|
||||
const getPage = async (options: {
|
||||
variables: Variables
|
||||
config: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<GetPageResult> {
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables } = options ?? {}
|
||||
config = getConfig(config)
|
||||
return {}
|
||||
|
||||
const { data }: GraphQLFetcherResult = await config.fetch(getPageQuery, {
|
||||
variables,
|
||||
})
|
||||
|
||||
const page: Page = data?.pageByHandle
|
||||
|
||||
return {
|
||||
page: page
|
||||
? {
|
||||
...page,
|
||||
url: page?.handle,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
export default getPage
|
||||
|
@ -1,30 +1,30 @@
|
||||
import { CollectionEdge } from '@framework/schema'
|
||||
import getCategories, { Category } from '@framework/utils/get-categories'
|
||||
import getVendors, { Brands } from '@framework/utils/get-vendors'
|
||||
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import getAllCollectionsQuery from '../utils/queries/get-all-collections-query'
|
||||
|
||||
export type GetSiteInfoResult<
|
||||
T extends { categories: any[]; brands: any[] } = {
|
||||
categories: Category[]
|
||||
brands: Brands
|
||||
}
|
||||
> = T
|
||||
|
||||
const getSiteInfo = async (options?: {
|
||||
variables?: any
|
||||
config: ShopifyConfig
|
||||
preview?: boolean
|
||||
}) => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
}): Promise<GetSiteInfoResult> => {
|
||||
let { config } = options ?? {}
|
||||
|
||||
config = getConfig(config)
|
||||
|
||||
const { data } = await config.fetch(getAllCollectionsQuery, { variables })
|
||||
const edges = data.collections?.edges ?? []
|
||||
|
||||
const categories = edges.map(
|
||||
({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({
|
||||
entityId,
|
||||
name,
|
||||
path: `/${handle}`,
|
||||
})
|
||||
)
|
||||
const categories = await getCategories(config)
|
||||
const brands = await getVendors(config)
|
||||
|
||||
return {
|
||||
categories,
|
||||
brands: [],
|
||||
brands,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import fetchAllProducts from '../api/utils/fetch-all-products'
|
||||
import { ProductEdge } from '../schema'
|
||||
import getAllProductsPathsQuery from '../utils/queries/get-all-products-paths-query'
|
||||
|
||||
@ -9,21 +10,19 @@ type ReturnType = {
|
||||
const getAllProductPaths = async (options?: {
|
||||
variables?: any
|
||||
config?: ShopifyConfig
|
||||
previe?: boolean
|
||||
preview?: boolean
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
config = getConfig(config)
|
||||
|
||||
const { data } = await config.fetch(getAllProductsPathsQuery, {
|
||||
const products = await fetchAllProducts({
|
||||
config,
|
||||
query: getAllProductsPathsQuery,
|
||||
variables,
|
||||
})
|
||||
|
||||
const edges = data?.products?.edges
|
||||
const productInfo = data?.products?.productInfo
|
||||
const hasNextPage = productInfo?.hasNextPage
|
||||
|
||||
return {
|
||||
products: edges?.map(({ node: { handle } }: ProductEdge) => ({
|
||||
products: products?.map(({ node: { handle } }: ProductEdge) => ({
|
||||
node: {
|
||||
path: `/${handle}`,
|
||||
},
|
||||
|
@ -1,22 +1,22 @@
|
||||
import useCommerceSearch from '@commerce/products/use-search'
|
||||
import getAllProductsQuery from '@framework/utils/queries/get-all-products-query'
|
||||
import {
|
||||
getAllProductsQuery,
|
||||
getCollectionProductsQuery,
|
||||
} from '@framework/utils/queries'
|
||||
|
||||
import type { Product } from 'framework/bigcommerce/schema'
|
||||
import type { HookFetcher } from '@commerce/utils/types'
|
||||
import type { SwrOptions } from '@commerce/utils/use-data'
|
||||
import type { ProductEdge } from '@framework/schema'
|
||||
|
||||
import {
|
||||
searchByProductType,
|
||||
searchByTag,
|
||||
} from '@framework/utils/get-search-variables'
|
||||
import getSearchVariables from '@framework/utils/get-search-variables'
|
||||
|
||||
import sortBy from '@framework/utils/get-sort-variables'
|
||||
import { normalizeProduct } from '@framework/lib/normalize'
|
||||
|
||||
export type SearchProductsInput = {
|
||||
search?: string
|
||||
categoryPath?: string
|
||||
categoryId?: string
|
||||
brandId?: string
|
||||
sort?: string
|
||||
}
|
||||
|
||||
@ -32,22 +32,20 @@ export type SearchProductsData = {
|
||||
export const fetcher: HookFetcher<
|
||||
SearchRequestProductsData,
|
||||
SearchProductsInput
|
||||
> = (options, { search, categoryPath, sort }, fetch) => {
|
||||
> = (options, input, fetch) => {
|
||||
return fetch({
|
||||
query: options?.query,
|
||||
method: options?.method,
|
||||
variables: {
|
||||
...searchByProductType(search),
|
||||
...searchByTag(categoryPath),
|
||||
...sortBy(sort),
|
||||
...getSearchVariables(input),
|
||||
},
|
||||
}).then(
|
||||
({ products }): SearchProductsData => {
|
||||
(resp): SearchProductsData => {
|
||||
const edges = resp.products?.edges
|
||||
|
||||
return {
|
||||
products: products?.edges?.map(({ node: p }: ProductEdge) =>
|
||||
normalizeProduct(p)
|
||||
),
|
||||
found: !!products?.edges?.length,
|
||||
products: edges?.map(({ node: p }: ProductEdge) => normalizeProduct(p)),
|
||||
found: !!edges?.length,
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -64,7 +62,8 @@ export function extendHook(
|
||||
},
|
||||
[
|
||||
['search', input.search],
|
||||
['categoryPath', input.categoryPath],
|
||||
['categoryId', input.categoryId],
|
||||
['brandId', input.brandId],
|
||||
['sort', input.sort],
|
||||
],
|
||||
customFetcher,
|
||||
|
@ -9,6 +9,7 @@ import { normalizeCart } from './lib/normalize'
|
||||
import { Cart } from './types'
|
||||
|
||||
import handleFetchResponse from './utils/handle-fetch-response'
|
||||
import { getCheckoutQuery } from './utils/queries'
|
||||
|
||||
const useCart: HookHandler<
|
||||
Cart | null,
|
||||
@ -19,8 +20,7 @@ const useCart: HookHandler<
|
||||
{ isEmpty?: boolean }
|
||||
> = {
|
||||
fetchOptions: {
|
||||
url: '/api/bigcommerce/cart',
|
||||
method: 'GET',
|
||||
query: getCheckoutQuery,
|
||||
},
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
@ -38,7 +38,7 @@ const useCart: HookHandler<
|
||||
},
|
||||
}
|
||||
|
||||
const fetcher: Fetcher = async ({ method = 'GET', variables, query }) => {
|
||||
const fetcher: Fetcher = async ({ method = 'POST', variables, query }) => {
|
||||
return handleFetchResponse(
|
||||
await fetch(API_URL, {
|
||||
method,
|
||||
|
29
framework/shopify/utils/get-categories.ts
Normal file
29
framework/shopify/utils/get-categories.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ShopifyConfig } from '@framework/api'
|
||||
import { CollectionEdge } from '@framework/schema'
|
||||
import getSiteCollectionsQuery from './queries/get-all-collections-query'
|
||||
|
||||
export type Category = {
|
||||
endityId: string
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
const getCategories = async (config: ShopifyConfig): Promise<Category[]> => {
|
||||
const { data } = await config.fetch(getSiteCollectionsQuery, {
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
data?.collections?.edges?.map(
|
||||
({ node: { title: name, handle } }: CollectionEdge) => ({
|
||||
entityId: handle,
|
||||
name,
|
||||
path: `/${handle}`,
|
||||
})
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export default getCategories
|
@ -1,15 +1,30 @@
|
||||
export const searchByProductType = (search?: string) => {
|
||||
return search
|
||||
? {
|
||||
query: `product_type:${search}`,
|
||||
}
|
||||
: {}
|
||||
import { SearchProductsInput } from '@framework/product/use-search'
|
||||
import getSortVariables from './get-sort-variables'
|
||||
|
||||
export const getSearchVariables = ({
|
||||
categoryId,
|
||||
brandId,
|
||||
search,
|
||||
sort,
|
||||
}: SearchProductsInput) => {
|
||||
let query = ''
|
||||
|
||||
if (search) {
|
||||
query += `product_type:${search} OR title:${search} OR tag:${search}`
|
||||
}
|
||||
|
||||
if (categoryId) {
|
||||
query += `tag:${categoryId}`
|
||||
}
|
||||
|
||||
if (brandId) {
|
||||
query += `${categoryId ? ' AND ' : ''}vendor:${brandId}`
|
||||
}
|
||||
|
||||
return {
|
||||
query,
|
||||
...getSortVariables(sort),
|
||||
}
|
||||
}
|
||||
|
||||
export const searchByTag = (categoryPath?: string) => {
|
||||
return categoryPath
|
||||
? {
|
||||
query: `tag:${categoryPath}`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
export default getSearchVariables
|
||||
|
36
framework/shopify/utils/get-vendors.ts
Normal file
36
framework/shopify/utils/get-vendors.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { ShopifyConfig } from '@framework/api'
|
||||
import fetchAllProducts from '@framework/api/utils/fetch-all-products'
|
||||
import getAllProductVendors from './queries/get-all-product-vendors-query'
|
||||
|
||||
export type BrandNode = {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type BrandEdge = {
|
||||
node: BrandNode
|
||||
}
|
||||
|
||||
export type Brands = BrandEdge[]
|
||||
|
||||
const getVendors = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
|
||||
const vendors = await fetchAllProducts({
|
||||
config,
|
||||
query: getAllProductVendors,
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
})
|
||||
|
||||
let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor)
|
||||
|
||||
return [...new Set(vendorsStrings)].map((v) => ({
|
||||
node: {
|
||||
entityId: v,
|
||||
name: v,
|
||||
path: `brands/${v}`,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export default getVendors
|
@ -0,0 +1,17 @@
|
||||
const getAllProductVendors = /* GraphQL */ `
|
||||
query getAllProductVendors($first: Int = 250, $cursor: String) {
|
||||
products(first: $first, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
vendor
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getAllProductVendors
|
@ -1,6 +1,6 @@
|
||||
const getAllProductsPathsQuery = /* GraphQL */ `
|
||||
query getAllProductPaths($first: Int!) {
|
||||
products(first: $first) {
|
||||
query getAllProductPaths($first: Int!, $cursor: String) {
|
||||
products(first: $first, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
@ -9,6 +9,7 @@ const getAllProductsPathsQuery = /* GraphQL */ `
|
||||
node {
|
||||
handle
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,46 @@
|
||||
export const productsFragment = `
|
||||
products(
|
||||
first: $first
|
||||
sortKey: $sortKey
|
||||
reverse: $reverse
|
||||
query: $query
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
vendor
|
||||
handle
|
||||
description
|
||||
priceRange {
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
images(first: 1) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const getAllProductsQuery = /* GraphQL */ `
|
||||
query getAllProducts(
|
||||
$first: Int = 250
|
||||
@ -5,46 +48,7 @@ const getAllProductsQuery = /* GraphQL */ `
|
||||
$sortKey: ProductSortKeys = RELEVANCE
|
||||
$reverse: Boolean = false
|
||||
) {
|
||||
products(
|
||||
first: $first
|
||||
sortKey: $sortKey
|
||||
reverse: $reverse
|
||||
query: $query
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
vendor
|
||||
handle
|
||||
description
|
||||
priceRange {
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
images(first: 1) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${productsFragment}
|
||||
}
|
||||
`
|
||||
export default getAllProductsQuery
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { productsFragment } from './get-all-products-query'
|
||||
|
||||
const getCollectionProductsQuery = /* GraphQL */ `
|
||||
query getProductsFromCollection(
|
||||
$categoryHandle: String!
|
||||
$first: Int = 250
|
||||
$query: String = ""
|
||||
$sortKey: ProductSortKeys = RELEVANCE
|
||||
$reverse: Boolean = false
|
||||
) {
|
||||
collectionByHandle(handle: $categoryHandle)
|
||||
{
|
||||
${productsFragment}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getCollectionProductsQuery
|
17
framework/shopify/utils/queries/get-page-query.ts
Normal file
17
framework/shopify/utils/queries/get-page-query.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const getPageQuery = /* GraphQL */ `
|
||||
query($first: Int!) {
|
||||
pages(first: $first) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
handle
|
||||
body
|
||||
bodySummary
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getPageQuery
|
@ -2,6 +2,9 @@ export { default as getSiteCollectionsQuery } from './get-all-collections-query'
|
||||
export { default as getProductQuery } from './get-all-products-paths-query'
|
||||
export { default as getAllProductsQuery } from './get-all-products-query'
|
||||
export { default as getAllProductsPathtsQuery } from './get-all-products-paths-query'
|
||||
export { default as getAllProductVendors } from './get-all-product-vendors-query'
|
||||
export { default as getCollectionProductsQuery } from './get-collection-products-query'
|
||||
export { default as getCheckoutQuery } from './get-checkout-query'
|
||||
export { default as getAllPagesQuery } from './get-all-pages-query'
|
||||
export { default as getPageQuery } from './get-page-query'
|
||||
export { default as getCustomerQuery } from './get-checkout-query'
|
||||
|
@ -31,7 +31,7 @@ export async function getStaticProps({
|
||||
brands,
|
||||
pages,
|
||||
},
|
||||
revalidate: 14400,
|
||||
revalidate: 1440,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,6 @@ export default function Search({
|
||||
const { data } = useSearch({
|
||||
search: typeof q === 'string' ? q : '',
|
||||
categoryId: activeCategory?.entityId,
|
||||
categoryPath: activeCategory?.path,
|
||||
brandId: activeBrand?.entityId,
|
||||
sort: typeof sort === 'string' ? sort : '',
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user