@faststore/core 3.0.14 → 3.0.16

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.
Files changed (66) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +15 -15
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/eslint/.cache_1gneedd +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/server-production/0.pack +0 -0
  9. package/.next/cache/webpack/server-production/index.pack +0 -0
  10. package/.next/next-minimal-server.js.nft.json +1 -1
  11. package/.next/next-server.js.nft.json +1 -1
  12. package/.next/prerender-manifest.js +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/640.js +1 -1
  16. package/.next/server/chunks/646.js +2 -2
  17. package/.next/server/chunks/659.js +2 -2
  18. package/.next/server/functions-config-manifest.json +1 -1
  19. package/.next/server/middleware-build-manifest.js +1 -1
  20. package/.next/server/pages/404.js +1 -1
  21. package/.next/server/pages/500.js +1 -1
  22. package/.next/server/pages/[...slug].js +1 -1
  23. package/.next/server/pages/[slug]/p.js +1 -1
  24. package/.next/server/pages/account.js +1 -1
  25. package/.next/server/pages/api/preview.js +1 -1
  26. package/.next/server/pages/checkout.js +1 -1
  27. package/.next/server/pages/en-US/404.html +2 -2
  28. package/.next/server/pages/en-US/404.json +1 -1
  29. package/.next/server/pages/en-US/500.html +2 -2
  30. package/.next/server/pages/en-US/500.json +1 -1
  31. package/.next/server/pages/en-US/account.html +2 -2
  32. package/.next/server/pages/en-US/account.json +1 -1
  33. package/.next/server/pages/en-US/checkout.html +2 -2
  34. package/.next/server/pages/en-US/checkout.json +1 -1
  35. package/.next/server/pages/en-US/login.html +2 -2
  36. package/.next/server/pages/en-US/login.json +1 -1
  37. package/.next/server/pages/en-US/s.html +2 -2
  38. package/.next/server/pages/en-US/s.json +1 -1
  39. package/.next/server/pages/en-US.html +4 -4
  40. package/.next/server/pages/en-US.json +1 -1
  41. package/.next/server/pages/index.js +1 -1
  42. package/.next/server/pages/login.js +1 -1
  43. package/.next/server/pages/s.js +1 -1
  44. package/.next/server/pages-manifest.json +1 -1
  45. package/.next/static/{OJ8d-uEqYHb0KXVz0Trnt → RX_p788L2keTWhdlgySlF}/_buildManifest.js +1 -1
  46. package/.next/static/chunks/104-4f83b1d87ad36358.js +1 -0
  47. package/.next/static/chunks/pages/{[...slug]-506fd56c3ad48553.js → [...slug]-84926ee31c8f6fee.js} +1 -1
  48. package/.next/static/chunks/pages/[slug]/{p-a4f7d7c00fdf4157.js → p-5fb8fe2c80ec1608.js} +1 -1
  49. package/.next/static/chunks/pages/{index-00798cca3b47590d.js → index-cd109119d65df8e3.js} +1 -1
  50. package/.next/static/chunks/pages/{s-e78f09767764a172.js → s-26e475975386c51a.js} +1 -1
  51. package/.next/trace +91 -91
  52. package/.turbo/turbo-test.log +10 -10
  53. package/cms/faststore/content-types.json +21 -2
  54. package/cypress/integration/seo.test.js +2 -6
  55. package/package.json +2 -2
  56. package/src/components/cms/GlobalSections.tsx +4 -4
  57. package/src/components/templates/LandingPage/LandingPage.tsx +6 -6
  58. package/src/pages/[...slug].tsx +6 -11
  59. package/src/pages/[slug]/p.tsx +6 -25
  60. package/src/pages/index.tsx +3 -3
  61. package/src/pages/s.tsx +3 -3
  62. package/src/server/{cms.ts → cms/index.ts} +35 -57
  63. package/src/server/cms/pdp.ts +82 -0
  64. package/src/utils/utilities.ts +65 -0
  65. package/.next/static/chunks/104-35697aed442af6cd.js +0 -1
  66. /package/.next/static/{OJ8d-uEqYHb0KXVz0Trnt → RX_p788L2keTWhdlgySlF}/_ssgManifest.js +0 -0
