@faststore/core 0.1.1 → 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 CHANGED
@@ -5,6 +5,13 @@ 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
+
8
15
  ### [0.1.1](https://github.com/vtex-sites/nextjs.store/compare/0.1.0...0.1.1) (2022-10-25)
9
16
 
10
17
  ## 0.1.0 (2022-10-25)
@@ -48,5 +48,10 @@
48
48
  ]
49
49
  }
50
50
  ]
51
+ },
52
+ {
53
+ "id": "pdp",
54
+ "name": "Product Page",
55
+ "configurationSchemaSets": []
51
56
  }
52
57
  ]
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.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "browserslist": "supports es6-module and not dead",
6
6
  "scripts": {
@@ -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
- sections?: Array<{ name: string; data: any }>
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?.map(({ name, data }, index) => {
33
- const Component = COMPONENTS[name]
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
- product: ProductDetailsFragment_ProductFragment
28
+ context: ProductDetailsFragment_ProductFragment
29
29
  }
30
30
 
31
- function ProductDetails({ product: staleProduct }: Props) {
31
+ function ProductDetails({ context: staleProduct }: Props) {
32
32
  const { currency } = useSession()
33
33
  const [addQuantity, setAddQuantity] = useState(1)
34
34
 
@@ -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 ProductDetails from 'src/components/sections/ProductDetails'
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 { ITEMS_PER_SECTION } from 'src/constants'
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
- type Props = ServerProductPageQueryQuery
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
- <ProductDetails product={product} />
89
-
90
- <ProductShelf
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
- ServerProductPageQueryQuery,
173
- { slug: string }
174
- > = async ({ params }) => {
175
- const { data, errors = [] } = await execute<
176
- ServerProductPageQueryQueryVariables,
177
- ServerProductPageQueryQuery
178
- >({
179
- variables: { slug: params?.slug ?? '' },
180
- operationName: query,
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: data,
201
+ props: {
202
+ ...data,
203
+ ...cmsPage,
204
+ },
197
205
  }
198
206
  }
199
207
 
@@ -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 type { PageContentType } from 'src/server/cms'
7
- import { getPage } from 'src/server/cms'
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) {
@@ -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 ?? { filters: { 'settings.seo.slug': '/' } }),
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 { Locator, ContentData } from '@vtex/client-cms'
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: {