@faststore/core 2.1.18 → 2.1.22

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 (90) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +49 -45
  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/next-server.js.nft.json +1 -1
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack +0 -0
  9. package/.next/cache/webpack/server-production/0.pack +0 -0
  10. package/.next/cache/webpack/server-production/index.pack +0 -0
  11. package/.next/next-server.js.nft.json +1 -1
  12. package/.next/prerender-manifest.json +1 -1
  13. package/.next/routes-manifest.json +1 -1
  14. package/.next/server/chunks/{205.js → 144.js} +619 -2
  15. package/.next/server/chunks/721.js +679 -0
  16. package/.next/server/chunks/74.js +54 -20
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/404.js.nft.json +1 -1
  19. package/.next/server/pages/500.js.nft.json +1 -1
  20. package/.next/server/pages/[...slug].js +298 -71
  21. package/.next/server/pages/[...slug].js.nft.json +1 -1
  22. package/.next/server/pages/[slug]/p.js +2 -2
  23. package/.next/server/pages/[slug]/p.js.nft.json +1 -1
  24. package/.next/server/pages/_app.js.nft.json +1 -1
  25. package/.next/server/pages/_document.js +5 -1
  26. package/.next/server/pages/_error.js.nft.json +1 -1
  27. package/.next/server/pages/account.js.nft.json +1 -1
  28. package/.next/server/pages/api/preview.js +35 -6
  29. package/.next/server/pages/checkout.js.nft.json +1 -1
  30. package/.next/server/pages/en-US/404.html +3 -3
  31. package/.next/server/pages/en-US/500.html +3 -3
  32. package/.next/server/pages/en-US/account.html +3 -3
  33. package/.next/server/pages/en-US/checkout.html +3 -3
  34. package/.next/server/pages/en-US/login.html +3 -3
  35. package/.next/server/pages/en-US/s.html +3 -3
  36. package/.next/server/pages/en-US.html +3 -3
  37. package/.next/server/pages/en-US.json +1 -1
  38. package/.next/server/pages/index.js +4 -474
  39. package/.next/server/pages/index.js.nft.json +1 -1
  40. package/.next/server/pages/login.js.nft.json +1 -1
  41. package/.next/server/pages/s.js +1 -1
  42. package/.next/server/pages/s.js.nft.json +1 -1
  43. package/.next/server/pages-manifest.json +4 -4
  44. package/.next/static/JOMe5dtqNwu-zmVu2HIcF/_buildManifest.js +1 -0
  45. package/.next/static/chunks/548-6b23e7ad82cd22b9.js +1 -0
  46. package/.next/static/chunks/706-8049971f9e5f6ad2.js +1 -0
  47. package/.next/static/chunks/{738-67a288ca3569cdbb.js → 738-a5ff304828f20cbf.js} +1 -1
  48. package/.next/static/chunks/791-c23aa4269c7955c8.js +1 -0
  49. package/.next/static/chunks/pages/{404-6da332b2c4ef0f41.js → 404-d5f20744ecd83121.js} +1 -1
  50. package/.next/static/chunks/pages/{500-88dd73506f17946c.js → 500-3911549ab88d0378.js} +1 -1
  51. package/.next/static/chunks/pages/[...slug]-3a37fd4d13cb2ba8.js +1 -0
  52. package/.next/static/chunks/pages/[slug]/{p-93d3f1c0f2d3aed2.js → p-bf47c90571846f86.js} +1 -1
  53. package/.next/static/chunks/pages/account-d248acc931146694.js +1 -0
  54. package/.next/static/chunks/pages/checkout-97f6d6f36f041a6f.js +1 -0
  55. package/.next/static/chunks/pages/index-e9727cb06d9ae961.js +1 -0
  56. package/.next/static/chunks/pages/{login-a688a70872ea61f5.js → login-4cc4b8e52608f076.js} +1 -1
  57. package/.next/static/chunks/pages/s-aabfa5c08338974a.js +1 -0
  58. package/.next/static/chunks/{webpack-8bf035049b590d82.js → webpack-764ce5753552a617.js} +1 -1
  59. package/.next/static/css/08e3fc80f9bad95d.css +1 -0
  60. package/.next/trace +80 -78
  61. package/.turbo/turbo-build.log +7 -8
  62. package/cms/faststore/content-types.json +2 -2
  63. package/package.json +2 -2
  64. package/src/components/ThirdPartyScripts/vtex.tsx +6 -0
  65. package/src/components/cms/GlobalSections.tsx +2 -3
  66. package/src/components/templates/LandingPage/LandingPage.tsx +102 -0
  67. package/src/components/templates/LandingPage/index.ts +2 -0
  68. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +138 -0
  69. package/src/components/templates/ProductListingPage/index.ts +2 -0
  70. package/src/pages/[...slug].tsx +45 -130
  71. package/src/pages/api/preview.ts +18 -2
  72. package/src/pages/index.tsx +1 -3
  73. package/src/sdk/error/MissingContentError/MissingContentError.ts +15 -0
  74. package/src/sdk/error/MissingContentError/index.ts +1 -0
  75. package/src/sdk/error/MultipleContentError/MultipleContentError.ts +15 -0
  76. package/src/sdk/error/MultipleContentError/index.ts +1 -0
  77. package/src/server/cms.ts +7 -21
  78. package/.next/server/chunks/513.js +0 -257
  79. package/.next/server/chunks/90.js +0 -623
  80. package/.next/static/chunks/113-207e8eceab001eea.js +0 -1
  81. package/.next/static/chunks/548-ab84e9e8b49413ab.js +0 -1
  82. package/.next/static/chunks/791-b6747a7c7ca0967e.js +0 -1
  83. package/.next/static/chunks/pages/[...slug]-ab804df4ac6c945e.js +0 -1
  84. package/.next/static/chunks/pages/account-06126db1f6dc537e.js +0 -1
  85. package/.next/static/chunks/pages/checkout-5138b7956d64dde8.js +0 -1
  86. package/.next/static/chunks/pages/index-7ba4f6708af42d8e.js +0 -1
  87. package/.next/static/chunks/pages/s-99aad326e6aafeb7.js +0 -1
  88. package/.next/static/css/13ddbbc10e89ff0e.css +0 -1
  89. package/.next/static/hlJfefjb0gNJqepjDW_Eh/_buildManifest.js +0 -1
  90. /package/.next/static/{hlJfefjb0gNJqepjDW_Eh → JOMe5dtqNwu-zmVu2HIcF}/_ssgManifest.js +0 -0
