@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 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
@@ -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.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
@@ -0,0 +1,6 @@
1
+ if (
2
+ process.cwd().includes('node_modules') ||
3
+ process.cwd().includes('.faststore')
4
+ ) {
5
+ process.exitCode = 1
6
+ }
@@ -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
 
@@ -0,0 +1 @@
1
+ export default {}
@@ -0,0 +1,3 @@
1
+ // ----------------------------------------------------------
2
+ // Stores' custom themes will override this file
3
+ // ----------------------------------------------------------
@@ -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 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'
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
- type Props = ServerProductPageQueryQuery
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
- <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"
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
- 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
- })
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: data,
203
+ props: {
204
+ ...data,
205
+ ...cmsPage,
206
+ },
197
207
  }
198
208
  }
199
209
 
@@ -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
- // Replace this with your theme style file
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'
@@ -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={storeConfig.theme}>
13
+ <body className="theme">
15
14
  <Main />
16
15
  <NextScript />
17
16
  </body>
@@ -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 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'
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 ?? { filters: { 'settings.seo.slug': '/' } }),
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 { 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: {