@faststore/api 1.7.31 → 1.7.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/api.cjs.development.js +313 -181
- package/dist/api.cjs.development.js.map +1 -1
- package/dist/api.cjs.production.min.js +1 -1
- package/dist/api.cjs.production.min.js.map +1 -1
- package/dist/api.esm.js +313 -181
- package/dist/api.esm.js.map +1 -1
- package/dist/index.d.ts +27 -13
- package/dist/platforms/vtex/clients/index.d.ts +4 -1
- package/dist/platforms/vtex/clients/search/index.d.ts +3 -3
- package/dist/platforms/vtex/clients/search/types/AttributeSearchResult.d.ts +25 -51
- package/dist/platforms/vtex/clients/search/types/FacetSearchResult.d.ts +31 -0
- package/dist/platforms/vtex/clients/search/types/ProductSearchResult.d.ts +146 -154
- package/dist/platforms/vtex/clients/sp/index.d.ts +13 -0
- package/dist/platforms/vtex/index.d.ts +28 -14
- package/dist/platforms/vtex/resolvers/aggregateOffer.d.ts +10 -6
- package/dist/platforms/vtex/resolvers/facet.d.ts +2 -2
- package/dist/platforms/vtex/resolvers/facetValue.d.ts +2 -2
- package/dist/platforms/vtex/resolvers/offer.d.ts +7 -6
- package/dist/platforms/vtex/resolvers/product.d.ts +11 -2
- package/dist/platforms/vtex/resolvers/productGroup.d.ts +5 -2
- package/dist/platforms/vtex/utils/enhanceSku.d.ts +3 -3
- package/dist/platforms/vtex/utils/price.d.ts +2 -0
- package/dist/platforms/vtex/utils/productStock.d.ts +5 -0
- package/dist/typings/index.d.ts +2 -0
- package/package.json +2 -2
- package/src/platforms/vtex/clients/fetch.ts +1 -1
- package/src/platforms/vtex/clients/index.ts +3 -0
- package/src/platforms/vtex/clients/search/index.ts +6 -6
- package/src/platforms/vtex/clients/search/types/AttributeSearchResult.ts +24 -53
- package/src/platforms/vtex/clients/search/types/FacetSearchResult.ts +33 -0
- package/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +135 -164
- package/src/platforms/vtex/clients/sp/index.ts +67 -0
- package/src/platforms/vtex/index.ts +2 -2
- package/src/platforms/vtex/loaders/sku.ts +2 -2
- package/src/platforms/vtex/resolvers/aggregateOffer.ts +17 -35
- package/src/platforms/vtex/resolvers/facet.ts +5 -5
- package/src/platforms/vtex/resolvers/facetValue.ts +7 -6
- package/src/platforms/vtex/resolvers/offer.ts +107 -17
- package/src/platforms/vtex/resolvers/product.ts +55 -73
- package/src/platforms/vtex/resolvers/productGroup.ts +22 -13
- package/src/platforms/vtex/resolvers/query.ts +3 -3
- package/src/platforms/vtex/resolvers/searchResult.ts +24 -12
- package/src/platforms/vtex/utils/enhanceSku.ts +4 -4
- package/src/platforms/vtex/utils/facets.ts +8 -2
- package/src/platforms/vtex/utils/price.ts +10 -0
- package/src/platforms/vtex/utils/productStock.ts +25 -0
- package/src/typings/index.ts +4 -0
|
@@ -1,41 +1,23 @@
|
|
|
1
|
+
import type { Item } from '../clients/search/types/ProductSearchResult'
|
|
2
|
+
import type { StoreProduct } from './product'
|
|
3
|
+
import type { PromiseType } from '../../../typings'
|
|
4
|
+
import type { Resolver } from '..'
|
|
1
5
|
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
2
|
-
import type { Simulation } from '../clients/commerce/types/Simulation'
|
|
3
6
|
|
|
4
|
-
type
|
|
7
|
+
type Root = PromiseType<ReturnType<typeof StoreProduct.offers>>
|
|
5
8
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
items.sort((a, b) => {
|
|
14
|
-
if (inStock(a) && !inStock(b)) {
|
|
15
|
-
return -1
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!inStock(a) && inStock(b)) {
|
|
19
|
-
return 1
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return a.sellingPrice - b.sellingPrice
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export const StoreAggregateOffer: Record<string, Resolvers> = {
|
|
26
|
-
highPrice: ({ items }) => {
|
|
27
|
-
const availableItems = items.filter(inStock)
|
|
28
|
-
const highPrice = availableItems.pop()?.sellingPrice
|
|
29
|
-
|
|
30
|
-
return (highPrice ?? 0) / 1e2
|
|
31
|
-
},
|
|
32
|
-
lowPrice: ({ items }) => {
|
|
33
|
-
const availableItems = items.filter(inStock)
|
|
34
|
-
const lowPrice = availableItems[0]?.sellingPrice
|
|
35
|
-
|
|
36
|
-
return (lowPrice ?? 0) / 1e2
|
|
37
|
-
},
|
|
9
|
+
export const StoreAggregateOffer: Record<string, Resolver<Root>> & {
|
|
10
|
+
offers: Resolver<Root, any, Array<Item & { product: EnhancedSku }>>
|
|
11
|
+
} = {
|
|
12
|
+
highPrice: ({ product }) =>
|
|
13
|
+
product.isVariantOf.priceRange.sellingPrice.highPrice ?? 0,
|
|
14
|
+
lowPrice: (root) =>
|
|
15
|
+
root.product.isVariantOf.priceRange.sellingPrice.lowPrice ?? 0,
|
|
38
16
|
offerCount: ({ items }) => items.length,
|
|
39
17
|
priceCurrency: () => '',
|
|
40
|
-
offers: ({ items, product }) =>
|
|
18
|
+
offers: ({ items, product }) =>
|
|
19
|
+
items.map((item) => ({
|
|
20
|
+
...item,
|
|
21
|
+
product,
|
|
22
|
+
})),
|
|
41
23
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Resolver } from '..'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Facet } from '../clients/search/types/FacetSearchResult'
|
|
3
3
|
|
|
4
|
-
type Root =
|
|
4
|
+
type Root = Facet
|
|
5
5
|
|
|
6
6
|
export const StoreFacet: Record<string, Resolver<Root>> = {
|
|
7
|
-
key: ({ key }) => key,
|
|
8
|
-
label: ({
|
|
7
|
+
key: ({ key }) => key ?? '',
|
|
8
|
+
label: ({ name }) => name ?? 'unknown',
|
|
9
9
|
values: ({ values }) => values,
|
|
10
|
-
type: ({ type }) => (type === '
|
|
10
|
+
type: ({ type }) => (type === 'TEXT' ? 'BOOLEAN' : 'RANGE'),
|
|
11
11
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Resolver } from '..'
|
|
2
|
-
import type {
|
|
2
|
+
import type { FacetValue } from '../clients/search/types/FacetSearchResult'
|
|
3
3
|
|
|
4
|
-
type Root =
|
|
4
|
+
type Root = FacetValue
|
|
5
5
|
|
|
6
6
|
export const StoreFacetValue: Record<string, Resolver<Root>> = {
|
|
7
|
-
value: ({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
value: ({ value, range }) =>
|
|
8
|
+
value ?? `${range?.from ?? ''}-to-${range?.to ?? ''}`,
|
|
9
|
+
label: ({ name }) => name || 'unknown',
|
|
10
|
+
selected: ({ selected }) => selected,
|
|
11
|
+
quantity: ({ quantity }) => quantity,
|
|
11
12
|
}
|
|
@@ -1,26 +1,116 @@
|
|
|
1
|
-
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
2
1
|
import type { Resolver } from '..'
|
|
3
|
-
import type {
|
|
2
|
+
import type { StoreAggregateOffer } from './aggregateOffer'
|
|
3
|
+
import {
|
|
4
|
+
getFirstSeller,
|
|
5
|
+
inStock,
|
|
6
|
+
inStockOrderFormItem,
|
|
7
|
+
} from '../utils/productStock'
|
|
8
|
+
import { getItemPriceByKey } from '../utils/price'
|
|
9
|
+
import type { ArrayElementType } from '../../../typings'
|
|
10
|
+
import type { Item } from '../clients/search/types/ProductSearchResult'
|
|
11
|
+
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
4
12
|
import type { OrderFormItem } from '../clients/commerce/types/OrderForm'
|
|
5
13
|
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
type OrderFormProduct = OrderFormItem & { product: Promise<EnhancedSku> }
|
|
15
|
+
type SearchProduct = ArrayElementType<
|
|
16
|
+
ReturnType<typeof StoreAggregateOffer.offers>
|
|
17
|
+
>
|
|
18
|
+
type Root = SearchProduct | OrderFormProduct
|
|
19
|
+
|
|
20
|
+
const isSearchItem = (item: any): item is Item => 'sellers' in item
|
|
21
|
+
const isOrderFormItem = (item: any): item is OrderFormProduct =>
|
|
22
|
+
'skuName' in item
|
|
23
|
+
|
|
24
|
+
const getAvailability = (available: boolean) =>
|
|
25
|
+
available ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'
|
|
9
26
|
|
|
10
27
|
export const StoreOffer: Record<string, Resolver<Root>> = {
|
|
11
28
|
priceCurrency: () => '',
|
|
12
|
-
priceValidUntil: (
|
|
29
|
+
priceValidUntil: (item) => {
|
|
30
|
+
if (isSearchItem(item)) {
|
|
31
|
+
return getFirstSeller(item.sellers)?.commertialOffer.PriceValidUntil ?? ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isOrderFormItem(item)) {
|
|
35
|
+
return item.priceValidUntil ?? ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null
|
|
39
|
+
},
|
|
13
40
|
itemCondition: () => 'https://schema.org/NewCondition',
|
|
14
|
-
availability: (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
availability: async (item) => {
|
|
42
|
+
if (isSearchItem(item)) {
|
|
43
|
+
return getAvailability(!!inStock(item))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isOrderFormItem(item)) {
|
|
47
|
+
return getAvailability(inStockOrderFormItem(item.availability))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null
|
|
51
|
+
},
|
|
52
|
+
seller: (item) => {
|
|
53
|
+
if (isSearchItem(item)) {
|
|
54
|
+
return {
|
|
55
|
+
identifier: getFirstSeller(item.sellers)?.sellerId ?? '',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isOrderFormItem(item)) {
|
|
60
|
+
return {
|
|
61
|
+
identifier: item.seller,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
},
|
|
67
|
+
price: (item) => {
|
|
68
|
+
if (isSearchItem(item)) {
|
|
69
|
+
return getItemPriceByKey(item, 'spotPrice')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isOrderFormItem(item)) {
|
|
73
|
+
return item.price / 1e2
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null
|
|
77
|
+
},
|
|
78
|
+
sellingPrice: (item) => {
|
|
79
|
+
if (isSearchItem(item)) {
|
|
80
|
+
return getItemPriceByKey(item, 'Price')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isOrderFormItem(item)) {
|
|
84
|
+
return item.sellingPrice / 1e2
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null
|
|
88
|
+
},
|
|
89
|
+
listPrice: (item) => {
|
|
90
|
+
if (isSearchItem(item)) {
|
|
91
|
+
return getItemPriceByKey(item, 'ListPrice')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isOrderFormItem(item)) {
|
|
95
|
+
return item.listPrice / 1e2
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null
|
|
99
|
+
},
|
|
24
100
|
itemOffered: ({ product }) => product,
|
|
25
|
-
quantity: (
|
|
101
|
+
quantity: (item) => {
|
|
102
|
+
if (isSearchItem(item)) {
|
|
103
|
+
return item.sellers.reduce(
|
|
104
|
+
(quantity, seller) =>
|
|
105
|
+
quantity + seller.commertialOffer.AvailableQuantity,
|
|
106
|
+
0
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isOrderFormItem(item)) {
|
|
111
|
+
return item.quantity
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null
|
|
115
|
+
},
|
|
26
116
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { slugify } from '../utils/slugify'
|
|
2
1
|
import type { Resolver } from '..'
|
|
2
|
+
import { sortOfferByPrice } from '../utils/productStock'
|
|
3
|
+
import type { PromiseType } from '../../../typings'
|
|
4
|
+
import type { Query } from './query'
|
|
5
|
+
import type { Item } from '../clients/search/types/ProductSearchResult'
|
|
3
6
|
import type { EnhancedSku } from '../utils/enhanceSku'
|
|
4
|
-
import { sortOfferByPrice } from './aggregateOffer'
|
|
5
7
|
|
|
6
|
-
type Root =
|
|
8
|
+
type Root = PromiseType<ReturnType<typeof Query.product>>
|
|
7
9
|
|
|
8
10
|
const DEFAULT_IMAGE = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
imageText: 'image',
|
|
12
|
+
imageUrl:
|
|
11
13
|
'https://storecomponents.vtexassets.com/assets/faststore/images/image___117a6d3e229a96ad0e0d0876352566e2.svg',
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -16,84 +18,64 @@ const getPath = (link: string, id: string) => `/${getSlug(link, id)}/p`
|
|
|
16
18
|
const nonEmptyArray = <T>(array: T[] | null | undefined) =>
|
|
17
19
|
Array.isArray(array) && array.length > 0 ? array : null
|
|
18
20
|
|
|
19
|
-
export const StoreProduct: Record<string, Resolver<Root>>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
export const StoreProduct: Record<string, Resolver<Root>> & {
|
|
22
|
+
offers: Resolver<Root, any, { items: Item[]; product: EnhancedSku }>
|
|
23
|
+
isVariantOf: Resolver<Root, any, Root>
|
|
24
|
+
} = {
|
|
25
|
+
productID: ({ itemId }) => itemId,
|
|
26
|
+
name: ({ isVariantOf, name }) => name ?? isVariantOf.productName,
|
|
27
|
+
slug: ({ isVariantOf: { linkText }, itemId }) => getSlug(linkText, itemId),
|
|
23
28
|
description: ({ isVariantOf: { description } }) => description,
|
|
24
|
-
seo: ({ isVariantOf: {
|
|
25
|
-
title:
|
|
29
|
+
seo: ({ isVariantOf: { description, productName } }) => ({
|
|
30
|
+
title: productName,
|
|
26
31
|
description,
|
|
27
32
|
}),
|
|
28
33
|
brand: ({ isVariantOf: { brand } }) => ({ name: brand }),
|
|
29
|
-
breadcrumbList: ({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})),
|
|
38
|
-
{
|
|
39
|
-
name,
|
|
40
|
-
item: getPath(link, id),
|
|
41
|
-
position: categoryTrees.length + 1,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
numberOfItems: categoryTrees.length,
|
|
45
|
-
}),
|
|
46
|
-
image: ({ isVariantOf, images }) =>
|
|
47
|
-
(
|
|
48
|
-
nonEmptyArray(images) ??
|
|
49
|
-
nonEmptyArray(isVariantOf.images) ?? [DEFAULT_IMAGE]
|
|
50
|
-
).map(({ name, value }) => ({
|
|
51
|
-
alternateName: name ?? '',
|
|
52
|
-
url: value.replace('vteximg.com.br', 'vtexassets.com'),
|
|
53
|
-
})),
|
|
54
|
-
sku: ({ id }) => id,
|
|
55
|
-
gtin: ({ reference }) => reference ?? '',
|
|
56
|
-
review: () => [],
|
|
57
|
-
aggregateRating: () => ({}),
|
|
58
|
-
offers: async (product, _, ctx) => {
|
|
59
|
-
const {
|
|
60
|
-
loaders: { simulationLoader },
|
|
61
|
-
storage: { channel },
|
|
62
|
-
} = ctx
|
|
63
|
-
|
|
64
|
-
const { id, policies } = product
|
|
65
|
-
|
|
66
|
-
const sellers = policies.find(
|
|
67
|
-
(policy) => policy.id === channel.salesChannel
|
|
68
|
-
)?.sellers
|
|
34
|
+
breadcrumbList: ({
|
|
35
|
+
isVariantOf: { categories, productName, linkText },
|
|
36
|
+
itemId,
|
|
37
|
+
}) => {
|
|
38
|
+
return {
|
|
39
|
+
itemListElement: [
|
|
40
|
+
...categories.reverse().map((categoryPath, index) => {
|
|
41
|
+
const categoryNames = categoryPath.split('/')
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
43
|
+
return {
|
|
44
|
+
name: categoryNames[categoryNames.length - 2],
|
|
45
|
+
item: categoryPath.toLowerCase(),
|
|
46
|
+
position: index + 1,
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
{
|
|
50
|
+
name: productName,
|
|
51
|
+
item: getPath(linkText, itemId),
|
|
52
|
+
position: categories.length + 1,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
numberOfItems: categories.length,
|
|
76
56
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
57
|
+
},
|
|
58
|
+
image: ({ images }) =>
|
|
59
|
+
(nonEmptyArray(images) ?? [DEFAULT_IMAGE]).map(
|
|
60
|
+
({ imageUrl, imageText }) => ({
|
|
61
|
+
alternateName: imageText ?? '',
|
|
62
|
+
url: imageUrl.replace('vteximg.com.br', 'vtexassets.com'),
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
sku: ({ itemId }) => itemId,
|
|
66
|
+
gtin: ({ referenceId }) => referenceId[0]?.Value ?? '',
|
|
67
|
+
review: () => [],
|
|
68
|
+
aggregateRating: () => ({}),
|
|
69
|
+
offers: (product): { items: Item[]; product: Root } => {
|
|
88
70
|
return {
|
|
89
|
-
|
|
90
|
-
items: sortOfferByPrice(simulation.items),
|
|
71
|
+
items: sortOfferByPrice(product.isVariantOf.items),
|
|
91
72
|
product,
|
|
92
73
|
}
|
|
93
74
|
},
|
|
94
|
-
isVariantOf: (
|
|
95
|
-
|
|
96
|
-
|
|
75
|
+
isVariantOf: (root) => root,
|
|
76
|
+
// TODO: get this value. Fix any type
|
|
77
|
+
additionalProperty: ({ attributes = [] }: any) =>
|
|
78
|
+
attributes.map((attribute: any) => ({
|
|
97
79
|
name: attribute.key,
|
|
98
80
|
value: attribute.value,
|
|
99
81
|
})),
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { enhanceSku } from '../utils/enhanceSku'
|
|
2
|
-
import type { Product } from '../clients/search/types/ProductSearchResult'
|
|
3
2
|
import type { Resolver } from '..'
|
|
3
|
+
import type { PromiseType } from '../../../typings'
|
|
4
|
+
import type { StoreProduct } from './product'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
hasVariant: (root) => root.skus.map((sku) => enhanceSku(sku, root)),
|
|
7
|
-
productGroupID: ({ product }) => product,
|
|
8
|
-
name: ({ name }) => name,
|
|
9
|
-
additionalProperty: ({ textAttributes = [], productSpecifications = [] }) => {
|
|
10
|
-
const specs = new Set(productSpecifications)
|
|
6
|
+
type Root = PromiseType<ReturnType<typeof StoreProduct.isVariantOf>>
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
const BLOCKED_PROPERTIES: Record<string, boolean> = {
|
|
9
|
+
sellerId: true,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const StoreProductGroup: Record<string, Resolver<Root>> = {
|
|
13
|
+
hasVariant: (root) =>
|
|
14
|
+
root.isVariantOf.items.map((item) => enhanceSku(item, root.isVariantOf)),
|
|
15
|
+
productGroupID: ({ isVariantOf }) => isVariantOf.productId,
|
|
16
|
+
name: ({ isVariantOf }) => isVariantOf.productName,
|
|
17
|
+
additionalProperty: ({ isVariantOf: { properties } }) =>
|
|
18
|
+
properties.flatMap((property) => {
|
|
19
|
+
if (BLOCKED_PROPERTIES[property.name]) {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return property.values.map((propertyValue) => ({
|
|
24
|
+
name: property.name,
|
|
25
|
+
value: propertyValue,
|
|
17
26
|
}))
|
|
18
|
-
|
|
27
|
+
}),
|
|
19
28
|
}
|
|
@@ -77,7 +77,7 @@ export const Query = {
|
|
|
77
77
|
})
|
|
78
78
|
|
|
79
79
|
const skus = products.products
|
|
80
|
-
.map((product) => product.
|
|
80
|
+
.map((product) => product.items.map((sku) => enhanceSku(sku, product)))
|
|
81
81
|
.flat()
|
|
82
82
|
.filter((sku) => sku.sellers.length > 0)
|
|
83
83
|
|
|
@@ -86,8 +86,8 @@ export const Query = {
|
|
|
86
86
|
hasNextPage: products.pagination.after.length > 0,
|
|
87
87
|
hasPreviousPage: products.pagination.before.length > 0,
|
|
88
88
|
startCursor: '0',
|
|
89
|
-
endCursor: products.
|
|
90
|
-
totalCount: products.
|
|
89
|
+
endCursor: products.recordsFiltered.toString(),
|
|
90
|
+
totalCount: products.recordsFiltered,
|
|
91
91
|
},
|
|
92
92
|
// after + index is bigger than after+first itself because of the array flat() above
|
|
93
93
|
edges: skus.map((sku, index) => ({
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import { enhanceSku } from '../utils/enhanceSku'
|
|
2
2
|
import type { Resolver } from '..'
|
|
3
3
|
import type { SearchArgs } from '../clients/search'
|
|
4
|
-
import type {
|
|
4
|
+
import type { Facet } from '../clients/search/types/FacetSearchResult'
|
|
5
5
|
|
|
6
6
|
type Root = Omit<SearchArgs, 'type'>
|
|
7
7
|
|
|
8
|
-
const REMOVED_FACETS_FROM_COLLECTION_PAGE = ['departamento']
|
|
8
|
+
const REMOVED_FACETS_FROM_COLLECTION_PAGE = ['departamento', 'Departamento']
|
|
9
9
|
|
|
10
10
|
export const StoreSearchResult: Record<string, Resolver<Root>> = {
|
|
11
11
|
products: async (searchArgs, _, ctx) => {
|
|
12
12
|
const {
|
|
13
|
-
clients: { search },
|
|
13
|
+
clients: { search, sp },
|
|
14
14
|
} = ctx
|
|
15
15
|
|
|
16
16
|
const products = await search.products(searchArgs)
|
|
17
17
|
|
|
18
|
+
// Raise event on search's analytics API when performing
|
|
19
|
+
// a full text search.
|
|
20
|
+
if (searchArgs.query) {
|
|
21
|
+
sp.sendEvent({
|
|
22
|
+
type: 'search.query',
|
|
23
|
+
text: searchArgs.query,
|
|
24
|
+
misspelled: products.correction.misspelled,
|
|
25
|
+
match: products.recordsFiltered,
|
|
26
|
+
operator: products.operator,
|
|
27
|
+
}).catch(console.error)
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
const skus = products.products
|
|
19
31
|
.map((product) => {
|
|
20
|
-
const [maybeSku] = product.
|
|
32
|
+
const [maybeSku] = product.items
|
|
21
33
|
|
|
22
34
|
return maybeSku && enhanceSku(maybeSku, product)
|
|
23
35
|
})
|
|
@@ -28,8 +40,8 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
|
|
|
28
40
|
hasNextPage: products.pagination.after.length > 0,
|
|
29
41
|
hasPreviousPage: products.pagination.before.length > 0,
|
|
30
42
|
startCursor: '0',
|
|
31
|
-
endCursor: products.
|
|
32
|
-
totalCount: products.
|
|
43
|
+
endCursor: products.recordsFiltered.toString(),
|
|
44
|
+
totalCount: products.recordsFiltered,
|
|
33
45
|
},
|
|
34
46
|
edges: skus.map((sku, index) => ({
|
|
35
47
|
node: sku,
|
|
@@ -42,12 +54,12 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
|
|
|
42
54
|
clients: { search: is },
|
|
43
55
|
} = ctx
|
|
44
56
|
|
|
45
|
-
const facets = await is.facets(searchArgs)
|
|
57
|
+
const { facets } = await is.facets(searchArgs)
|
|
46
58
|
|
|
47
59
|
const isCollectionPage = !searchArgs.query
|
|
48
|
-
const filteredFacets = facets?.
|
|
60
|
+
const filteredFacets = facets?.reduce((acc, currentFacet) => {
|
|
49
61
|
const shouldFilterFacet = REMOVED_FACETS_FROM_COLLECTION_PAGE.includes(
|
|
50
|
-
currentFacet.
|
|
62
|
+
currentFacet.name
|
|
51
63
|
)
|
|
52
64
|
|
|
53
65
|
const shouldRemoveFacetFromCollectionPage =
|
|
@@ -58,8 +70,8 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
currentFacet.values.sort((a, b) => {
|
|
61
|
-
const firstItemLabel = a.
|
|
62
|
-
const secondItemLabel = b.
|
|
73
|
+
const firstItemLabel = a.name ?? ''
|
|
74
|
+
const secondItemLabel = b.name ?? ''
|
|
63
75
|
|
|
64
76
|
return firstItemLabel.localeCompare(secondItemLabel)
|
|
65
77
|
})
|
|
@@ -67,7 +79,7 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
|
|
|
67
79
|
acc.push(currentFacet)
|
|
68
80
|
|
|
69
81
|
return acc
|
|
70
|
-
}, [] as
|
|
82
|
+
}, [] as Facet[])
|
|
71
83
|
|
|
72
84
|
return filteredFacets ?? []
|
|
73
85
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Product,
|
|
1
|
+
import type { Product, Item } from '../clients/search/types/ProductSearchResult'
|
|
2
2
|
|
|
3
|
-
export type EnhancedSku =
|
|
3
|
+
export type EnhancedSku = Item & { isVariantOf: Product }
|
|
4
4
|
|
|
5
|
-
export const enhanceSku = (
|
|
6
|
-
...
|
|
5
|
+
export const enhanceSku = (item: Item, product: Product): EnhancedSku => ({
|
|
6
|
+
...item,
|
|
7
7
|
isVariantOf: product,
|
|
8
8
|
})
|
|
@@ -13,9 +13,15 @@ export const transformSelectedFacet = ({ key, value }: SelectedFacet) => {
|
|
|
13
13
|
switch (key) {
|
|
14
14
|
case 'channel': {
|
|
15
15
|
const channel = ChannelMarshal.parse(value)
|
|
16
|
+
const channelFacets = [
|
|
17
|
+
{ key: 'trade-policy', value: channel.salesChannel },
|
|
18
|
+
]
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
if (channel.regionId) {
|
|
21
|
+
channelFacets.push({ key: 'region-id', value: channel.regionId })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return channelFacets
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
default:
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommertialOffer,
|
|
3
|
+
Item,
|
|
4
|
+
} from '../clients/search/types/ProductSearchResult'
|
|
5
|
+
import { getFirstSeller } from './productStock'
|
|
6
|
+
|
|
7
|
+
export const getItemPriceByKey = (
|
|
8
|
+
item: Item,
|
|
9
|
+
key: keyof CommertialOffer
|
|
10
|
+
): number => getFirstSeller(item.sellers)?.commertialOffer[key] ?? 0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Item, Seller } from '../clients/search/types/ProductSearchResult'
|
|
2
|
+
import { getItemPriceByKey } from './price'
|
|
3
|
+
|
|
4
|
+
export const inStock = (item: Item) =>
|
|
5
|
+
item.sellers.find((seller) => seller.commertialOffer.AvailableQuantity > 0)
|
|
6
|
+
|
|
7
|
+
export const getFirstSeller = (sellers: Seller[]): Seller | undefined =>
|
|
8
|
+
sellers[0]
|
|
9
|
+
|
|
10
|
+
// Smallest Available Selling Price First
|
|
11
|
+
export const sortOfferByPrice = (items: Item[]): Item[] =>
|
|
12
|
+
items.sort((a, b) => {
|
|
13
|
+
if (inStock(a) && !inStock(b)) {
|
|
14
|
+
return -1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!inStock(a) && inStock(b)) {
|
|
18
|
+
return 1
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return getItemPriceByKey(a, 'Price') - getItemPriceByKey(b, 'Price')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const inStockOrderFormItem = (availability: string) =>
|
|
25
|
+
availability === 'available'
|