@@ -30,26 +30,25 @@ info - Generating static pages (7/7)
30
30
  info - Finalizing page optimization...
31
31
 
32
32
  Route (pages) Size First Load JS
33
- ┌ ● / 3.21 kB 197 kB
34
- ├ └ css/584640ffee46aa49.css 6.51 kB
33
+ ┌ ● / 3.23 kB 197 kB
35
34
  ├ /_app 0 B 77.9 kB
36
- ├ ● /[...slug] 8.81 kB 135 kB
37
- ├ └ css/13ddbbc10e89ff0e.css 7.99 kB
35
+ ├ ● /[...slug] 4.14 kB 209 kB
36
+ ├ └ css/08e3fc80f9bad95d.css 1.83 kB
38
37
  ├ ● /[slug]/p 11 kB 205 kB
39
38
  ├ └ css/0e00026896a2ee3e.css 11.2 kB
40
39
  ├ ○ /404 1.28 kB 114 kB
41
40
  ├ ● /500 1.3 kB 114 kB
42
- ├ ● /account 669 B 113 kB
41
+ ├ ● /account 670 B 113 kB
43
42
  ├ λ /api/graphql 0 B 77.9 kB
44
43
  ├ λ /api/preview 0 B 77.9 kB
45
- ├ ● /checkout 656 B 113 kB
44
+ ├ ● /checkout 657 B 113 kB
46
45
  ├ ● /login 1.2 kB 114 kB
47
- └ ● /s 1.11 kB 127 kB
46
+ └ ● /s 4.55 kB 127 kB
48
47
  + First Load JS shared by all 80.6 kB
