@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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +3 -3
- package/.next/build-manifest.json +25 -25
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/eslint/.cache_1gneedd +1 -1
- package/.next/cache/fetch-cache/50912854cb7c781522a6ff8792d714e549515fcbbbfd660761961b06afe01c07 +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.js.nft.json +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/fs-next-update/page.js.nft.json +1 -1
- package/.next/server/app/fs-next-update.html +2 -2
- package/.next/server/app/fs-next-update.rsc +1 -1
- package/.next/server/chunks/2133.js +5 -5
- package/.next/server/chunks/{6112.js → 2381.js} +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.js.nft.json +1 -1
- package/.next/server/pages/[...slug].js +2 -2
- package/.next/server/pages/[...slug].js.nft.json +1 -1
- package/.next/server/pages/[slug]/p.js +5 -5
- package/.next/server/pages/[slug]/p.js.nft.json +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/account.js.nft.json +1 -1
- package/.next/server/pages/api/graphql.js.nft.json +1 -1
- package/.next/server/pages/api/health/live.js.nft.json +1 -1
- package/.next/server/pages/api/health/ready.js.nft.json +1 -1
- package/.next/server/pages/api/preview.js.nft.json +1 -1
- package/.next/server/pages/checkout.js.nft.json +1 -1
- package/.next/server/pages/en-US/404.html +2 -2
- package/.next/server/pages/en-US/500.html +2 -2
- package/.next/server/pages/en-US/account.html +2 -2
- package/.next/server/pages/en-US/checkout.html +2 -2
- package/.next/server/pages/en-US/login.html +2 -2
- package/.next/server/pages/en-US/s.html +2 -2
- package/.next/server/pages/en-US.html +2 -2
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/login.js.nft.json +1 -1
- package/.next/server/pages/s.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/{lIYvWbgkX76Tpj8gVu1kU → 4w3LMmusMebDghIVyNvPn}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/{404-be2217109dab18cf.js → 404-6d926d3bdc58852b.js} +1 -1
- package/.next/static/chunks/pages/{500-64a07a16830e129c.js → 500-cbffc24e6eaddf18.js} +1 -1
- package/.next/static/chunks/pages/{login-258e5b7ecec36481.js → login-78b2d6166a2ebbaa.js} +1 -1
- package/.next/static/chunks/{webpack-a45dc84c9a0ef3fd.js → webpack-4f403c9b52a287c6.js} +1 -1
- package/.next/static/css/9b7689964b990431.css +1 -0
- package/.next/trace +62 -62
- package/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-test.log +6 -5
- package/package.json +2 -2
- package/src/pages/[...slug].tsx +3 -1
- package/src/pages/[slug]/p.tsx +1 -1
- package/src/server/cms/pdp.ts +8 -16
- package/src/server/cms/plp.ts +33 -14
- package/src/utils/multipleTemplates.ts +216 -0
- package/src/utils/utilities.ts +0 -118
- package/test/utils/multipleTemplates.test.ts +144 -0
- package/.next/static/css/548bab931c45c770.css +0 -1
- /package/.next/static/{lIYvWbgkX76Tpj8gVu1kU → 4w3LMmusMebDghIVyNvPn}/_ssgManifest.js +0 -0
- /package/.next/static/chunks/{590-a0b6f16148203bf2.js → 590-da547057f2ae283b.js} +0 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -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-
|
|
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-
|
|
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)
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
$ jest
|
|
2
|
-
PASS test/server/cms/index.test.ts (
|
|
3
|
-
PASS test/
|
|
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:
|
|
6
|
-
Tests:
|
|
6
|
+
Test Suites: 3 passed, 3 total
|
|
7
|
+
Tests: 19 passed, 19 total
|
|
7
8
|
Snapshots: 0 total
|
|
8
|
-
Time:
|
|
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.
|
|
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": "
|
|
128
|
+
"gitHead": "9ce5509d24e1a1f6eca08c77544b94db7ef4037b"
|
|
129
129
|
}
|
package/src/pages/[...slug].tsx
CHANGED
|
@@ -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)
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -231,7 +231,7 @@ export const getStaticProps: GetStaticProps<
|
|
|
231
231
|
throw errors[0]
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const cmsPage: PDPContentType = await getPDP(
|
|
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
|
package/src/server/cms/pdp.ts
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
73
|
+
return findBestPDPTemplate(pages, product)
|
|
82
74
|
}
|
package/src/server/cms/plp.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ContentData, Locator } from '@vtex/client-cms'
|
|
2
2
|
import MissingContentError from 'src/sdk/error/MissingContentError'
|
|
3
|
-
import {
|
|
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 (
|
|
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(
|
|
32
|
-
|
|
33
|
-
|
|
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(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
package/src/utils/utilities.ts
CHANGED
|
@@ -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
|
-
}
|