@faststore/core 0.1.0 → 0.2.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/CHANGELOG.md +9 -0
- package/cms/content-types.json +5 -0
- package/cms/sections.json +47 -0
- package/package.json +1 -1
- 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/pages/[slug]/p.tsx +44 -36
- package/src/pages/index.tsx +28 -6
- package/src/server/cms.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.0](https://github.com/vtex-sites/nextjs.store/compare/0.1.1...0.2.0) (2022-10-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Integrate CMS with PDP ([#283](https://github.com/vtex-sites/nextjs.store/issues/283)) ([e3c3ec8](https://github.com/vtex-sites/nextjs.store/commit/e3c3ec889cb76b185488ba8f8fa9e1007ffc99d5))
|
|
14
|
+
|
|
15
|
+
### [0.1.1](https://github.com/vtex-sites/nextjs.store/compare/0.1.0...0.1.1) (2022-10-25)
|
|
16
|
+
|
|
8
17
|
## 0.1.0 (2022-10-25)
|
|
9
18
|
|
|
10
19
|
|
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,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
|
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -2,14 +2,18 @@ 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'
|
|
13
17
|
import type {
|
|
14
18
|
ServerProductPageQueryQuery,
|
|
15
19
|
ServerProductPageQueryQueryVariables,
|
|
@@ -17,9 +21,19 @@ import type {
|
|
|
17
21
|
|
|
18
22
|
import storeConfig from '../../../store.config'
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Sections: Components imported from '../components/sections' only.
|
|
26
|
+
* Do not import or render components from any other folder in here.
|
|
27
|
+
*/
|
|
28
|
+
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
29
|
+
ProductDetails,
|
|
30
|
+
BannerNewsletter,
|
|
31
|
+
CrossSellingShelf,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Props = ServerProductPageQueryQuery & PDPContentType
|
|
21
35
|
|
|
22
|
-
function Page({ product }: Props) {
|
|
36
|
+
function Page({ product, sections }: Props) {
|
|
23
37
|
const { currency } = useSession()
|
|
24
38
|
const { seo } = product
|
|
25
39
|
const title = seo.title || storeConfig.seo.title
|
|
@@ -84,27 +98,11 @@ function Page({ product }: Props) {
|
|
|
84
98
|
If needed, wrap your component in a <Section /> component
|
|
85
99
|
(not the HTML tag) before rendering it here.
|
|
86
100
|
*/}
|
|
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"
|
|
101
|
+
<RenderPageSections
|
|
102
|
+
context={product}
|
|
103
|
+
sections={sections}
|
|
104
|
+
components={COMPONENTS}
|
|
105
105
|
/>
|
|
106
|
-
|
|
107
|
-
<BannerNewsletter />
|
|
108
106
|
</>
|
|
109
107
|
)
|
|
110
108
|
}
|
|
@@ -169,16 +167,23 @@ const query = gql`
|
|
|
169
167
|
`
|
|
170
168
|
|
|
171
169
|
export const getStaticProps: GetStaticProps<
|
|
172
|
-
|
|
173
|
-
{ slug: string }
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
170
|
+
Props,
|
|
171
|
+
{ slug: string },
|
|
172
|
+
Locator
|
|
173
|
+
> = async ({ params, previewData }) => {
|
|
174
|
+
const slug = params?.slug ?? ''
|
|
175
|
+
const [cmsPage, searchResult] = await Promise.all([
|
|
176
|
+
getPage<PDPContentType>({
|
|
177
|
+
...(previewData?.contentType === 'pdp' ? previewData : null),
|
|
178
|
+
contentType: 'pdp',
|
|
179
|
+
}),
|
|
180
|
+
execute<ServerProductPageQueryQueryVariables, ServerProductPageQueryQuery>({
|
|
181
|
+
variables: { slug },
|
|
182
|
+
operationName: query,
|
|
183
|
+
}),
|
|
184
|
+
])
|
|
185
|
+
|
|
186
|
+
const { data, errors = [] } = searchResult
|
|
182
187
|
|
|
183
188
|
const notFound = errors.find(isNotFoundError)
|
|
184
189
|
|
|
@@ -193,7 +198,10 @@ export const getStaticProps: GetStaticProps<
|
|
|
193
198
|
}
|
|
194
199
|
|
|
195
200
|
return {
|
|
196
|
-
props:
|
|
201
|
+
props: {
|
|
202
|
+
...data,
|
|
203
|
+
...cmsPage,
|
|
204
|
+
},
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
|
package/src/pages/index.tsx
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
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'
|
|
9
16
|
|
|
10
17
|
import storeConfig from '../../store.config'
|
|
11
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Sections: Components imported from '../components/sections' only.
|
|
21
|
+
* Do not import or render components from any other folder in here.
|
|
22
|
+
*/
|
|
23
|
+
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
24
|
+
Hero,
|
|
25
|
+
BannerText,
|
|
26
|
+
IncentivesHeader,
|
|
27
|
+
ProductShelf,
|
|
28
|
+
ProductTiles,
|
|
29
|
+
Newsletter,
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
type Props = PageContentType
|
|
13
33
|
|
|
14
34
|
function Page({ sections, settings }: Props) {
|
|
@@ -31,8 +51,8 @@ function Page({ sections, settings }: Props) {
|
|
|
31
51
|
url={storeConfig.storeUrl}
|
|
32
52
|
potentialActions={[
|
|
33
53
|
{
|
|
34
|
-
target: `${storeConfig.storeUrl}/s/?q
|
|
35
|
-
queryInput: '
|
|
54
|
+
target: `${storeConfig.storeUrl}/s/?q`,
|
|
55
|
+
queryInput: 'search_term_string',
|
|
36
56
|
},
|
|
37
57
|
]}
|
|
38
58
|
/>
|
|
@@ -48,7 +68,7 @@ function Page({ sections, settings }: Props) {
|
|
|
48
68
|
If needed, wrap your component in a <Section /> component
|
|
49
69
|
(not the HTML tag) before rendering it here.
|
|
50
70
|
*/}
|
|
51
|
-
<RenderPageSections sections={sections} />
|
|
71
|
+
<RenderPageSections sections={sections} components={COMPONENTS} />
|
|
52
72
|
</>
|
|
53
73
|
)
|
|
54
74
|
}
|
|
@@ -59,7 +79,9 @@ export const getStaticProps: GetStaticProps<
|
|
|
59
79
|
Locator
|
|
60
80
|
> = async (context) => {
|
|
61
81
|
const page = await getPage<PageContentType>({
|
|
62
|
-
...(context.previewData
|
|
82
|
+
...(context.previewData?.contentType === 'page'
|
|
83
|
+
? context.previewData
|
|
84
|
+
: { filters: { 'settings.seo.slug': '/' } }),
|
|
63
85
|
contentType: 'page',
|
|
64
86
|
})
|
|
65
87
|
|
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: {
|