diff --git a/commerce.config.json b/commerce.config.json index ad72b58de..6fb526aaf 100644 --- a/commerce.config.json +++ b/commerce.config.json @@ -2,8 +2,8 @@ "features": { "cart": true, "search": true, - "wishlist": false, - "customerAuth": false, - "customCheckout": false + "wishlist": true, + "customerAuth": true, + "customCheckout": true } } diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index 2910a2d82..ca9b325c2 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -1,3 +1,4 @@ +import { GetAllFacetsOperation } from './../types/facet'; import type { ServerResponse } from 'http' import type { LoginOperation } from '../types/login' import type { GetAllPagesOperation, GetPageOperation } from '../types/page' @@ -9,6 +10,7 @@ import type { GetProductOperation, } from '../types/product' import type { APIProvider, CommerceAPI } from '.' +import { GetAllCollectionsOperation } from '@commerce/types/collection'; const noop = () => { throw new Error('Not implemented') @@ -23,6 +25,9 @@ export const OPERATIONS = [ 'getAllProductPaths', 'getAllProducts', 'getProduct', + 'getAllFacets', + 'getAllCollections', + ] as const export const defaultOperations = OPERATIONS.reduce((ops, k) => { @@ -154,8 +159,43 @@ export type Operations

= { } & OperationOptions ): Promise } + + getAllFacets: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } + + getAllCollections: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } + + } + export type APIOperations