@@ -1,23 +1,23 @@
1
1
  $ jest
2
- PASS test/server/index.test.ts (20.679 s)
2
+ PASS test/server/index.test.ts (21.654 s)
3
3
  FastStore GraphQL Layer
4
4
  @faststore/api
5
- ✓ should return a valid GraphQL schema (8 ms)
6
- ✓ should return a valid GraphQL schema contain all expected types (6 ms)
7
- ✓ should return a valid GraphQL schema contain all expected queries (1 ms)
5
+ ✓ should return a valid GraphQL schema (17 ms)
6
+ ✓ should return a valid GraphQL schema contain all expected types (14 ms)
7
+ ✓ should return a valid GraphQL schema contain all expected queries (2 ms)
8
8
  ✓ should return a valid GraphQL schema contain all expected mutations
9
9
  VTEX API Extension
10
- ✓ getTypeDefsFromFolder function should return an Array (21 ms)
10
+ ✓ getTypeDefsFromFolder function should return an Array (8 ms)
11
11
  Third Party API Extension
12
- ✓ getTypeDefsFromFolder function should return an Array (20 ms)
12
+ ✓ getTypeDefsFromFolder function should return an Array (29 ms)
13
13
  Final Schema after merging
14
- ✓ should return a valid merged GraphQL schema (49 ms)
14
+ ✓ should return a valid merged GraphQL schema (42 ms)
15
15
  Envelop
16
- ✓ should exist with its plugins (40 ms)
17
- ✓ should handle options and execute (180 ms)
16
+ ✓ should exist with its plugins (42 ms)
17
+ ✓ should handle options and execute (372 ms)
18
18
 
19
19
  Test Suites: 1 passed, 1 total
20
20
  Tests: 9 passed, 9 total
21
21
  Snapshots: 0 total
22
- Time: 20.722 s
22
+ Time: 21.713 s
23
23
  Ran all test suites.
@@ -103,8 +103,27 @@
103
103
  "id": "pdp",
104
104
  "name": "Product Page",
105
105
  "scopes": ["pdp"],
