@faststore/core 0.1.1 → 0.2.1
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 +14 -0
- package/bun.lockb +0 -0
- package/cms/content-types.json +5 -0
- package/cms/sections.json +47 -0
- package/package.json +2 -2
- package/postinstall.js +6 -0
- package/src/components/cms/RenderPageSections.tsx +7 -25
- package/src/components/sections/CrossSellingShelf/CrossSellingShelf.tsx +25 -0
- package/src/components/sections/CrossSellingShelf/index.tsx +1 -0
- package/src/components/sections/ProductDetails/ProductDetails.tsx +2 -2
- package/src/customizations/index.ts +1 -0
- package/src/customizations/themes/index.scss +3 -0
- package/src/pages/[slug]/p.tsx +46 -36
- package/src/pages/_app.tsx +1 -2
- package/src/pages/_document.tsx +1 -2
- package/src/pages/index.tsx +28 -4
- package/src/server/cms.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog),
|
|
6
6
|
and this project adheres to [Calendar Versioning](https://calver.org/).
|
|
7
7
|
|
|
8
|
+
### [0.2.1](https://github.com/vtex-sites/nextjs.store/compare/0.2.0...0.2.1) (2022-10-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
|
|
13
|
+
* make store extendable ([#293](https://github.com/vtex-sites/nextjs.store/issues/293)) ([f8c3632](https://github.com/vtex-sites/nextjs.store/commit/f8c3632740d263c8d435033678ed1591f23f0359))
|
|
14
|
+
|
|
15
|
+
## [0.2.0](https://github.com/vtex-sites/nextjs.store/compare/0.1.1...0.2.0) (2022-10-25)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* Integrate CMS with PDP ([#283](https://github.com/vtex-sites/nextjs.store/issues/283)) ([e3c3ec8](https://github.com/vtex-sites/nextjs.store/commit/e3c3ec889cb76b185488ba8f8fa9e1007ffc99d5))
|
|
21
|
+
|
|
8
22
|
### [0.1.1](https://github.com/vtex-sites/nextjs.store/compare/0.1.0...0.1.1) (2022-10-25)
|
|
9
23
|
|
|
10
24
|
## 0.1.0 (2022-10-25)
|
package/bun.lockb
CHANGED
|
Binary file
|
package/cms/content-types.json
CHANGED
package/cms/sections.json
CHANGED
|
@@ -249,6 +249,34 @@
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
},
|
|
252
|
+
{
|
|
253
|
+
"name": "CrossSellingShelf",
|
|
254
|
+
"schema": {
|
|
255
|
+
"title": "Cross Selling Shelf",
|
|
256
|
+
"description": "Add cross selling product data to your users",
|
|
257
|
+
"type": "object",
|
|
258
|
+
"required": ["title", "items", "kind"],
|
|
259
|
+
"properties": {
|
|
260
|
+
"title": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"title": "Title"
|
|
263
|
+
},
|
|
264
|
+
"items": {
|
|
265
|
+
"type": "integer",
|
|
266
|
+
"title": "First",
|
|
267
|
+
"default": 5,
|
|
268
|
+
"description": "Number of items to display"
|
|
269
|
+
},
|
|
270
|
+
"kind": {
|
|
271
|
+
"title": "Kind",
|
|
272
|
+
"description": "Change cross selling types",
|
|
273
|
+
"default": "buy",
|
|
274
|
+
"enum": ["buy", "view"],
|
|
275
|
+
"enumNames": ["Who bought also bought", "Who saw also saw"]
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
252
280
|
{
|
|
253
281
|
"name": "ProductTiles",
|
|
254
282
|
"schema": {
|
|
@@ -346,5 +374,24 @@
|
|
|
346
374
|
}
|
|
347
375
|
}
|
|
348
376
|
}
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"name": "BannerNewsletter",
|
|
380
|
+
"schema": {
|
|
381
|
+
"title": "Banner Newsletter",
|
|
382
|
+
"description": "Add newsletter with a banner",
|
|
383
|
+
"type": "object",
|
|
384
|
+
"required": [],
|
|
385
|
+
"properties": {}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"name": "ProductDetails",
|
|
390
|
+
"schema": {
|
|
391
|
+
"title": "Product Details",
|
|
392
|
+
"description": "Display product gallery with buy button and shipping",
|
|
393
|
+
"type": "object",
|
|
394
|
+
"properties": {}
|
|
395
|
+
}
|
|
349
396
|
}
|
|
350
397
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"browserslist": "supports es6-module and not dead",
|
|
6
6
|
"scripts": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"lint": "eslint --ext js,ts,jsx,tsx .",
|
|
16
16
|
"stylelint": "stylelint \"**/*.scss\"",
|
|
17
17
|
"stylelint:fix": "stylelint \"**/*.scss\" --fix",
|
|
18
|
-
"postinstall": "is-ci || husky install",
|
|
18
|
+
"postinstall": "node postinstall.js && (is-ci || husky install) || echo Skipped postinstall step for @faststore/core",
|
|
19
19
|
"partytown": "partytown copylib ./public/~partytown",
|
|
20
20
|
"storybook": "start-storybook --docs -p 6006",
|
|
21
21
|
"build-storybook": "build-storybook",
|
package/postinstall.js
ADDED
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type { ComponentType } from 'react'
|
|
3
3
|
|
|
4
|
-
import BannerText from 'src/components/sections/BannerText'
|
|
5
|
-
import Hero from 'src/components/sections/Hero'
|
|
6
|
-
import IncentivesHeader from 'src/components/sections/Incentives/IncentivesHeader'
|
|
7
|
-
import ProductShelf from 'src/components/sections/ProductShelf'
|
|
8
|
-
import ProductTiles from 'src/components/sections/ProductTiles'
|
|
9
|
-
import Newsletter from 'src/components/sections/Newsletter'
|
|
10
|
-
|
|
11
4
|
import SectionBoundary from './SectionBoundary'
|
|
12
5
|
|
|
13
|
-
/**
|
|
14
|
-
* Sections: Components imported from '../components/sections' only.
|
|
15
|
-
* Do not import or render components from any other folder in here.
|
|
16
|
-
*/
|
|
17
|
-
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
18
|
-
Hero,
|
|
19
|
-
BannerText,
|
|
20
|
-
IncentivesHeader,
|
|
21
|
-
ProductShelf,
|
|
22
|
-
ProductTiles,
|
|
23
|
-
Newsletter,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
6
|
interface Props {
|
|
27
|
-
|
|
7
|
+
components: Record<string, ComponentType<any>>
|
|
8
|
+
sections: Array<{ name: string; data: any }>
|
|
9
|
+
context?: unknown
|
|
28
10
|
}
|
|
29
11
|
|
|
30
|
-
const RenderPageSections = ({ sections }: Props) => (
|
|
12
|
+
const RenderPageSections = ({ sections = [], context, components }: Props) => (
|
|
31
13
|
<>
|
|
32
|
-
{sections
|
|
33
|
-
const Component =
|
|
14
|
+
{sections.map(({ name, data }, index) => {
|
|
15
|
+
const Component = components[name]
|
|
34
16
|
|
|
35
17
|
if (!Component) {
|
|
36
18
|
console.info(
|
|
@@ -42,7 +24,7 @@ const RenderPageSections = ({ sections }: Props) => (
|
|
|
42
24
|
|
|
43
25
|
return (
|
|
44
26
|
<SectionBoundary key={`cms-section-${index}`} name={name}>
|
|
45
|
-
<Component {...data} />
|
|
27
|
+
<Component {...data} context={context} />
|
|
46
28
|
</SectionBoundary>
|
|
47
29
|
)
|
|
48
30
|
})}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { ProductDetailsFragment_ProductFragment } from '@generated/graphql'
|
|
4
|
+
|
|
5
|
+
import ProductShelf from '../ProductShelf'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
items: number
|
|
9
|
+
title: string
|
|
10
|
+
context: ProductDetailsFragment_ProductFragment
|
|
11
|
+
kind: 'buy' | 'view'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CrossSellingShelf = ({ items, title, context, kind }: Props) => {
|
|
15
|
+
const selectedFacets = useMemo(
|
|
16
|
+
() => [{ key: kind, value: context.isVariantOf.productGroupID }],
|
|
17
|
+
[kind, context.isVariantOf.productGroupID]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<ProductShelf first={items} title={title} selectedFacets={selectedFacets} />
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default CrossSellingShelf
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './CrossSellingShelf'
|
|
@@ -25,10 +25,10 @@ import Section from '../Section'
|
|
|
25
25
|
import ProductDetailsContent from '../ProducDetailsContent'
|
|
26
26
|
|
|
27
27
|
interface Props {
|
|
28
|
-
|
|
28
|
+
context: ProductDetailsFragment_ProductFragment
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function ProductDetails({
|
|
31
|
+
function ProductDetails({ context: staleProduct }: Props) {
|
|
32
32
|
const { currency } = useSession()
|
|
33
33
|
const [addQuantity, setAddQuantity] = useState(1)
|
|
34
34
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {}
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -2,14 +2,19 @@ import { isNotFoundError } from '@faststore/api'
|
|
|
2
2
|
import { gql } from '@faststore/graphql-utils'
|
|
3
3
|
import { BreadcrumbJsonLd, NextSeo, ProductJsonLd } from 'next-seo'
|
|
4
4
|
import type { GetStaticPaths, GetStaticProps } from 'next'
|
|
5
|
+
import type { ComponentType } from 'react'
|
|
6
|
+
import type { Locator } from '@vtex/client-cms'
|
|
5
7
|
|
|
6
|
-
import
|
|
7
|
-
import ProductShelf from 'src/components/sections/ProductShelf'
|
|
8
|
+
import RenderPageSections from 'src/components/cms/RenderPageSections'
|
|
8
9
|
import BannerNewsletter from 'src/components/sections/BannerNewsletter/BannerNewsletter'
|
|
9
|
-
import
|
|
10
|
+
import CrossSellingShelf from 'src/components/sections/CrossSellingShelf'
|
|
11
|
+
import ProductDetails from 'src/components/sections/ProductDetails'
|
|
10
12
|
import { useSession } from 'src/sdk/session'
|
|
11
13
|
import { mark } from 'src/sdk/tests/mark'
|
|
12
14
|
import { execute } from 'src/server'
|
|
15
|
+
import { getPage } from 'src/server/cms'
|
|
16
|
+
import type { PDPContentType } from 'src/server/cms'
|
|
17
|
+
import CUSTOM_SECTIONS from 'src/customizations'
|
|
13
18
|
import type {
|
|
14
19
|
ServerProductPageQueryQuery,
|
|
15
20
|
ServerProductPageQueryQueryVariables,
|
|
@@ -17,9 +22,20 @@ import type {
|
|
|
17
22
|
|
|
18
23
|
import storeConfig from '../../../store.config'
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Sections: Components imported from each store's custom components and '../components/sections' only.
|
|
27
|
+
* Do not import or render components from any other folder in here.
|
|
28
|
+
*/
|
|
29
|
+
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
30
|
+
ProductDetails,
|
|
31
|
+
BannerNewsletter,
|
|
32
|
+
CrossSellingShelf,
|
|
33
|
+
...CUSTOM_SECTIONS,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Props = ServerProductPageQueryQuery & PDPContentType
|
|
21
37
|
|
|
22
|
-
function Page({ product }: Props) {
|
|
38
|
+
function Page({ product, sections }: Props) {
|
|
23
39
|
const { currency } = useSession()
|
|
24
40
|
const { seo } = product
|
|
25
41
|
const title = seo.title || storeConfig.seo.title
|
|
@@ -84,27 +100,11 @@ function Page({ product }: Props) {
|
|
|
84
100
|
If needed, wrap your component in a <Section /> component
|
|
85
101
|
(not the HTML tag) before rendering it here.
|
|
86
102
|
*/}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
first={ITEMS_PER_SECTION}
|
|
92
|
-
selectedFacets={[
|
|
93
|
-
{ key: 'buy', value: product.isVariantOf.productGroupID },
|
|
94
|
-
]}
|
|
95
|
-
title="People also bought"
|
|
96
|
-
withDivisor
|
|
97
|
-
/>
|
|
98
|
-
|
|
99
|
-
<ProductShelf
|
|
100
|
-
first={ITEMS_PER_SECTION}
|
|
101
|
-
selectedFacets={[
|
|
102
|
-
{ key: 'view', value: product.isVariantOf.productGroupID },
|
|
103
|
-
]}
|
|
104
|
-
title="People also view"
|
|
103
|
+
<RenderPageSections
|
|
104
|
+
context={product}
|
|
105
|
+
sections={sections}
|
|
106
|
+
components={COMPONENTS}
|
|
105
107
|
/>
|
|
106
|
-
|
|
107
|
-
<BannerNewsletter />
|
|
108
108
|
</>
|
|
109
109
|
)
|
|
110
110
|
}
|
|
@@ -169,16 +169,23 @@ const query = gql`
|
|
|
169
169
|
`
|
|
170
170
|
|
|
171
171
|
export const getStaticProps: GetStaticProps<
|
|
172
|
-
|
|
173
|
-
{ slug: string }
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
Props,
|
|
173
|
+
{ slug: string },
|
|
174
|
+
Locator
|
|
175
|
+
> = async ({ params, previewData }) => {
|
|
176
|
+
const slug = params?.slug ?? ''
|
|
177
|
+
const [cmsPage, searchResult] = await Promise.all([
|
|
178
|
+
getPage<PDPContentType>({
|
|
179
|
+
...(previewData?.contentType === 'pdp' ? previewData : null),
|
|
180
|
+
contentType: 'pdp',
|
|
181
|
+
}),
|
|
182
|
+
execute<ServerProductPageQueryQueryVariables, ServerProductPageQueryQuery>({
|
|
183
|
+
variables: { slug },
|
|
184
|
+
operationName: query,
|
|
185
|
+
}),
|
|
186
|
+
])
|
|
187
|
+
|
|
188
|
+
const { data, errors = [] } = searchResult
|
|
182
189
|
|
|
183
190
|
const notFound = errors.find(isNotFoundError)
|
|
184
191
|
|
|
@@ -193,7 +200,10 @@ export const getStaticProps: GetStaticProps<
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
return {
|
|
196
|
-
props:
|
|
203
|
+
props: {
|
|
204
|
+
...data,
|
|
205
|
+
...cmsPage,
|
|
206
|
+
},
|
|
197
207
|
}
|
|
198
208
|
}
|
|
199
209
|
|
package/src/pages/_app.tsx
CHANGED
|
@@ -4,8 +4,7 @@ import '../styles/global/typography.scss'
|
|
|
4
4
|
import '../styles/global/layout.scss'
|
|
5
5
|
import '../styles/global/components.scss'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
import '../styles/themes/custom-theme.scss'
|
|
7
|
+
import '../customizations/themes/index.scss'
|
|
9
8
|
|
|
10
9
|
import NextNProgress from 'nextjs-progressbar'
|
|
11
10
|
import type { AppProps } from 'next/app'
|
package/src/pages/_document.tsx
CHANGED
|
@@ -2,7 +2,6 @@ import { Head, Html, Main, NextScript } from 'next/document'
|
|
|
2
2
|
|
|
3
3
|
import WebFonts from 'src/fonts/WebFonts'
|
|
4
4
|
import ThirdPartyScripts from 'src/components/ThirdPartyScripts'
|
|
5
|
-
import storeConfig from 'store.config'
|
|
6
5
|
|
|
7
6
|
function Document() {
|
|
8
7
|
return (
|
|
@@ -11,7 +10,7 @@ function Document() {
|
|
|
11
10
|
{!process.env.DISABLE_3P_SCRIPTS && <ThirdPartyScripts />}
|
|
12
11
|
<WebFonts />
|
|
13
12
|
</Head>
|
|
14
|
-
<body className=
|
|
13
|
+
<body className="theme">
|
|
15
14
|
<Main />
|
|
16
15
|
<NextScript />
|
|
17
16
|
</body>
|
package/src/pages/index.tsx
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
import { NextSeo, SiteLinksSearchBoxJsonLd } from 'next-seo'
|
|
2
|
+
import type { ComponentType } from 'react'
|
|
2
3
|
import type { GetStaticProps } from 'next'
|
|
3
4
|
import type { Locator } from '@vtex/client-cms'
|
|
4
5
|
|
|
5
6
|
import RenderPageSections from 'src/components/cms/RenderPageSections'
|
|
6
|
-
import
|
|
7
|
-
import
|
|
7
|
+
import BannerText from 'src/components/sections/BannerText'
|
|
8
|
+
import Hero from 'src/components/sections/Hero'
|
|
9
|
+
import IncentivesHeader from 'src/components/sections/Incentives/IncentivesHeader'
|
|
10
|
+
import Newsletter from 'src/components/sections/Newsletter'
|
|
11
|
+
import ProductShelf from 'src/components/sections/ProductShelf'
|
|
12
|
+
import ProductTiles from 'src/components/sections/ProductTiles'
|
|
8
13
|
import { mark } from 'src/sdk/tests/mark'
|
|
14
|
+
import { getPage } from 'src/server/cms'
|
|
15
|
+
import type { PageContentType } from 'src/server/cms'
|
|
16
|
+
import CUSTOM_SECTIONS from 'src/customizations'
|
|
9
17
|
|
|
10
18
|
import storeConfig from '../../store.config'
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Sections: Components imported from each store's custom components and '../components/sections'.
|
|
22
|
+
* Do not import or render components from any other folder in here.
|
|
23
|
+
*/
|
|
24
|
+
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
25
|
+
Hero,
|
|
26
|
+
BannerText,
|
|
27
|
+
IncentivesHeader,
|
|
28
|
+
ProductShelf,
|
|
29
|
+
ProductTiles,
|
|
30
|
+
Newsletter,
|
|
31
|
+
...CUSTOM_SECTIONS,
|
|
32
|
+
}
|
|
33
|
+
|
|
12
34
|
type Props = PageContentType
|
|
13
35
|
|
|
14
36
|
function Page({ sections, settings }: Props) {
|
|
@@ -48,7 +70,7 @@ function Page({ sections, settings }: Props) {
|
|
|
48
70
|
If needed, wrap your component in a <Section /> component
|
|
49
71
|
(not the HTML tag) before rendering it here.
|
|
50
72
|
*/}
|
|
51
|
-
<RenderPageSections sections={sections} />
|
|
73
|
+
<RenderPageSections sections={sections} components={COMPONENTS} />
|
|
52
74
|
</>
|
|
53
75
|
)
|
|
54
76
|
}
|
|
@@ -59,7 +81,9 @@ export const getStaticProps: GetStaticProps<
|
|
|
59
81
|
Locator
|
|
60
82
|
> = async (context) => {
|
|
61
83
|
const page = await getPage<PageContentType>({
|
|
62
|
-
...(context.previewData
|
|
84
|
+
...(context.previewData?.contentType === 'page'
|
|
85
|
+
? context.previewData
|
|
86
|
+
: { filters: { 'settings.seo.slug': '/' } }),
|
|
63
87
|
contentType: 'page',
|
|
64
88
|
})
|
|
65
89
|
|
package/src/server/cms.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ClientCMS from '@vtex/client-cms'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ContentData, Locator } from '@vtex/client-cms'
|
|
3
3
|
|
|
4
4
|
import config from '../../store.config'
|
|
5
5
|
|
|
@@ -54,6 +54,8 @@ export const getPage = async <T extends ContentData>(options: Options) => {
|
|
|
54
54
|
return pages[0] as T
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export type PDPContentType = ContentData
|
|
58
|
+
|
|
57
59
|
export type PageContentType = ContentData & {
|
|
58
60
|
settings: {
|
|
59
61
|
seo: {
|