@faststore/core 3.0.59 → 3.0.61

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 (72) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +3 -3
  3. package/.next/build-manifest.json +25 -25
  4. package/.next/cache/.tsbuildinfo +1 -1
  5. package/.next/cache/config.json +3 -3
  6. package/.next/cache/eslint/.cache_1gneedd +1 -1
  7. package/.next/cache/fetch-cache/50912854cb7c781522a6ff8792d714e549515fcbbbfd660761961b06afe01c07 +1 -1
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-minimal-server.js.nft.json +1 -1
  13. package/.next/next-server.js.nft.json +1 -1
  14. package/.next/prerender-manifest.js +1 -1
  15. package/.next/prerender-manifest.json +1 -1
  16. package/.next/routes-manifest.json +1 -1
  17. package/.next/server/app/_not-found.html +2 -2
  18. package/.next/server/app/_not-found.js.nft.json +1 -1
  19. package/.next/server/app/_not-found.rsc +1 -1
  20. package/.next/server/app/fs-next-update/page.js.nft.json +1 -1
  21. package/.next/server/app/fs-next-update.html +2 -2
  22. package/.next/server/app/fs-next-update.rsc +1 -1
  23. package/.next/server/chunks/2133.js +5 -5
  24. package/.next/server/chunks/{6112.js → 2381.js} +1 -1
  25. package/.next/server/middleware-build-manifest.js +1 -1
  26. package/.next/server/pages/404.html +2 -2
  27. package/.next/server/pages/404.js.nft.json +1 -1
  28. package/.next/server/pages/500.js.nft.json +1 -1
  29. package/.next/server/pages/[...slug].js +2 -2
  30. package/.next/server/pages/[...slug].js.nft.json +1 -1
  31. package/.next/server/pages/[slug]/p.js +5 -5
  32. package/.next/server/pages/[slug]/p.js.nft.json +1 -1
  33. package/.next/server/pages/_app.js.nft.json +1 -1
  34. package/.next/server/pages/_document.js.nft.json +1 -1
  35. package/.next/server/pages/_error.js.nft.json +1 -1
  36. package/.next/server/pages/account.js.nft.json +1 -1
  37. package/.next/server/pages/api/graphql.js.nft.json +1 -1
  38. package/.next/server/pages/api/health/live.js.nft.json +1 -1
  39. package/.next/server/pages/api/health/ready.js.nft.json +1 -1
  40. package/.next/server/pages/api/preview.js.nft.json +1 -1
  41. package/.next/server/pages/checkout.js.nft.json +1 -1
  42. package/.next/server/pages/en-US/404.html +2 -2
  43. package/.next/server/pages/en-US/500.html +2 -2
  44. package/.next/server/pages/en-US/account.html +2 -2
  45. package/.next/server/pages/en-US/checkout.html +2 -2
  46. package/.next/server/pages/en-US/login.html +2 -2
  47. package/.next/server/pages/en-US/s.html +2 -2
  48. package/.next/server/pages/en-US.html +2 -2
  49. package/.next/server/pages/index.js.nft.json +1 -1
  50. package/.next/server/pages/login.js.nft.json +1 -1
  51. package/.next/server/pages/s.js.nft.json +1 -1
  52. package/.next/server/pages-manifest.json +1 -1
  53. package/.next/static/{lIYvWbgkX76Tpj8gVu1kU → 4w3LMmusMebDghIVyNvPn}/_buildManifest.js +1 -1
  54. package/.next/static/chunks/pages/{404-be2217109dab18cf.js → 404-6d926d3bdc58852b.js} +1 -1
  55. package/.next/static/chunks/pages/{500-64a07a16830e129c.js → 500-cbffc24e6eaddf18.js} +1 -1
  56. package/.next/static/chunks/pages/{login-258e5b7ecec36481.js → login-78b2d6166a2ebbaa.js} +1 -1
  57. package/.next/static/chunks/{webpack-a45dc84c9a0ef3fd.js → webpack-4f403c9b52a287c6.js} +1 -1
  58. package/.next/static/css/9b7689964b990431.css +1 -0
  59. package/.next/trace +62 -62
  60. package/.turbo/turbo-build.log +2 -2
  61. package/.turbo/turbo-test.log +6 -5
  62. package/package.json +2 -2
  63. package/src/pages/[...slug].tsx +3 -1
  64. package/src/pages/[slug]/p.tsx +1 -1
  65. package/src/server/cms/pdp.ts +8 -16
  66. package/src/server/cms/plp.ts +33 -14
  67. package/src/utils/multipleTemplates.ts +216 -0
  68. package/src/utils/utilities.ts +0 -118
  69. package/test/utils/multipleTemplates.test.ts +144 -0
  70. package/.next/static/css/548bab931c45c770.css +0 -1
  71. /package/.next/static/{lIYvWbgkX76Tpj8gVu1kU → 4w3LMmusMebDghIVyNvPn}/_ssgManifest.js +0 -0
  72. /package/.next/static/chunks/{590-a0b6f16148203bf2.js → 590-da547057f2ae283b.js} +0 -0
