From 8d801ce5d7af1b3e2554b6ea8eae4eca7d0896c3 Mon Sep 17 00:00:00 2001
From: cond0r <pinte_catalin@yahoo.com>
Date: Fri, 12 Feb 2021 09:56:03 +0200
Subject: [PATCH] query all products for vendors & paths, improve search

---
 framework/shopify/api/checkout/index.ts       |  3 +-
 .../shopify/api/utils/fetch-all-products.ts   | 41 +++++++++
 .../shopify/cart/utils/checkout-create.ts     |  3 +-
 framework/shopify/common/get-all-pages.ts     | 12 ++-
 framework/shopify/common/get-page.ts          | 48 +++++++----
 framework/shopify/common/get-site-info.ts     | 30 +++----
 .../shopify/product/get-all-product-paths.ts  | 13 ++-
 framework/shopify/product/use-search.tsx      | 33 ++++----
 framework/shopify/provider.ts                 |  6 +-
 framework/shopify/utils/get-categories.ts     | 29 +++++++
 .../shopify/utils/get-search-variables.ts     | 41 ++++++---
 framework/shopify/utils/get-vendors.ts        | 36 ++++++++
 .../queries/get-all-product-vendors-query.ts  | 17 ++++
 .../queries/get-all-products-paths-query.ts   |  5 +-
 .../utils/queries/get-all-products-query.ts   | 84 ++++++++++---------
 .../queries/get-collection-products-query.ts  | 17 ++++
 .../shopify/utils/queries/get-page-query.ts   | 17 ++++
 framework/shopify/utils/queries/index.ts      |  3 +
 pages/index.tsx                               |  2 +-
 pages/search.tsx                              |  1 -
 20 files changed, 315 insertions(+), 126 deletions(-)
 create mode 100644 framework/shopify/api/utils/fetch-all-products.ts
 create mode 100644 framework/shopify/utils/get-categories.ts
 create mode 100644 framework/shopify/utils/get-vendors.ts
 create mode 100644 framework/shopify/utils/queries/get-all-product-vendors-query.ts
 create mode 100644 framework/shopify/utils/queries/get-collection-products-query.ts
 create mode 100644 framework/shopify/utils/queries/get-page-query.ts

diff --git a/framework/shopify/api/checkout/index.ts b/framework/shopify/api/checkout/index.ts
index 1f1a5c491..0d0e752f0 100644
--- a/framework/shopify/api/checkout/index.ts
+++ b/framework/shopify/api/checkout/index.ts
@@ -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'
 
diff --git a/framework/shopify/api/utils/fetch-all-products.ts b/framework/shopify/api/utils/fetch-all-products.ts
new file mode 100644
index 000000000..0f6660cd5
--- /dev/null
+++ b/framework/shopify/api/utils/fetch-all-products.ts
@@ -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
diff --git a/framework/shopify/cart/utils/checkout-create.ts b/framework/shopify/cart/utils/checkout-create.ts
index cb2038a10..9975befe8 100644
--- a/framework/shopify/cart/utils/checkout-create.ts
+++ b/framework/shopify/cart/utils/checkout-create.ts
@@ -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'
 
diff --git a/framework/shopify/common/get-all-pages.ts b/framework/shopify/common/get-all-pages.ts
index 9546c3fb3..6cec2ef73 100644
--- a/framework/shopify/common/get-all-pages.ts
+++ b/framework/shopify/common/get-all-pages.ts
@@ -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 }
 }
diff --git a/framework/shopify/common/get-page.ts b/framework/shopify/common/get-page.ts
index 8c2fb7165..803272918 100644
--- a/framework/shopify/common/get-page.ts
+++ b/framework/shopify/common/get-page.ts
@@ -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
diff --git a/framework/shopify/common/get-site-info.ts b/framework/shopify/common/get-site-info.ts
index 1a87e2d5d..f6cdaad85 100644
--- a/framework/shopify/common/get-site-info.ts
+++ b/framework/shopify/common/get-site-info.ts
@@ -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,
   }
 }
 