106
- "isSingleton": true,
107
- "configurationSchemaSets": []
106
+ "configurationSchemaSets": [
107
+ {
108
+ "name": "Settings",
109
+ "configurations": [
110
+ {
111
+ "name": "template",
112
+ "schema": {
113
+ "title": "Template",
114
+ "type": "object",
115
+ "properties": {
116
+ "value": {
117
+ "title": "PDP template value (e.g. Slug: /apple-magic-mouse/p OR /department/category/subcategory/)",
118
+ "type": "string",
119
+ "description": "Possible values: the PDP slug template (e.g. '/apple-magic-mouse/p'); A PLP template (e.g. '/department/' OR '/department/category/' OR '/department/category/subcategory/'). If empty, this template will be the generic PDP."
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ]
125
+ }
126
+ ]
108
127
  },
109
128
  {
110
129
  "id": "plp",
@@ -176,9 +176,7 @@ describe('Collection Page Seo', () => {
176
176
  cy.get('link[rel="canonical"]')
177
177
  .should('exist')
178
178
  .should(($link) => {
179
- expect($link.attr('href')).to.eq(
180
- `${storeUrl}${pages.collection}`
181
- )
179
+ expect($link.attr('href')).to.eq(`${storeUrl}${pages.collection}`)
182
180
  })
183
181
  })
184
182
 
@@ -233,9 +231,7 @@ describe('Filtered Collection Page Seo', () => {
233
231
  cy.get('link[rel="canonical"]')
234
232
  .should('exist')
235
233
  .should(($link) => {
236
- expect($link.attr('href')).to.eq(
237
- `${storeUrl}${pages.collection}`
238
- )
234
+ expect($link.attr('href')).to.eq(`${storeUrl}${pages.collection}`)
239
235
  })
240
236
  })
241
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.0.14",
3
+ "version": "3.0.16",
4
4
  "license": "MIT",
5
5
  "repository": "vtex/faststore",
6
6
  "browserslist": "supports es6-module and not dead",
@@ -128,5 +128,5 @@
128
128
  "node": "18.19.0",
129
129
  "yarn": "1.19.1"
130
130
  },
131
- "gitHead": "2cf5776a96f56a356ab958e4aa488ad239e54a62"
131
+ "gitHead": "ae371cd0fb3a8eab012ed79c65a55aedf074070e"
132
132
  }
@@ -1,17 +1,17 @@
1
1
  import { Locator, Section } from '@vtex/client-cms'
2
+ import storeConfig from 'faststore.config'
2
3
  import type { ComponentType } from 'react'
3
4
  import { PropsWithChildren, lazy } from 'react'
4
- import storeConfig from 'faststore.config'
5
5
  import CUSTOM_COMPONENTS from 'src/customizations/src/components'
6
- import { PageContentType, getPage, getPageByVersionId } from 'src/server/cms'
6
+ import { PageContentType, getPage } from 'src/server/cms'
7
7
 
8
8
  import Toast from 'src/components/common/Toast'
9
9
  import RenderSections from './RenderSections'
10
10
 
11
11
  import { OverriddenDefaultAlert as Alert } from 'src/components/sections/Alert/OverriddenDefaultAlert'
12
+ import Footer from 'src/components/sections/Footer'
12
13
  import { OverriddenDefaultNavbar as Navbar } from 'src/components/sections/Navbar/OverriddenDefaultNavbar'
13
14
  import { OverriddenDefaultRegionBar as RegionBar } from 'src/components/sections/RegionBar/OverriddenDefaultRegionBar'
14
- import Footer from 'src/components/sections/Footer'
15
15
 
16
16
  const RegionModal = lazy(() => import('src/components/region/RegionModal'))
17
17
  const CartSidebar = lazy(() => import('src/components/cart/CartSidebar'))
@@ -56,7 +56,7 @@ export const getGlobalSectionsData = async (
56
56
  const page = cmsData[GLOBAL_SECTIONS_CONTENT_TYPE][0]
57
57
 
58
58
  if (page) {
59
- const pageData = await getPageByVersionId<PageContentType>({
59
+ const pageData = await getPage<PageContentType>({
60
60
  contentType: GLOBAL_SECTIONS_CONTENT_TYPE,
61
61
  documentId: page.documentId,
62
62
  versionId: page.versionId,
@@ -1,18 +1,18 @@
1
+ import type { Locator } from '@vtex/client-cms'
1
2
  import { NextSeo, SiteLinksSearchBoxJsonLd } from 'next-seo'
2
3
  import type { ComponentType } from 'react'
3
- import type { Locator } from '@vtex/client-cms'
4
4
 
5
- import MissingContentError from 'src/sdk/error/MissingContentError/MissingContentError'
6
5
  import RenderSections from 'src/components/cms/RenderSections'
7
6
  import { OverriddenDefaultBannerText as BannerText } from 'src/components/sections/BannerText/OverriddenDefaultBannerText'
8
7
  import { OverriddenDefaultHero as Hero } from 'src/components/sections/Hero/OverriddenDefaultHero'
8
+ import Incentives from 'src/components/sections/Incentives'
9
9
  import { OverriddenDefaultNewsletter as Newsletter } from 'src/components/sections/Newsletter/OverriddenDefaultNewsletter'
10
10
  import { OverriddenDefaultProductShelf as ProductShelf } from 'src/components/sections/ProductShelf/OverriddenDefaultProductShelf'
11
- import Incentives from 'src/components/sections/Incentives'
12
11
  import ProductTiles from 'src/components/sections/ProductTiles'
13
- import { getPage, getPageByVersionId } from 'src/server/cms'
14
- import type { PageContentType } from 'src/server/cms'
15
12
  import CUSTOM_COMPONENTS from 'src/customizations/src/components'
13
+ import MissingContentError from 'src/sdk/error/MissingContentError/MissingContentError'
14
+ import type { PageContentType } from 'src/server/cms'
15
+ import { getPage } from 'src/server/cms'
16
16
 
17
17
  import storeConfig from 'faststore.config'
18
18
 
@@ -92,7 +92,7 @@ export const getLandingPageBySlug = async (
92
92
  })
93
93
 
94
94
  if (pageBySlug) {
95
- const landingPageData = await getPageByVersionId<PageContentType>({
95
+ const landingPageData = await getPage<PageContentType>({
96
96
  contentType: 'landingPage',
97
97
  documentId: pageBySlug.documentId,
98
98
  versionId: pageBySlug.versionId,
@@ -1,6 +1,6 @@
1
1
  import { isNotFoundError } from '@faststore/api'
2
- import type { GetStaticPaths, GetStaticProps } from 'next'
3
2
  import storeConfig from 'faststore.config'
3
+ import type { GetStaticPaths, GetStaticProps } from 'next'
4
4
 
5
5
  import { gql } from '@generated'
6
6
  import type {
@@ -15,19 +15,14 @@ import GlobalSections, {
15
15
  getGlobalSectionsData,
16
16
  GlobalSectionsData,
17
17
  } from 'src/components/cms/GlobalSections'
18
- import {
19
- getPage,
20
- getPageByVersionId,
21
- PageContentType,
22
- PLPContentType,
23
- } from 'src/server/cms'
24
- import ProductListingPage, {
25
- ProductListingPageProps,
26
- } from 'src/components/templates/ProductListingPage'
27
18
  import LandingPage, {
28
19
  getLandingPageBySlug,
29
20
  LandingPageProps,
30
21
  } from 'src/components/templates/LandingPage'
22
+ import ProductListingPage, {
23
+ ProductListingPageProps,
24
+ } from 'src/components/templates/ProductListingPage'
25
+ import { getPage, PageContentType, PLPContentType } from 'src/server/cms'
31
26
 
32
27
  type BaseProps = {
33
28
  globalSections: GlobalSectionsData
@@ -123,7 +118,7 @@ export const getStaticProps: GetStaticProps<
123
118
  const page = cmsData['plp'][0]
124
119
 
125
120
  if (page) {
126
- pageData = await getPageByVersionId<PLPContentType>({
121
+ pageData = await getPage<PLPContentType>({
127
122
  contentType: 'plp',
128
123
  documentId: page.documentId,
129
124
  versionId: page.versionId,
@@ -1,9 +1,9 @@
1
1
  import { isNotFoundError } from '@faststore/api'
2
2
  import type { Locator } from '@vtex/client-cms'
3
+ import deepmerge from 'deepmerge'
3
4
  import type { GetStaticPaths, GetStaticProps } from 'next'
4
5
  import { BreadcrumbJsonLd, NextSeo, ProductJsonLd } from 'next-seo'
5
6
  import type { ComponentType } from 'react'
6
- import deepmerge from 'deepmerge'
7
7
 
8
8
  import { gql } from '@generated'
9
9
  import {
@@ -20,16 +20,15 @@ import CUSTOM_COMPONENTS from 'src/customizations/src/components'
20
20
  import { useSession } from 'src/sdk/session'
21
21
  import { mark } from 'src/sdk/tests/mark'
22
22
  import { execute } from 'src/server'
23
- import type { PDPContentType } from 'src/server/cms'
24
- import { getPage, getPageByVersionId } from 'src/server/cms'
25
23
 
24
+ import storeConfig from 'faststore.config'
26
25
  import GlobalSections, {
27
26
  GlobalSectionsData,
28
27
  getGlobalSectionsData,
29
28
  } from 'src/components/cms/GlobalSections'
30
- import storeConfig from 'faststore.config'
31
- import { useProductQuery } from 'src/sdk/product/useProductQuery'
32
29
  import PageProvider, { PDPContext } from 'src/sdk/overrides/PageProvider'
30
+ import { useProductQuery } from 'src/sdk/product/useProductQuery'
31
+ import { PDPContentType, getPDP } from 'src/server/cms/pdp'
33
32
 
34
33
  /**
35
34
  * Sections: Components imported from each store's custom components and '../components/sections' only.
@@ -208,26 +207,6 @@ export const getStaticProps: GetStaticProps<
208
207
  getGlobalSectionsData(previewData),
209
208
  ])
210
209
 
211
- let cmsPage
212
-
213
- if (storeConfig.cms.data) {
214
- const cmsData = JSON.parse(storeConfig.cms.data)
215
- const page = cmsData['pdp'][0]
216
-
217
- if (page) {
218
- cmsPage = getPageByVersionId<PDPContentType>({
219
- contentType: 'pdp',
220
- documentId: page.documentId,
221
- versionId: page.versionId,
222
- })
223
- }
224
- } else {
225
- cmsPage = getPage<PDPContentType>({
226
- ...(previewData?.contentType === 'pdp' ? previewData : null),
227
- contentType: 'pdp',
228
- })
229
- }
230
-
231
210
  const { data, errors = [] } = searchResult
232
211
 
233
212
  const notFound = errors.find(isNotFoundError)
@@ -242,6 +221,8 @@ export const getStaticProps: GetStaticProps<
242
221
  throw errors[0]
243
222
  }
244
223
 
224
+ const cmsPage: PDPContentType = await getPDP(slug, data.product, previewData)
225
+
245
226
  const { seo } = data.product
246
227
  const title = seo.title || storeConfig.seo.title
247
228
  const description = seo.description || storeConfig.seo.description
@@ -6,14 +6,14 @@ import type { ComponentType } from 'react'
6
6
  import RenderSections from 'src/components/cms/RenderSections'
7
7
  import { OverriddenDefaultBannerText as BannerText } from 'src/components/sections/BannerText/OverriddenDefaultBannerText'
8
8
  import { OverriddenDefaultHero as Hero } from 'src/components/sections/Hero/OverriddenDefaultHero'
9
+ import Incentives from 'src/components/sections/Incentives'
9
10
  import { OverriddenDefaultNewsletter as Newsletter } from 'src/components/sections/Newsletter/OverriddenDefaultNewsletter'
10
11
  import { OverriddenDefaultProductShelf as ProductShelf } from 'src/components/sections/ProductShelf/OverriddenDefaultProductShelf'
11
- import Incentives from 'src/components/sections/Incentives'
12
12
  import ProductTiles from 'src/components/sections/ProductTiles'
13
13
  import CUSTOM_COMPONENTS from 'src/customizations/src/components'
14
14
  import { mark } from 'src/sdk/tests/mark'
15
15
  import type { PageContentType } from 'src/server/cms'
16
- import { getPage, getPageByVersionId } from 'src/server/cms'
16
+ import { getPage } from 'src/server/cms'
17
17
 
18
18
  import GlobalSections, {
19
19
  GlobalSectionsData,
@@ -92,7 +92,7 @@ export const getStaticProps: GetStaticProps<
92
92
  const page = cmsData['home'][0]
93
93
 
94
94
  if (page) {
95
- const pageData = await getPageByVersionId<PageContentType>({
95
+ const pageData = await getPage<PageContentType>({
96
96
  contentType: 'home',
97
97
  documentId: page.documentId,
98
98
  versionId: page.versionId,
package/src/pages/s.tsx CHANGED
@@ -14,14 +14,14 @@ import { useApplySearchState } from 'src/sdk/search/state'
14
14
  import { mark } from 'src/sdk/tests/mark'
15
15
 
16
16
  import { Locator } from '@vtex/client-cms'
17
+ import storeConfig from 'faststore.config'
17
18
  import { GetStaticProps } from 'next'
18
19
  import GlobalSections, {
19
20
  getGlobalSectionsData,
20
21
  GlobalSectionsData,
21
22
  } from 'src/components/cms/GlobalSections'
22
- import { getPage, getPageByVersionId, SearchContentType } from 'src/server/cms'
23
- import storeConfig from 'faststore.config'
24
23
  import SearchPage from 'src/components/templates/SearchPage/SearchPage'
24
+ import { getPage, SearchContentType } from 'src/server/cms'
25
25
 
26
26
  type Props = {
27
27
  page: SearchContentType
@@ -125,7 +125,7 @@ export const getStaticProps: GetStaticProps<
125
125
  const page = cmsData['search'][0]
126
126
 
127
127
  if (page) {
128
- const pageData = await getPageByVersionId<SearchContentType>({
128
+ const pageData = await getPage<SearchContentType>({
129
129
  contentType: 'search',
130
130
  documentId: page.documentId,
131
131
  versionId: page.versionId,
@@ -1,14 +1,8 @@
1
1
  import type { ContentData, ContentTypeOptions, Locator } from '@vtex/client-cms'
2
2
  import ClientCMS from '@vtex/client-cms'
3
-
4
- import config from '../../faststore.config'
5
- import MultipleContentError from 'src/sdk/error/MultipleContentError'
6
3
  import MissingContentError from 'src/sdk/error/MissingContentError'
7
-
8
- export const clientCMS = new ClientCMS({
9
- workspace: config.api.workspace,
10
- tenant: config.api.storeId,
11
- })
4
+ import MultipleContentError from 'src/sdk/error/MultipleContentError'
5
+ import config from '../../../faststore.config'
12
6
 
13
7
  export type Options =
14
8
  | Locator
@@ -17,54 +11,6 @@ export type Options =
17
11
  filters?: Partial<ContentTypeOptions>
18
12
  }
19
13
 
20
- const isLocator = (x: any): x is Locator =>
21
- typeof x.contentType === 'string' &&
22
- (typeof x.releaseId === 'string' || typeof x.documentId === 'string')
23
-
24
- export const getPage = async <T extends ContentData>(options: Options) => {
25
- const result = await (isLocator(options)
26
- ? clientCMS.getCMSPage(options).then((page) => ({ data: [page] }))
27
- : clientCMS.getCMSPagesByContentType(options.contentType, options.filters))
28
-
29
- const pages = result.data
30
-
31
- if (!pages[0]) {
32
- throw new MissingContentError(options)
33
- }
34
-
35
- if (pages.length !== 1) {
36
- throw new MultipleContentError(options)
37
- }
38
-
39
- return pages[0] as T
40
- }
41
-
42
- export type VersionOptions = {
43
- contentType: string
44
- documentId: string
45
- versionId: string
46
- }
47
-
48
- export const getPageByVersionId = async <T extends ContentData>(
49
- options: VersionOptions
50
- ) => {
51
- const result = await clientCMS
52
- .getCMSPage(options)
53
- .then((page) => ({ data: [page] }))
54
-
55
- const pages = result.data
56
-
57
- if (!pages[0]) {
58
- throw new MissingContentError(options)
59
- }
60
-
61
- if (pages.length !== 1) {
62
- throw new MultipleContentError(options)
63
- }
64
-
65
- return pages[0] as T
66
- }
67
-
68
14
  type ProductGallerySettings = {
69
15
  settings: {
70
16
  productGallery: {
@@ -74,7 +20,6 @@ type ProductGallerySettings = {
74
20
  }
75
21
  }
76
22
 
77
- export type PDPContentType = ContentData
78
23
  export type PLPContentType = ContentData & ProductGallerySettings
79
24
  export type SearchContentType = ContentData & ProductGallerySettings
80
25
 
@@ -88,3 +33,36 @@ export type PageContentType = ContentData & {
88
33
  }
89
34
  }
90
35
  }
36
+
37
+ const isLocator = (x: any): x is Locator =>
38
+ typeof x.contentType === 'string' &&
39
+ (typeof x.releaseId === 'string' ||
40
+ typeof x.documentId === 'string' ||
41
+ typeof x.versionId === 'string')
42
+
43
+ export const clientCMS = new ClientCMS({
44
+ workspace: config.api.workspace,
45
+ tenant: config.api.storeId,
46
+ })
47
+
48
+ export const getCMSPage = async (options: Options) => {
49
+ return await (isLocator(options)
50
+ ? clientCMS.getCMSPage(options).then((page) => ({ data: [page] }))
51
+ : clientCMS.getCMSPagesByContentType(options.contentType, options.filters))
52
+ }
53
+
54
+ export const getPage = async <T extends ContentData>(options: Options) => {
55
+ const result = await getCMSPage(options)
56
+
57
+ const pages = result.data
58
+
59
+ if (!pages[0]) {
60
+ throw new MissingContentError(options)
61
+ }
62
+
63
+ if (pages.length !== 1) {
64
+ throw new MultipleContentError(options)
65
+ }
66
+
67
+ return pages[0] as T
68
+ }
@@ -0,0 +1,82 @@
1
+ import { ServerProductQueryQuery } from '@generated/graphql'
2
+ import type { ContentData, Locator } from '@vtex/client-cms'
3
+ import MissingContentError from 'src/sdk/error/MissingContentError'
4
+ import { findBestPDPTemplate } from 'src/utils/utilities'
5
+ import { Options, getCMSPage, getPage } from '.'
6
+ import config from '../../../faststore.config'
7
+
8
+ type PDPSettings = {
9
+ settings: {
10
+ template?: {
11
+ value?: string
12
+ }
13
+ }
14
+ }
15
+
16
+ type PDPfromCmsEnvData = {
17
+ documentId: string
18
+ versionId: string
19
+ } & PDPSettings
20
+
21
+ export type PDPContentType = ContentData & PDPSettings
22
+
23
+ export const getPDP = async (
24
+ slug: string,
25
+ product: ServerProductQueryQuery['product'],
26
+ previewData: Locator
27
+ ) => {
28
+ if (config.cms.data) {
29
+ const cmsData = JSON.parse(config.cms.data)
30
+ const allPDPsFromCmsEnvData: PDPfromCmsEnvData[] = cmsData['pdp']
31
+
32
+ return await getPDPFromCmsEnvData(
33
+ `/${slug}/p`,
34
+ product,
35
+ allPDPsFromCmsEnvData,
36
+ {
37
+ ...(previewData?.contentType === 'pdp' ? previewData : null),
38
+ contentType: 'pdp',
39
+ }
40
+ )
41
+ }
42
+
43
+ return (await getPDPFromCms(`/${slug}/p`, product, {
44
+ ...(previewData?.contentType === 'pdp' ? previewData : null),
45
+ contentType: 'pdp',
46
+ })) as PDPContentType
47
+ }
48
+
49
+ const getPDPFromCmsEnvData = async (
50
+ slug: string,
51
+ product: ServerProductQueryQuery['product'],
52
+ allPDPsFromCMSData: PDPfromCmsEnvData[],
53
+ options: Options
54
+ ): Promise<PDPContentType> => {
55
+ const pages: PDPfromCmsEnvData[] = allPDPsFromCMSData ?? []
56
+
57
+ if (!pages[0]) {
58
+ throw new MissingContentError(options)
59
+ }
60
+
61
+ const template = findBestPDPTemplate(pages, slug, product)
62
+
63
+ return getPage<PDPContentType>({
64
+ contentType: 'pdp',
65
+ documentId: template.documentId as string,
66
+ versionId: template.versionId,
67
+ })
68
+ }
69
+
70
+ const getPDPFromCms = async (
71
+ slug: string,
72
+ product: ServerProductQueryQuery['product'],
73
+ options: Options
74
+ ): Promise<Partial<PDPContentType>> => {
75
+ const pages = (await getCMSPage(options)).data
76
+
77
+ if (!pages[0]) {
78
+ throw new MissingContentError(options)
79
+ }
80
+
81
+ return findBestPDPTemplate(pages, slug, product)
82
+ }
@@ -1,3 +1,6 @@
1
+ import { ServerProductQueryQuery } from '@generated/graphql'
2
+ import { PDPContentType } from 'src/server/cms/pdp'
3
+
1
4
  //Input "Example Text!". Output: example-text
2
5
  export function textToKebabCase(text: string): string {
3
6
  // Replace spaces and special characters with hyphens
@@ -11,3 +14,65 @@ export function textToKebabCase(text: string): string {
11
14
 
12
15
  return kebabCase ?? ''
13
16
  }
17
+
18
+ export function normalizePDPTemplate(templateValue: string) {
19
+ // Remove extra slashes, white spaces at the beginning and end
20
+ let formattedValue = templateValue.trim().replace(/^\/+|\/+$/g, '')
21
+
22
+ // Add a slash at the beginning if not present
23
+ if (!formattedValue.startsWith('/')) {
24
+ formattedValue = '/' + formattedValue
25
+ }
26
+
27
+ // Add a slash at the end if not ending with '/p'
28
+ if (!formattedValue.endsWith('/p')) {
29
+ formattedValue += '/'
30
+ }
31
+
32
+ // Remove duplicate slashes and white spaces throughout the string
33
+ formattedValue = formattedValue.replace(/\/+/g, '/').replace(/\s+/g, '')
34
+
35
+ return formattedValue.toLowerCase()
36
+ }
37
+
38
+ /**
39
+ * Find the best PDP template from the CMS based on the slug or in the product category tree.
40
+ * Prioritizing the following order:
41
+ *
42
+ * 1. A PDP template that matches the page slug (e.g. slug = /apple-magic-mouse/p).
43
+ * 2. A PDP template that matches the product subcategory (e.g. /department/category/subcategory).
44
+ * 3. A PDP template that matches the product category (e.g. /department/category).
45
+ * 4. A PDP template that matches the product department (e.g. /department).
46
+ * 5. If no matches are found, use the generic PDP template.
47
+ *
48
+ * @param pages
49
+ * @param originalSlug
50
+ * @param product
51
+ * @returns The best PDP template page for the slug
52
+ */
53
+ export function findBestPDPTemplate(
54
+ pages: Partial<PDPContentType>[],
55
+ slug: string,
56
+ product: ServerProductQueryQuery['product']
57
+ ) {
58
+ // productSlugAndCategoryTree with the prioritized order. [slug, subcategory tree, category tree, department]
59
+ const productSlugAndCategoryTree = product?.breadcrumbList?.itemListElement
60
+ ? [...product?.breadcrumbList?.itemListElement]
61
+ .reverse()
62
+ .map(({ item }) => item)
63
+ : []
64
+ productSlugAndCategoryTree.unshift(slug)
65
+
66
+ for (const item of productSlugAndCategoryTree) {
67
+ for (const page of pages) {
68
+ if (!page.settings?.template?.value) continue
69
+
70
+ const templateValue = normalizePDPTemplate(page.settings.template.value)
71
+ if (templateValue === item) {
72
+ return page
73
+ }
74
+ }
75
+ }
76
+
77
+ return pages.find((page) => !page.settings?.template?.value) || pages[0]
78
+ }