@faststore/core 3.14.2 → 3.15.0
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/.husky/_/husky.sh +30 -0
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +43 -43
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +6 -6
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/4951.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/[slug]/p.js +1 -1
- package/.next/server/pages/api/graphql.js +1 -1
- package/.next/server/pages/en-US/404.html +2 -2
- package/.next/server/pages/en-US/500.html +2 -2
- package/.next/server/pages/en-US/account.html +2 -2
- package/.next/server/pages/en-US/checkout.html +2 -2
- package/.next/server/pages/en-US/login.html +2 -2
- package/.next/server/pages/en-US/s.html +2 -2
- package/.next/server/pages/en-US.html +2 -2
- package/.next/server/pages/s.js +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/I_svGPugEd8B39I034SPS/_buildManifest.js +1 -0
- package/.next/static/chunks/{1153.d7522522b6c917ed.js → 1153.7f616071da309cf5.js} +1 -1
- package/.next/static/chunks/4750-e5a9418eaebfb4c8.js +28 -0
- package/.next/static/chunks/{5698-27d87aae8c109e79.js → 5698-fa65b2992114887e.js} +1 -1
- package/.next/static/chunks/{667-12b06e64d8d692d5.js → 667-53c75d8fc0155d58.js} +1 -1
- package/.next/static/chunks/{9466-2f8573654062ca33.js → 9466-e249304f28f683ae.js} +1 -1
- package/.next/static/chunks/{BannerNewsletter.cd67899702cefc74.js → BannerNewsletter.e9cddf0d9db5a289.js} +1 -1
- package/.next/static/chunks/{Newsletter.16d512eea4770e48.js → Newsletter.b145b5c84a40f69a.js} +1 -1
- package/.next/static/chunks/pages/{404-23b334702035ebee.js → 404-c90c20c167cc0e7a.js} +1 -1
- package/.next/static/chunks/pages/{500-2fbf273f5ce9ceca.js → 500-0cd22d12b85fffda.js} +1 -1
- package/.next/static/chunks/pages/{[...slug]-25f38fa57ace66f5.js → [...slug]-519b90475cfac29a.js} +1 -1
- package/.next/static/chunks/pages/[slug]/p-076e901f67a41f5a.js +1 -0
- package/.next/static/chunks/pages/{account-432a55e008dba859.js → account-978432926cc98c7f.js} +1 -1
- package/.next/static/chunks/pages/{checkout-d811420956257b8d.js → checkout-fc1167ff76bcc8ed.js} +1 -1
- package/.next/static/chunks/pages/{index-196c7f6cfa8ed5f3.js → index-5118f05c0bfc0853.js} +1 -1
- package/.next/static/chunks/pages/{login-66c720bdd1b3034e.js → login-fcb4f67351defd0e.js} +1 -1
- package/.next/static/chunks/pages/{s-1835e91d63b5f928.js → s-ffb1e51598f8ad52.js} +1 -1
- package/.next/static/chunks/{webpack-afc8087dfb1d8df4.js → webpack-95968624e480b497.js} +1 -1
- package/.next/trace +103 -102
- package/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-test.log +5 -5
- package/CHANGELOG.md +12 -0
- package/package.json +11 -10
- package/src/components/sections/ProductDetails/ProductDetails.tsx +79 -73
- package/src/components/templates/SearchPage/SearchWrapper.tsx +1 -1
- package/src/pages/[slug]/p.tsx +42 -7
- package/src/sdk/offer/aggregate.ts +48 -0
- package/src/sdk/offer/enhance.ts +20 -0
- package/src/sdk/offer/fetcher.ts +19 -0
- package/src/sdk/offer/index.ts +45 -0
- package/src/sdk/offer/sort.ts +28 -0
- package/src/sdk/product/useProductLink.ts +1 -3
- package/.next/static/3H_fV7jqbTCbj1uDyqkdL/_buildManifest.js +0 -1
- package/.next/static/chunks/4579-0b95def0ee1ecb0f.js +0 -33
- package/.next/static/chunks/pages/[slug]/p-a3a9ea694ac3b322.js +0 -1
- /package/.next/static/{3H_fV7jqbTCbj1uDyqkdL → I_svGPugEd8B39I034SPS}/_ssgManifest.js +0 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -47,11 +47,11 @@ Warning: Dynamic Content not found for the page: home. Refer to the Dynamic Cont
|
|
|
47
47
|
Collecting build traces ...
|
|
48
48
|
|
|
49
49
|
Route (pages) Size First Load JS
|
|
50
|
-
┌ ● / 3.36 kB
|
|
50
|
+
┌ ● / 3.36 kB 123 kB
|
|
51
51
|
├ └ css/b1806cbafd0c1f81.css 3.06 kB
|
|
52
52
|
├ /_app 0 B 92 kB
|
|
53
53
|
├ ● /[...slug] 2.09 kB 131 kB
|
|
54
|
-
├ ● /[slug]/p 34.
|
|
54
|
+
├ ● /[slug]/p 34.9 kB 154 kB
|
|
55
55
|
├ ├ css/bf1560439df2c1a1.css 5.66 kB
|
|
56
56
|
├ ├ css/e3ff5d95518a5c79.css 6.01 kB
|
|
57
57
|
├ └ css/d23b961580181439.css 15.6 kB
|
|
@@ -62,18 +62,18 @@ Route (pages) Size First Load JS
|
|
|
62
62
|
├ λ /api/health/live 0 B 92 kB
|
|
63
63
|
├ λ /api/health/ready 0 B 92 kB
|
|
64
64
|
├ λ /api/preview 0 B 92 kB
|
|
65
|
-
├ ● /checkout
|
|
65
|
+
├ ● /checkout 693 B 120 kB
|
|
66
66
|
├ ● /login 1.59 kB 121 kB
|
|
67
67
|
└ ● /s 3.16 kB 132 kB
|
|
68
68
|
+ First Load JS shared by all 95.1 kB
|
|
69
69
|
├ chunks/framework-12a146e94cfcf7c4.js 45.4 kB
|
|
70
70
|
├ chunks/main-bcf4c5804681e40a.js 33.1 kB
|
|
71
71
|
├ chunks/pages/_app-fa96080af43b51d4.js 10 kB
|
|
72
|
-
├ chunks/webpack-
|
|
72
|
+
├ chunks/webpack-95968624e480b497.js 3.57 kB
|
|
73
73
|
└ css/2eafb8997a3946dc.css 3.07 kB
|
|
74
74
|
|
|
75
75
|
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
|
|
76
76
|
○ (Static) automatically rendered as static HTML (uses no initial props)
|
|
77
77
|
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
|
|
78
78
|
|
|
79
|
-
Done in
|
|
79
|
+
Done in 55.72s.
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
yarn run v1.22.22
|
|
2
2
|
$ jest
|
|
3
|
-
PASS test/
|
|
4
|
-
PASS test/
|
|
5
|
-
PASS test/server/index.test.ts (
|
|
3
|
+
PASS test/server/cms/index.test.ts (36.98 s)
|
|
4
|
+
PASS test/utils/multipleTemplates.test.ts (37.179 s)
|
|
5
|
+
PASS test/server/index.test.ts (40.48 s)
|
|
6
6
|
|
|
7
7
|
Test Suites: 3 passed, 3 total
|
|
8
8
|
Tests: 19 passed, 19 total
|
|
9
9
|
Snapshots: 0 total
|
|
10
|
-
Time:
|
|
10
|
+
Time: 41.692 s
|
|
11
11
|
Ran all test suites.
|
|
12
|
-
Done in
|
|
12
|
+
Done in 43.67s.
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.15.0 (2025-02-04)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- Offer SDK ([#2575](https://github.com/vtex/faststore/issues/2575)) ([f4e11ac](https://github.com/vtex/faststore/commit/f4e11ac68ae4afd79a1a89c122155e18bc452ad5))
|
|
11
|
+
|
|
12
|
+
## [3.14.3](https://github.com/vtex/faststore/compare/v3.14.2...v3.14.3) (2025-02-04)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- remove unnecessary colon ([#2669](https://github.com/vtex/faststore/issues/2669)) ([b0475c3](https://github.com/vtex/faststore/commit/b0475c38fe71a1ef2930cd58445c2387794d5066))
|
|
17
|
+
|
|
6
18
|
## 3.14.2 (2025-02-03)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @faststore/core
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "vtex/faststore",
|
|
6
6
|
"browserslist": "supports es6-module and not dead",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"@envelop/graphql-jit": "^8.0.3",
|
|
44
44
|
"@envelop/parser-cache": "^6.0.2",
|
|
45
45
|
"@envelop/validation-cache": "^6.0.2",
|
|
46
|
-
"@faststore/api": "^3.
|
|
47
|
-
"@faststore/components": "^3.
|
|
48
|
-
"@faststore/graphql-utils": "^3.
|
|
49
|
-
"@faststore/lighthouse": "^3.
|
|
50
|
-
"@faststore/sdk": "^3.
|
|
51
|
-
"@faststore/ui": "^3.
|
|
46
|
+
"@faststore/api": "^3.15.0",
|
|
47
|
+
"@faststore/components": "^3.15.0",
|
|
48
|
+
"@faststore/graphql-utils": "^3.15.0",
|
|
49
|
+
"@faststore/lighthouse": "^3.15.0",
|
|
50
|
+
"@faststore/sdk": "^3.15.0",
|
|
51
|
+
"@faststore/ui": "^3.15.0",
|
|
52
52
|
"@graphql-codegen/cli": "5.0.2",
|
|
53
53
|
"@graphql-codegen/client-preset": "4.2.6",
|
|
54
54
|
"@graphql-codegen/typescript": "4.0.7",
|
|
@@ -80,8 +80,9 @@
|
|
|
80
80
|
"sass-loader": "^12.6.0",
|
|
81
81
|
"sharp": "^0.32.6",
|
|
82
82
|
"style-loader": "^3.3.1",
|
|
83
|
-
"swr": "^
|
|
84
|
-
"tsx": "^4.6.2"
|
|
83
|
+
"swr": "^2.2.5",
|
|
84
|
+
"tsx": "^4.6.2",
|
|
85
|
+
"typescript": "4.7.3"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
87
88
|
"@cypress/code-coverage": "^3.12.1",
|
|
@@ -101,5 +102,5 @@
|
|
|
101
102
|
"jest": "^29.7.0",
|
|
102
103
|
"ts-jest": "29.1.1"
|
|
103
104
|
},
|
|
104
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "2cf5890d1b5edd9fbf7c9e7e94fbba57771c2a0a"
|
|
105
106
|
}
|
|
@@ -11,11 +11,22 @@ import Section from '../Section'
|
|
|
11
11
|
|
|
12
12
|
import styles from './section.module.scss'
|
|
13
13
|
|
|
14
|
+
import storeConfig from 'discovery.config'
|
|
14
15
|
import { getOverridableSection } from '../../../sdk/overrides/getOverriddenSection'
|
|
15
16
|
import { useOverrideComponents } from '../../../sdk/overrides/OverrideContext'
|
|
16
17
|
import { usePDP } from '../../../sdk/overrides/PageProvider'
|
|
17
18
|
import { ProductDetailsDefaultComponents } from './DefaultComponents'
|
|
18
19
|
|
|
20
|
+
type StoreConfig = typeof storeConfig & {
|
|
21
|
+
experimental: {
|
|
22
|
+
revalidate?: number
|
|
23
|
+
enableClientOffer?: boolean
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isClientOfferEnabled = (storeConfig as StoreConfig).experimental
|
|
28
|
+
.enableClientOffer
|
|
29
|
+
|
|
19
30
|
export interface ProductDetailsProps {
|
|
20
31
|
productTitle: {
|
|
21
32
|
refNumber: boolean
|
|
@@ -214,82 +225,77 @@ function ProductDetails({
|
|
|
214
225
|
{...ImageGallery.props}
|
|
215
226
|
images={productImages}
|
|
216
227
|
/>
|
|
217
|
-
<section data-fs-product-details-info>
|
|
218
|
-
<section
|
|
219
|
-
data-fs-product-details-settings
|
|
220
|
-
data-fs-product-details-section
|
|
221
|
-
>
|
|
222
|
-
<ProductDetailsSettings.Component
|
|
223
|
-
buyButtonTitle={buyButtonTitle}
|
|
224
|
-
buyButtonIcon={buyButtonIcon}
|
|
225
|
-
notAvailableButtonTitle={
|
|
226
|
-
notAvailableButtonTitle ?? NotAvailableButton.props.title
|
|
227
|
-
}
|
|
228
|
-
useUnitMultiplier={quantitySelector?.useUnitMultiplier ?? false}
|
|
229
|
-
{...ProductDetailsSettings.props}
|
|
230
|
-
// Dynamic props shouldn't be overridable
|
|
231
|
-
// This decision can be reviewed later if needed
|
|
232
|
-
quantity={quantity}
|
|
233
|
-
setQuantity={setQuantity}
|
|
234
|
-
product={product}
|
|
235
|
-
isValidating={isValidating}
|
|
236
|
-
taxesConfiguration={taxesConfiguration}
|
|
237
|
-
/>
|
|
238
|
-
|
|
239
|
-
{skuMatrix?.shouldDisplaySKUMatrix &&
|
|
240
|
-
Object.keys(slugsMap).length > 1 && (
|
|
241
|
-
<>
|
|
242
|
-
<div data-fs-product-details-settings-separator>
|
|
243
|
-
{skuMatrix.separatorButtonsText}
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
<SKUMatrix.Component>
|
|
247
|
-
<SKUMatrixTrigger.Component disabled={isValidating}>
|
|
248
|
-
{skuMatrix.triggerButtonLabel}
|
|
249
|
-
</SKUMatrixTrigger.Component>
|
|
250
228
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
229
|
+
{isClientOfferEnabled && isValidating ? (
|
|
230
|
+
<section data-fs-product-details-info>
|
|
231
|
+
<section
|
|
232
|
+
data-fs-product-details-settings
|
|
233
|
+
data-fs-product-details-section
|
|
234
|
+
>
|
|
235
|
+
<p>Loading...</p>
|
|
236
|
+
</section>
|
|
259
237
|
</section>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
<
|
|
238
|
+
) : (
|
|
239
|
+
<section data-fs-product-details-info>
|
|
240
|
+
<section
|
|
241
|
+
data-fs-product-details-settings
|
|
263
242
|
data-fs-product-details-section
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
243
|
+
>
|
|
244
|
+
<ProductDetailsSettings.Component
|
|
245
|
+
buyButtonTitle={buyButtonTitle}
|
|
246
|
+
buyButtonIcon={buyButtonIcon}
|
|
247
|
+
notAvailableButtonTitle={
|
|
248
|
+
notAvailableButtonTitle ?? NotAvailableButton.props.title
|
|
249
|
+
}
|
|
250
|
+
useUnitMultiplier={
|
|
251
|
+
quantitySelector?.useUnitMultiplier ?? false
|
|
252
|
+
}
|
|
253
|
+
{...ProductDetailsSettings.props}
|
|
254
|
+
// Dynamic props shouldn't be overridable
|
|
255
|
+
// This decision can be reviewed later if needed
|
|
256
|
+
quantity={quantity}
|
|
257
|
+
setQuantity={setQuantity}
|
|
258
|
+
product={product}
|
|
259
|
+
isValidating={isValidating}
|
|
260
|
+
taxesConfiguration={taxesConfiguration}
|
|
261
|
+
/>
|
|
262
|
+
</section>
|
|
263
|
+
|
|
264
|
+
{!outOfStock && (
|
|
265
|
+
<ShippingSimulation.Component
|
|
266
|
+
data-fs-product-details-section
|
|
267
|
+
data-fs-product-details-shipping
|
|
268
|
+
formatter={useFormattedPrice}
|
|
269
|
+
{...ShippingSimulation.props}
|
|
270
|
+
idkPostalCodeLinkProps={{
|
|
271
|
+
...ShippingSimulation.props.idkPostalCodeLinkProps,
|
|
272
|
+
href:
|
|
273
|
+
shippingSimulatorLinkUrl ??
|
|
274
|
+
ShippingSimulation.props.idkPostalCodeLinkProps?.href,
|
|
275
|
+
children:
|
|
276
|
+
shippingSimulatorLinkText ??
|
|
277
|
+
ShippingSimulation.props.idkPostalCodeLinkProps?.children,
|
|
278
|
+
}}
|
|
279
|
+
productShippingInfo={{
|
|
280
|
+
id,
|
|
281
|
+
quantity,
|
|
282
|
+
seller: seller.identifier,
|
|
283
|
+
}}
|
|
284
|
+
title={
|
|
285
|
+
shippingSimulatorTitle ?? ShippingSimulation.props.title
|
|
286
|
+
}
|
|
287
|
+
inputLabel={
|
|
288
|
+
shippingSimulatorInputLabel ??
|
|
289
|
+
ShippingSimulation.props.inputLabel
|
|
290
|
+
}
|
|
291
|
+
optionsLabel={
|
|
292
|
+
shippingSimulatorOptionsTableTitle ??
|
|
293
|
+
ShippingSimulation.props.optionsLabel
|
|
294
|
+
}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
</section>
|
|
298
|
+
)}
|
|
293
299
|
|
|
294
300
|
{shouldDisplayProductDescription && (
|
|
295
301
|
<ProductDescription.Component
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import type { Locator } from '@vtex/client-cms'
|
|
|
3
3
|
import deepmerge from 'deepmerge'
|
|
4
4
|
import type { GetStaticPaths, GetStaticProps } from 'next'
|
|
5
5
|
import { BreadcrumbJsonLd, NextSeo, ProductJsonLd } from 'next-seo'
|
|
6
|
+
import Head from 'next/head'
|
|
6
7
|
import type { ComponentType } from 'react'
|
|
7
8
|
|
|
8
9
|
import { gql } from '@generated'
|
|
@@ -21,19 +22,27 @@ import { OverriddenDefaultNewsletter as Newsletter } from 'src/components/sectio
|
|
|
21
22
|
import { OverriddenDefaultProductDetails as ProductDetails } from 'src/components/sections/ProductDetails/OverriddenDefaultProductDetails'
|
|
22
23
|
import { OverriddenDefaultProductShelf as ProductShelf } from 'src/components/sections/ProductShelf/OverriddenDefaultProductShelf'
|
|
23
24
|
import ProductTiles from 'src/components/sections/ProductTiles'
|
|
24
|
-
import PLUGINS_COMPONENTS from 'src/plugins'
|
|
25
25
|
import CUSTOM_COMPONENTS from 'src/customizations/src/components'
|
|
26
|
+
import PLUGINS_COMPONENTS from 'src/plugins'
|
|
26
27
|
import { useSession } from 'src/sdk/session'
|
|
27
28
|
import { execute } from 'src/server'
|
|
28
29
|
|
|
29
30
|
import storeConfig from 'discovery.config'
|
|
30
31
|
import {
|
|
31
|
-
type GlobalSectionsData,
|
|
32
32
|
getGlobalSectionsData,
|
|
33
|
+
type GlobalSectionsData,
|
|
33
34
|
} from 'src/components/cms/GlobalSections'
|
|
35
|
+
import { getOfferUrl, useOffer } from 'src/sdk/offer'
|
|
34
36
|
import PageProvider, { type PDPContext } from 'src/sdk/overrides/PageProvider'
|
|
35
37
|
import { useProductQuery } from 'src/sdk/product/useProductQuery'
|
|
36
|
-
import { type PDPContentType
|
|
38
|
+
import { getPDP, type PDPContentType } from 'src/server/cms/pdp'
|
|
39
|
+
|
|
40
|
+
type StoreConfig = typeof storeConfig & {
|
|
41
|
+
experimental: {
|
|
42
|
+
revalidate?: number
|
|
43
|
+
enableClientOffer?: boolean
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
48
|
* Sections: Components imported from each store's custom components and '../components/sections' only.
|
|
@@ -68,15 +77,31 @@ type Props = PDPContentType & {
|
|
|
68
77
|
// https://www.npmjs.com/package/deepmerge
|
|
69
78
|
const overwriteMerge = (_: any[], sourceArray: any[]) => sourceArray
|
|
70
79
|
|
|
80
|
+
const isClientOfferEnabled = (storeConfig as StoreConfig).experimental
|
|
81
|
+
.enableClientOffer
|
|
82
|
+
|
|
71
83
|
function Page({ data: server, sections, globalSections, offers, meta }: Props) {
|
|
72
84
|
const { product } = server
|
|
73
85
|
const { currency } = useSession()
|
|
74
86
|
const titleTemplate = storeConfig?.seo?.titleTemplate ?? ''
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
const { client, isValidating } = isClientOfferEnabled
|
|
89
|
+
? (() => {
|
|
90
|
+
const offer = useOffer({ skuId: product.sku })
|
|
91
|
+
return {
|
|
92
|
+
client: { product: { offers: offer.offers } },
|
|
93
|
+
isValidating: offer.isValidating,
|
|
94
|
+
}
|
|
95
|
+
})()
|
|
96
|
+
: (() => {
|
|
97
|
+
const productQuery = useProductQuery(product.id, {
|
|
98
|
+
product: product,
|
|
99
|
+
})
|
|
100
|
+
return {
|
|
101
|
+
client: productQuery.data,
|
|
102
|
+
isValidating: productQuery.isValidating,
|
|
103
|
+
}
|
|
104
|
+
})()
|
|
80
105
|
|
|
81
106
|
const context = {
|
|
82
107
|
data: {
|
|
@@ -87,6 +112,15 @@ function Page({ data: server, sections, globalSections, offers, meta }: Props) {
|
|
|
87
112
|
|
|
88
113
|
return (
|
|
89
114
|
<>
|
|
115
|
+
<Head>
|
|
116
|
+
<link
|
|
117
|
+
rel="preload"
|
|
118
|
+
href={getOfferUrl(product.sku)}
|
|
119
|
+
as="fetch"
|
|
120
|
+
crossOrigin="anonymous"
|
|
121
|
+
fetchPriority="high"
|
|
122
|
+
/>
|
|
123
|
+
</Head>
|
|
90
124
|
{/* SEO */}
|
|
91
125
|
<NextSeo
|
|
92
126
|
title={meta.title}
|
|
@@ -271,6 +305,7 @@ export const getStaticProps: GetStaticProps<
|
|
|
271
305
|
globalSections,
|
|
272
306
|
key: seo.canonical,
|
|
273
307
|
},
|
|
308
|
+
revalidate: (storeConfig as StoreConfig).experimental.revalidate ?? 0,
|
|
274
309
|
}
|
|
275
310
|
}
|
|
276
311
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Item, Seller } from '@faststore/api'
|
|
2
|
+
import type { EnhancedCommercialOffer } from './enhance'
|
|
3
|
+
import { inStock, price } from './sort'
|
|
4
|
+
|
|
5
|
+
type Root = EnhancedCommercialOffer<Seller, Item>
|
|
6
|
+
|
|
7
|
+
const withTax = (price: number, tax = 0, unitMultiplier = 1) => {
|
|
8
|
+
const unitTax = tax / unitMultiplier
|
|
9
|
+
return Math.round((price + unitTax) * 100) / 100
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getHighPrice = (
|
|
13
|
+
offers: Root[],
|
|
14
|
+
options: { includeTaxes: boolean } = { includeTaxes: false }
|
|
15
|
+
) => {
|
|
16
|
+
const availableOffers = offers.filter(inStock)
|
|
17
|
+
const highOffer = availableOffers[availableOffers.length - 1]
|
|
18
|
+
const highPrice = highOffer ? price(highOffer) : 0
|
|
19
|
+
if (!options.includeTaxes) {
|
|
20
|
+
return highPrice
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return withTax(highPrice, highOffer?.Tax, highOffer?.product?.unitMultiplier)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getLowPrice = (
|
|
27
|
+
offers: Root[],
|
|
28
|
+
options: { includeTaxes: boolean } = { includeTaxes: false }
|
|
29
|
+
) => {
|
|
30
|
+
const [lowOffer] = offers.filter(inStock)
|
|
31
|
+
|
|
32
|
+
const lowPrice = lowOffer ? price(lowOffer) : 0
|
|
33
|
+
|
|
34
|
+
if (!options.includeTaxes) {
|
|
35
|
+
return lowPrice
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return withTax(lowPrice, lowOffer?.Tax, lowOffer?.product?.unitMultiplier)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function aggregateOffer(offers: Root[]) {
|
|
42
|
+
return {
|
|
43
|
+
highPrice: getHighPrice(offers),
|
|
44
|
+
lowPrice: getLowPrice(offers),
|
|
45
|
+
lowPriceWithTaxes: getLowPrice(offers, { includeTaxes: true }),
|
|
46
|
+
offerCount: offers.length,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CommertialOffer } from '@faststore/api'
|
|
2
|
+
|
|
3
|
+
export type EnhancedCommercialOffer<S, P> = CommertialOffer & {
|
|
4
|
+
seller: S
|
|
5
|
+
product: P
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const enhanceCommercialOffer = <S, P>({
|
|
9
|
+
offer,
|
|
10
|
+
seller,
|
|
11
|
+
product,
|
|
12
|
+
}: {
|
|
13
|
+
offer: CommertialOffer
|
|
14
|
+
seller: S
|
|
15
|
+
product: P
|
|
16
|
+
}): EnhancedCommercialOffer<S, P> => ({
|
|
17
|
+
...offer,
|
|
18
|
+
product,
|
|
19
|
+
seller,
|
|
20
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProductSearchResult } from '@faststore/api'
|
|
2
|
+
import { api, storeUrl } from '../../../discovery.config'
|
|
3
|
+
|
|
4
|
+
const IS_PROD = process.env.NODE_ENV === 'production'
|
|
5
|
+
|
|
6
|
+
export function getUrl(skuId: string) {
|
|
7
|
+
const base = IS_PROD
|
|
8
|
+
? storeUrl
|
|
9
|
+
: `https://${api.storeId}.${api.environment}.com.br`
|
|
10
|
+
const url = new URL(`${base}/api/intelligent-search/product_search`)
|
|
11
|
+
url.searchParams.append('query', `sku.id:${skuId}`)
|
|
12
|
+
return url.toString()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function fetcher(skuId: string) {
|
|
16
|
+
return fetch(getUrl(skuId)).then((res) =>
|
|
17
|
+
res.json()
|
|
18
|
+
) as Promise<ProductSearchResult>
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import useSWR from 'swr'
|
|
2
|
+
import { aggregateOffer } from './aggregate'
|
|
3
|
+
import { enhanceCommercialOffer } from './enhance'
|
|
4
|
+
import { fetcher } from './fetcher'
|
|
5
|
+
import { bestOfferFirst } from './sort'
|
|
6
|
+
export { getUrl as getOfferUrl } from './fetcher'
|
|
7
|
+
|
|
8
|
+
const ERROR_DATA = { offers: {}, isValidating: false }
|
|
9
|
+
|
|
10
|
+
export function useOffer(args: { skuId: string }) {
|
|
11
|
+
const { data, error, isValidating } = useSWR(args.skuId, fetcher)
|
|
12
|
+
|
|
13
|
+
if (error || !data || data.products.length === 0) {
|
|
14
|
+
console.warn('Error or no data fetching offer to SKU', args.skuId, error)
|
|
15
|
+
return ERROR_DATA
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const product = data.products[0]
|
|
19
|
+
|
|
20
|
+
if (!product || product.items.length === 0) {
|
|
21
|
+
console.warn('Product not found or has no items for SKU', args.skuId)
|
|
22
|
+
return ERROR_DATA
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const item = product.items.find((item) => item.itemId === args.skuId)
|
|
26
|
+
|
|
27
|
+
if (!item) {
|
|
28
|
+
console.warn('Item not found for SKU', args.skuId)
|
|
29
|
+
return ERROR_DATA
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sellers = item.sellers
|
|
33
|
+
.map((seller) =>
|
|
34
|
+
enhanceCommercialOffer({
|
|
35
|
+
offer: seller.commertialOffer,
|
|
36
|
+
seller,
|
|
37
|
+
product: item,
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
.sort(bestOfferFirst)
|
|
41
|
+
|
|
42
|
+
const offers = aggregateOffer(sellers)
|
|
43
|
+
|
|
44
|
+
return { offers, isValidating }
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CommertialOffer } from '@faststore/api'
|
|
2
|
+
|
|
3
|
+
export const inStock = (offer: Pick<CommertialOffer, 'AvailableQuantity'>) =>
|
|
4
|
+
offer.AvailableQuantity > 0
|
|
5
|
+
|
|
6
|
+
export const price = (offer: Pick<CommertialOffer, 'spotPrice'>) =>
|
|
7
|
+
offer.spotPrice ?? 0
|
|
8
|
+
|
|
9
|
+
export const availability = (available: boolean) =>
|
|
10
|
+
available ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'
|
|
11
|
+
|
|
12
|
+
export const bestOfferFirst = (
|
|
13
|
+
a: Pick<CommertialOffer, 'AvailableQuantity' | 'spotPrice'>,
|
|
14
|
+
b: Pick<CommertialOffer, 'AvailableQuantity' | 'spotPrice'>
|
|
15
|
+
) => {
|
|
16
|
+
if (inStock(a) && !inStock(b)) {
|
|
17
|
+
return -1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!inStock(a) && inStock(b)) {
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return price(a) - price(b)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const inStockOrderFormItem = (itemAvailability: string) =>
|
|
28
|
+
itemAvailability === 'available'
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { CurrencyCode, SelectItemEvent } from '@faststore/sdk'
|
|
2
|
-
import { useCallback } from 'react'
|
|
3
|
-
|
|
4
2
|
import type { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
5
|
-
|
|
3
|
+
import { useCallback } from 'react'
|
|
6
4
|
import type { AnalyticsItem, SearchSelectItemEvent } from '../analytics/types'
|
|
7
5
|
import { useSession } from '../session'
|
|
8
6
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
self.__BUILD_MANIFEST=function(s,c,a,t,e,f){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,c,a,"static/css/b1806cbafd0c1f81.css","static/chunks/pages/index-196c7f6cfa8ed5f3.js"],"/404":[s,c,a,t,"static/chunks/pages/404-23b334702035ebee.js"],"/500":[s,c,a,t,"static/chunks/pages/500-2fbf273f5ce9ceca.js"],"/_error":["static/chunks/pages/_error-c259d89560ef7ac1.js"],"/account":[s,c,a,"static/chunks/pages/account-432a55e008dba859.js"],"/checkout":[s,c,a,"static/chunks/pages/checkout-d811420956257b8d.js"],"/login":[s,c,a,t,"static/chunks/pages/login-66c720bdd1b3034e.js"],"/s":[s,c,a,e,f,t,"static/chunks/pages/s-1835e91d63b5f928.js"],"/[slug]/p":[s,"static/chunks/2599-67df8c38c483737b.js",c,a,"static/chunks/7498-745c09c46899ea49.js","static/css/bf1560439df2c1a1.css","static/css/e3ff5d95518a5c79.css","static/chunks/667-12b06e64d8d692d5.js","static/css/d23b961580181439.css","static/chunks/pages/[slug]/p-a3a9ea694ac3b322.js"],"/[...slug]":[s,c,a,e,f,"static/chunks/pages/[...slug]-25f38fa57ace66f5.js"],sortedPages:["/","/404","/500","/_app","/_error","/account","/checkout","/login","/s","/[slug]/p","/[...slug]"]}}("static/chunks/4579-0b95def0ee1ecb0f.js","static/css/93d239c461485bdf.css","static/chunks/5698-27d87aae8c109e79.js","static/css/2980acad3f8e1028.css","static/css/449c3f167b150caf.css","static/chunks/9466-2f8573654062ca33.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|