diff --git a/framework/shopify/product/get-all-product-paths.ts b/framework/shopify/product/get-all-product-paths.ts
index e632219f7..7eff4e657 100644
--- a/framework/shopify/product/get-all-product-paths.ts
+++ b/framework/shopify/product/get-all-product-paths.ts
@@ -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}`,
       },
diff --git a/framework/shopify/product/use-search.tsx b/framework/shopify/product/use-search.tsx
index 51c390bba..6574db172 100644
--- a/framework/shopify/product/use-search.tsx
+++ b/framework/shopify/product/use-search.tsx
@@ -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,
diff --git a/framework/shopify/provider.ts b/framework/shopify/provider.ts
index bd8872b1f..7c3a7a14e 100644
--- a/framework/shopify/provider.ts
+++ b/framework/shopify/provider.ts
@@ -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,
diff --git a/framework/shopify/utils/get-categories.ts b/framework/shopify/utils/get-categories.ts
new file mode 100644
index 000000000..3319d827d
--- /dev/null
+++ b/framework/shopify/utils/get-categories.ts
@@ -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
diff --git a/framework/shopify/utils/get-search-variables.ts b/framework/shopify/utils/get-search-variables.ts
index 0b37f015f..9bc91eca3 100644
--- a/framework/shopify/utils/get-search-variables.ts
+++ b/framework/shopify/utils/get-search-variables.ts
@@ -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
diff --git a/framework/shopify/utils/get-vendors.ts b/framework/shopify/utils/get-vendors.ts
new file mode 100644
index 000000000..d3ebce194
--- /dev/null
+++ b/framework/shopify/utils/get-vendors.ts
@@ -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
diff --git a/framework/shopify/utils/queries/get-all-product-vendors-query.ts b/framework/shopify/utils/queries/get-all-product-vendors-query.ts
new file mode 100644
index 000000000..be08b8ec6
--- /dev/null
+++ b/framework/shopify/utils/queries/get-all-product-vendors-query.ts
@@ -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
diff --git a/framework/shopify/utils/queries/get-all-products-paths-query.ts b/framework/shopify/utils/queries/get-all-products-paths-query.ts
index 74e9f490d..b8fe23b5b 100644
--- a/framework/shopify/utils/queries/get-all-products-paths-query.ts
+++ b/framework/shopify/utils/queries/get-all-products-paths-query.ts
@@ -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
       }
     }
   }
diff --git a/framework/shopify/utils/queries/get-all-products-query.ts b/framework/shopify/utils/queries/get-all-products-query.ts
index de84d60bf..4a6c20b6e 100644
--- a/framework/shopify/utils/queries/get-all-products-query.ts
+++ b/framework/shopify/utils/queries/get-all-products-query.ts
@@ -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
diff --git a/framework/shopify/utils/queries/get-collection-products-query.ts b/framework/shopify/utils/queries/get-collection-products-query.ts
new file mode 100644
index 000000000..dd504b575
--- /dev/null
+++ b/framework/shopify/utils/queries/get-collection-products-query.ts
@@ -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
diff --git a/framework/shopify/utils/queries/get-page-query.ts b/framework/shopify/utils/queries/get-page-query.ts
new file mode 100644
index 000000000..91f80f9e7
--- /dev/null
+++ b/framework/shopify/utils/queries/get-page-query.ts
@@ -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
diff --git a/framework/shopify/utils/queries/index.ts b/framework/shopify/utils/queries/index.ts
index e0506cb5a..f41cb2797 100644
--- a/framework/shopify/utils/queries/index.ts
+++ b/framework/shopify/utils/queries/index.ts
@@ -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'
diff --git a/pages/index.tsx b/pages/index.tsx
index 811472674..4ddf561aa 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -31,7 +31,7 @@ export async function getStaticProps({
       brands,
       pages,
     },
-    revalidate: 14400,
+    revalidate: 1440,
   }
 }
 
diff --git a/pages/search.tsx b/pages/search.tsx
index 64fea5600..97bee34d4 100644
--- a/pages/search.tsx
+++ b/pages/search.tsx
@@ -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 : '',
   })