= { [K in keyof Operations

]?: (ctx: OperationContext

) => Operations

[K] } diff --git a/framework/commerce/new-provider.md b/framework/commerce/new-provider.md index 8c2feeab2..3c7f05b7a 100644 --- a/framework/commerce/new-provider.md +++ b/framework/commerce/new-provider.md @@ -15,6 +15,8 @@ Adding a commerce provider means adding a new folder in `framework` with a folde - useSearch - getProduct - getAllProducts + - getAllFacets + - getAllCollections - `wishlist` - useWishlist - useAddItem diff --git a/framework/commerce/types/collection.ts b/framework/commerce/types/collection.ts new file mode 100644 index 000000000..3ccb72ea3 --- /dev/null +++ b/framework/commerce/types/collection.ts @@ -0,0 +1,54 @@ +import { Asset } from '../../vendure/schema.d'; + +export type Collection = { + id: string + name: string + slug: string + description: string + featuredAsse: Asset + asset: Asset[] +} + +export type SearchCollectionsBody = { + search?: string + sort?: string + locale?: string +} + +export type CollectionTypes = { + collection: Collection + searchBody: SearchCollectionsBody +} + +export type SearchCollectionsHook = { + data: { + collections: T['collection'][] + found: boolean + } + body: T['searchBody'] + input: T['searchBody'] + fetcherInput: T['searchBody'] +} + +export type CollectionsSchema = { + endpoint: { + options: {} + handlers: { + getCollections: SearchCollectionsHook + } + } +} + + +export type GetAllCollectionsOperation = { + data: { collections: T['collection'][] } + variables: { + ids?: string[] + first?: number + } +} + +export type GetCollectionOperation = { + data: { collection?: T['collection'] } + variables: { code: string; } | { code?: never; } +} diff --git a/framework/commerce/types/facet.ts b/framework/commerce/types/facet.ts new file mode 100644 index 000000000..adfe1e061 --- /dev/null +++ b/framework/commerce/types/facet.ts @@ -0,0 +1,52 @@ +import { FacetValue } from './../../vendure/schema.d'; + +export type Facet = { + id: string + name: string + code: string + values: FacetValue[] +} + +export type SearchFacetsBody = { + search?: string + sort?: string + locale?: string +} + +export type FacetTypes = { + facet: Facet + searchBody: SearchFacetsBody +} + +export type SearchFacetsHook = { + data: { + facets: T['facet'][] + found: boolean + } + body: T['searchBody'] + input: T['searchBody'] + fetcherInput: T['searchBody'] +} + +export type FacetsSchema = { + endpoint: { + options: {} + handlers: { + getFacets: SearchFacetsHook + } + } +} + + +export type GetAllFacetsOperation = { + data: { facets: T['facet'][] } + variables: { + ids?: string[] + first?: number + } +} + +export type GetFacetOperation = { + data: { facet?: T['facet'] } + variables: { code: string; } | { code?: never; } +} diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index 6a68d8ad1..ddf51c518 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -1,3 +1,6 @@ +import { CurrencyCode } from './../../vendure/schema.d'; +import { FacetValueFilterInput, LogicalOperator, SearchResultSortParameter } from "@framework/schema" + export type ProductImage = { url: string alt?: string @@ -40,9 +43,28 @@ export type Product = { slug?: string path?: string images: ProductImage[] - variants: ProductVariant[] - price: ProductPrice + price: number + currencyCode: CurrencyCode options: ProductOption[] + facetValueIds?: string[] + collectionIds?: string[] + collection?: string, +} + +export type ProductCard = { + id: string + name: string + slug?: string + imageSrc: string + price: number + currencyCode: CurrencyCode + oldPrice?: number + discount?: number + weight?: number + facetValueIds?: string[], + collectionIds?: string[], + collection?: string, + isNotSell?: boolean } export type SearchProductsBody = { @@ -79,17 +101,24 @@ export type ProductsSchema = { export type GetAllProductPathsOperation< T extends ProductTypes = ProductTypes -> = { - data: { products: Pick[] } - variables: { first?: number } -} + > = { + data: { products: Pick[] } + variables: { first?: number } + } export type GetAllProductsOperation = { data: { products: T['product'][] } variables: { - relevance?: 'featured' | 'best_selling' | 'newest' - ids?: string[] - first?: number + term?: String + facetValueIds?: string[] + facetValueOperator?: LogicalOperator + facetValueFilters?: FacetValueFilterInput[] + collectionId?: string + collectionSlug?: string + groupByProduct?: Boolean + take?: number + skip?: number + sort?: SearchResultSortParameter } } diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts index 6762ee6aa..95ec47d66 100644 --- a/framework/vendure/api/index.ts +++ b/framework/vendure/api/index.ts @@ -1,15 +1,17 @@ -import type { APIProvider, CommerceAPIConfig } from '@commerce/api' +import type { CommerceAPIConfig } from '@commerce/api' import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api' -import fetchGraphqlApi from './utils/fetch-graphql-api' - -import login from './operations/login' +import getAllFacets from './operations/get-all-facets' +import getAllCollections from './operations/get-all-collection' import getAllPages from './operations/get-all-pages' -import getPage from './operations/get-page' -import getSiteInfo from './operations/get-site-info' -import getCustomerWishlist from './operations/get-customer-wishlist' import getAllProductPaths from './operations/get-all-product-paths' import getAllProducts from './operations/get-all-products' +import getCustomerWishlist from './operations/get-customer-wishlist' +import getPage from './operations/get-page' import getProduct from './operations/get-product' +import getSiteInfo from './operations/get-site-info' +import login from './operations/login' +import fetchGraphqlApi from './utils/fetch-graphql-api' + export interface VendureConfig extends CommerceAPIConfig {} @@ -40,6 +42,8 @@ const operations = { getAllProductPaths, getAllProducts, getProduct, + getAllFacets, + getAllCollections, } export const provider = { config, operations } diff --git a/framework/vendure/api/operations/get-all-collection.ts b/framework/vendure/api/operations/get-all-collection.ts new file mode 100644 index 000000000..b7cc3a725 --- /dev/null +++ b/framework/vendure/api/operations/get-all-collection.ts @@ -0,0 +1,45 @@ +import { OperationContext } from '@commerce/api/operations' +import { Collection } from '@commerce/types/collection' +import { Provider, VendureConfig } from '..' +import { GetAllCollectionsQuery } from '../../schema' +import { getAllCollectionsQuery } from '../../utils/queries/get-all-collections-query' + +export type CollectionVariables = { first?: number } + +export default function getAllCollectionsOperation({ + commerce, +}: OperationContext) { + async function getAllCollections(opts?: { + variables?: CollectionVariables + config?: Partial + preview?: boolean + }): Promise<{ collections: Collection[] }> + + async function getAllCollections({ + query = getAllCollectionsQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: CollectionVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ collections: Collection[] | any[] }> { + const config = commerce.getConfig(cfg) + const variables = { + input: { + take: vars.first, + groupByCollection: true, + }, + } + const { data } = await config.fetch(query, { + variables, + }) + + return { + collections: data.collections.items, + } + } + + return getAllCollections +} diff --git a/framework/vendure/api/operations/get-all-facets.ts b/framework/vendure/api/operations/get-all-facets.ts new file mode 100644 index 000000000..0bde04090 --- /dev/null +++ b/framework/vendure/api/operations/get-all-facets.ts @@ -0,0 +1,46 @@ +import { OperationContext } from '@commerce/api/operations' +import { Facet } from '@commerce/types/facet' +import { Provider, VendureConfig } from '../' +import { FacetFilterParameter, FacetSortParameter, GetAllFacetsQuery } from '../../schema' +import { getAllFacetsQuery } from '../../utils/queries/get-all-facets-query' + +export type FacetVariables = { first?: number, filter?: FacetFilterParameter, sort?: FacetSortParameter } + +export default function getAllFacetsOperation({ + commerce, +}: OperationContext) { + async function getAllFacets(opts?: { + variables?: FacetVariables + config?: Partial + preview?: boolean + }): Promise<{ facets: Facet[] }> + + async function getAllFacets({ + query = getAllFacetsQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: FacetVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ facets: Facet[] | any[] }> { + const config = commerce.getConfig(cfg) + const variables = { + options: { + take: vars.first, + filter: vars.filter, + sort: vars.sort, + }, + } + const { data } = await config.fetch(query, { + variables, + }) + + return { + facets: data.facets.items, + } + } + + return getAllFacets +} diff --git a/framework/vendure/api/operations/get-all-products.ts b/framework/vendure/api/operations/get-all-products.ts index 68d4ce9b7..5fb458f67 100644 --- a/framework/vendure/api/operations/get-all-products.ts +++ b/framework/vendure/api/operations/get-all-products.ts @@ -5,7 +5,7 @@ import { normalizeSearchResult } from '../../utils/normalize' import { getAllProductsQuery } from '../../utils/queries/get-all-products-query' import { OperationContext } from '@commerce/api/operations' -export type ProductVariables = { first?: number } +export type ProductVariables = { first?: number, facetValueIds?: string[] } export default function getAllProductsOperation({ commerce, @@ -14,7 +14,7 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - }): Promise<{ products: Product[] }> + }): Promise<{ products: Product[], totalItems: number }> async function getAllProducts({ query = getAllProductsQuery, @@ -25,11 +25,12 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - } = {}): Promise<{ products: Product[] | any[] }> { + } = {}): Promise<{ products: Product[] | any[], totalItems: number }> { const config = commerce.getConfig(cfg) const variables = { input: { take: vars.first, + facetValueIds: vars.facetValueIds, groupByProduct: true, }, } @@ -39,6 +40,7 @@ export default function getAllProductsOperation({ return { products: data.search.items.map((item) => normalizeSearchResult(item)), + totalItems: data.search.totalItems as number, } } diff --git a/framework/vendure/api/operations/get-product.ts b/framework/vendure/api/operations/get-product.ts index 4ab9ed2d9..0aa761ab0 100644 --- a/framework/vendure/api/operations/get-product.ts +++ b/framework/vendure/api/operations/get-product.ts @@ -2,7 +2,7 @@ import { Product } from '@commerce/types/product' import { OperationContext } from '@commerce/api/operations' import { Provider, VendureConfig } from '../' import { GetProductQuery } from '../../schema' -import { getProductQuery } from '../../utils/queries/get-product-query' +import { getProductQuery, getProductDetailQuery } from '../../utils/queries/get-product-query' export default function getProductOperation({ commerce, @@ -16,10 +16,8 @@ export default function getProductOperation({ variables: { slug: string } config?: Partial preview?: boolean - }): Promise { + }): Promise { const config = commerce.getConfig(cfg) - - const locale = config.locale const { data } = await config.fetch(query, { variables }) const product = data.product @@ -28,7 +26,6 @@ export default function getProductOperation({ return product.optionGroups.find((og) => og.id === id)!.name } return { - product: { id: product.id, name: product.name, description: product.description, @@ -49,20 +46,19 @@ export default function getProductOperation({ values: [{ label: o.name }], })), })), - price: { - value: product.variants[0].priceWithTax / 100, - currencyCode: product.variants[0].currencyCode, - }, + price: product.variants[0].priceWithTax / 100, + currencyCode: product.variants[0].currencyCode, options: product.optionGroups.map((og) => ({ id: og.id, displayName: og.name, values: og.options.map((o) => ({ label: o.name })), })), - } as Product, - } + facetValueIds: product.facetValues.map(item=> item.id), + collectionIds: product.collections.map(item => item.id) + } as Product } - return {} + return null } return getProduct diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index af6eb5051..2785626f9 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -1,5 +1,6 @@ import { ResetPassword } from './schema.d'; import { requestPasswordReset } from '@framework/schema'; +import { FacetValue } from './schema.d'; export type Maybe = T | null export type Exact = { [K in keyof T]: T[K] @@ -95,6 +96,10 @@ export type QueryProductsArgs = { options?: Maybe } +export type QueryFacetsArgs = { + options?: Maybe +} + export type QuerySearchArgs = { input: SearchInput } @@ -2729,6 +2734,13 @@ export type ProductListOptions = { filter?: Maybe } +export type FacetListOptions = { + skip?: Maybe + take?: Maybe + sort?: Maybe + filter?: Maybe +} + export type UpdateOrderItemsResult = | Order | OrderModificationError @@ -2886,6 +2898,23 @@ export type ProductVariantSortParameter = { discountPrice?: Maybe } + +export type FacetFilterParameter = { + createdAt?: Maybe + updatedAt?: Maybe + languageCode?: Maybe + name?: Maybe + code?: Maybe +} + +export type FacetSortParameter = { + id?: Maybe + createdAt?: Maybe + updatedAt?: Maybe + name?: Maybe + code?: Maybe +} + export type CustomerFilterParameter = { createdAt?: Maybe updatedAt?: Maybe @@ -3010,7 +3039,9 @@ export type CartFragment = { __typename?: 'Order' } & Pick< export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick< SearchResult, - 'productId' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' + 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' + | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' + | 'collectionIds' | 'facetValueIds' | 'collectionIds' > & { productAsset?: Maybe< { __typename?: 'SearchResultAsset' } & Pick< @@ -3223,7 +3254,43 @@ export type GetAllProductsQueryVariables = Exact<{ export type GetAllProductsQuery = { __typename?: 'Query' } & { search: { __typename?: 'SearchResponse' } & { - items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment> + items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>, + 'totalItems' + } +} + +export type GetAllFacetsQuery = { __typename?: 'Query' } & { + facets: { __typename?: 'FacetList' } & { + items: Array< + { __typename?: 'Facet' } & Pick< + Facet, + 'id' | 'name' | 'code' | 'values' + > + & { + parent?: Maybe<{ __typename?: 'Facet' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Facet' } & Pick> + > + } + >, + 'totalItems' + } +} + +export type GetAllCollectionsQuery = { __typename?: 'Query' } & { + collections: { __typename?: 'CollectionList' } & { + items: Array< + { __typename?: 'Collection' } & Pick< + Collection, + 'id' | 'name' | 'slug' + > & { + parent?: Maybe<{ __typename?: 'Collection' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Collection' } & Pick> + > + } + >, + 'totalItems' } } @@ -3271,7 +3338,7 @@ export type GetProductQuery = { __typename?: 'Query' } & { variants: Array< { __typename?: 'ProductVariant' } & Pick< ProductVariant, - 'id' | 'priceWithTax' | 'currencyCode' + 'id' | 'priceWithTax' | 'currencyCode' | 'price' > & { options: Array< { __typename?: 'ProductOption' } & Pick< @@ -3306,6 +3373,18 @@ export type GetProductQuery = { __typename?: 'Query' } & { > } > + facetValues: Array< + { __typename?: 'FacetValue' } & Pick< + FacetValue, + 'id' + > + > + collections: Array< + { __typename?: 'Collection' } & Pick< + Collection, + 'id' + > + > } > } diff --git a/framework/vendure/utils/fragments/search-result-fragment.ts b/framework/vendure/utils/fragments/search-result-fragment.ts index 6155b5b47..d2d82f42e 100644 --- a/framework/vendure/utils/fragments/search-result-fragment.ts +++ b/framework/vendure/utils/fragments/search-result-fragment.ts @@ -19,6 +19,8 @@ export const searchResultFragment = /* GraphQL */ ` min max } - } + }, + facetValueIds, + collectionIds, } ` diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index 09c1c6e42..cc76e5f97 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,22 +1,23 @@ -import { Product } from '@commerce/types/product' import { Cart } from '@commerce/types/cart' +import { ProductCard } from '@commerce/types/product' import { CartFragment, SearchResultFragment } from '../schema' -export function normalizeSearchResult(item: SearchResultFragment): Product { +export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { id: item.productId, name: item.productName, - description: item.description, slug: item.slug, - path: item.slug, - images: [{ url: item.productAsset?.preview + '?w=800&mode=crop' || '' }], - variants: [], - price: { - value: (item.priceWithTax as any).min / 100, - currencyCode: item.currencyCode, - }, - options: [], - sku: item.sku, + imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '', + price: (item.priceWithTax as any).min / 100, + currencyCode: item.currencyCode, + facetValueIds: item.facetValueIds, + collectionIds: item.collectionIds, + + // TODO: + // oldPrice: item.price + // discount + // isNotSell + // weight } } diff --git a/framework/vendure/utils/queries/get-all-collections-query.ts b/framework/vendure/utils/queries/get-all-collections-query.ts new file mode 100644 index 000000000..359c81ea9 --- /dev/null +++ b/framework/vendure/utils/queries/get-all-collections-query.ts @@ -0,0 +1,12 @@ +export const getAllCollectionsQuery = /* GraphQL */ ` +query collections ($options: CollectionListOptions) { + collections (options: $options){ + totalItems, + items { + id + name + slug + } + } +} +` diff --git a/framework/vendure/utils/queries/get-all-facets-query.ts b/framework/vendure/utils/queries/get-all-facets-query.ts new file mode 100644 index 000000000..906507c69 --- /dev/null +++ b/framework/vendure/utils/queries/get-all-facets-query.ts @@ -0,0 +1,17 @@ +export const getAllFacetsQuery = /* GraphQL */ ` +query facets ($options: FacetListOptions) { + facets (options: $options){ + totalItems, + items { + id + name + code + values { + id + name + code + } + } + } +} +` diff --git a/framework/vendure/utils/queries/get-all-products-query.ts b/framework/vendure/utils/queries/get-all-products-query.ts index 1b44b2017..007c6594d 100644 --- a/framework/vendure/utils/queries/get-all-products-query.ts +++ b/framework/vendure/utils/queries/get-all-products-query.ts @@ -3,6 +3,7 @@ import { searchResultFragment } from '../fragments/search-result-fragment' export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts($input: SearchInput!) { search(input: $input) { + totalItems items { ...SearchResult } diff --git a/framework/vendure/utils/queries/get-collections-query.ts b/framework/vendure/utils/queries/get-collections-query.ts index 79e00a292..f07a85249 100644 --- a/framework/vendure/utils/queries/get-collections-query.ts +++ b/framework/vendure/utils/queries/get-collections-query.ts @@ -24,7 +24,7 @@ export const getCollectionsNameQuery = /* GraphQL */ ` collections{ items{ name - link:slug + slug } } } diff --git a/framework/vendure/utils/queries/get-product-query.ts b/framework/vendure/utils/queries/get-product-query.ts index b2c502da9..6db960a96 100644 --- a/framework/vendure/utils/queries/get-product-query.ts +++ b/framework/vendure/utils/queries/get-product-query.ts @@ -36,6 +36,28 @@ export const getProductQuery = /* GraphQL */ ` name } } + facetValues { + id + } + collections { + id + } } } ` +export const getProductDetailQuery = /* GraphQL */ ` + query GetProductDetail($slug: String! = "hand-trowel") { + product(slug: $slug) { + name + description + variants { + price + priceWithTax + } + assets { + preview + name + } + } +} +` \ No newline at end of file diff --git a/package.json b/package.json index 84a77cf71..8474be667 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "body-scroll-lock": "^3.1.5", "classnames": "^2.3.1", "cookie": "^0.4.1", + "dns": "^0.2.2", "email-validator": "^2.0.4", "eslint": "^7.32.0", "eslint-config-next": "^11.1.2", @@ -35,6 +36,7 @@ "lodash.debounce": "^4.0.8", "lodash.random": "^3.2.0", "lodash.throttle": "^4.1.1", + "net": "^1.0.2", "next": "^11.0.0", "next-seo": "^4.26.0", "next-themes": "^0.0.14", diff --git a/pages/index.tsx b/pages/index.tsx index 3fa86079d..010094de4 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,19 +1,37 @@ +import { ProductCard } from '@commerce/types/product'; +import { ProductVariables } from '@framework/api/operations/get-all-products'; +import { Collection, FacetValue } from '@framework/schema'; +import commerce from '@lib/api/commerce'; +import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; -import { FeaturedProductsCarousel, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; +import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED } from 'src/utils/constanst.utils'; +import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getAllPromies, getFreshFacetId } from 'src/utils/funtion.utils'; +import { PromiseWithKey } from 'src/utils/types.utils'; -export default function Home() { +interface Props { + featuredAndDiscountFacetsValue: FacetValue[], + freshProducts: ProductCard[], + featuredProducts: ProductCard[], + collections: Collection[] + +} +export default function Home({ featuredAndDiscountFacetsValue, + freshProducts, featuredProducts, + collections }: Props) { return ( <> + - - + + - + {/* // todo: uncomment @@ -22,4 +40,83 @@ export default function Home() { ) } + +export async function getStaticProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const { facets } = await commerce.getAllFacets({ + variables: {}, + config, + preview, + }) + props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) + + // fresh products + const freshProductvariables: ProductVariables = {} + const freshFacetId = getFreshFacetId(facets) + if (freshFacetId) { + freshProductvariables.facetValueIds = [freshFacetId] + const freshProductsPromise = commerce.getAllProducts({ + variables: freshProductvariables, + config, + preview, + }) + promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) + } else { + props.freshProducts = [] + } + + + // featured products + const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) + const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) + const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds] + + if (facetValueIdsForFeaturedProducts.length > 0) { + const featuredProductsPromise = commerce.getAllProducts({ + variables: { + facetValueIds: facetValueIdsForFeaturedProducts + }, + config, + preview, + }) + promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + } else { + props.featuredProducts = [] + } + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + + return { + props, + revalidate: 60, + } + } catch (err) { + + } +} + + Home.Layout = Layout diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index ab9a1c17c..2da14a995 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,17 +1,114 @@ +import { Product } from '@framework/schema' +import commerce from '@lib/api/commerce' +import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' +import { MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils' import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' +import { getAllPromies } from 'src/utils/funtion.utils' +import { PromiseWithKey } from 'src/utils/types.utils' + +export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType) { -export default function Slug() { return <> - + - + - + } +export async function getStaticProps({ + params, + locale, + locales, + preview, +}: GetStaticPropsContext<{ slug: string }>) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const product = await commerce.getProduct({ + variables: { slug: params!.slug }, + config, + preview, + }) + props.product = product + + + if (!product) { + throw new Error(`Product with slug '${params!.slug}' not found`) + } + + // relevant product (filter by product detail's facetIds) + const relevantFacetIds = product.facetValueIds + if (relevantFacetIds && relevantFacetIds.length > 0) { + const relevantProductsPromise = commerce.getAllProducts({ + variables: { + first: MAX_PRODUCT_CAROUSEL, + facetValueIds: relevantFacetIds, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'relevantProducts', promise: relevantProductsPromise, keyResult: 'products' }) + } else { + props.relevantProducts = [] + } + + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + + if (props.relevantProducts.length > 0) { + const relevantProducts = props.relevantProducts.filter((item: Product) => item.id !== product.id) + props.relevantProducts = relevantProducts + } + + return { + props, + revalidate: REVALIDATE_TIME, + } + } catch (err) { + console.log('err: ', err) + } +} + + +export async function getStaticPaths({ locales }: GetStaticPathsContext) { + const { products } = await commerce.getAllProductPaths() + + return { + paths: locales + ? locales.reduce((arr, locale) => { + // Add a product path for every locale + products.forEach((product: any) => { + arr.push(`/${locale}/product${product.path}`) + }) + return arr + }, []) + : products.map((product: any) => `/product${product.path}`), + fallback: 'blocking', + } +} + + Slug.Layout = Layout diff --git a/pages/products.tsx b/pages/products.tsx index 4f9c4eb66..4a37ab097 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -1,19 +1,97 @@ +import { ProductCard } from '@commerce/types/product'; +import { Collection, Facet } from '@framework/schema'; +import commerce from '@lib/api/commerce'; +import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { ViewedProducts } from 'src/components/modules/product-detail'; import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter'; -import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner'; -import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE, REVALIDATE_TIME } from 'src/utils/constanst.utils'; +import { getAllPromies } from 'src/utils/funtion.utils'; +import { PromiseWithKey, SortOrder } from 'src/utils/types.utils'; import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner'; +interface Props { + facets: Facet[], + collections: Collection[], + productsResult: { products: ProductCard[], totalItems: number }, -export default function Products() { +} + +export default function Products({ facets, collections, productsResult }: Props) { return ( <> - - + + ) } +export async function getStaticProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const facetsPromise = commerce.getAllFacets({ + variables: { + sort: { + code: SortOrder.Asc + }, + filter: { + code: { + in: [CODE_FACET_FEATURED, CODE_FACET_BRAND] + } + } + }, + config, + preview, + }) + + promisesWithKey.push({ key: 'facets', promise: facetsPromise, keyResult: 'facets' }) + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + // products + const productsPromise = commerce.getAllProducts({ + variables: { + first: DEFAULT_PAGE_SIZE, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'productsResult', promise: productsPromise }) + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + + return { + props, + revalidate: REVALIDATE_TIME, + } + } catch (err) { + + } +} + Products.Layout = Layout diff --git a/pages/test.tsx b/pages/test.tsx index b60fe63c7..9a4db4421 100644 --- a/pages/test.tsx +++ b/pages/test.tsx @@ -1,18 +1,34 @@ -import { Layout } from 'src/components/common' -import { useMessage } from 'src/components/contexts' - -export default function Test() { - const { showMessageError } = useMessage() - - const handleClick = () => { - showMessageError("Create account successfully") - } +import commerce from '@lib/api/commerce'; +import { GetStaticPropsContext } from 'next'; +import { ProductCard } from '@commerce/types/product'; +import { Layout, ListProductCardSkeleton } from 'src/components/common'; +interface Props { + productDetail: ProductCard[], +} +export default function Home({ productDetail }: Props) { return ( <> - + {/* */} + {/* */} + + Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ab qui magnam debitis ex laborum laboriosam suscipit! Totam excepturi eum libero. + ) } -Test.Layout = Layout + +export async function getServerSideProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + + return { + props: {}, + } +} + + +Home.Layout = Layout diff --git a/public/assets/images/default_img.jpg b/public/assets/images/default_img.jpg new file mode 100644 index 000000000..f72d03596 Binary files /dev/null and b/public/assets/images/default_img.jpg differ diff --git a/src/components/common/EmptyCommon/EmptyCommon.module.scss b/src/components/common/EmptyCommon/EmptyCommon.module.scss index a31ba4374..4014faeea 100644 --- a/src/components/common/EmptyCommon/EmptyCommon.module.scss +++ b/src/components/common/EmptyCommon/EmptyCommon.module.scss @@ -4,13 +4,16 @@ padding: 1.6rem; margin: auto; .imgWrap { - min-width: 10rem; + min-height: 10rem; text-align: center; + img { + min-height: 10rem; + } } .description { color: var(--disabled); text-align: center; - margin-top: .8rem; + margin-top: 0.8rem; } } diff --git a/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss b/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss index 036e84629..3f95f92da 100644 --- a/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss +++ b/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss @@ -21,6 +21,9 @@ width: 24rem; height: 24rem; } + img { + object-fit: contain; + } } .right{ margin-left: 1.2rem; diff --git a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx index a82b6857b..23b9ac987 100644 --- a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx +++ b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx @@ -1,39 +1,55 @@ +import { ProductCard } from '@commerce/types/product' +import { Facet, FacetValue } from '@framework/schema' +import Link from 'next/link' import React from 'react' -import { FeaturedProductProps } from 'src/utils/types.utils' -import s from './FeaturedProductCard.module.scss' -import { LANGUAGE } from '../../../utils/language.utils' -import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy' -import ButtonCommon from '../ButtonCommon/ButtonCommon' +import { ROUTE } from 'src/utils/constanst.utils' import { ImgWithLink } from '..' -export interface FeaturedProductCardProps extends FeaturedProductProps { - buttonText?: string +import { LANGUAGE } from '../../../utils/language.utils' +import ButtonCommon from '../ButtonCommon/ButtonCommon' +import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy' +import s from './FeaturedProductCard.module.scss' +export interface FeaturedProductCardProps extends ProductCard { + buttonText?: string, + subText?: string, } const FeaturedProductCard = ({ imageSrc, - title, - subTitle, + name, + slug, price, - originPrice, + subText, + currencyCode, buttonText = LANGUAGE.BUTTON_LABEL.BUY_NOW, }: FeaturedProductCardProps) => { + + return (

- + + + + +
-
{title}
-
{subTitle}
+ + +
{name}
+
+ +
{subText}
-
{price}
-
{originPrice}
+
{price} {currencyCode}
+ {/* TODO: */} + {/*
{originPrice}
*/}
- +
{buttonText} diff --git a/src/components/common/Header/Header.tsx b/src/components/common/Header/Header.tsx index f3a514970..092bcc8e7 100644 --- a/src/components/common/Header/Header.tsx +++ b/src/components/common/Header/Header.tsx @@ -1,5 +1,6 @@ import classNames from 'classnames' import React, { memo, useEffect, useRef, useState } from 'react' +import { useProductFilter } from 'src/components/contexts' import { useModalCommon } from 'src/components/hooks' import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate' import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo' @@ -9,12 +10,12 @@ import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu' import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile' import s from './Header.module.scss' interface props { - toggleFilter: () => void, - visibleFilter: boolean + } -const Header = memo(({ toggleFilter, visibleFilter }: props) => { +const Header = memo(({ }: props) => { const headeFullRef = useRef(null) + const { toggleProductFilter: toggleFilter } = useProductFilter() const [isFullHeader, setIsFullHeader] = useState(true) const [isModeAuthenRegister, setIsModeAuthenRegister] = useState(false) const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }) @@ -63,7 +64,6 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
void openModalRegister: () => void openModalInfo: () => void @@ -38,7 +37,6 @@ const HeaderMenu = memo( ({ isFull, isStickyHeader, - visibleFilter, openModalLogin, openModalRegister, openModalInfo, @@ -94,6 +92,7 @@ const HeaderMenu = memo( ], [logout] ) + return (
-
+
)} +
-
-
-
- -
+
+
- + { + (!initialQueryFlag && loading && !productSearchResult) && + } +
diff --git a/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx b/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx new file mode 100644 index 000000000..a10cec443 --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx @@ -0,0 +1,40 @@ +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; +import { SelectCommon } from 'src/components/common'; +import { OPTIONS_SORT_PRODUCT, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; + +const ProductSort = () => { + const router = useRouter() + const [sortValue, setSortValue] = useState(); + + useEffect(() => { + const rs = router.query[QUERY_KEY.SORTBY] as string + if (rs) { + setSortValue(rs) + } + }, [router.query]) + + const onSortChange = (value: string) => { + setSortValue(value) + router.push({ + pathname: ROUTE.PRODUCTS, + query: { + ...router.query, + [QUERY_KEY.SORTBY]: value + } + }, + undefined, { shallow: true } + ) + } + + return ( + + ); +}; + +export default ProductSort; \ No newline at end of file diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss new file mode 100644 index 000000000..a27b395c3 --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss @@ -0,0 +1,12 @@ + +.productsMenuNavigationTablet { + @apply hidden; + @screen md { + @apply block; + padding-right: 2.4rem; + } + @screen xl { + @apply block; + width: 25%; + } +} diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx new file mode 100644 index 000000000..d31609639 --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx @@ -0,0 +1,32 @@ +import { Collection, Facet } from '@framework/schema' +import React from 'react' +import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation' +import { QUERY_KEY } from 'src/utils/constanst.utils' +import s from './ProductsMenuNavigationTablet.module.scss' + +interface Props { + facets: Facet[] + collections: Collection[] + +} + +const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => { + return ( +
+ + { + facets.map(item => ) + } +
+ ) +} + +export default ProductsMenuNavigationTablet diff --git a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx index f636157fa..e91599a2c 100644 --- a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx +++ b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx @@ -189,13 +189,13 @@ const RecipesList = ({ data =recipe}:Props) => {
- +
- +
diff --git a/src/styles/_base.scss b/src/styles/_base.scss index 1eab49e38..e41b6763f 100644 --- a/src/styles/_base.scss +++ b/src/styles/_base.scss @@ -4,7 +4,7 @@ :root { --primary: #5b9a74; --primary-light: #e3f2e9; - --primary-lightest: #effaf4; + --primary-lightest: #F1F8F4; --info-dark: #00317a; --info: #3468B7; diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index f77991604..7d0cf9ba5 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -1,135 +1,177 @@ +import DefaultImg from '../../public/assets/images/default_img.jpg' + +export const REVALIDATE_TIME = 60 +export const MAX_PRODUCT_CAROUSEL = 20 export const BLUR_DATA_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8fBIAApUBruKYvzsAAAAASUVORK5CYII=' +export const DEFAULT_IMG = DefaultImg export const SOCIAL_LINKS = { - FB: 'FB', - TWITTER: 'TWITTER', - YOUTUBE: 'YOUTUBE', - IG: 'IG', + FB: 'FB', + TWITTER: 'TWITTER', + YOUTUBE: 'YOUTUBE', + IG: 'IG', } export const ROUTE = { - HOME: '/', - ABOUT: '/about', - ACCOUNT: '/account', + HOME: '/', + ABOUT: '/about', + ACCOUNT: '/account', - PRODUCTS: '/products', - PRODUCT_DETAIL: '/product', - - BLOGS: '/blogs', - BLOG_DETAIL: '/blog', + PRODUCTS: '/products', + PRODUCT_DETAIL: '/product', - RECIPES: '/recipes', - RECIPE_DETAIL: '/recipe', + BLOGS: '/blogs', + BLOG_DETAIL: '/blog', - NOTIFICATION: '/notifications', - BUSSINESS: '/bussiness', - CONTACT: '/contact', - CHECKOUT: '/checkout', - FAQ: '/faq', - CUSTOMER_SERVICE: '/customer-service', - TERM_CONDITION: '/term-condition', - PRIVACY_POLICY: '/privacy-policy', - FORGOT_PASSWORD: '/forgot-password' + RECIPES: '/recipes', + RECIPE_DETAIL: '/recipe', + + NOTIFICATION: '/notifications', + BUSSINESS: '/bussiness', + CONTACT: '/contact', + CHECKOUT: '/checkout', + FAQ: '/faq', + CUSTOMER_SERVICE: '/customer-service', + TERM_CONDITION: '/term-condition', + PRIVACY_POLICY: '/privacy-policy', + FORGOT_PASSWORD: '/forgot-password' } export const ACCOUNT_TAB = { - CUSTOMER_INFO: '', - ORDER: 'orders', - FAVOURITE: 'wishlist', + CUSTOMER_INFO: '', + ORDER: 'orders', + FAVOURITE: 'wishlist', } export const LOCAL_STORAGE_KEY = { TOKEN: 'token' } +export const QUERY_SPLIT_SEPERATOR = ',' export const QUERY_KEY = { - TAB: 'tab', - CATEGORY: 'category', - BRAND: 'brand', - FEATURED: 'feature', - SORTBY:'sortby', - RECIPES:'recipes' + TAB: 'tab', + CATEGORY: 'category', + BRAND: 'brand', + FEATURED: 'featured', + SORTBY: 'sortby', + RECIPES: 'recipes', + PAGE: 'page', +} + +export const PRODUCT_SORT_OPTION_VALUE = { + NAME_ASC: 'name_asc', + NAME_DESC: 'name_desc', + PRICE_ASC: 'price_asc', + PRICE_DESC: 'price_desc', + } export enum ProductFeature { - BestSellers = 'Best Sellers', - Sales = 'Sales', - NewItem = 'New Item', - Viewed = 'Viewed', + BestSellers = 'Best Sellers', + Sales = 'Sales', + NewItem = 'New Item', + Viewed = 'Viewed', } export const KEY = { - ENTER: 'Enter', + ENTER: 'Enter', } export const OPTION_ALL = 'all'; -export const DEFAULT_PAGE_SIZE=20; +export const DEFAULT_PAGE_SIZE = 20; export const CATEGORY = [ - { - name: 'All', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, - }, - { - name: 'Veggie', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, - }, - { - name: 'Seafood', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, - }, - { - name: 'Frozen', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, - }, - { - name: 'Coffee Bean', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, - }, - { - name: 'Sauce', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, - }, - ] - - export const BRAND = [ - { - name: 'Maggi', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, - }, - { - name: 'Chomilex', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, - }, - { - name: 'Chinsu', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, - }, - ] - -export const FEATURED = [ - { - name: 'Best Sellers', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=best_sellers`, - }, - { - name: 'Sales', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=sales`, - }, - { - name: 'New Item', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=new_item`, - }, - { - name: 'Viewed', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=viewed`, - }, - ] - -export const DEFAULT_BLOG_PAGE_SIZE=6; + { + name: 'All', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, + }, + { + name: 'Veggie', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, + }, + { + name: 'Seafood', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, + }, + { + name: 'Frozen', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, + }, + { + name: 'Coffee Bean', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, + }, + { + name: 'Sauce', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, + }, +] -export const FILTER_PAGE = [ROUTE.HOME,ROUTE.PRODUCTS] +export const BRAND = [ + { + name: 'Maggi', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, + }, + { + name: 'Chomilex', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, + }, + { + name: 'Chinsu', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, + }, +] + +export const CODE_FACET_FEATURED = 'featured' +export const CODE_FACET_DISCOUNT = 'discount' +export const CODE_FACET_BRAND = 'brand' +export const CODE_FACET_FEATURED_VARIANT = { + FRESH: 'fresh', +} + +export const OPTIONS_SORT_PRODUCT = [ + { + name: 'By Name (A-Z)', + value: PRODUCT_SORT_OPTION_VALUE.NAME_ASC, + }, + { + name: 'By Name (Z-A)', + value: PRODUCT_SORT_OPTION_VALUE.NAME_DESC, + }, + { + name: 'Price (Low to High)', + value: PRODUCT_SORT_OPTION_VALUE.PRICE_ASC, + }, + { + name: 'Price (High to Low)', + value: PRODUCT_SORT_OPTION_VALUE.PRICE_DESC, + }, +]; + + +export const FEATURED = [ + { + name: 'Best Sellers', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=best_sellers`, + }, + { + name: 'Sales', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=sales`, + }, + { + name: 'New Item', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=new_item`, + }, + { + name: 'Viewed', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=viewed`, + }, +] + +export const DEFAULT_BLOG_PAGE_SIZE = 6; + +export const FILTER_PAGE = [ROUTE.HOME, ROUTE.PRODUCTS] export const STATE_OPTIONS = [ { @@ -141,3 +183,4 @@ export const STATE_OPTIONS = [ value: 'Hà Nội', }, ] + diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index 43d517589..408d9d0fd 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,11 +1,130 @@ +import { Facet } from "@commerce/types/facet"; +import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; +import { PromiseWithKey, SortOrder } from "./types.utils"; + export function isMobile() { - return window.innerWidth < 768 + return window.innerWidth < 768 } -export function removeItem(arr: Array, value: T): Array { - const index = arr.indexOf(value); - if (index > -1) { - arr.splice(index, 1); +export function getPageFromQuery(pageQuery: string) { + let page = 0 + try { + page = +pageQuery + if (isNaN(page)) { + page = 0 } - return [...arr]; + } catch (err) { + page = 0 + } + return page +} + + +export function getProductSortParamFromQuery(query: string) { + let rs = {} as SearchResultSortParameter + switch (query) { + case PRODUCT_SORT_OPTION_VALUE.NAME_ASC: + rs = { + name: SortOrder.Asc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.NAME_DESC: + rs = { + name: SortOrder.Desc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.PRICE_ASC: + rs = { + price: SortOrder.Asc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.PRICE_DESC: + rs = { + price: SortOrder.Desc + } + break; + + default: + break; + } + + return rs +} + +export function removeItem(arr: Array, value: T): Array { + const index = arr.indexOf(value); + if (index > -1) { + arr.splice(index, 1); + } + return [...arr]; +} + +function findFacetByCode(code: string, facets?: Facet) { + return facets?.values.find((item: FacetValue) => item.code === code) +} + +export function getFeaturedFacetId(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) + return featuredFacet?.id +} + +export function getFreshFacetId(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) + const freshFacetValue = findFacetByCode(CODE_FACET_FEATURED_VARIANT.FRESH, featuredFacet) + + return freshFacetValue?.id +} + +export function getAllFacetValueIdsByParentCode(facets: Facet[], code: string) { + const featuredFacet = facets.find((item: Facet) => item.code === code) + const rs = featuredFacet?.values.map((item: FacetValue) => item.id) + + return rs || [] +} + +export function getAllFacetValuesForFeatuedProducts(facets: Facet[]) { + const facetsRs = facets.filter((item: Facet) => item.code === CODE_FACET_FEATURED || item.code === CODE_FACET_DISCOUNT) + let rs = [] as FacetValue[] + facetsRs.map((item: Facet) => { + rs = rs.concat(item.values) + return null + }) + return rs +} + +export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): string { + if (!ids || ids?.length === 0) { + return '' + } + + const facetItems = facets.filter((item: FacetValue) => ids.includes(item.id)) + const names = facetItems.map((item: FacetValue) => item.name) + return names.join(", ") +} + +export function getFacetIdsFromCodes(facets: FacetValue[], codes?: string[]): string[] { + if (!codes || codes?.length === 0) { + return [] + } + + const facetItems = facets.filter((item: FacetValue) => codes.includes(item.code)) + const ids = facetItems.map((item: FacetValue) => item.id) + return ids +} + +export const getCategoryNameFromCollectionId = (colelctions: Collection[], collectionId?: string ) => { + if (!collectionId) { + return '' + } + + const collection = colelctions.find(item => item.id === collectionId) + return collection?.name || '' +} + +export function getAllPromies(promies: PromiseWithKey[]) { + return promies.map(item => item.promise) } \ No newline at end of file diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index e2ce516d4..59243b80f 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -34,20 +34,31 @@ export interface BlogProps { export interface CheckOutForm { name?: string - email?:string + email?: string address?: string - city?:string - state?:string - code?:number - phone?:number - method?:string - shipping_fee?:number + city?: string + state?: string + code?: number + phone?: number + method?: string + shipping_fee?: number } export type MouseAndTouchEvent = MouseEvent | TouchEvent +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC', +} + export type filterContextType = { visible: boolean; open: () => void; close: () => void; -}; \ No newline at end of file +}; + +export type PromiseWithKey = { + key: string + promise: PromiseLike + keyResult?: string, +}