@@ -38,7 +38,7 @@ Route (app) Size First Load JS
38
38
  ├ chunks/472-369461a1f39981d5.js 28.4 kB
39
39
  ├ chunks/fd9d1056-43c43818840d7811.js 51.1 kB
40
40
  ├ chunks/main-app-e13fa67c2c3ceca5.js 230 B
41
- └ chunks/webpack-a45dc84c9a0ef3fd.js 2.43 kB
41
+ └ chunks/webpack-4f403c9b52a287c6.js 2.43 kB
42
42
 
43
43
  Route (pages) Size First Load JS
44
44
  ┌ ● / 1.04 kB 148 kB
@@ -62,7 +62,7 @@ Route (pages) Size First Load JS
62
62
  ├ chunks/framework-21e9365486ba23a6.js 45.4 kB
63
63
  ├ chunks/main-9c9c62c368c0a47e.js 34.8 kB
64
64
  ├ chunks/pages/_app-1930798899758fda.js 11.2 kB
65
- ├ chunks/webpack-a45dc84c9a0ef3fd.js 2.43 kB
65
+ ├ chunks/webpack-4f403c9b52a287c6.js 2.43 kB
66
66
  └ css/5d1f64b61ea581f4.css 3.05 kB
67
67
 
68
68
  λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
@@ -1,9 +1,10 @@
1
1
  $ jest
2
- PASS test/server/cms/index.test.ts (29.963 s)
3
- PASS test/server/index.test.ts (31.672 s)
2
+ PASS test/server/cms/index.test.ts (31.47 s)
3
+ PASS test/utils/multipleTemplates.test.ts (31.586 s)
4
+ PASS test/server/index.test.ts (32.977 s)
4
5
 
5
- Test Suites: 2 passed, 2 total
6
- Tests: 11 passed, 11 total
6
+ Test Suites: 3 passed, 3 total
7
+ Tests: 19 passed, 19 total
7
8
  Snapshots: 0 total
8
- Time: 32.63 s
9
+ Time: 34.047 s
9
10
  Ran all test suites.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.0.59",
3
+ "version": "3.0.61",
4
4
  "license": "MIT",
5
5
  "repository": "vtex/faststore",
6
6
  "browserslist": "supports es6-module and not dead",
@@ -125,5 +125,5 @@
125
125
  "node": "18.19.0",
126
126
  "yarn": "1.19.1"
127
127
  },
128
- "gitHead": "6f2082f0a7f34b560d13060a7ab5d907871e7353"
128
+ "gitHead": "9ce5509d24e1a1f6eca08c77544b94db7ef4037b"
129
129
  }
@@ -1,4 +1,5 @@
1
1
  import { isNotFoundError } from '@faststore/api'
2
+ import storeConfig from 'faststore.config'
2
3
  import type { GetStaticPaths, GetStaticProps } from 'next'
3
4
 
4
5
  import { gql } from '@generated'
@@ -86,6 +87,7 @@ export const getStaticProps: GetStaticProps<
86
87
  Locator
87
88
  > = async ({ params, previewData }) => {
88
89
  const slug = params?.slug.join('/') ?? ''
90
+ const rewrites = (await storeConfig.rewrites?.()) ?? []
89
91
 
90
92
  const [landingPagePromise, globalSectionsPromise] = [
91
93
  getLandingPageBySlug(slug, previewData),
@@ -114,7 +116,7 @@ export const getStaticProps: GetStaticProps<
114
116
  variables: { slug },
115
117
  operation: query,
116
118
  }),
117
- getPLP(slug, previewData),
119
+ getPLP(slug, previewData, rewrites),
118
120
  ])
119
121
 
120
122
  const notFound = errors.find(isNotFoundError)
