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 => { + 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 - -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 { +}): Promise => { + 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 => { + 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 => { 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 => { + 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 => { + 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 : '', })