@faststore/core 3.41.9 → 3.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +36 -36
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- 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/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +26 -20
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/1454.js +1 -0
- package/.next/server/chunks/1506.js +1 -1
- package/.next/server/chunks/3684.js +1 -1
- package/.next/server/chunks/416.js +1 -0
- package/.next/server/chunks/4289.js +13 -5
- package/.next/server/chunks/4746.js +1 -1
- package/.next/server/chunks/4816.js +1 -0
- package/.next/server/chunks/6030.js +1 -0
- package/.next/server/chunks/6076.js +1 -1
- package/.next/server/chunks/6594.js +1 -0
- package/.next/server/chunks/{8112.js → 6968.js} +2 -2
- package/.next/server/chunks/6999.js +1 -0
- package/.next/server/chunks/7371.js +1 -0
- package/.next/server/chunks/831.js +1 -0
- package/.next/server/chunks/8482.js +1 -0
- package/.next/server/chunks/8646.js +1 -1
- package/.next/server/chunks/UIBannerText.js +1 -1
- package/.next/server/chunks/UISKUMatrixSidebar.js +1 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.js +1 -1
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.js +1 -1
- package/.next/server/pages/500.js.nft.json +1 -1
- package/.next/server/pages/[...slug].js +1 -1
- package/.next/server/pages/[...slug].js.nft.json +1 -1
- package/.next/server/pages/[slug]/p.js +1 -1
- 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/profile.js +1 -1
- package/.next/server/pages/account/profile.js.nft.json +1 -1
- package/.next/server/pages/account.js.nft.json +1 -1
- package/.next/server/pages/api/graphql.js +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 +1 -1
- package/.next/server/pages/api/preview.js.nft.json +1 -1
- package/.next/server/pages/checkout.js +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/404.json +1 -1
- package/.next/server/pages/en-US/500.html +2 -2
- package/.next/server/pages/en-US/500.json +1 -1
- package/.next/server/pages/en-US/checkout.html +2 -2
- package/.next/server/pages/en-US/checkout.json +1 -1
- package/.next/server/pages/en-US/login.html +2 -2
- package/.next/server/pages/en-US/login.json +1 -1
- package/.next/server/pages/en-US/s.html +2 -2
- package/.next/server/pages/en-US/s.json +1 -1
- package/.next/server/pages/en-US.html +2 -2
- package/.next/server/pages/en-US.json +1 -1
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/login.js +1 -1
- package/.next/server/pages/login.js.nft.json +1 -1
- package/.next/server/pages/s.js +1 -1
- package/.next/server/pages/s.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/chunks/417.c39c1c5e5ef57b4a.js +1 -0
- package/.next/static/chunks/4865.3e2ae9feb511c870.js +1 -0
- package/.next/static/chunks/630.13d3dd939b789798.js +6 -0
- package/.next/static/chunks/6335-5870fc075bf96b86.js +1 -0
- package/.next/static/chunks/{7498-49bf89838314b503.js → 7498-415859c993f5002b.js} +1 -1
- package/.next/static/chunks/8827.179b49f8ab3afe48.js +1 -0
- package/.next/static/chunks/UISKUMatrixSidebar.c68540bda6d40e13.js +1 -0
- package/.next/static/chunks/pages/[...slug]-2cfb2b1b8ee3b7a9.js +1 -0
- package/.next/static/chunks/pages/[slug]/p-3b513ae37c648620.js +1 -0
- package/.next/static/chunks/pages/{_app-0f16b6b5d7dfab2a.js → _app-f02182ccd58f2781.js} +1 -1
- package/.next/static/chunks/webpack-0dd5a14ceff64065.js +1 -0
- package/.next/static/css/31fb64e064998460.css +1 -0
- package/.next/static/{YZbl2rCQaXL1-tNjqawX_ → f5jWOXDXh3GdBy9EK8IDc}/_buildManifest.js +1 -1
- package/.next/trace +111 -109
- package/.turbo/turbo-build.log +18 -24
- package/.turbo/turbo-test.log +5 -5
- package/@generated/gql.ts +2 -2
- package/@generated/graphql.ts +59 -7
- package/@generated/persisted-documents.json +3 -3
- package/CHANGELOG.md +12 -0
- package/cms/faststore/sections.json +85 -0
- package/discovery.config.default.js +6 -0
- package/package.json +5 -4
- package/src/components/cms/GlobalSections.tsx +13 -9
- package/src/components/navigation/Navbar/Navbar.tsx +2 -0
- package/src/components/product/ProductCard/ProductCard.tsx +8 -0
- package/src/components/search/SearchDropdown/SearchDropdown.tsx +14 -1
- package/src/components/search/SearchInput/SearchInput.tsx +17 -2
- package/src/components/search/SearchProductItem/SearchProductItem.tsx +120 -1
- package/src/components/sections/Navbar/DefaultComponents.ts +7 -0
- package/src/components/sections/Navbar/Navbar.tsx +16 -0
- package/src/components/sections/Navbar/section.module.scss +11 -0
- package/src/components/templates/LandingPage/LandingPage.tsx +22 -18
- package/src/components/templates/ProductListingPage/ProductListing.tsx +6 -1
- package/src/components/ui/SKUMatrix/SKUMatrixSidebar.tsx +12 -4
- package/src/experimental/searchServerSideFunctions/getServerSideProps.ts +8 -5
- package/src/experimental/searchServerSideFunctions/getStaticProps.ts +9 -7
- package/src/pages/404.tsx +6 -5
- package/src/pages/500.tsx +6 -5
- package/src/pages/[...slug].tsx +11 -4
- package/src/pages/[slug]/p.tsx +14 -5
- package/src/pages/api/preview.ts +43 -18
- package/src/pages/index.tsx +9 -7
- package/src/pages/login.tsx +6 -5
- package/src/sdk/cart/useBuyButton.ts +7 -2
- package/src/sdk/error/MissingContentError/MissingContentError.ts +6 -4
- package/src/sdk/error/MultipleContentError/MultipleContentError.ts +6 -4
- package/src/server/cms/index.ts +1 -2
- package/src/server/content/service.ts +227 -0
- package/src/server/content/types.ts +26 -0
- package/src/server/content/utils.ts +8 -0
- package/src/typings/overrides.ts +1 -0
- package/.next/server/chunks/5071.js +0 -1
- package/.next/server/chunks/5284.js +0 -1
- package/.next/server/chunks/6198.js +0 -1
- package/.next/server/chunks/804.js +0 -1
- package/.next/server/chunks/9019.js +0 -1
- package/.next/server/chunks/9068.js +0 -1
- package/.next/static/chunks/1036.27f5244aaf7d0915.js +0 -1
- package/.next/static/chunks/417.08663bd4b5809548.js +0 -1
- package/.next/static/chunks/485.a35fab0c7c75a11b.js +0 -1
- package/.next/static/chunks/4865.8b1970610c412187.js +0 -1
- package/.next/static/chunks/6335-7aa183582a89bc0e.js +0 -1
- package/.next/static/chunks/pages/[...slug]-f4fd6c8d7dc53f8f.js +0 -1
- package/.next/static/chunks/pages/[slug]/p-2e02254149cef33d.js +0 -1
- package/.next/static/chunks/webpack-f0ee32c953ec8952.js +0 -1
- package/.next/static/css/b0c0e0632c5d7f52.css +0 -1
- /package/.next/static/{YZbl2rCQaXL1-tNjqawX_ → f5jWOXDXh3GdBy9EK8IDc}/_ssgManifest.js +0 -0
package/src/pages/500.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Locator } from '@vtex/client-cms'
|
|
2
1
|
import type { GetStaticProps } from 'next'
|
|
3
2
|
import { NextSeo } from 'next-seo'
|
|
4
3
|
import type { ComponentType } from 'react'
|
|
@@ -12,8 +11,10 @@ import RenderSections from 'src/components/cms/RenderSections'
|
|
|
12
11
|
import { OverriddenDefaultEmptyState as EmptyState } from 'src/components/sections/EmptyState/OverriddenDefaultEmptyState'
|
|
13
12
|
import CUSTOM_COMPONENTS from 'src/customizations/src/components'
|
|
14
13
|
import PLUGINS_COMPONENTS from 'src/plugins'
|
|
15
|
-
import {
|
|
14
|
+
import type { PageContentType } from 'src/server/cms'
|
|
16
15
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
16
|
+
import { contentService } from 'src/server/content/service'
|
|
17
|
+
import type { PreviewData } from 'src/server/content/types'
|
|
17
18
|
|
|
18
19
|
/* A list of components that can be used in the CMS. */
|
|
19
20
|
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
@@ -56,7 +57,7 @@ function Page({ page: { sections }, globalSections }: Props) {
|
|
|
56
57
|
export const getStaticProps: GetStaticProps<
|
|
57
58
|
Props,
|
|
58
59
|
Record<string, string>,
|
|
59
|
-
|
|
60
|
+
PreviewData
|
|
60
61
|
> = async ({ previewData }) => {
|
|
61
62
|
const [
|
|
62
63
|
globalSectionsPromise,
|
|
@@ -66,9 +67,9 @@ export const getStaticProps: GetStaticProps<
|
|
|
66
67
|
|
|
67
68
|
const [page, globalSections, globalSectionsHeader, globalSectionsFooter] =
|
|
68
69
|
await Promise.all([
|
|
69
|
-
|
|
70
|
-
...(previewData?.contentType === '500' && previewData),
|
|
70
|
+
contentService.getSingleContent<PageContentType>({
|
|
71
71
|
contentType: '500',
|
|
72
|
+
previewData,
|
|
72
73
|
}),
|
|
73
74
|
globalSectionsPromise,
|
|
74
75
|
globalSectionsHeaderPromise,
|
package/src/pages/[...slug].tsx
CHANGED
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
import { execute } from 'src/server'
|
|
13
13
|
|
|
14
14
|
import type { SearchState } from '@faststore/sdk'
|
|
15
|
-
import type { Locator } from '@vtex/client-cms'
|
|
16
15
|
import dynamic from 'next/dynamic'
|
|
17
16
|
import {
|
|
18
17
|
getGlobalSectionsData,
|
|
@@ -28,9 +27,11 @@ import ProductListingPage, {
|
|
|
28
27
|
import { getRedirect } from 'src/sdk/redirects'
|
|
29
28
|
import type { PageContentType } from 'src/server/cms'
|
|
30
29
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
31
|
-
import {
|
|
30
|
+
import type { PLPContentType } from 'src/server/cms/plp'
|
|
32
31
|
import { getDynamicContent } from 'src/utils/dynamicContent'
|
|
33
32
|
import { fetchServerManyProducts } from 'src/utils/fetchProductGallerySSR'
|
|
33
|
+
import { contentService } from 'src/server/content/service'
|
|
34
|
+
import type { PreviewData } from 'src/server/content/types'
|
|
34
35
|
|
|
35
36
|
const LandingPage = dynamic(
|
|
36
37
|
() => import('src/components/templates/LandingPage')
|
|
@@ -103,7 +104,7 @@ const query = gql(`
|
|
|
103
104
|
export const getStaticProps: GetStaticProps<
|
|
104
105
|
Props,
|
|
105
106
|
{ slug: string[] },
|
|
106
|
-
|
|
107
|
+
PreviewData
|
|
107
108
|
> = async ({ params, previewData }) => {
|
|
108
109
|
const slug = params?.slug.join('/') ?? ''
|
|
109
110
|
const rewrites = (await storeConfig.rewrites?.()) ?? []
|
|
@@ -162,7 +163,13 @@ export const getStaticProps: GetStaticProps<
|
|
|
162
163
|
variables: { slug },
|
|
163
164
|
operation: query,
|
|
164
165
|
}),
|
|
165
|
-
|
|
166
|
+
contentService.getPlpContent(
|
|
167
|
+
{
|
|
168
|
+
previewData,
|
|
169
|
+
slug,
|
|
170
|
+
},
|
|
171
|
+
rewrites
|
|
172
|
+
),
|
|
166
173
|
globalSectionsPromise,
|
|
167
174
|
globalSectionsHeaderPromise,
|
|
168
175
|
globalSectionsFooterPromise,
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isNotFoundError } from '@faststore/api'
|
|
2
|
-
import type { Locator } from '@vtex/client-cms'
|
|
3
2
|
import deepmerge from 'deepmerge'
|
|
4
3
|
import type { GetStaticPaths, GetStaticProps } from 'next'
|
|
5
4
|
import { BreadcrumbJsonLd, NextSeo, ProductJsonLd } from 'next-seo'
|
|
@@ -37,7 +36,9 @@ import { getOfferUrl, useOffer } from 'src/sdk/offer'
|
|
|
37
36
|
import PageProvider, { type PDPContext } from 'src/sdk/overrides/PageProvider'
|
|
38
37
|
import { useProductQuery } from 'src/sdk/product/useProductQuery'
|
|
39
38
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
40
|
-
import {
|
|
39
|
+
import type { PDPContentType } from 'src/server/cms/pdp'
|
|
40
|
+
import { contentService } from 'src/server/content/service'
|
|
41
|
+
import type { PreviewData } from 'src/server/content/types'
|
|
41
42
|
|
|
42
43
|
type StoreConfig = typeof storeConfig & {
|
|
43
44
|
experimental: {
|
|
@@ -183,7 +184,9 @@ function Page({
|
|
|
183
184
|
<BreadcrumbJsonLd itemListElements={itemListElements} />
|
|
184
185
|
<ProductJsonLd
|
|
185
186
|
id={`${meta.canonical}${settings?.seo?.id ?? ''}`}
|
|
186
|
-
mainEntityOfPage={`${meta.canonical}${
|
|
187
|
+
mainEntityOfPage={`${meta.canonical}${
|
|
188
|
+
settings?.seo?.mainEntityOfPage ?? ''
|
|
189
|
+
}`}
|
|
187
190
|
productName={product.name}
|
|
188
191
|
description={product.description}
|
|
189
192
|
brand={product.brand.name}
|
|
@@ -283,7 +286,7 @@ const query = gql(`
|
|
|
283
286
|
export const getStaticProps: GetStaticProps<
|
|
284
287
|
Props,
|
|
285
288
|
{ slug: string },
|
|
286
|
-
|
|
289
|
+
PreviewData
|
|
287
290
|
> = async ({ params, previewData }) => {
|
|
288
291
|
const slug = params?.slug ?? ''
|
|
289
292
|
|
|
@@ -333,7 +336,13 @@ export const getStaticProps: GetStaticProps<
|
|
|
333
336
|
throw errors[0]
|
|
334
337
|
}
|
|
335
338
|
|
|
336
|
-
const cmsPage: PDPContentType = await
|
|
339
|
+
const cmsPage: PDPContentType = await contentService.getPdpContent(
|
|
340
|
+
data.product,
|
|
341
|
+
{
|
|
342
|
+
previewData,
|
|
343
|
+
slug,
|
|
344
|
+
}
|
|
345
|
+
)
|
|
337
346
|
|
|
338
347
|
const { seo } = data.product
|
|
339
348
|
const title = seo.title
|
package/src/pages/api/preview.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { NextApiHandler, NextApiRequest } from 'next'
|
|
2
|
-
import type { Locator } from '@vtex/client-cms'
|
|
3
2
|
|
|
4
|
-
import { clientCMS } from 'src/server/cms'
|
|
5
3
|
import { previewRedirects } from '../../../discovery.config'
|
|
4
|
+
import { contentService } from 'src/server/content/service'
|
|
5
|
+
import { isLocator } from 'src/server/cms'
|
|
6
|
+
import { isContentPlatformSource } from 'src/server/content/utils'
|
|
6
7
|
|
|
7
8
|
type Settings = {
|
|
8
9
|
seo: {
|
|
@@ -22,17 +23,29 @@ class StatusError extends Error {
|
|
|
22
23
|
|
|
23
24
|
const pickParam = (req: NextApiRequest, parameter: string) => {
|
|
24
25
|
const maybeParam = req.query[parameter]
|
|
26
|
+
return typeof maybeParam === 'string' ? maybeParam : undefined
|
|
27
|
+
}
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const setPreviewAndRedirect = (
|
|
30
|
+
res: any,
|
|
31
|
+
previewData: Record<string, string>,
|
|
32
|
+
redirectPath: string
|
|
33
|
+
) => {
|
|
34
|
+
res.setPreviewData(previewData, {
|
|
35
|
+
maxAge: 3600,
|
|
36
|
+
path: redirectPath,
|
|
37
|
+
})
|
|
38
|
+
res.redirect(redirectPath)
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
// TODO: Improve security by disabling CMS preview in production
|
|
34
42
|
const handler: NextApiHandler = async (req, res) => {
|
|
35
43
|
try {
|
|
44
|
+
let slug = pickParam(req, 'slug')
|
|
45
|
+
if (slug && !slug.startsWith('/')) {
|
|
46
|
+
slug = `/${slug}`
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
const locator = [
|
|
37
50
|
'contentType',
|
|
38
51
|
'documentId',
|
|
@@ -65,8 +78,19 @@ const handler: NextApiHandler = async (req, res) => {
|
|
|
65
78
|
)
|
|
66
79
|
}
|
|
67
80
|
|
|
81
|
+
if (!isLocator(locator)) {
|
|
82
|
+
throw new StatusError('Invalid locator object', 400)
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
// Fetch CMS to check if the provided `locator` exists
|
|
69
|
-
const page = await
|
|
86
|
+
const page = await contentService.getSingleContent({
|
|
87
|
+
contentType: locator.contentType,
|
|
88
|
+
previewData: locator,
|
|
89
|
+
slug,
|
|
90
|
+
documentId: locator.documentId,
|
|
91
|
+
versionId: locator.versionId,
|
|
92
|
+
releaseId: locator.releaseId,
|
|
93
|
+
})
|
|
70
94
|
|
|
71
95
|
// If the content doesn't exist prevent preview mode from being enabled
|
|
72
96
|
if (!page) {
|
|
@@ -76,25 +100,26 @@ const handler: NextApiHandler = async (req, res) => {
|
|
|
76
100
|
)
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
if (
|
|
104
|
+
isContentPlatformSource() &&
|
|
105
|
+
slug &&
|
|
106
|
+
['landingPage', 'plp', 'pdp'].includes(locator.contentType)
|
|
107
|
+
) {
|
|
108
|
+
return setPreviewAndRedirect(res, locator, slug)
|
|
109
|
+
}
|
|
84
110
|
|
|
85
111
|
// Redirect to the path from the fetched locator
|
|
86
112
|
const redirects = previewRedirects as Record<string, string>
|
|
87
113
|
if (redirects[locator.contentType]) {
|
|
88
|
-
res
|
|
89
|
-
return
|
|
114
|
+
return setPreviewAndRedirect(res, locator, redirects[locator.contentType])
|
|
90
115
|
}
|
|
91
116
|
|
|
92
117
|
if (locator.contentType === 'landingPage') {
|
|
93
|
-
|
|
94
|
-
return
|
|
118
|
+
slug = (page.settings as Settings)?.seo?.slug
|
|
119
|
+
return setPreviewAndRedirect(res, locator, slug)
|
|
95
120
|
}
|
|
96
121
|
|
|
97
|
-
res
|
|
122
|
+
return setPreviewAndRedirect(res, locator, '/')
|
|
98
123
|
} catch (error) {
|
|
99
124
|
if (error instanceof StatusError) {
|
|
100
125
|
res.status(error.status).end(error.message)
|
package/src/pages/index.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { Locator } from '@vtex/client-cms'
|
|
2
1
|
import type { GetStaticProps } from 'next'
|
|
3
2
|
import { NextSeo, OrganizationJsonLd, SiteLinksSearchBoxJsonLd } from 'next-seo'
|
|
4
3
|
|
|
5
4
|
import RenderSections from 'src/components/cms/RenderSections'
|
|
6
5
|
import type { PageContentType } from 'src/server/cms'
|
|
7
|
-
import { getPage } from 'src/server/cms'
|
|
8
6
|
|
|
9
7
|
import {
|
|
10
8
|
type GlobalSectionsData,
|
|
@@ -15,6 +13,8 @@ import PageProvider from 'src/sdk/overrides/PageProvider'
|
|
|
15
13
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
16
14
|
import { getDynamicContent } from 'src/utils/dynamicContent'
|
|
17
15
|
import storeConfig from '../../discovery.config'
|
|
16
|
+
import { contentService } from 'src/server/content/service'
|
|
17
|
+
import type { PreviewData } from 'src/server/content/types'
|
|
18
18
|
|
|
19
19
|
type Props = {
|
|
20
20
|
page: PageContentType
|
|
@@ -146,14 +146,13 @@ function Page({
|
|
|
146
146
|
export const getStaticProps: GetStaticProps<
|
|
147
147
|
Props,
|
|
148
148
|
Record<string, string>,
|
|
149
|
-
|
|
149
|
+
PreviewData
|
|
150
150
|
> = async ({ previewData }) => {
|
|
151
151
|
const [
|
|
152
152
|
globalSectionsPromise,
|
|
153
153
|
globalSectionsHeaderPromise,
|
|
154
154
|
globalSectionsFooterPromise,
|
|
155
155
|
] = getGlobalSectionsData(previewData)
|
|
156
|
-
|
|
157
156
|
const serverDataPromise = getDynamicContent({ pageType: 'home' })
|
|
158
157
|
|
|
159
158
|
let cmsPage = null
|
|
@@ -161,15 +160,18 @@ export const getStaticProps: GetStaticProps<
|
|
|
161
160
|
const cmsData = JSON.parse(storeConfig.cms.data)
|
|
162
161
|
cmsPage = cmsData['home'][0]
|
|
163
162
|
}
|
|
163
|
+
|
|
164
164
|
const pagePromise = cmsPage
|
|
165
|
-
?
|
|
165
|
+
? contentService.getSingleContent<PageContentType>({
|
|
166
166
|
contentType: 'home',
|
|
167
|
+
previewData,
|
|
167
168
|
documentId: cmsPage.documentId,
|
|
168
169
|
versionId: cmsPage.versionId,
|
|
170
|
+
releaseId: cmsPage.releaseId,
|
|
169
171
|
})
|
|
170
|
-
:
|
|
171
|
-
...(previewData?.contentType === 'home' && previewData),
|
|
172
|
+
: contentService.getSingleContent<PageContentType>({
|
|
172
173
|
contentType: 'home',
|
|
174
|
+
previewData,
|
|
173
175
|
})
|
|
174
176
|
|
|
175
177
|
const [
|
package/src/pages/login.tsx
CHANGED
|
@@ -2,7 +2,6 @@ import { NextSeo } from 'next-seo'
|
|
|
2
2
|
import type { ComponentType } from 'react'
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
4
|
|
|
5
|
-
import type { Locator } from '@vtex/client-cms'
|
|
6
5
|
import type { GetStaticProps } from 'next'
|
|
7
6
|
import { default as GLOBAL_COMPONENTS } from 'src/components/cms/global/Components'
|
|
8
7
|
import {
|
|
@@ -13,9 +12,11 @@ import RenderSections from 'src/components/cms/RenderSections'
|
|
|
13
12
|
import { OverriddenDefaultEmptyState as EmptyState } from 'src/components/sections/EmptyState/OverriddenDefaultEmptyState'
|
|
14
13
|
import CUSTOM_COMPONENTS from 'src/customizations/src/components'
|
|
15
14
|
import PLUGINS_COMPONENTS from 'src/plugins'
|
|
16
|
-
import {
|
|
15
|
+
import type { PageContentType } from 'src/server/cms'
|
|
17
16
|
import { injectGlobalSections } from 'src/server/cms/global'
|
|
18
17
|
import storeConfig from '../../discovery.config'
|
|
18
|
+
import { contentService } from 'src/server/content/service'
|
|
19
|
+
import type { PreviewData } from 'src/server/content/types'
|
|
19
20
|
|
|
20
21
|
/* A list of components that can be used in the CMS. */
|
|
21
22
|
const COMPONENTS: Record<string, ComponentType<any>> = {
|
|
@@ -68,7 +69,7 @@ function Page({ page: { sections }, globalSections }: Props) {
|
|
|
68
69
|
export const getStaticProps: GetStaticProps<
|
|
69
70
|
Props,
|
|
70
71
|
Record<string, string>,
|
|
71
|
-
|
|
72
|
+
PreviewData
|
|
72
73
|
> = async ({ previewData }) => {
|
|
73
74
|
const [
|
|
74
75
|
globalSectionsPromise,
|
|
@@ -78,9 +79,9 @@ export const getStaticProps: GetStaticProps<
|
|
|
78
79
|
|
|
79
80
|
const [page, globalSections, globalSectionsHeader, globalSectionsFooter] =
|
|
80
81
|
await Promise.all([
|
|
81
|
-
|
|
82
|
-
...(previewData?.contentType === 'login' && previewData),
|
|
82
|
+
contentService.getSingleContent<PageContentType>({
|
|
83
83
|
contentType: 'login',
|
|
84
|
+
previewData,
|
|
84
85
|
}),
|
|
85
86
|
globalSectionsPromise,
|
|
86
87
|
globalSectionsHeaderPromise,
|
|
@@ -8,7 +8,10 @@ import { useUI } from '@faststore/ui'
|
|
|
8
8
|
import { useSession } from '../session'
|
|
9
9
|
import { cartStore } from './index'
|
|
10
10
|
|
|
11
|
-
export const useBuyButton = (
|
|
11
|
+
export const useBuyButton = (
|
|
12
|
+
item: CartItem | CartItem[] | null,
|
|
13
|
+
shouldOpenCart = true
|
|
14
|
+
) => {
|
|
12
15
|
const { openCart } = useUI()
|
|
13
16
|
const {
|
|
14
17
|
currency: { code },
|
|
@@ -68,7 +71,9 @@ export const useBuyButton = (item: CartItem | CartItem[] | null) => {
|
|
|
68
71
|
? item.forEach((value) => cartStore.addItem(value))
|
|
69
72
|
: cartStore.addItem(item)
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
if (shouldOpenCart) {
|
|
75
|
+
openCart()
|
|
76
|
+
}
|
|
72
77
|
},
|
|
73
78
|
[code, item, openCart, itemIsArray]
|
|
74
79
|
)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Options } from 'src/server/cms'
|
|
2
|
+
import type { EntryPathParams } from '@vtex/client-cp'
|
|
3
|
+
import { contentSource } from 'discovery.config.default'
|
|
2
4
|
|
|
3
5
|
export default class MissingContentError extends Error {
|
|
4
|
-
constructor(
|
|
6
|
+
constructor(params: Options | EntryPathParams) {
|
|
5
7
|
super(
|
|
6
|
-
`Missing content on the
|
|
7
|
-
|
|
8
|
+
`Missing content on the ${contentSource.type} for content type ${
|
|
9
|
+
params.contentType
|
|
8
10
|
}. Add content before proceeding. Context: ${JSON.stringify(
|
|
9
|
-
|
|
11
|
+
params,
|
|
10
12
|
null,
|
|
11
13
|
2
|
|
12
14
|
)}`
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Options } from 'src/server/cms'
|
|
2
|
+
import type { EntryPathParams } from '@vtex/client-cp'
|
|
3
|
+
import { contentSource } from 'discovery.config.default'
|
|
2
4
|
|
|
3
5
|
export default class MultipleContentError extends Error {
|
|
4
|
-
constructor(
|
|
6
|
+
constructor(params: Options | EntryPathParams) {
|
|
5
7
|
super(
|
|
6
|
-
`Multiple content defined on the
|
|
7
|
-
|
|
8
|
+
`Multiple content defined on the ${contentSource.type} for content type ${
|
|
9
|
+
params.contentType
|
|
8
10
|
}. Remove duplicated content before proceeding. Context: ${JSON.stringify(
|
|
9
|
-
|
|
11
|
+
params,
|
|
10
12
|
null,
|
|
11
13
|
2
|
|
12
14
|
)}`
|
package/src/server/cms/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ContentData, ContentTypeOptions, Locator } from '@vtex/client-cms'
|
|
2
2
|
import ClientCMS from '@vtex/client-cms'
|
|
3
3
|
|
|
4
|
-
import MissingContentError from 'src/sdk/error/MissingContentError'
|
|
5
4
|
import MultipleContentError from 'src/sdk/error/MultipleContentError'
|
|
6
5
|
import config from '../../../discovery.config'
|
|
7
6
|
|
|
@@ -57,7 +56,7 @@ export type PageContentType = ContentData & {
|
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
const isLocator = (x: any): x is Locator =>
|
|
59
|
+
export const isLocator = (x: any): x is Locator =>
|
|
61
60
|
typeof x.contentType === 'string' &&
|
|
62
61
|
(typeof x.releaseId === 'string' ||
|
|
63
62
|
typeof x.documentId === 'string' ||
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { ContentData, Locator } from '@vtex/client-cms'
|
|
2
|
+
import ClientCP from '@vtex/client-cp'
|
|
3
|
+
import type { ContentEntry, EntryPathParams } from '@vtex/client-cp'
|
|
4
|
+
import { getCMSPage, getPage, type PageContentType } from 'src/server/cms'
|
|
5
|
+
import type { ContentOptions, ContentParams } from './types'
|
|
6
|
+
import config from '../../../discovery.config'
|
|
7
|
+
import { getPLP, type PLPContentType } from '../cms/plp'
|
|
8
|
+
import {
|
|
9
|
+
findBestPDPTemplate,
|
|
10
|
+
findBestPLPTemplate,
|
|
11
|
+
type Rewrite,
|
|
12
|
+
type RewritesConfig,
|
|
13
|
+
} from 'src/utils/multipleTemplates'
|
|
14
|
+
import MissingContentError from 'src/sdk/error/MissingContentError'
|
|
15
|
+
import { getPDP, type PDPContentType } from '../cms/pdp'
|
|
16
|
+
import MultipleContentError from 'src/sdk/error/MultipleContentError'
|
|
17
|
+
import type { ServerProductQueryQuery } from '@generated/graphql'
|
|
18
|
+
import { isContentPlatformSource } from './utils'
|
|
19
|
+
|
|
20
|
+
export class ContentService {
|
|
21
|
+
private clientCP: ClientCP
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.clientCP = new ClientCP({
|
|
25
|
+
tenant: config.api.storeId,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getSingleContent<T extends ContentData>(
|
|
30
|
+
params: ContentParams
|
|
31
|
+
): Promise<T> {
|
|
32
|
+
const options = this.createContentOptions(params)
|
|
33
|
+
|
|
34
|
+
if (isContentPlatformSource()) {
|
|
35
|
+
return this.getFromCP<T>(options)
|
|
36
|
+
}
|
|
37
|
+
return getPage(options.cmsOptions)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getContent(params: ContentParams) {
|
|
41
|
+
const options = this.createContentOptions(params)
|
|
42
|
+
|
|
43
|
+
if (isContentPlatformSource()) {
|
|
44
|
+
const serviceParams = this.convertOptionsToParams(options)
|
|
45
|
+
const { entries } = await this.clientCP.listEntries(serviceParams)
|
|
46
|
+
const data = await Promise.all(
|
|
47
|
+
entries.map(async (entry) => {
|
|
48
|
+
const entryData = await this.getSingleEntry(
|
|
49
|
+
{ ...serviceParams, entryId: entry.id },
|
|
50
|
+
!!options.isPreview
|
|
51
|
+
)
|
|
52
|
+
return this.mergeEntryWithData(entry, entryData)
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
return { data }
|
|
56
|
+
}
|
|
57
|
+
return getCMSPage(options.cmsOptions)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getPlpContent(
|
|
61
|
+
params: ContentParams,
|
|
62
|
+
rewrites: Rewrite[] | RewritesConfig
|
|
63
|
+
): Promise<PLPContentType> {
|
|
64
|
+
const plpParams = { ...params, contentType: 'plp' }
|
|
65
|
+
const options = this.createContentOptions(plpParams)
|
|
66
|
+
|
|
67
|
+
if (isContentPlatformSource()) {
|
|
68
|
+
const pages = (await this.getContent(plpParams)).data
|
|
69
|
+
if (!pages?.length) throw new MissingContentError(options.cmsOptions)
|
|
70
|
+
return findBestPLPTemplate(
|
|
71
|
+
pages,
|
|
72
|
+
options.slug,
|
|
73
|
+
rewrites
|
|
74
|
+
) as PLPContentType
|
|
75
|
+
}
|
|
76
|
+
return getPLP(options.slug, options.cmsOptions as Locator, rewrites)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getPdpContent(
|
|
80
|
+
product: ServerProductQueryQuery['product'],
|
|
81
|
+
params: ContentParams
|
|
82
|
+
): Promise<PDPContentType> {
|
|
83
|
+
const pdpParams = { ...params, contentType: 'pdp' }
|
|
84
|
+
const options = this.createContentOptions(pdpParams)
|
|
85
|
+
|
|
86
|
+
if (isContentPlatformSource()) {
|
|
87
|
+
const pages = (await this.getContent(pdpParams)).data
|
|
88
|
+
if (!pages.length) throw new MissingContentError(options.cmsOptions)
|
|
89
|
+
return findBestPDPTemplate(pages, product) as PDPContentType
|
|
90
|
+
}
|
|
91
|
+
return getPDP(product, options.cmsOptions as Locator)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async getFromCP<T extends ContentData>(
|
|
95
|
+
options: ContentOptions
|
|
96
|
+
): Promise<T> {
|
|
97
|
+
const params = this.convertOptionsToParams(options)
|
|
98
|
+
try {
|
|
99
|
+
const entry: PageContentType =
|
|
100
|
+
params.entryId || params.slug
|
|
101
|
+
? await this.getSingleEntry(params, !!options.isPreview)
|
|
102
|
+
: await this.fetchFirstEntry(params, !!options.isPreview)
|
|
103
|
+
|
|
104
|
+
return entry as T
|
|
105
|
+
} catch (err: unknown) {
|
|
106
|
+
if (isNotFoundError(err)) console.error('Content not found', err)
|
|
107
|
+
else throw err
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private async getSingleEntry(
|
|
112
|
+
params: EntryPathParams,
|
|
113
|
+
isPreview: boolean
|
|
114
|
+
): Promise<PageContentType> {
|
|
115
|
+
if (isPreview) {
|
|
116
|
+
if (params.entryId)
|
|
117
|
+
return this.clientCP.previewEntryById(
|
|
118
|
+
params
|
|
119
|
+
) as Promise<PageContentType>
|
|
120
|
+
if (params.slug)
|
|
121
|
+
return this.clientCP.previewEntryBySlug(
|
|
122
|
+
params
|
|
123
|
+
) as Promise<PageContentType>
|
|
124
|
+
throw new Error('Preview requires entryId or slug')
|
|
125
|
+
}
|
|
126
|
+
if (params.entryId)
|
|
127
|
+
return this.clientCP.getEntry(params) as Promise<PageContentType>
|
|
128
|
+
if (params.slug)
|
|
129
|
+
return this.clientCP.getEntryBySlug(params) as Promise<PageContentType>
|
|
130
|
+
throw new Error('getEntry requires entryId or slug')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async fetchFirstEntry(
|
|
134
|
+
params: EntryPathParams,
|
|
135
|
+
isPreview: boolean
|
|
136
|
+
): Promise<PageContentType> {
|
|
137
|
+
const { entries } = await this.clientCP.listEntries(params)
|
|
138
|
+
if (!entries || entries.length === 0) {
|
|
139
|
+
console.error('No entries found for params', params)
|
|
140
|
+
return {} as PageContentType
|
|
141
|
+
}
|
|
142
|
+
if (entries.length > 1) {
|
|
143
|
+
throw new MultipleContentError(params)
|
|
144
|
+
}
|
|
145
|
+
return this.getSingleEntry({ ...params, entryId: entries[0].id }, isPreview)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private mergeEntryWithData(
|
|
149
|
+
entry: ContentEntry,
|
|
150
|
+
data: PageContentType
|
|
151
|
+
): ContentEntry & PageContentType {
|
|
152
|
+
return {
|
|
153
|
+
...entry,
|
|
154
|
+
...data,
|
|
155
|
+
id: entry.id,
|
|
156
|
+
name: entry.name || '',
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private convertOptionsToParams(options: ContentOptions): EntryPathParams {
|
|
161
|
+
const { cmsOptions } = options
|
|
162
|
+
const params: Partial<EntryPathParams> = {
|
|
163
|
+
accountName: config.api.storeId,
|
|
164
|
+
storeId: 'faststore',
|
|
165
|
+
contentType: cmsOptions.contentType,
|
|
166
|
+
slug: options.slug,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if ('documentId' in cmsOptions && cmsOptions.documentId) {
|
|
170
|
+
params.entryId = cmsOptions.documentId
|
|
171
|
+
}
|
|
172
|
+
if ('versionId' in cmsOptions && cmsOptions.versionId) {
|
|
173
|
+
params.branchId = cmsOptions.versionId
|
|
174
|
+
}
|
|
175
|
+
if ('releaseId' in cmsOptions && cmsOptions.releaseId) {
|
|
176
|
+
params.branchId = cmsOptions.releaseId
|
|
177
|
+
}
|
|
178
|
+
if ('filters' in cmsOptions && cmsOptions.filters) {
|
|
179
|
+
const nested = cmsOptions.filters.filters as Record<string, any>
|
|
180
|
+
if (nested['settings.seo.slug']) {
|
|
181
|
+
const seo = nested['settings.seo.slug'] as string
|
|
182
|
+
params.slug = seo.replace(/^\//, '')
|
|
183
|
+
}
|
|
184
|
+
Object.assign(params, cmsOptions.filters)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return params as EntryPathParams
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private createContentOptions(params: ContentParams): ContentOptions {
|
|
191
|
+
const {
|
|
192
|
+
contentType,
|
|
193
|
+
previewData,
|
|
194
|
+
slug,
|
|
195
|
+
documentId,
|
|
196
|
+
versionId,
|
|
197
|
+
releaseId,
|
|
198
|
+
filters,
|
|
199
|
+
} = params
|
|
200
|
+
|
|
201
|
+
const isPreview = previewData?.contentType === contentType
|
|
202
|
+
const { slug: _, ...previewLocator } = previewData || {}
|
|
203
|
+
|
|
204
|
+
const cmsOptions = {
|
|
205
|
+
contentType,
|
|
206
|
+
...(isPreview ? previewLocator : {}),
|
|
207
|
+
...(documentId !== undefined && { documentId }),
|
|
208
|
+
...(versionId !== undefined && { versionId }),
|
|
209
|
+
...(releaseId !== undefined && { releaseId }),
|
|
210
|
+
...(filters && { filters }),
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
cmsOptions,
|
|
215
|
+
...(slug !== undefined && { slug }),
|
|
216
|
+
isPreview,
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isNotFoundError(err: unknown): boolean {
|
|
222
|
+
if (err instanceof MissingContentError) return true
|
|
223
|
+
if (err instanceof Error && /\b404\b/.test(err.message)) return true
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const contentService = new ContentService()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Locator } from '@vtex/client-cms'
|
|
2
|
+
import type { Options } from '../cms'
|
|
3
|
+
|
|
4
|
+
export const ContentSourceType = {
|
|
5
|
+
ContentPlatform: 'cp',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type PreviewData = Locator & {
|
|
9
|
+
slug?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ContentParams {
|
|
13
|
+
contentType?: string
|
|
14
|
+
previewData?: PreviewData | null
|
|
15
|
+
slug?: string
|
|
16
|
+
documentId?: string
|
|
17
|
+
versionId?: string
|
|
18
|
+
releaseId?: string
|
|
19
|
+
filters?: Record<string, any>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ContentOptions {
|
|
23
|
+
cmsOptions: Options
|
|
24
|
+
slug?: string
|
|
25
|
+
isPreview: boolean
|
|
26
|
+
}
|