@@ -231,7 +231,7 @@ export const getStaticProps: GetStaticProps<
231
231
  throw errors[0]
232
232
  }
233
233
 
234
- const cmsPage: PDPContentType = await getPDP(slug, data.product, previewData)
234
+ const cmsPage: PDPContentType = await getPDP(data.product, previewData)
235
235
 
236
236
  const { seo } = data.product
237
237
  const title = seo.title || storeConfig.seo.title
@@ -1,7 +1,7 @@
1
1
  import { ServerProductQueryQuery } from '@generated/graphql'
2
2
  import type { ContentData, Locator } from '@vtex/client-cms'
3
3
  import MissingContentError from 'src/sdk/error/MissingContentError'
4
- import { findBestPDPTemplate } from 'src/utils/utilities'
4
+ import { findBestPDPTemplate } from 'src/utils/multipleTemplates'
5
5
  import { Options, getCMSPage, getPage } from '.'
6
6
  import config from '../../../faststore.config'
7
7
 
@@ -21,7 +21,6 @@ type PDPfromCmsEnvData = {
21
21
  export type PDPContentType = ContentData & PDPSettings
22
22
 
23
23
  export const getPDP = async (
24
- slug: string,
25
24
  product: ServerProductQueryQuery['product'],
26
25
  previewData: Locator
27
26
  ) => {
@@ -29,25 +28,19 @@ export const getPDP = async (
29
28
  const cmsData = JSON.parse(config.cms.data)
30
29
  const allPDPsFromCmsEnvData: PDPfromCmsEnvData[] = cmsData['pdp']
31
30
 
32
- return await getPDPFromCmsEnvData(
33
- `/${slug}/p`,
34
- product,
35
- allPDPsFromCmsEnvData,
36
- {
37
- ...(previewData?.contentType === 'pdp' ? previewData : null),
38
- contentType: 'pdp',
39
- }
40
- )
31
+ return await getPDPFromCmsEnvData(product, allPDPsFromCmsEnvData, {
32
+ ...(previewData?.contentType === 'pdp' ? previewData : null),
33
+ contentType: 'pdp',
34
+ })
41
35
  }
42
36
 
43
- return (await getPDPFromCms(`/${slug}/p`, product, {
37
+ return (await getPDPFromCms(product, {
44
38
  ...(previewData?.contentType === 'pdp' ? previewData : null),
45
39
  contentType: 'pdp',
46
40
  })) as PDPContentType
47
41
  }
48
42
 
49
43
  const getPDPFromCmsEnvData = async (
50
- slug: string,
51
44
  product: ServerProductQueryQuery['product'],
52
45
  allPDPsFromCMSData: PDPfromCmsEnvData[],
53
46
  options: Options
@@ -58,7 +51,7 @@ const getPDPFromCmsEnvData = async (
58
51
  throw new MissingContentError(options)
59
52
  }
60
53
 
61
- const template = findBestPDPTemplate(pages, slug, product)
54
+ const template = findBestPDPTemplate(pages, product)
62
55
 
63
56
  return getPage<PDPContentType>({
64
57
  contentType: 'pdp',
@@ -68,7 +61,6 @@ const getPDPFromCmsEnvData = async (
68
61
  }
69
62
 
70
63
  const getPDPFromCms = async (
71
- slug: string,
72
64
  product: ServerProductQueryQuery['product'],
73
65
  options: Options
74
66
  ): Promise<Partial<PDPContentType>> => {
@@ -78,5 +70,5 @@ const getPDPFromCms = async (
78
70
  throw new MissingContentError(options)
79
71
  }
80
72
 
81
- return findBestPDPTemplate(pages, slug, product)
73
+ return findBestPDPTemplate(pages, product)
82
74
  }
@@ -1,6 +1,10 @@
1
1
  import { ContentData, Locator } from '@vtex/client-cms'
2
2
  import MissingContentError from 'src/sdk/error/MissingContentError'
3
- import { findBestPLPTemplate } from 'src/utils/utilities'
3
+ import {
4
+ Rewrite,
5
+ RewritesConfig,
6
+ findBestPLPTemplate,
7
+ } from 'src/utils/multipleTemplates'
4
8
  import config from '../../../faststore.config'
5
9
  import { Options, getCMSPage, getPage } from '../cms'
6
10
 
@@ -23,27 +27,41 @@ type PLPfromCmsEnvData = {
23
27
 
24
28
  export type PLPContentType = ContentData & PLPSettings
25
29
 
26
- export const getPLP = async (slug: string, previewData: Locator) => {
30
+ export const getPLP = async (
31
+ slug: string,
32
+ previewData: Locator,
33
+ rewrites: Rewrite[] | RewritesConfig
34
+ ) => {
27
35
  if (config.cms.data) {
28
36
  const cmsData = JSON.parse(config.cms.data)
29
37
  const allPLPsFromCmsEnvData: PLPfromCmsEnvData[] = cmsData['plp']
30
38
 
31
- return await getPLPFromCmsEnvData(`/${slug}/`, allPLPsFromCmsEnvData, {
32
- ...(previewData?.contentType === 'plp' ? previewData : null),
33
- contentType: 'plp',
34
- })
39
+ return await getPLPFromCmsEnvData(
40
+ `/${slug}/`,
41
+ allPLPsFromCmsEnvData,
42
+ {
43
+ ...(previewData?.contentType === 'plp' ? previewData : null),
44
+ contentType: 'plp',
45
+ },
46
+ rewrites
47
+ )
35
48
  }
36
49
 
37
- return (await getPLPFromCms(`/${slug}/`, {
38
- ...(previewData?.contentType === 'plp' ? previewData : null),
39
- contentType: 'plp',
40
- })) as PLPContentType
50
+ return (await getPLPFromCms(
51
+ `/${slug}/`,
52
+ {
53
+ ...(previewData?.contentType === 'plp' ? previewData : null),
54
+ contentType: 'plp',
55
+ },
56
+ rewrites
57
+ )) as PLPContentType
41
58
  }
42
59
 
43
60
  const getPLPFromCmsEnvData = async (
44
61
  slug: string,
45
62
  allPLPsFromCMSData: PLPfromCmsEnvData[],
46
- options: Options
63
+ options: Options,
64
+ rewrites: Rewrite[] | RewritesConfig
47
65
  ): Promise<PLPContentType> => {
48
66
  const pages: PLPfromCmsEnvData[] = allPLPsFromCMSData ?? []
49
67
 
@@ -51,7 +69,7 @@ const getPLPFromCmsEnvData = async (
51
69
  throw new MissingContentError(options)
52
70
  }
53
71
 
54
- const template = findBestPLPTemplate(pages, slug)
72
+ const template = findBestPLPTemplate(pages, slug, rewrites)
55
73
 
56
74
  return getPage<PLPContentType>({
57
75
  contentType: 'plp',
@@ -62,7 +80,8 @@ const getPLPFromCmsEnvData = async (
62
80
 
63
81
  export const getPLPFromCms = async (
64
82
  slug: string,
65
- options: Options
83
+ options: Options,
84
+ rewrites: Rewrite[] | RewritesConfig
66
85
  ): Promise<Partial<PLPContentType>> => {
67
86
  const pages = (await getCMSPage(options)).data
68
87
 
@@ -70,5 +89,5 @@ export const getPLPFromCms = async (
70
89
  throw new MissingContentError(options)
71
90
  }
72
91
 
73
- return findBestPLPTemplate(pages, slug)
92
+ return findBestPLPTemplate(pages, slug, rewrites)
74
93
  }
@@ -0,0 +1,216 @@
1
+ import { ServerProductQueryQuery } from '@generated/graphql'
2
+ import { PDPContentType } from 'src/server/cms/pdp'
3
+ import { PLPContentType } from 'src/server/cms/plp'
4
+
5
+ export type Rewrite = {
6
+ source: string
7
+ destination: string
8
+ }
9
+
10
+ export type RewritesConfig = {
11
+ beforeFiles?: Rewrite[]
12
+ afterFiles?: Rewrite[]
13
+ fallback?: Rewrite[]
14
+ }
15
+
16
+ export function normalizePLPSlug(slug: string) {
17
+ // Remove extra slashes at the beginning and end
18
+ let normalizedSlug = slug.replace(/^\/+|\/+$/g, '')
19
+
20
+ // Remove duplicate slashes and white spaces throughout the string
21
+ normalizedSlug = normalizedSlug.replace(/\/+/g, '/').replace(/\s+/g, '')
22
+
23
+ return '/' + normalizedSlug.toLowerCase()
24
+ }
25
+
26
+ function findPLPTemplateBySlug(
27
+ pages: Partial<PLPContentType>[],
28
+ slug: string,
29
+ rewrites: Rewrite[] | RewritesConfig
30
+ ) {
31
+ return pages.find((page) => {
32
+ // generic PLP template
33
+ if (!page.settings?.template?.value) return false
34
+
35
+ const templateValue = normalizePLPSlug(page.settings?.template?.value)
36
+ return (
37
+ templateValue === slug ||
38
+ hasRewritesConfigForSlug({ rewrites, templateValue, slug })
39
+ )
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Normalizes the rewrites object, and returns:
45
+ * true if the templateValue from hCMS passed as param match the source value from the rewrites object AND the slug match the destination.
46
+ * see https://nextjs.org/docs/app/api-reference/next-config-js/rewrites
47
+ * @param rewrites
48
+ * @param templateValue
49
+ * @param slug
50
+ * @returns boolean indicating if the slug exist as a rewrite destination
51
+ */
52
+ export function hasRewritesConfigForSlug({
53
+ rewrites,
54
+ templateValue,
55
+ slug,
56
+ }: {
57
+ rewrites: Rewrite[] | RewritesConfig
58
+ templateValue: string
59
+ slug: string
60
+ }): boolean {
61
+ if (!rewrites) {
62
+ return false
63
+ }
64
+
65
+ let allRewrites: Rewrite[] = []
66
+
67
+ if (Array.isArray(rewrites)) {
68
+ allRewrites = rewrites
69
+ } else {
70
+ allRewrites = [
71
+ ...(rewrites?.beforeFiles || []),
72
+ ...(rewrites?.afterFiles || []),
73
+ ...(rewrites?.fallback || []),
74
+ ]
75
+ }
76
+
77
+ const foundRewrite = allRewrites.find(
78
+ (rewriteConfig) =>
79
+ rewriteConfig.source === templateValue &&
80
+ rewriteConfig.destination === slug
81
+ )
82
+ return Boolean(foundRewrite)
83
+ }
84
+
85
+ /**
86
+ * Find the best PLP template from the CMS based on the slug passed as param.
87
+ *
88
+ * This function iterates the slug until there is no slashes (/), prioritizing the following order:
89
+ * 1. A PLP template that matches the subcategory (e.g. slug = /department/category/subcategory).
90
+ * 2. A PLP template that matches the category (e.g. slug = /department/category).
91
+ * 3. A PLP template that matches the department (e.g. slug = /department).
92
+ * 4. If no matches are found, use the generic PLP template.
93
+ *
94
+ * @param pages
95
+ * @param originalSlug
96
+ * @returns The best PLP template page for the slug
97
+ */
98
+ export function findBestPLPTemplate(
99
+ pages: Partial<PLPContentType>[],
100
+ originalSlug: string,
101
+ rewrites: Rewrite[] | RewritesConfig
102
+ ) {
103
+ let slug = normalizePLPSlug(originalSlug)
104
+ let foundPageTemplate = findPLPTemplateBySlug(pages, slug, rewrites)
105
+
106
+ while (!foundPageTemplate && slug.lastIndexOf('/') !== -1) {
107
+ slug = slug.substring(0, slug.lastIndexOf('/'))
108
+ foundPageTemplate = findPLPTemplateBySlug(pages, slug, rewrites)
109
+ }
110
+
111
+ return (
112
+ foundPageTemplate ||
113
+ pages.find((page) => !page.settings?.template?.value) ||
114
+ pages[0]
115
+ )
116
+ }
117
+
118
+ export function normalizePDPTemplate(templateValue: string) {
119
+ // Remove extra slashes, white spaces at the beginning and end
120
+ let formattedValue = templateValue.trim().replace(/^\/+|\/+$/g, '')
121
+
122
+ // Add a slash at the beginning if not present
123
+ if (!formattedValue.startsWith('/')) {
124
+ formattedValue = '/' + formattedValue
125
+ }
126
+
127
+ // Add a slash at the end if not ending with '/p'
128
+ if (!formattedValue.endsWith('/p')) {
129
+ formattedValue += '/'
130
+ }
131
+
132
+ // Remove duplicate slashes and white spaces throughout the string
133
+ formattedValue = formattedValue.replace(/\/+/g, '/').replace(/\s+/g, '')
134
+
135
+ return formattedValue.toLowerCase()
136
+ }
137
+
138
+ // Returns an array of slugs without the number at the end ("-{number}/p") at each interaction if it matches, otherwise returns the slug as is.
139
+ const getSlugsWithoutSkuIdFromPDP = (slug: string) => {
140
+ const slugs = []
141
+ let currentSlug = slug
142
+ let match = currentSlug.match(/-\d+\/p$/)
143
+
144
+ while (match) {
145
+ const newSlug = currentSlug.replace(/-\d+\/p$/, '/p')
146
+ slugs.push(newSlug)
147
+ currentSlug = newSlug
148
+ match = currentSlug.match(/-\d+\/p$/)
149
+ }
150
+
151
+ return slugs
152
+ }
153
+
154
+ /**
155
+ * Find the best PDP template from the CMS based on the slug or in the product category tree.
156
+ * Prioritizing the following order:
157
+ *
158
+ * 1. A PDP template that matches the page slug with skuId (e.g. slug = /apple-magic-mouse-12345/p).
159
+ * 2. A PDP template that matches the page slug (e.g. slug = /apple-magic-mouse/p).
160
+ * 3. A PDP template that matches the product subcategory (e.g. /department/category/subcategory).
161
+ * 4. A PDP template that matches the product category (e.g. /department/category).
162
+ * 5. A PDP template that matches the product department (e.g. /department).
163
+ * 6. If no matches are found, use the generic PDP template.
164
+ *
165
+ * @param pages
166
+ * @param product
167
+ * @returns The best PDP template page for the product
168
+ */
169
+ export function findBestPDPTemplate(
170
+ pages: Partial<PDPContentType>[],
171
+ product: ServerProductQueryQuery['product']
172
+ ) {
173
+ const templateValues = getPDPTemplateValues({
174
+ itemListElement: product.breadcrumbList.itemListElement ?? [],
175
+ })
176
+
177
+ for (const template of templateValues) {
178
+ for (const page of pages) {
179
+ if (!page.settings?.template?.value) continue
180
+
181
+ const templateValueFromCms = normalizePDPTemplate(
182
+ page.settings.template.value
183
+ )
184
+ if (templateValueFromCms === template) {
185
+ return page
186
+ }
187
+ }
188
+ }
189
+
190
+ return pages.find((page) => !page.settings?.template?.value) || pages[0]
191
+ }
192
+
193
+ export function getPDPTemplateValues({
194
+ itemListElement = [],
195
+ }: {
196
+ itemListElement: ServerProductQueryQuery['product']['breadcrumbList']['itemListElement']
197
+ }) {
198
+ // productSlugAndCategoryTree with the prioritized order. [link-skuId/p, subcategory tree, category tree, department]
199
+ const productSlugAndCategoryTree = [...itemListElement]
200
+ .reverse()
201
+ .map(({ item }) => item)
202
+
203
+ // PDP slug comes from FastStore API with the format `${link}-${skuId}/p`, the most specific for multiple page templates,
204
+ // so it should be the first element
205
+ const slugWithSkuId = productSlugAndCategoryTree[0]
206
+
207
+ // PDP slug without skuId `${link}/p`, should be the second element
208
+ const slugsWithoutSkuId = getSlugsWithoutSkuIdFromPDP(slugWithSkuId)
209
+
210
+ // removes duplicated and undefined
211
+ return [
212
+ slugWithSkuId,
213
+ ...slugsWithoutSkuId,
214
+ ...productSlugAndCategoryTree.slice(1),
215
+ ].filter((item, index, arr) => item && arr.indexOf(item) === index)
216
+ }
@@ -1,7 +1,3 @@
1
- import { ServerProductQueryQuery } from '@generated/graphql'
2
- import { PDPContentType } from 'src/server/cms/pdp'
3
- import { PLPContentType } from 'src/server/cms/plp'
4
-
5
1
  //Input "Example Text!". Output: example-text
6
2
  export function textToKebabCase(text: string): string {
7
3
  // Replace spaces and special characters with hyphens
@@ -15,117 +11,3 @@ export function textToKebabCase(text: string): string {
15
11
 
16
12
  return kebabCase ?? ''
17
13
  }
18
-
19
- export function normalizePLPSlug(slug: string) {
20
- // Remove extra slashes at the beginning and end
21
- let normalizedSlug = slug.replace(/^\/+|\/+$/g, '')
22
-
23
- // Remove duplicate slashes and white spaces throughout the string
24
- normalizedSlug = normalizedSlug.replace(/\/+/g, '/').replace(/\s+/g, '')
25
-
26
- return '/' + normalizedSlug.toLowerCase()
27
- }
28
-
29
- function findPLPTemplateBySlug(pages: Partial<PLPContentType>[], slug: string) {
30
- return pages.find((page) => {
31
- // generic PLP template
32
- if (!page.settings?.template?.value) return false
33
-
34
- const templateValue = normalizePLPSlug(page.settings?.template?.value)
35
- return templateValue === slug
36
- })
37
- }
38
-
39
- /**
40
- * Find the best PLP template from the CMS based on the slug passed as param.
41
- *
42
- * This function iterates the slug until there is no slashes (/), prioritizing the following order:
43
- * 1. A PLP template that matches the subcategory (e.g. slug = /department/category/subcategory).
44
- * 2. A PLP template that matches the category (e.g. slug = /department/category).
45
- * 3. A PLP template that matches the department (e.g. slug = /department).
46
- * 4. If no matches are found, use the generic PLP template.
47
- *
48
- * @param pages
49
- * @param originalSlug
50
- * @returns The best PLP template page for the slug
51
- */
52
- export function findBestPLPTemplate(
53
- pages: Partial<PLPContentType>[],
54
- originalSlug: string
55
- ) {
56
- let slug = normalizePLPSlug(originalSlug)
57
- let foundPageTemplate = findPLPTemplateBySlug(pages, slug)
58
-
59
- while (!foundPageTemplate && slug.lastIndexOf('/') !== -1) {
60
- slug = slug.substring(0, slug.lastIndexOf('/'))
61
- foundPageTemplate = findPLPTemplateBySlug(pages, slug)
62
- }
63
-
64
- return (
65
- foundPageTemplate ||
66
- pages.find((page) => !page.settings?.template?.value) ||
67
- pages[0]
68
- )
69
- }
70
-
71
- export function normalizePDPTemplate(templateValue: string) {
72
- // Remove extra slashes, white spaces at the beginning and end
73
- let formattedValue = templateValue.trim().replace(/^\/+|\/+$/g, '')
74
-
75
- // Add a slash at the beginning if not present
76
- if (!formattedValue.startsWith('/')) {
77
- formattedValue = '/' + formattedValue
78
- }
79
-
80
- // Add a slash at the end if not ending with '/p'
81
- if (!formattedValue.endsWith('/p')) {
82
- formattedValue += '/'
83
- }
84
-
85
- // Remove duplicate slashes and white spaces throughout the string
86
- formattedValue = formattedValue.replace(/\/+/g, '/').replace(/\s+/g, '')
87
-
88
- return formattedValue.toLowerCase()
89
- }
90
-
91
- /**
92
- * Find the best PDP template from the CMS based on the slug or in the product category tree.
93
- * Prioritizing the following order:
94
- *
95
- * 1. A PDP template that matches the page slug (e.g. slug = /apple-magic-mouse/p).
96
- * 2. A PDP template that matches the product subcategory (e.g. /department/category/subcategory).
97
- * 3. A PDP template that matches the product category (e.g. /department/category).
98
- * 4. A PDP template that matches the product department (e.g. /department).
99
- * 5. If no matches are found, use the generic PDP template.
100
- *
101
- * @param pages
102
- * @param originalSlug
103
- * @param product
104
- * @returns The best PDP template page for the slug
105
- */
106
- export function findBestPDPTemplate(
107
- pages: Partial<PDPContentType>[],
108
- slug: string,
109
- product: ServerProductQueryQuery['product']
110
- ) {
111
- // productSlugAndCategoryTree with the prioritized order. [slug, subcategory tree, category tree, department]
112
- const productSlugAndCategoryTree = product?.breadcrumbList?.itemListElement
113
- ? [...product?.breadcrumbList?.itemListElement]
114
- .reverse()
115
- .map(({ item }) => item)
116
- : []
117
- productSlugAndCategoryTree.unshift(slug)
118
-
119
- for (const item of productSlugAndCategoryTree) {
120
- for (const page of pages) {
121
- if (!page.settings?.template?.value) continue
122
-
123
- const templateValue = normalizePDPTemplate(page.settings.template.value)
124
- if (templateValue === item) {
125
- return page
126
- }
127
- }
128
- }
129
-
130
- return pages.find((page) => !page.settings?.template?.value) || pages[0]
131
- }