@faststore/core 3.6.0 → 3.8.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +35 -34
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/eslint/.cache_1gneedd +1 -1
- 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/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +38 -38
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/1153.js +1 -1
- package/.next/server/chunks/1163.js +2 -2
- package/.next/server/chunks/1377.js +1 -1
- package/.next/server/chunks/2082.js +3 -3
- package/.next/server/chunks/2245.js +1 -1
- package/.next/server/chunks/2295.js +1 -1
- package/.next/server/chunks/2552.js +1 -1
- package/.next/server/chunks/2710.js +1 -1
- package/.next/server/chunks/2880.js +1 -1
- package/.next/server/chunks/2918.js +2 -2
- package/.next/server/chunks/3157.js +1 -1
- package/.next/server/chunks/319.js +1 -1
- package/.next/server/chunks/3202.js +1 -1
- package/.next/server/chunks/371.js +1 -1
- package/.next/server/chunks/3716.js +2 -2
- package/.next/server/chunks/3779.js +1 -1
- package/.next/server/chunks/3922.js +1 -1
- package/.next/server/chunks/4012.js +1 -1
- package/.next/server/chunks/4222.js +1 -1
- package/.next/server/chunks/4358.js +1 -1
- package/.next/server/chunks/4451.js +1 -1
- package/.next/server/chunks/5110.js +1 -1
- package/.next/server/chunks/5156.js +1 -1
- package/.next/server/chunks/5284.js +1 -1
- package/.next/server/chunks/5342.js +1 -1
- package/.next/server/chunks/5380.js +1 -1
- package/.next/server/chunks/5430.js +1 -1
- package/.next/server/chunks/5476.js +1 -1
- package/.next/server/chunks/5484.js +1 -1
- package/.next/server/chunks/5671.js +1 -1
- package/.next/server/chunks/5754.js +2 -2
- package/.next/server/chunks/6198.js +1 -0
- package/.next/server/chunks/6335.js +1 -1
- package/.next/server/chunks/64.js +1 -1
- package/.next/server/chunks/6414.js +1 -1
- package/.next/server/chunks/6859.js +3 -3
- package/.next/server/chunks/7169.js +1 -1
- package/.next/server/chunks/7228.js +1 -1
- package/.next/server/chunks/7468.js +1 -1
- package/.next/server/chunks/7675.js +1 -1
- package/.next/server/chunks/7986.js +1 -1
- package/.next/server/chunks/8096.js +1 -1
- package/.next/server/chunks/8640.js +1 -1
- package/.next/server/chunks/8724.js +1 -1
- package/.next/server/chunks/8737.js +1 -1
- package/.next/server/chunks/8857.js +1 -1
- package/.next/server/chunks/9088.js +1 -1
- package/.next/server/chunks/9160.js +1 -1
- package/.next/server/chunks/9369.js +1 -1
- package/.next/server/chunks/9410.js +1 -1
- package/.next/server/chunks/945.js +1 -1
- package/.next/server/chunks/9570.js +1 -1
- package/.next/server/chunks/9572.js +49 -3
- package/.next/server/chunks/983.js +1 -1
- package/.next/server/chunks/9844.js +1 -1
- package/.next/server/chunks/ButtonSignIn.js +1 -1
- package/.next/server/chunks/Dropdown.js +1 -1
- package/.next/server/chunks/DropdownButton.js +1 -1
- package/.next/server/chunks/DropdownItem.js +1 -1
- package/.next/server/chunks/DropdownMenu.js +1 -1
- package/.next/server/chunks/FilterSkeleton.js +1 -1
- package/.next/server/chunks/ScrollToTopButton.js +1 -1
- package/.next/server/chunks/UIBannerText.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/404.js +1 -1
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.js +1 -1
- package/.next/server/pages/500.js.nft.json +1 -1
- package/.next/server/pages/[...slug].js +1 -1
- package/.next/server/pages/[...slug].js.nft.json +1 -1
- package/.next/server/pages/[slug]/p.js +1 -1
- package/.next/server/pages/[slug]/p.js.nft.json +1 -1
- package/.next/server/pages/_app.js +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/account.js +1 -1
- package/.next/server/pages/account.js.nft.json +1 -1
- package/.next/server/pages/api/graphql.js +1 -1
- package/.next/server/pages/api/graphql.js.nft.json +1 -1
- package/.next/server/pages/api/health/live.js +1 -1
- package/.next/server/pages/api/health/live.js.nft.json +1 -1
- package/.next/server/pages/api/health/ready.js +1 -1
- package/.next/server/pages/api/health/ready.js.nft.json +1 -1
- package/.next/server/pages/api/preview.js +1 -1
- package/.next/server/pages/api/preview.js.nft.json +1 -1
- package/.next/server/pages/checkout.js +1 -1
- package/.next/server/pages/checkout.js.nft.json +1 -1
- package/.next/server/pages/en-US/404.html +1 -1
- package/.next/server/pages/en-US/500.html +1 -1
- package/.next/server/pages/en-US/account.html +1 -1
- package/.next/server/pages/en-US/checkout.html +1 -1
- package/.next/server/pages/en-US/login.html +1 -1
- package/.next/server/pages/en-US/s.html +1 -1
- package/.next/server/pages/en-US.html +1 -1
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/login.js +1 -1
- package/.next/server/pages/login.js.nft.json +1 -1
- package/.next/server/pages/s.js +1 -1
- package/.next/server/pages/s.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/{1153.d7522522b6c917ed.js → 1153.7f616071da309cf5.js} +1 -1
- package/.next/static/chunks/{1978.6d1246731da0f1b0.js → 1978.afceeb8879bd2646.js} +1 -1
- package/.next/static/chunks/2552.9070ea604ee3c214.js +1 -0
- package/.next/static/chunks/2599-67df8c38c483737b.js +1 -0
- package/.next/static/chunks/3285.419d379c827c993d.js +1 -0
- package/.next/static/chunks/6379-e49fe5643b85d5b5.js +1 -0
- package/.next/static/chunks/7498-791ad2ef2c9fc716.js +1 -0
- package/.next/static/chunks/7563-e2275f3ee79ddd83.js +1 -0
- package/.next/static/chunks/BannerNewsletter.29403e046f34b6c1.js +1 -0
- package/.next/static/chunks/BannerText.24939a8013e30e18.js +1 -0
- package/.next/static/chunks/CartSidebar.71febd97344a7b4c.js +1 -0
- package/.next/static/chunks/ProductShelf.d40d1e693c5a302b.js +1 -0
- package/.next/static/chunks/RegionModal.cec6e7d1faeeae2d.js +1 -0
- package/.next/static/chunks/Toast.0e3d2c547b67cf60.js +1 -0
- package/.next/static/chunks/pages/{404-358f6795222bf991.js → 404-71d5202357fb9e69.js} +1 -1
- package/.next/static/chunks/pages/{500-7adc48c3231ccee1.js → 500-75704d5230292280.js} +1 -1
- package/.next/static/chunks/pages/{[...slug]-0203b74377537f7d.js → [...slug]-21a03c0e2b98783d.js} +1 -1
- package/.next/static/chunks/pages/[slug]/p-fcdc2b26731a7f07.js +1 -0
- package/.next/static/chunks/pages/{account-9db0ef5c4174c7dd.js → account-1d2b1635afa59ace.js} +1 -1
- package/.next/static/chunks/pages/{checkout-abaa6374ae946641.js → checkout-4b85659485b4500c.js} +1 -1
- package/.next/static/chunks/pages/{index-ad532cf8840c5d3f.js → index-2ec096f367505f35.js} +1 -1
- package/.next/static/chunks/pages/{login-8d2eb8db226d6363.js → login-363e38c5172ec5a4.js} +1 -1
- package/.next/static/chunks/pages/{s-ba5734fbe496d9af.js → s-d74217745109de5d.js} +1 -1
- package/.next/static/chunks/webpack-bd90055e2ebc407d.js +1 -0
- package/.next/static/css/d92839440872e246.css +1 -0
- package/.next/static/er25JH1Tpwm62rGwg_DUa/_buildManifest.js +1 -0
- package/.next/trace +98 -98
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +5 -5
- package/@generated/gql.ts +18 -2
- package/@generated/graphql.ts +157 -0
- package/@generated/persisted-documents.json +1 -0
- package/@generated/schema.graphql +2 -0
- package/CHANGELOG.md +12 -0
- package/cms/faststore/sections.json +193 -406
- package/package.json +5 -5
- package/src/components/cms/RenderSections.tsx +55 -8
- package/src/components/cms/ViewportObserver.tsx +3 -1
- package/src/components/sections/ProductDetails/DefaultComponents.ts +9 -1
- package/src/components/sections/ProductDetails/ProductDetails.tsx +46 -3
- package/src/components/sections/ProductDetails/section.module.scss +39 -3
- package/src/components/ui/SKUMatrix/SKUMatrixSidebar.tsx +132 -0
- package/src/sdk/cart/useBuyButton.ts +41 -20
- package/src/sdk/performance/useTTI.ts +35 -0
- package/src/sdk/product/useAllVariantProducts.ts +114 -0
- package/src/typings/overrides.ts +21 -5
- package/.next/server/chunks/463.js +0 -1
- package/.next/static/chunks/1550-b2d0f274d1db008a.js +0 -1
- package/.next/static/chunks/2552.35321485d927aa08.js +0 -1
- package/.next/static/chunks/299.c58d37a55fab85ad.js +0 -1
- package/.next/static/chunks/6379-fc541ff2f12fab06.js +0 -1
- package/.next/static/chunks/9638-24a86482d4a4b434.js +0 -1
- package/.next/static/chunks/BannerNewsletter.96d85bb5fbde1d76.js +0 -1
- package/.next/static/chunks/BannerText.f58f1afdd622dd6c.js +0 -1
- package/.next/static/chunks/CartSidebar.33f111ff908118cf.js +0 -1
- package/.next/static/chunks/ProductShelf.58b9c11d0b9d412c.js +0 -1
- package/.next/static/chunks/RegionModal.8d2c6065d8aaf911.js +0 -1
- package/.next/static/chunks/Toast.9ad46e15a6444bd6.js +0 -1
- package/.next/static/chunks/pages/[slug]/p-b5e429fbf8e96d36.js +0 -1
- package/.next/static/chunks/webpack-712cba50a8ffee1b.js +0 -1
- package/.next/static/css/9718991cd57978e9.css +0 -1
- package/.next/static/hTBj6SJWSWID7EQkobAYG/_buildManifest.js +0 -1
- /package/.next/static/{hTBj6SJWSWID7EQkobAYG → er25JH1Tpwm62rGwg_DUa}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.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.
|
|
46
|
+
"@faststore/api": "^3.7.0",
|
|
47
|
+
"@faststore/components": "^3.7.0",
|
|
48
48
|
"@faststore/graphql-utils": "^3.5.0",
|
|
49
49
|
"@faststore/lighthouse": "^3.5.0",
|
|
50
50
|
"@faststore/sdk": "^3.5.0",
|
|
51
|
-
"@faststore/ui": "^3.
|
|
51
|
+
"@faststore/ui": "^3.7.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",
|
|
@@ -128,5 +128,5 @@
|
|
|
128
128
|
"node": "18.19.0",
|
|
129
129
|
"yarn": "1.19.1"
|
|
130
130
|
},
|
|
131
|
-
"gitHead": "
|
|
131
|
+
"gitHead": "16615e73214a680a16cf1f92839c8b6ca2c8467e"
|
|
132
132
|
}
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
} from 'react'
|
|
8
8
|
|
|
9
|
+
import { useUI } from '@faststore/ui'
|
|
9
10
|
import { Section } from '@vtex/client-cms'
|
|
10
11
|
import dynamic from 'next/dynamic'
|
|
12
|
+
import useTTI from 'src/sdk/performance/useTTI'
|
|
11
13
|
import SectionBoundary from './SectionBoundary'
|
|
12
14
|
import ViewportObserver from './ViewportObserver'
|
|
13
15
|
import COMPONENTS from './global/Components'
|
|
@@ -16,6 +18,7 @@ interface Props {
|
|
|
16
18
|
components?: Record<string, ComponentType<any>>
|
|
17
19
|
globalSections?: Array<{ name: string; data: any }>
|
|
18
20
|
sections?: Array<{ name: string; data: any }>
|
|
21
|
+
isInteractive?: boolean
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const SECTIONS_OUT_OF_VIEWPORT = ['CartSidebar', 'RegionModal']
|
|
@@ -49,20 +52,47 @@ const useDividedSections = (sections: Section[]) => {
|
|
|
49
52
|
export const LazyLoadingSection = ({
|
|
50
53
|
sectionName,
|
|
51
54
|
children,
|
|
55
|
+
debug = false,
|
|
56
|
+
isInteractive = false,
|
|
52
57
|
}: {
|
|
53
58
|
sectionName: string
|
|
54
59
|
children: ReactNode
|
|
60
|
+
debug?: boolean
|
|
61
|
+
isInteractive?: boolean
|
|
55
62
|
}) => {
|
|
63
|
+
const { cart: displayCart, modal: displayModal } = useUI()
|
|
56
64
|
if (SECTIONS_OUT_OF_VIEWPORT.includes(sectionName)) {
|
|
57
|
-
|
|
65
|
+
const shouldLoad =
|
|
66
|
+
isInteractive ||
|
|
67
|
+
(sectionName === 'CartSidebar' && displayCart) ||
|
|
68
|
+
(sectionName === 'RegionModal' && displayModal)
|
|
69
|
+
|
|
70
|
+
if (debug) {
|
|
71
|
+
console.log(
|
|
72
|
+
`section SECTIONS_OUT_OF_VIEWPORT '${sectionName}' shouldLoad:`,
|
|
73
|
+
shouldLoad
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return shouldLoad ? <>{children}</> : null
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
return (
|
|
61
|
-
<ViewportObserver
|
|
81
|
+
<ViewportObserver
|
|
82
|
+
sectionName={sectionName}
|
|
83
|
+
debug={debug}
|
|
84
|
+
isInteractive={isInteractive}
|
|
85
|
+
>
|
|
86
|
+
{children}
|
|
87
|
+
</ViewportObserver>
|
|
62
88
|
)
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
const RenderSectionsBase = ({
|
|
91
|
+
const RenderSectionsBase = ({
|
|
92
|
+
sections = [],
|
|
93
|
+
components,
|
|
94
|
+
isInteractive,
|
|
95
|
+
}: Props) => {
|
|
66
96
|
return (
|
|
67
97
|
<>
|
|
68
98
|
{sections.map(({ name, data = {} }, index) => {
|
|
@@ -79,7 +109,10 @@ const RenderSectionsBase = ({ sections = [], components }: Props) => {
|
|
|
79
109
|
|
|
80
110
|
return (
|
|
81
111
|
<SectionBoundary key={`cms-section-${name}-${index}`} name={name}>
|
|
82
|
-
<LazyLoadingSection
|
|
112
|
+
<LazyLoadingSection
|
|
113
|
+
sectionName={name}
|
|
114
|
+
isInteractive={isInteractive}
|
|
115
|
+
>
|
|
83
116
|
<Component {...data} />
|
|
84
117
|
</LazyLoadingSection>
|
|
85
118
|
</SectionBoundary>
|
|
@@ -99,21 +132,35 @@ function RenderSections({
|
|
|
99
132
|
globalSections ?? sections
|
|
100
133
|
)
|
|
101
134
|
|
|
135
|
+
const { isInteractive } = useTTI()
|
|
136
|
+
|
|
102
137
|
return (
|
|
103
138
|
<>
|
|
104
139
|
{firstSections && (
|
|
105
|
-
<RenderSectionsBase
|
|
140
|
+
<RenderSectionsBase
|
|
141
|
+
sections={firstSections}
|
|
142
|
+
components={components}
|
|
143
|
+
isInteractive={isInteractive}
|
|
144
|
+
/>
|
|
106
145
|
)}
|
|
107
146
|
{sections && sections.length > 0 && (
|
|
108
|
-
<RenderSectionsBase
|
|
147
|
+
<RenderSectionsBase
|
|
148
|
+
sections={sections}
|
|
149
|
+
components={components}
|
|
150
|
+
isInteractive={isInteractive}
|
|
151
|
+
/>
|
|
109
152
|
)}
|
|
110
153
|
{children}
|
|
111
|
-
<LazyLoadingSection sectionName="Toast">
|
|
154
|
+
<LazyLoadingSection sectionName="Toast" isInteractive={isInteractive}>
|
|
112
155
|
<Toast />
|
|
113
156
|
</LazyLoadingSection>
|
|
114
157
|
|
|
115
158
|
{lastSections && (
|
|
116
|
-
<RenderSectionsBase
|
|
159
|
+
<RenderSectionsBase
|
|
160
|
+
sections={lastSections}
|
|
161
|
+
components={components}
|
|
162
|
+
isInteractive={isInteractive}
|
|
163
|
+
/>
|
|
117
164
|
)}
|
|
118
165
|
</>
|
|
119
166
|
)
|
|
@@ -14,6 +14,7 @@ type ViewportObserverProps = {
|
|
|
14
14
|
* Debug/test purposes: enables visual debugging to identify the visibility of the section.
|
|
15
15
|
*/
|
|
16
16
|
debug?: boolean
|
|
17
|
+
isInteractive?: boolean
|
|
17
18
|
} & IntersectionObserverInit
|
|
18
19
|
|
|
19
20
|
function ViewportObserver({
|
|
@@ -23,6 +24,7 @@ function ViewportObserver({
|
|
|
23
24
|
rootMargin,
|
|
24
25
|
children,
|
|
25
26
|
debug = false,
|
|
27
|
+
isInteractive = false,
|
|
26
28
|
}: PropsWithChildren<ViewportObserverProps>) {
|
|
27
29
|
const [isVisible, setVisible] = useState(false)
|
|
28
30
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
@@ -80,7 +82,7 @@ function ViewportObserver({
|
|
|
80
82
|
></div>
|
|
81
83
|
)}
|
|
82
84
|
|
|
83
|
-
{isVisible && children}
|
|
85
|
+
{(isVisible || isInteractive) && children}
|
|
84
86
|
</>
|
|
85
87
|
)
|
|
86
88
|
}
|
|
@@ -9,13 +9,17 @@ import {
|
|
|
9
9
|
QuantitySelector as UIQuantitySelector,
|
|
10
10
|
ImageGalleryViewer as UIImageGalleryViewer,
|
|
11
11
|
ImageGallery as UIImageGallery,
|
|
12
|
+
SKUMatrix as UISKUMatrix,
|
|
13
|
+
SKUMatrixSidebar as UISKUMatrixSidebar,
|
|
14
|
+
SKUMatrixTrigger as UISKUMatrixTrigger,
|
|
12
15
|
} from '@faststore/ui'
|
|
13
16
|
|
|
14
17
|
import LocalImageGallery from 'src/components/ui/ImageGallery'
|
|
15
18
|
import LocalShippingSimulation from 'src/components/ui/ShippingSimulation/ShippingSimulation'
|
|
16
19
|
import { Image } from 'src/components/ui/Image'
|
|
17
20
|
import LocalNotAvailableButton from 'src/components/product/NotAvailableButton'
|
|
18
|
-
import
|
|
21
|
+
import LocalSKUMatrixSidebar from 'src/components/ui/SKUMatrix/SKUMatrixSidebar'
|
|
22
|
+
import LocalProductDescription from 'src/components/ui/ProductDescription/ProductDescription'
|
|
19
23
|
import { ProductDetailsSettings as LocalProductDetailsSettings } from 'src/components/ui/ProductDetails'
|
|
20
24
|
|
|
21
25
|
export const ProductDetailsDefaultComponents = {
|
|
@@ -29,9 +33,13 @@ export const ProductDetailsDefaultComponents = {
|
|
|
29
33
|
ShippingSimulation: UIShippingSimulation,
|
|
30
34
|
ImageGallery: UIImageGallery,
|
|
31
35
|
ImageGalleryViewer: UIImageGalleryViewer,
|
|
36
|
+
SKUMatrix: UISKUMatrix,
|
|
37
|
+
SKUMatrixTrigger: UISKUMatrixTrigger,
|
|
38
|
+
SKUMatrixSidebar: UISKUMatrixSidebar,
|
|
32
39
|
__experimentalImageGalleryImage: Image,
|
|
33
40
|
__experimentalImageGallery: LocalImageGallery,
|
|
34
41
|
__experimentalShippingSimulation: LocalShippingSimulation,
|
|
42
|
+
__experimentalSKUMatrixSidebar: LocalSKUMatrixSidebar,
|
|
35
43
|
__experimentalNotAvailableButton: LocalNotAvailableButton,
|
|
36
44
|
__experimentalProductDescription: LocalProductDescription,
|
|
37
45
|
__experimentalProductDetailsSettings: LocalProductDetailsSettings,
|
|
@@ -55,6 +55,21 @@ export interface ProductDetailsProps {
|
|
|
55
55
|
usePriceWithTaxes?: boolean
|
|
56
56
|
taxesLabel?: string
|
|
57
57
|
}
|
|
58
|
+
skuMatrix?: {
|
|
59
|
+
shouldDisplaySKUMatrix?: boolean
|
|
60
|
+
triggerButtonLabel: string
|
|
61
|
+
separatorButtonsText: string
|
|
62
|
+
columns: {
|
|
63
|
+
name: string
|
|
64
|
+
additionalColumns: Array<{ label: string; value: string }>
|
|
65
|
+
availability: {
|
|
66
|
+
label: string
|
|
67
|
+
stockDisplaySettings: 'showAvailability' | 'showStockQuantity'
|
|
68
|
+
}
|
|
69
|
+
price: number
|
|
70
|
+
quantitySelector: number
|
|
71
|
+
}
|
|
72
|
+
}
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
function ProductDetails({
|
|
@@ -74,6 +89,7 @@ function ProductDetails({
|
|
|
74
89
|
initiallyExpanded: productDescriptionInitiallyExpanded,
|
|
75
90
|
displayDescription: shouldDisplayProductDescription,
|
|
76
91
|
},
|
|
92
|
+
skuMatrix,
|
|
77
93
|
notAvailableButton: { title: notAvailableButtonTitle },
|
|
78
94
|
quantitySelector,
|
|
79
95
|
taxesConfiguration,
|
|
@@ -81,17 +97,19 @@ function ProductDetails({
|
|
|
81
97
|
const {
|
|
82
98
|
DiscountBadge,
|
|
83
99
|
ProductTitle,
|
|
100
|
+
SKUMatrix,
|
|
101
|
+
SKUMatrixTrigger,
|
|
84
102
|
__experimentalImageGallery: ImageGallery,
|
|
85
103
|
__experimentalShippingSimulation: ShippingSimulation,
|
|
86
104
|
__experimentalNotAvailableButton: NotAvailableButton,
|
|
87
105
|
__experimentalProductDescription: ProductDescription,
|
|
88
106
|
__experimentalProductDetailsSettings: ProductDetailsSettings,
|
|
107
|
+
__experimentalSKUMatrixSidebar: SKUMatrixSidebar,
|
|
89
108
|
} = useOverrideComponents<'ProductDetails'>()
|
|
90
109
|
const { currency } = useSession()
|
|
91
110
|
const context = usePDP()
|
|
92
111
|
const { product, isValidating } = context?.data
|
|
93
112
|
const [quantity, setQuantity] = useState(1)
|
|
94
|
-
|
|
95
113
|
if (!product) {
|
|
96
114
|
throw new Error('NotFound')
|
|
97
115
|
}
|
|
@@ -104,7 +122,11 @@ function ProductDetails({
|
|
|
104
122
|
brand,
|
|
105
123
|
isVariantOf,
|
|
106
124
|
description,
|
|
107
|
-
isVariantOf: {
|
|
125
|
+
isVariantOf: {
|
|
126
|
+
name,
|
|
127
|
+
productGroupID: productId,
|
|
128
|
+
skuVariants: { slugsMap },
|
|
129
|
+
},
|
|
108
130
|
image: productImages,
|
|
109
131
|
offers: {
|
|
110
132
|
offers: [{ availability, price, listPrice, listPriceWithTaxes, seller }],
|
|
@@ -213,6 +235,27 @@ function ProductDetails({
|
|
|
213
235
|
isValidating={isValidating}
|
|
214
236
|
taxesConfiguration={taxesConfiguration}
|
|
215
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
|
+
|
|
251
|
+
<SKUMatrixSidebar.Component
|
|
252
|
+
formatter={useFormattedPrice}
|
|
253
|
+
columns={skuMatrix.columns}
|
|
254
|
+
overlayProps={{ className: styles.section }}
|
|
255
|
+
/>
|
|
256
|
+
</SKUMatrix.Component>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
216
259
|
</section>
|
|
217
260
|
|
|
218
261
|
{!outOfStock && (
|
|
@@ -277,7 +320,7 @@ export const fragment = gql(`
|
|
|
277
320
|
isVariantOf {
|
|
278
321
|
name
|
|
279
322
|
productGroupID
|
|
280
|
-
|
|
323
|
+
skuVariants {
|
|
281
324
|
activeVariations
|
|
282
325
|
slugsMap
|
|
283
326
|
availableVariations
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
@layer components {
|
|
2
2
|
.section {
|
|
3
3
|
// Taxes label
|
|
4
|
-
--fs-product-details-taxes-label-color
|
|
5
|
-
--fs-product-details-taxes-text-size
|
|
6
|
-
--fs-product-details-taxes-text-weight
|
|
4
|
+
--fs-product-details-taxes-label-color : var(--fs-color-info-text);
|
|
5
|
+
--fs-product-details-taxes-text-size : var(--fs-text-size-tiny);
|
|
6
|
+
--fs-product-details-taxes-text-weight : var(--fs-text-weight-regular);
|
|
7
|
+
|
|
8
|
+
// Separator colors
|
|
9
|
+
--fs-product-details-separator-color : var(--fs-color-neutral-2);
|
|
10
|
+
--fs-product-details-separator-color-text : var(--fs-color-text-light);
|
|
7
11
|
|
|
8
12
|
margin-top: 0;
|
|
9
13
|
|
|
@@ -29,11 +33,43 @@
|
|
|
29
33
|
@import "@faststore/ui/src/components/organisms/ShippingSimulation/styles.scss";
|
|
30
34
|
@import "@faststore/ui/src/components/organisms/ImageGallery/styles.scss";
|
|
31
35
|
@import "@faststore/ui/src/components/organisms/ProductDetails/styles.scss";
|
|
36
|
+
@import "@faststore/ui/src/components/organisms/SlideOver/styles.scss";
|
|
37
|
+
@import "@faststore/ui/src/components/organisms/SKUMatrix/styles.scss";
|
|
32
38
|
|
|
33
39
|
[data-fs-product-details-taxes-label] {
|
|
34
40
|
font-size: var(--fs-product-details-taxes-text-size);
|
|
35
41
|
font-weight: var(--fs-product-details-taxes-text-weight);
|
|
36
42
|
color: var(--fs-product-details-taxes-label-color);
|
|
37
43
|
}
|
|
44
|
+
|
|
45
|
+
[data-fs-product-details-settings-separator] {
|
|
46
|
+
position: relative;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: space-between;
|
|
50
|
+
color: var(--fs-product-details-separator-color-text);
|
|
51
|
+
|
|
52
|
+
&::after {
|
|
53
|
+
display: inline-block;
|
|
54
|
+
width: 45%;
|
|
55
|
+
height: 1px;
|
|
56
|
+
content: "";
|
|
57
|
+
background-color: var(--fs-product-details-separator-color);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&::before {
|
|
61
|
+
display: inline-block;
|
|
62
|
+
width: 45%;
|
|
63
|
+
height: 1px;
|
|
64
|
+
content: "";
|
|
65
|
+
background-color: var(--fs-product-details-separator-color);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[data-fs-sku-matrix] {
|
|
70
|
+
> [data-fs-button] {
|
|
71
|
+
width: 100%;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
38
74
|
}
|
|
39
75
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { SKUMatrixSidebarProps as UISKUMatrixSidebarProps } from '@faststore/ui'
|
|
2
|
+
import {
|
|
3
|
+
SKUMatrixSidebar as UISKUMatrixSidebar,
|
|
4
|
+
useSKUMatrix,
|
|
5
|
+
} from '@faststore/ui'
|
|
6
|
+
import { gql } from '@generated/gql'
|
|
7
|
+
import { useBuyButton } from 'src/sdk/cart/useBuyButton'
|
|
8
|
+
import { usePDP } from 'src/sdk/overrides/PageProvider'
|
|
9
|
+
import { useAllVariantProducts } from 'src/sdk/product/useAllVariantProducts'
|
|
10
|
+
|
|
11
|
+
interface SKUMatrixProps extends UISKUMatrixSidebarProps {}
|
|
12
|
+
|
|
13
|
+
function SKUMatrixSidebar(props: SKUMatrixProps) {
|
|
14
|
+
const {
|
|
15
|
+
data: { product },
|
|
16
|
+
} = usePDP()
|
|
17
|
+
|
|
18
|
+
const { allVariantProducts, isOpen, setAllVariantProducts } = useSKUMatrix()
|
|
19
|
+
const { isValidating } = useAllVariantProducts(
|
|
20
|
+
product.id,
|
|
21
|
+
isOpen,
|
|
22
|
+
setAllVariantProducts
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
gtin,
|
|
27
|
+
unitMultiplier,
|
|
28
|
+
brand,
|
|
29
|
+
additionalProperty,
|
|
30
|
+
isVariantOf,
|
|
31
|
+
offers: {
|
|
32
|
+
offers: [{ seller }],
|
|
33
|
+
},
|
|
34
|
+
} = product
|
|
35
|
+
|
|
36
|
+
const buyButtonProps = allVariantProducts
|
|
37
|
+
.filter((item) => item.selectedCount)
|
|
38
|
+
.map((item) => {
|
|
39
|
+
const {
|
|
40
|
+
offers: {
|
|
41
|
+
offers: [{ price, priceWithTaxes, listPrice, listPriceWithTaxes }],
|
|
42
|
+
},
|
|
43
|
+
} = item
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
id: item.id,
|
|
47
|
+
price,
|
|
48
|
+
priceWithTaxes,
|
|
49
|
+
listPrice,
|
|
50
|
+
listPriceWithTaxes,
|
|
51
|
+
seller,
|
|
52
|
+
quantity: item.selectedCount,
|
|
53
|
+
itemOffered: {
|
|
54
|
+
sku: item.id,
|
|
55
|
+
name: item.name,
|
|
56
|
+
gtin,
|
|
57
|
+
image: [item.image],
|
|
58
|
+
brand,
|
|
59
|
+
isVariantOf: {
|
|
60
|
+
...isVariantOf,
|
|
61
|
+
skuVariants: {
|
|
62
|
+
...isVariantOf.skuVariants,
|
|
63
|
+
activeVariations: item.specifications,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
additionalProperty,
|
|
67
|
+
unitMultiplier,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const buyProps = useBuyButton(buyButtonProps)
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<UISKUMatrixSidebar
|
|
76
|
+
buyProps={buyProps}
|
|
77
|
+
title={product.isVariantOf.name ?? ''}
|
|
78
|
+
loading={isValidating}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const fragment = gql(`
|
|
85
|
+
fragment ProductSKUMatrixSidebarFragment_product on StoreProduct {
|
|
86
|
+
id: productID
|
|
87
|
+
isVariantOf {
|
|
88
|
+
name
|
|
89
|
+
productGroupID
|
|
90
|
+
skuVariants {
|
|
91
|
+
activeVariations
|
|
92
|
+
slugsMap
|
|
93
|
+
availableVariations
|
|
94
|
+
allVariantProducts {
|
|
95
|
+
sku
|
|
96
|
+
name
|
|
97
|
+
image {
|
|
98
|
+
url
|
|
99
|
+
alternateName
|
|
100
|
+
}
|
|
101
|
+
offers {
|
|
102
|
+
highPrice
|
|
103
|
+
lowPrice
|
|
104
|
+
lowPriceWithTaxes
|
|
105
|
+
offerCount
|
|
106
|
+
priceCurrency
|
|
107
|
+
offers {
|
|
108
|
+
listPrice
|
|
109
|
+
listPriceWithTaxes
|
|
110
|
+
sellingPrice
|
|
111
|
+
priceCurrency
|
|
112
|
+
price
|
|
113
|
+
priceWithTaxes
|
|
114
|
+
priceValidUntil
|
|
115
|
+
itemCondition
|
|
116
|
+
availability
|
|
117
|
+
quantity
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
additionalProperty {
|
|
121
|
+
propertyID
|
|
122
|
+
value
|
|
123
|
+
name
|
|
124
|
+
valueReference
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
`)
|
|
131
|
+
|
|
132
|
+
export default SKUMatrixSidebar
|
|
@@ -8,12 +8,14 @@ import { useUI } from '@faststore/ui'
|
|
|
8
8
|
import { useSession } from '../session'
|
|
9
9
|
import { cartStore } from './index'
|
|
10
10
|
|
|
11
|
-
export const useBuyButton = (item: CartItem | null) => {
|
|
11
|
+
export const useBuyButton = (item: CartItem | CartItem[] | null) => {
|
|
12
12
|
const { openCart } = useUI()
|
|
13
13
|
const {
|
|
14
14
|
currency: { code },
|
|
15
15
|
} = useSession()
|
|
16
16
|
|
|
17
|
+
const itemIsArray = Array.isArray(item)
|
|
18
|
+
|
|
17
19
|
const onClick = useCallback(
|
|
18
20
|
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
19
21
|
e.preventDefault()
|
|
@@ -22,6 +24,33 @@ export const useBuyButton = (item: CartItem | null) => {
|
|
|
22
24
|
return
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
const value = itemIsArray
|
|
28
|
+
? item.reduce((sum, item) => (sum += item.price * item.quantity), 0)
|
|
29
|
+
: item.price * item.quantity
|
|
30
|
+
|
|
31
|
+
function generatedItem(item: CartItem) {
|
|
32
|
+
return {
|
|
33
|
+
item_id: item.itemOffered.isVariantOf.productGroupID,
|
|
34
|
+
item_name: item.itemOffered.isVariantOf.name,
|
|
35
|
+
item_brand: item.itemOffered.brand.name,
|
|
36
|
+
item_variant: item.itemOffered.sku,
|
|
37
|
+
quantity: item.quantity,
|
|
38
|
+
price: item.price,
|
|
39
|
+
discount: item.listPrice - item.price,
|
|
40
|
+
currency: code as CurrencyCode,
|
|
41
|
+
item_variant_name: item.itemOffered.name,
|
|
42
|
+
product_reference_id: item.itemOffered.gtin,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getItems() {
|
|
47
|
+
if (!itemIsArray) {
|
|
48
|
+
return [generatedItem(item)]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return item.map(generatedItem)
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
import('@faststore/sdk').then(({ sendAnalyticsEvent }) => {
|
|
26
55
|
sendAnalyticsEvent<AddToCartEvent<AnalyticsItem>>({
|
|
27
56
|
name: 'add_to_cart',
|
|
@@ -29,35 +58,27 @@ export const useBuyButton = (item: CartItem | null) => {
|
|
|
29
58
|
currency: code as CurrencyCode,
|
|
30
59
|
// TODO: In the future, we can explore more robust ways of
|
|
31
60
|
// calculating the value (gift items, discounts, etc.).
|
|
32
|
-
value
|
|
33
|
-
items:
|
|
34
|
-
{
|
|
35
|
-
item_id: item.itemOffered.isVariantOf.productGroupID,
|
|
36
|
-
item_name: item.itemOffered.isVariantOf.name,
|
|
37
|
-
item_brand: item.itemOffered.brand.name,
|
|
38
|
-
item_variant: item.itemOffered.sku,
|
|
39
|
-
quantity: item.quantity,
|
|
40
|
-
price: item.price,
|
|
41
|
-
discount: item.listPrice - item.price,
|
|
42
|
-
currency: code as CurrencyCode,
|
|
43
|
-
item_variant_name: item.itemOffered.name,
|
|
44
|
-
product_reference_id: item.itemOffered.gtin,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
61
|
+
value,
|
|
62
|
+
items: getItems(),
|
|
47
63
|
},
|
|
48
64
|
})
|
|
49
65
|
})
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
itemIsArray
|
|
68
|
+
? item.forEach((value) => cartStore.addItem(value))
|
|
69
|
+
: cartStore.addItem(item)
|
|
70
|
+
|
|
52
71
|
openCart()
|
|
53
72
|
},
|
|
54
|
-
[code, item, openCart]
|
|
73
|
+
[code, item, openCart, itemIsArray]
|
|
55
74
|
)
|
|
56
75
|
|
|
57
76
|
return {
|
|
58
77
|
onClick,
|
|
59
78
|
'data-testid': 'buy-button',
|
|
60
|
-
'data-sku': item?.itemOffered.sku,
|
|
61
|
-
'data-seller':
|
|
79
|
+
'data-sku': itemIsArray ? 'sku-matrix-sidebar' : item?.itemOffered.sku,
|
|
80
|
+
'data-seller': itemIsArray
|
|
81
|
+
? item[0]?.seller.identifier
|
|
82
|
+
: item?.seller.identifier,
|
|
62
83
|
}
|
|
63
84
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const TTI_TIMEOUT = 5000 // 5 seconds without long tasks as a criterion for Time To Interactive - https://web.dev/articles/tti
|
|
4
|
+
export default function useTTI() {
|
|
5
|
+
const [isInteractive, setIsInteractive] = useState(false)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if ('PerformanceObserver' in window) {
|
|
9
|
+
let lastTaskEnd = 0
|
|
10
|
+
|
|
11
|
+
const observer = new PerformanceObserver((list) => {
|
|
12
|
+
for (const entry of list.getEntries()) {
|
|
13
|
+
lastTaskEnd = entry.startTime + entry.duration
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
observer.observe({ type: 'longtask', buffered: true })
|
|
18
|
+
|
|
19
|
+
// Monitoring when TTI might have been reached
|
|
20
|
+
const checkTTI = () => {
|
|
21
|
+
const now = performance.now()
|
|
22
|
+
if (now - lastTaskEnd >= TTI_TIMEOUT) {
|
|
23
|
+
observer.disconnect()
|
|
24
|
+
setIsInteractive(true) // Sets the state to true when TTI is estimated
|
|
25
|
+
} else {
|
|
26
|
+
requestIdleCallback(checkTTI) // Keeps checking while the browser is idle
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
requestIdleCallback(checkTTI)
|
|
31
|
+
}
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
return { isInteractive }
|
|
35
|
+
}
|