49
48
  ├ chunks/framework-dfd14d7ce6600b03.js 45.3 kB
50
49
  ├ chunks/main-fd466221927468fd.js 23.9 kB
51
50
  ├ chunks/pages/_app-79d333aa6001a806.js 6.38 kB
52
- ├ chunks/webpack-8bf035049b590d82.js 2.28 kB
51
+ ├ chunks/webpack-764ce5753552a617.js 2.29 kB
53
52
  └ css/f7ed956d370744ea.css 2.77 kB
54
53
 
55
54
  λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
@@ -6,8 +6,8 @@
6
6
  "isSingleton": true
7
7
  },
8
8
  {
9
- "id": "page",
10
- "name": "Page",
9
+ "id": "landingPage",
10
+ "name": "Landing Page",
11
11
  "configurationSchemaSets": [
12
12
  {
13
13
  "name": "Settings",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "2.1.18",
3
+ "version": "2.1.22",
4
4
  "license": "MIT",
5
5
  "browserslist": "supports es6-module and not dead",
6
6
  "scripts": {
@@ -110,5 +110,5 @@
110
110
  "msw": {
111
111
  "workerDirectory": "public"
112
112
  },
113
- "gitHead": "378c0274278a2e2d3385ca37c9d70883b60654d4"
113
+ "gitHead": "72c6d601f027a15482fd8564b2ff32b6d348f5c6"
114
114
  }
@@ -16,6 +16,12 @@ function VTEX() {
16
16
  async
17
17
  src="https://io.vtex.com.br/rc/rc.js"
18
18
  />
19
+ <script
20
+ key="vtex-af.js-script"
21
+ type="text/partytown"
22
+ async
23
+ src="https://activity-flow.vtex.com/af/af.js"
24
+ />
19
25
  </>
20
26
  )
21
27
  }
@@ -51,9 +51,8 @@ export const getGlobalSectionsData = async (
51
51
  previewData: Locator
52
52
  ): Promise<GlobalSectionsData> => {
53
53
  const { sections } = await getPage<PageContentType>({
54
- ...(previewData?.contentType === GLOBAL_SECTIONS_CONTENT_TYPE
55
- ? previewData
56
- : { filters: { 'settings.seo.slug': '/' } }),
54
+ ...(previewData?.contentType === GLOBAL_SECTIONS_CONTENT_TYPE &&
55
+ previewData),
57
56
  contentType: GLOBAL_SECTIONS_CONTENT_TYPE,
58
57
  })
59
58
 
@@ -0,0 +1,102 @@
1
+ import { NextSeo, SiteLinksSearchBoxJsonLd } from 'next-seo'
2
+ import type { ComponentType } from 'react'
3
+ import type { Locator } from '@vtex/client-cms'
4
+
5
+ import MissingContentError from 'src/sdk/error/MissingContentError/MissingContentError'
6
+ import RenderSections from 'src/components/cms/RenderSections'
7
+ import Hero from 'src/components/sections/Hero'
8
+ import Incentives from 'src/components/sections/Incentives'
9
+ import ProductShelf from 'src/components/sections/ProductShelf'
10
+ import ProductTiles from 'src/components/sections/ProductTiles'
11
+ import BannerText from 'src/components/sections/BannerText'
12
+ import Newsletter from 'src/components/sections/Newsletter'
13
+ import { getPage } from 'src/server/cms'
14
+ import type { PageContentType } from 'src/server/cms'
15
+ import CUSTOM_COMPONENTS from 'src/customizations/components'
16
+
17
+ import storeConfig from '../../../../faststore.config'
18
+
19
+ /* A list of components that can be used in the CMS. */
20
+ const COMPONENTS: Record<string, ComponentType<any>> = {
21
+ Hero,
22
+ Incentives,
23
+ ProductShelf,
24
+ ProductTiles,
25
+ BannerText,
26
+ Newsletter,
27
+ ...CUSTOM_COMPONENTS,
28
+ }
29
+
30
+ export type LandingPageProps = {
31
+ page: PageContentType
32
+ }
33
+
34
+ export default function LandingPage({
35
+ page: { sections, settings },
36
+ }: LandingPageProps) {
37
+ return (
38
+ <>
39
+ {/* SEO */}
40
+ <NextSeo
41
+ title={settings?.seo?.title ?? storeConfig.seo.title}
42
+ description={settings?.seo?.description ?? storeConfig.seo?.description}
43
+ titleTemplate={storeConfig.seo?.titleTemplate ?? storeConfig.seo?.title}
44
+ canonical={settings?.seo?.canonical ?? storeConfig.storeUrl}
45
+ openGraph={{
46
+ type: 'website',
47
+ url: storeConfig.storeUrl,
48
+ title: settings?.seo?.title ?? storeConfig.seo.title,
49
+ description:
50
+ settings?.seo?.description ?? storeConfig.seo.description,
51
+ }}
52
+ />
53
+ <SiteLinksSearchBoxJsonLd
54
+ url={storeConfig.storeUrl}
55
+ potentialActions={[
56
+ {
57
+ target: `${storeConfig.storeUrl}/s/?q`,
58
+ queryInput: 'search_term_string',
59
+ },
60
+ ]}
61
+ />
62
+
63
+ {/*
64
+ WARNING: Do not import or render components from any
65
+ other folder than '../components/sections' in here.
66
+
67
+ This is necessary to keep the integration with the CMS
68
+ easy and consistent, enabling the change and reorder
69
+ of elements on this page.
70
+
71
+ If needed, wrap your component in a <Section /> component
72
+ (not the HTML tag) before rendering it here.
73
+ */}
74
+ <RenderSections sections={sections} components={COMPONENTS} />
75
+ </>
76
+ )
77
+ }
78
+
79
+ export const getLandingPageBySlug = async (
80
+ slug: string,
81
+ previewData: Locator
82
+ ) => {
83
+ try {
84
+ const landingPageData = await getPage<PageContentType>({
85
+ ...(previewData?.contentType === 'landingPage'
86
+ ? previewData
87
+ : {
88
+ filters: {
89
+ filters: { 'settings.seo.slug': `/${slug}` },
90
+ },
91
+ }),
92
+ contentType: 'landingPage',
93
+ })
94
+ return landingPageData
95
+ } catch (error) {
96
+ if (error instanceof MissingContentError) {
97
+ return null
98
+ }
99
+
100
+ throw error
101
+ }
102
+ }
@@ -0,0 +1,2 @@
1
+ export { default, getLandingPageBySlug } from './LandingPage'
2
+ export type { LandingPageProps } from './LandingPage'
@@ -0,0 +1,138 @@
1
+ import type { SearchState } from '@faststore/sdk'
2
+ import {
3
+ formatSearchState,
4
+ parseSearchState,
5
+ SearchProvider,
6
+ } from '@faststore/sdk'
7
+ import { BreadcrumbJsonLd, NextSeo } from 'next-seo'
8
+ import { useRouter } from 'next/router'
9
+ import { useMemo } from 'react'
10
+
11
+ import type { ServerCollectionPageQueryQuery } from '@generated/graphql'
12
+ import Breadcrumb from 'src/components/sections/Breadcrumb'
13
+ import Hero from 'src/components/sections/Hero'
14
+ import ProductGallery from 'src/components/sections/ProductGallery'
15
+ import ProductShelf from 'src/components/sections/ProductShelf'
16
+ import ScrollToTopButton from 'src/components/sections/ScrollToTopButton'
17
+ import { ITEMS_PER_PAGE } from 'src/constants'
18
+ import { useApplySearchState } from 'src/sdk/search/state'
19
+
20
+ import type { ComponentType } from 'react'
21
+ import RenderSections from 'src/components/cms/RenderSections'
22
+ import CUSTOM_COMPONENTS from 'src/customizations/components'
23
+ import { PLPContentType } from 'src/server/cms'
24
+
25
+ import storeConfig from '../../../../faststore.config'
26
+
27
+ export type ProductListingPageProps = ServerCollectionPageQueryQuery & {
28
+ page: PLPContentType
29
+ }
30
+
31
+ /**
32
+ * Sections: Components imported from each store's custom components and '../components/sections' only.
33
+ * Do not import or render components from any other folder in here.
34
+ */
35
+ const COMPONENTS: Record<string, ComponentType<any>> = {
36
+ Breadcrumb,
37
+ Hero,
38
+ ProductGallery,
39
+ ProductShelf,
40
+ ...CUSTOM_COMPONENTS,
41
+ }
42
+
43
+ type UseSearchParams = ServerCollectionPageQueryQuery & {
44
+ sort: SearchState['sort']
45
+ }
46
+ const useSearchParams = ({
47
+ collection,
48
+ sort,
49
+ }: UseSearchParams): SearchState => {
50
+ const selectedFacets = collection?.meta.selectedFacets
51
+ const { asPath } = useRouter()
52
+
53
+ const hrefState = useMemo(() => {
54
+ const url = new URL(asPath, 'http://localhost')
55
+
56
+ const shouldUpdateDefaultSort = sort && !url.searchParams.has('sort')
57
+ if (shouldUpdateDefaultSort) {
58
+ url.searchParams.set('sort', sort)
59
+ }
60
+
61
+ const newState = parseSearchState(url)
62
+ // In case we are in an incomplete url
63
+ if (newState.selectedFacets.length === 0) {
64
+ newState.selectedFacets = selectedFacets
65
+ }
66
+
67
+ return formatSearchState(newState).href
68
+ }, [asPath, selectedFacets, sort])
69
+
70
+ return useMemo(() => parseSearchState(new URL(hrefState)), [hrefState])
71
+ }
72
+
73
+ export default function ProductListingPage({
74
+ page: { sections, settings },
75
+ ...otherProps
76
+ }: ProductListingPageProps) {
77
+ const { collection } = otherProps
78
+ const router = useRouter()
79
+ const applySearchState = useApplySearchState()
80
+ const searchParams = useSearchParams({
81
+ ...otherProps,
82
+ sort: settings?.productGallery?.sortBySelection as SearchState['sort'],
83
+ })
84
+
85
+ const { page, sort } = searchParams
86
+ const title = collection?.seo.title ?? storeConfig.seo.title
87
+ const description = collection?.seo.description ?? storeConfig.seo.title
88
+ const pageQuery = page !== 0 ? `?page=${page}` : ''
89
+ const separator = pageQuery !== '' ? '&' : '?'
90
+ const sortQuery = !!sort ? `${separator}sort=${sort}` : ''
91
+ const [pathname] = router.asPath.split('?')
92
+ const canonical = `${storeConfig.storeUrl}${pathname}${pageQuery}${sortQuery}`
93
+
94
+ return (
95
+ <>
96
+ <SearchProvider
97
+ onChange={applySearchState}
98
+ itemsPerPage={settings?.productGallery?.itemsPerPage ?? ITEMS_PER_PAGE}
99
+ {...searchParams}
100
+ >
101
+ {/* SEO */}
102
+ <NextSeo
103
+ title={title}
104
+ description={description}
105
+ titleTemplate={storeConfig.seo.titleTemplate}
106
+ canonical={canonical}
107
+ openGraph={{
108
+ type: 'website',
109
+ title,
110
+ description,
111
+ }}
112
+ />
113
+ <BreadcrumbJsonLd
114
+ itemListElements={collection?.breadcrumbList.itemListElement ?? []}
115
+ />
116
+
117
+ {/*
118
+ WARNING: Do not import or render components from any
119
+ other folder than '../components/sections' in here.
120
+
121
+ This is necessary to keep the integration with the CMS
122
+ easy and consistent, enabling the change and reorder
123
+ of elements on this page.
124
+
125
+ If needed, wrap your component in a <Section /> component
126
+ (not the HTML tag) before rendering it here.
127
+ */}
128
+ <RenderSections
129
+ context={collection}
130
+ sections={sections}
131
+ components={COMPONENTS}
132
+ />
133
+
134
+ <ScrollToTopButton />
135
+ </SearchProvider>
136
+ </>
137
+ )
138
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './ProductListingPage'
2
+ export type { ProductListingPageProps } from './ProductListingPage'
@@ -1,152 +1,51 @@
1
1
  import { isNotFoundError } from '@faststore/api'
2
2
  import { gql } from '@faststore/graphql-utils'
3
- import type { SearchState } from '@faststore/sdk'
4
- import {
5
- formatSearchState,
6
- parseSearchState,
7
- SearchProvider,
8
- } from '@faststore/sdk'
9
3
  import type { GetStaticPaths, GetStaticProps } from 'next'
10
- import { BreadcrumbJsonLd, NextSeo } from 'next-seo'
11
- import { useRouter } from 'next/router'
12
- import { useMemo } from 'react'
13
4
 
14
5
  import type {
15
6
  ServerCollectionPageQueryQuery,
16
7
  ServerCollectionPageQueryQueryVariables,
17
8
  } from '@generated/graphql'
18
- import Breadcrumb from 'src/components/sections/Breadcrumb'
19
- import Hero from 'src/components/sections/Hero'
20
- import ProductGallery from 'src/components/sections/ProductGallery'
21
- import ProductShelf from 'src/components/sections/ProductShelf'
22
- import ScrollToTopButton from 'src/components/sections/ScrollToTopButton'
23
- import { ITEMS_PER_PAGE, ITEMS_PER_SECTION } from 'src/constants'
24
- import { useApplySearchState } from 'src/sdk/search/state'
25
9
  import { mark } from 'src/sdk/tests/mark'
26
10
  import { execute } from 'src/server'
27
11
 
28
12
  import { Locator } from '@vtex/client-cms'
29
- import type { ComponentType } from 'react'
30
13
  import GlobalSections, {
31
14
  getGlobalSectionsData,
32
15
  GlobalSectionsData,
33
16
  } from 'src/components/cms/GlobalSections'
34
- import RenderSections from 'src/components/cms/RenderSections'
35
- import CUSTOM_COMPONENTS from 'src/customizations/components'
36
- import { getPage, PLPContentType } from 'src/server/cms'
37
- import storeConfig from '../../faststore.config'
38
-
39
- type Props = ServerCollectionPageQueryQuery & {
40
- page: PLPContentType
17
+ import { getPage, PageContentType, PLPContentType } from 'src/server/cms'
18
+ import ProductListingPage, {
19
+ ProductListingPageProps,
20
+ } from 'src/components/templates/ProductListingPage'
21
+ import LandingPage, {
22
+ getLandingPageBySlug,
23
+ LandingPageProps,
24
+ } from 'src/components/templates/LandingPage'
25
+
26
+ type BaseProps = {
41
27
  globalSections: GlobalSectionsData
42
28
  }
43
29
 
44
- /**
45
- * Sections: Components imported from each store's custom components and '../components/sections' only.
46
- * Do not import or render components from any other folder in here.
47
- */
48
- const COMPONENTS: Record<string, ComponentType<any>> = {
49
- Breadcrumb,
50
- Hero,
51
- ProductGallery,
52
- ProductShelf,
53
- ...CUSTOM_COMPONENTS,
54
- }
55
-
56
- type UseSearchParams = ServerCollectionPageQueryQuery & {
57
- sort: SearchState['sort']
58
- }
59
- const useSearchParams = ({
60
- collection,
61
- sort,
62
- }: UseSearchParams): SearchState => {
63
- const selectedFacets = collection?.meta.selectedFacets
64
- const { asPath } = useRouter()
65
-
66
- const hrefState = useMemo(() => {
67
- const url = new URL(asPath, 'http://localhost')
68
-
69
- const shouldUpdateDefaultSort = sort && !url.searchParams.has('sort')
70
- if (shouldUpdateDefaultSort) {
71
- url.searchParams.set('sort', sort)
72
- }
73
-
74
- const newState = parseSearchState(url)
75
- // In case we are in an incomplete url
76
- if (newState.selectedFacets.length === 0) {
77
- newState.selectedFacets = selectedFacets
78
- }
79
-
80
- return formatSearchState(newState).href
81
- }, [asPath, selectedFacets, sort])
82
-
83
- return useMemo(() => parseSearchState(new URL(hrefState)), [hrefState])
84
- }
85
-
86
- function Page({
87
- page: { sections, settings },
88
- globalSections,
89
- ...otherProps
90
- }: Props) {
91
- const { collection } = otherProps
92
- const router = useRouter()
93
- const applySearchState = useApplySearchState()
94
- const searchParams = useSearchParams({
95
- ...otherProps,
96
- sort: settings?.productGallery?.sortBySelection as SearchState['sort'],
97
- })
98
-
99
- const { page, sort } = searchParams
100
- const title = collection?.seo.title ?? storeConfig.seo.title
101
- const description = collection?.seo.description ?? storeConfig.seo.title
102
- const pageQuery = page !== 0 ? `?page=${page}` : ''
103
- const separator = pageQuery !== '' ? '&' : '?'
104
- const sortQuery = !!sort ? `${separator}sort=${sort}` : ''
105
- const [pathname] = router.asPath.split('?')
106
- const canonical = `${storeConfig.storeUrl}${pathname}${pageQuery}${sortQuery}`
30
+ type Props = BaseProps &
31
+ (
32
+ | ({
33
+ type: 'plp'
34
+ page: PLPContentType
35
+ } & ServerCollectionPageQueryQuery)
36
+ | {
37
+ type: 'page'
38
+ page: PageContentType
39
+ }
40
+ )
107
41
 
42
+ function Page({ globalSections, type, ...otherProps }: Props) {
108
43
  return (
109
44
  <GlobalSections {...globalSections}>
110
- <SearchProvider
111
- onChange={applySearchState}
112
- itemsPerPage={settings?.productGallery?.itemsPerPage ?? ITEMS_PER_PAGE}
113
- {...searchParams}
114
- >
115
- {/* SEO */}
116
- <NextSeo
117
- title={title}
118
- description={description}
119
- titleTemplate={storeConfig.seo.titleTemplate}
120
- canonical={canonical}
121
- openGraph={{
122
- type: 'website',
123
- title,
124
- description,
125
- }}
126
- />
127
- <BreadcrumbJsonLd
128
- itemListElements={collection?.breadcrumbList.itemListElement ?? []}
129
- />
130
-
131
- {/*
132
- WARNING: Do not import or render components from any
133
- other folder than '../components/sections' in here.
134
-
135
- This is necessary to keep the integration with the CMS
136
- easy and consistent, enabling the change and reorder
137
- of elements on this page.
138
-
139
- If needed, wrap your component in a <Section /> component
140
- (not the HTML tag) before rendering it here.
141
- */}
142
- <RenderSections
143
- context={collection}
144
- sections={sections}
145
- components={COMPONENTS}
146
- />
147
-
148
- <ScrollToTopButton />
149
- </SearchProvider>
45
+ {type === 'plp' && (
46
+ <ProductListingPage {...(otherProps as ProductListingPageProps)} />
47
+ )}
48
+ {type === 'page' && <LandingPage {...(otherProps as LandingPageProps)} />}
150
49
  </GlobalSections>
151
50
  )
152
51
  }
@@ -180,7 +79,22 @@ export const getStaticProps: GetStaticProps<
180
79
  { slug: string[] },
181
80
  Locator
182
81
  > = async ({ params, previewData }) => {
183
- const [{ data, errors = [] }, page, globalSections] = await Promise.all([
82
+ const [landingPagePromise, globalSectionsPromise] = [
83
+ getLandingPageBySlug(params?.slug[0], previewData),
84
+ getGlobalSectionsData(previewData),
85
+ ]
86
+
87
+ if (await landingPagePromise) {
88
+ return {
89
+ props: {
90
+ page: await landingPagePromise,
91
+ globalSections: await globalSectionsPromise,
92
+ type: 'page',
93
+ },
94
+ }
95
+ }
96
+
97
+ const [{ data, errors = [] }, page] = await Promise.all([
184
98
  execute<
185
99
  ServerCollectionPageQueryQueryVariables,
186
100
  ServerCollectionPageQueryQuery
@@ -192,7 +106,6 @@ export const getStaticProps: GetStaticProps<
192
106
  ...(previewData?.contentType === 'plp' ? previewData : null),
193
107
  contentType: 'plp',
194
108
  }),
195
- getGlobalSectionsData(previewData),
196
109
  ])
197
110
 
198
111
  const notFound = errors.find(isNotFoundError)
@@ -204,6 +117,7 @@ export const getStaticProps: GetStaticProps<
204
117
  }
205
118
 
206
119
  if (errors.length > 0) {
120
+ console.error(...errors)
207
121
  throw errors[0]
208
122
  }
209
123
 
@@ -211,7 +125,8 @@ export const getStaticProps: GetStaticProps<
211
125
  props: {
212
126
  ...data,
213
127
  page,
214
- globalSections,
128
+ globalSections: await globalSectionsPromise,
129
+ type: 'plp',
215
130
  },
216
131
  }
217
132
  }
@@ -3,6 +3,13 @@ import type { NextApiHandler, NextApiRequest } from 'next'
3
3
  import { clientCMS } from 'src/server/cms'
4
4
  import { previewRedirects } from '../../../faststore.config'
5
5
 
6
+ type Settings = {
7
+ seo: {
8
+ slug: string
9
+ title: string
10
+ description: string
11
+ }
12
+ }
6
13
  class StatusError extends Error {
7
14
  constructor(message: string, public status: number) {
8
15
  super(message)
@@ -46,8 +53,17 @@ const handler: NextApiHandler = async (req, res) => {
46
53
  res.setPreviewData(locator, { maxAge: 3600 })
47
54
 
48
55
  // Redirect to the path from the fetched locator
49
- // TODO: apply redirect based on the content
50
- res.redirect(previewRedirects[locator.contentType] ?? '/')
56
+ if (previewRedirects[locator.contentType]) {
57
+ res.redirect(previewRedirects[locator.contentType])
58
+ return
59
+ }
60
+
61
+ if (locator.contentType === 'landingPage') {
62
+ res.redirect(`${(page.settings as Settings)?.seo.slug}`)
63
+ return
64
+ }
65
+
66
+ res.redirect('/')
51
67
  } catch (error) {
52
68
  if (error instanceof StatusError) {
53
69
  res.status(error.status).end(error.message)
@@ -87,9 +87,7 @@ export const getStaticProps: GetStaticProps<
87
87
  > = async ({ previewData }) => {
88
88
  const [page, globalSections] = await Promise.all([
89
89
  getPage<PageContentType>({
90
- ...(previewData?.contentType === 'home'
91
- ? previewData
92
- : { filters: { 'settings.seo.slug': '/' } }),
90
+ ...(previewData?.contentType === 'home' && previewData),
93
91
  contentType: 'home',
94
92
  }),
95
93
  getGlobalSectionsData(previewData),
@@ -0,0 +1,15 @@
1
+ import { Options } from 'src/server/cms'
2
+
3
+ export default class MissingContentError extends Error {
4
+ constructor(options: Options) {
5
+ super(
6
+ `Missing content on the CMS for content type ${
7
+ options.contentType
8
+ }. Add content before proceeding. Context: ${JSON.stringify(
9
+ options,
10
+ null,
11
+ 2
12
+ )}`
13
+ )
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export { default } from './MissingContentError'
@@ -0,0 +1,15 @@
1
+ import { Options } from 'src/server/cms'
2
+
3
+ export default class MultipleContentError extends Error {
4
+ constructor(options: Options) {
5
+ super(
6
+ `Multiple content defined on the CMS for content type ${
7
+ options.contentType
8
+ }. Remove duplicated content before proceeding. Context: ${JSON.stringify(
9
+ options,
10
+ null,
11
+ 2
12
+ )}`
13
+ )
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export { default } from './MultipleContentError'