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 (
-
+
)}