@faststore/core 2.1.17 → 2.1.19

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 (187) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/build-manifest.json +129 -0
  3. package/.next/cache/.tsbuildinfo +1 -0
  4. package/.next/cache/config.json +7 -0
  5. package/.next/cache/eslint/.cache_1gneedd +1 -0
  6. package/.next/cache/next-server.js.nft.json +1 -0
  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/export-marker.json +1 -0
  12. package/.next/images-manifest.json +1 -0
  13. package/.next/next-server.js.nft.json +1 -0
  14. package/.next/package.json +1 -0
  15. package/.next/prerender-manifest.json +1 -0
  16. package/.next/react-loadable-manifest.json +44 -0
  17. package/.next/required-server-files.json +1 -0
  18. package/.next/routes-manifest.json +1 -0
  19. package/.next/server/chunks/123.js +58 -0
  20. package/.next/server/chunks/143.js +106 -0
  21. package/.next/server/chunks/144.js +1338 -0
  22. package/.next/server/chunks/183.js +90 -0
  23. package/.next/server/chunks/184.js +61 -0
  24. package/.next/server/chunks/186.js +136 -0
  25. package/.next/server/chunks/247.js +61 -0
  26. package/.next/server/chunks/253.js +535 -0
  27. package/.next/server/chunks/269.js +517 -0
  28. package/.next/server/chunks/287.js +58 -0
  29. package/.next/server/chunks/289.js +242 -0
  30. package/.next/server/chunks/312.js +697 -0
  31. package/.next/server/chunks/350.js +143 -0
  32. package/.next/server/chunks/487.js +9142 -0
  33. package/.next/server/chunks/502.js +626 -0
  34. package/.next/server/chunks/576.js +90 -0
  35. package/.next/server/chunks/597.js +211 -0
  36. package/.next/server/chunks/650.js +9142 -0
  37. package/.next/server/chunks/676.js +32 -0
  38. package/.next/server/chunks/721.js +679 -0
  39. package/.next/server/chunks/74.js +4100 -0
  40. package/.next/server/chunks/825.js +4039 -0
  41. package/.next/server/chunks/854.js +72 -0
  42. package/.next/server/chunks/859.js +959 -0
  43. package/.next/server/chunks/886.js +120 -0
  44. package/.next/server/chunks/907.js +1956 -0
  45. package/.next/server/chunks/98.js +124 -0
  46. package/.next/server/chunks/font-manifest.json +1 -0
  47. package/.next/server/font-manifest.json +1 -0
  48. package/.next/server/middleware-build-manifest.js +1 -0
  49. package/.next/server/middleware-manifest.json +6 -0
  50. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  51. package/.next/server/pages/404.js +386 -0
  52. package/.next/server/pages/404.js.nft.json +1 -0
  53. package/.next/server/pages/500.js +388 -0
  54. package/.next/server/pages/500.js.nft.json +1 -0
  55. package/.next/server/pages/[...slug].js +1005 -0
  56. package/.next/server/pages/[...slug].js.nft.json +1 -0
  57. package/.next/server/pages/[slug]/p.js +2390 -0
  58. package/.next/server/pages/[slug]/p.js.nft.json +1 -0
  59. package/.next/server/pages/_app.js +281 -0
  60. package/.next/server/pages/_app.js.nft.json +1 -0
  61. package/.next/server/pages/_document.js +340 -0
  62. package/.next/server/pages/_document.js.nft.json +1 -0
  63. package/.next/server/pages/_error.js +164 -0
  64. package/.next/server/pages/_error.js.nft.json +1 -0
  65. package/.next/server/pages/account.js +363 -0
  66. package/.next/server/pages/account.js.nft.json +1 -0
  67. package/.next/server/pages/api/graphql.js +365 -0
  68. package/.next/server/pages/api/graphql.js.nft.json +1 -0
  69. package/.next/server/pages/api/preview.js +148 -0
  70. package/.next/server/pages/api/preview.js.nft.json +1 -0
  71. package/.next/server/pages/checkout.js +363 -0
  72. package/.next/server/pages/checkout.js.nft.json +1 -0
  73. package/.next/server/pages/en-US/404.html +81 -0
  74. package/.next/server/pages/en-US/404.json +1 -0
  75. package/.next/server/pages/en-US/500.html +81 -0
  76. package/.next/server/pages/en-US/500.json +1 -0
  77. package/.next/server/pages/en-US/account.html +81 -0
  78. package/.next/server/pages/en-US/account.json +1 -0
  79. package/.next/server/pages/en-US/checkout.html +81 -0
  80. package/.next/server/pages/en-US/checkout.json +1 -0
  81. package/.next/server/pages/en-US/login.html +81 -0
  82. package/.next/server/pages/en-US/login.json +1 -0
  83. package/.next/server/pages/en-US/s.html +81 -0
  84. package/.next/server/pages/en-US/s.json +1 -0
  85. package/.next/server/pages/en-US.html +81 -0
  86. package/.next/server/pages/en-US.json +1 -0
  87. package/.next/server/pages/index.js +439 -0
  88. package/.next/server/pages/index.js.nft.json +1 -0
  89. package/.next/server/pages/login.js +368 -0
  90. package/.next/server/pages/login.js.nft.json +1 -0
  91. package/.next/server/pages/s.js +466 -0
  92. package/.next/server/pages/s.js.nft.json +1 -0
  93. package/.next/server/pages-manifest.json +16 -0
  94. package/.next/server/webpack-api-runtime.js +229 -0
  95. package/.next/server/webpack-runtime.js +229 -0
  96. package/.next/static/HWyxnnh37qprVz28Rroho/_buildManifest.js +1 -0
  97. package/.next/static/HWyxnnh37qprVz28Rroho/_ssgManifest.js +1 -0
  98. package/.next/static/chunks/130-15325805e3c8d5f4.js +1 -0
  99. package/.next/static/chunks/143.dd8a556e6957baa1.js +1 -0
  100. package/.next/static/chunks/495.0ecd099878b2a36d.js +1 -0
  101. package/.next/static/chunks/502.b14533723651e5a1.js +1 -0
  102. package/.next/static/chunks/548-ab84e9e8b49413ab.js +1 -0
  103. package/.next/static/chunks/597.f8d0595b113c70af.js +1 -0
  104. package/.next/static/chunks/64.7ea3677ac3a10e00.js +1 -0
  105. package/.next/static/chunks/651.7142f31ce1e052b3.js +1 -0
  106. package/.next/static/chunks/706-5aea41dd07970d2d.js +1 -0
  107. package/.next/static/chunks/738-67a288ca3569cdbb.js +1 -0
  108. package/.next/static/chunks/741.52f7fb873418346f.js +1 -0
  109. package/.next/static/chunks/791-63b1ccf9964b6517.js +1 -0
  110. package/.next/static/chunks/98.97381d2021f86cd9.js +1 -0
  111. package/.next/static/chunks/framework-dfd14d7ce6600b03.js +1 -0
  112. package/.next/static/chunks/main-fd466221927468fd.js +1 -0
  113. package/.next/static/chunks/pages/404-d5f20744ecd83121.js +1 -0
  114. package/.next/static/chunks/pages/500-3911549ab88d0378.js +1 -0
  115. package/.next/static/chunks/pages/[...slug]-3a37fd4d13cb2ba8.js +1 -0
  116. package/.next/static/chunks/pages/[slug]/p-bf47c90571846f86.js +1 -0
  117. package/.next/static/chunks/pages/_app-79d333aa6001a806.js +1 -0
  118. package/.next/static/chunks/pages/_error-a7a0c1d9bfbb4f38.js +1 -0
  119. package/.next/static/chunks/pages/account-d248acc931146694.js +1 -0
  120. package/.next/static/chunks/pages/checkout-97f6d6f36f041a6f.js +1 -0
  121. package/.next/static/chunks/pages/index-e9727cb06d9ae961.js +1 -0
  122. package/.next/static/chunks/pages/login-4cc4b8e52608f076.js +1 -0
  123. package/.next/static/chunks/pages/s-aabfa5c08338974a.js +1 -0
  124. package/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +1 -0
  125. package/.next/static/chunks/webpack-cbb5ca873d13aa82.js +1 -0
  126. package/.next/static/css/08e3fc80f9bad95d.css +1 -0
  127. package/.next/static/css/0e00026896a2ee3e.css +1 -0
  128. package/.next/static/css/1b23beb5af203ffb.css +1 -0
  129. package/.next/static/css/584640ffee46aa49.css +1 -0
  130. package/.next/static/css/7d822a137c54a781.css +1 -0
  131. package/.next/static/css/a49f71ae6bb528e0.css +1 -0
  132. package/.next/static/css/ccbb6df6cae68586.css +1 -0
  133. package/.next/static/css/e02cdad8fc000339.css +1 -0
  134. package/.next/static/css/f7ed956d370744ea.css +1 -0
  135. package/.next/static/css/f9d59f597a4d8f82.css +1 -0
  136. package/.next/trace +80 -0
  137. package/.turbo/turbo-build.log +13 -14
  138. package/README.md +1 -2
  139. package/cms/faststore/content-types.json +2 -2
  140. package/package.json +4 -4
  141. package/public/~partytown/debug/partytown-atomics.js +556 -0
  142. package/public/~partytown/debug/partytown-media.js +374 -0
  143. package/public/~partytown/debug/partytown-sandbox-sw.js +543 -0
  144. package/public/~partytown/debug/partytown-sw.js +59 -0
  145. package/public/~partytown/debug/partytown-ww-atomics.js +1789 -0
  146. package/public/~partytown/debug/partytown-ww-sw.js +1781 -0
  147. package/public/~partytown/debug/partytown.js +72 -0
  148. package/public/~partytown/partytown-atomics.js +2 -0
  149. package/public/~partytown/partytown-media.js +2 -0
  150. package/public/~partytown/partytown-sw.js +2 -0
  151. package/public/~partytown/partytown.js +2 -0
  152. package/src/components/cms/GlobalSections.tsx +2 -3
  153. package/src/components/common/Alert/section.module.scss +2 -0
  154. package/src/components/common/Footer/Footer.tsx +5 -14
  155. package/src/components/navigation/Navbar/Navbar.tsx +1 -1
  156. package/src/components/navigation/NavbarLinks/NavbarLinks.tsx +1 -1
  157. package/src/components/product/ProductCard/ProductCard.tsx +1 -1
  158. package/src/components/sections/BannerNewsletter/BannerNewsletter.tsx +2 -4
  159. package/src/components/sections/BannerText/BannerText.tsx +0 -2
  160. package/src/components/sections/Breadcrumb/Breadcrumb.tsx +1 -1
  161. package/src/components/sections/Incentives/Incentives.tsx +1 -1
  162. package/src/components/sections/Incentives/section.module.scss +2 -0
  163. package/src/components/sections/Navbar/section.module.scss +4 -0
  164. package/src/components/sections/Newsletter/section.module.scss +2 -0
  165. package/src/components/sections/ProductDetails/ProductDetails.tsx +2 -4
  166. package/src/components/sections/ProductGallery/ProductGallery.tsx +2 -4
  167. package/src/components/sections/ProductGallery/section.module.scss +13 -0
  168. package/src/components/sections/ProductShelf/section.module.scss +5 -0
  169. package/src/components/sections/ProductTiles/ProductTiles.tsx +18 -20
  170. package/src/components/sections/Section/section.scss +8 -0
  171. package/src/components/skeletons/ProductShelfSkeleton/ProductShelfSkeleton.tsx +1 -1
  172. package/src/components/templates/LandingPage/LandingPage.tsx +102 -0
  173. package/src/components/templates/LandingPage/index.ts +2 -0
  174. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +138 -0
  175. package/src/components/templates/ProductListingPage/index.ts +2 -0
  176. package/src/components/ui/Incentives/Incentives.tsx +1 -1
  177. package/src/components/ui/Newsletter/Newsletter.tsx +1 -1
  178. package/src/components/ui/ProductGallery/ProductGallery.tsx +5 -2
  179. package/src/pages/[...slug].tsx +45 -130
  180. package/src/pages/api/preview.ts +18 -2
  181. package/src/pages/index.tsx +1 -3
  182. package/src/sdk/error/MissingContentError/MissingContentError.ts +15 -0
  183. package/src/sdk/error/MissingContentError/index.ts +1 -0
  184. package/src/sdk/error/MultipleContentError/MultipleContentError.ts +15 -0
  185. package/src/sdk/error/MultipleContentError/index.ts +1 -0
  186. package/src/server/cms.ts +7 -21
  187. package/src/styles/global/components.scss +0 -6
@@ -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'
@@ -35,7 +35,7 @@ function Incentives({
35
35
  data-fs-incentives-colored={colored}
36
36
  data-fs-incentives-variant={variant}
37
37
  >
38
- <UIList className="layout__content">
38
+ <UIList data-fs-content="incentives">
39
39
  {incentives.map((incentive, index) => (
40
40
  <li role="listitem" key={String(index)}>
41
41
  <UIIncentive tabIndex={0}>
@@ -179,8 +179,8 @@ const Newsletter = forwardRef<HTMLFormElement, NewsletterProps>(
179
179
  <form
180
180
  ref={ref}
181
181
  data-fs-newsletter-form
182
+ data-fs-content="newsletter"
182
183
  onSubmit={handleSubmit}
183
- className="layout__content"
184
184
  >
185
185
  <header data-fs-newsletter-header>
186
186
  <h3>
@@ -95,13 +95,16 @@ function ProductGallery({
95
95
  return (
96
96
  <section data-testid="product-gallery" data-fs-product-listing>
97
97
  {searchTerm && (
98
- <header data-fs-product-listing-search-term className="layout__content">
98
+ <header data-fs-product-listing-search-term>
99
99
  <h1>
100
100
  {searchTermLabel} <span>{searchTerm}</span>
101
101
  </h1>
102
102
  </header>
103
103
  )}
104
- <div data-fs-product-listing-content-grid className="layout__content">
104
+ <div
105
+ data-fs-product-listing-content-grid
106
+ data-fs-content="product-gallery"
107
+ >
105
108
  <div data-fs-product-listing-filters>
106
109
  <FilterSkeleton loading={facets?.length === 0}>
107
110
  <Filter facets={facets} filter={filter} />
@@ -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'
package/src/server/cms.ts CHANGED
@@ -1,18 +1,20 @@
1
- import type { ContentData, Locator } from '@vtex/client-cms'
1
+ import type { ContentData, ContentTypeOptions, Locator } from '@vtex/client-cms'
2
2
  import ClientCMS from '@vtex/client-cms'
3
3
 
4
4
  import config from '../../faststore.config'
5
+ import MultipleContentError from 'src/sdk/error/MultipleContentError'
6
+ import MissingContentError from 'src/sdk/error/MissingContentError'
5
7
 
6
8
  export const clientCMS = new ClientCMS({
7
9
  workspace: config.api.workspace,
8
10
  tenant: config.api.storeId,
9
11
  })
10
12
 
11
- type Options =
13
+ export type Options =
12
14
  | Locator
13
15
  | {
14
16
  contentType: string
15
- filters?: Record<string, string>
17
+ filters?: Partial<ContentTypeOptions>
16
18
  }
17
19
 
18
20
  const isLocator = (x: any): x is Locator =>
@@ -27,27 +29,11 @@ export const getPage = async <T extends ContentData>(options: Options) => {
27
29
  const pages = result.data
28
30
 
29
31
  if (!pages[0]) {
30
- throw new Error(
31
- `Missing content on the CMS for content type ${
32
- options.contentType
33
- }. Add content before proceeding. Context: ${JSON.stringify(
34
- options,
35
- null,
36
- 2
37
- )}`
38
- )
32
+ throw new MissingContentError(options)
39
33
  }
40
34
 
41
35
  if (pages.length !== 1) {
42
- throw new Error(
43
- `Multiple content defined on the CMS for content type ${
44
- options.contentType
45
- }. Remove duplicated content before proceeding. Context: ${JSON.stringify(
46
- options,
47
- null,
48
- 2
49
- )}`
50
- )
36
+ throw new MultipleContentError(options)
51
37
  }
52
38
 
53
39
  return pages[0] as T
@@ -2,9 +2,3 @@
2
2
  @import "@faststore/ui/src/components/atoms/Overlay/styles.scss";
3
3
  @import "@faststore/ui/src/components/atoms/SROnly/styles.scss";
4
4
  @import "src/components/sections/Section/section.scss";
5
-
6
- .section-navbar {
7
- position: sticky;
8
- top: 0;
9
- z-index: var(--fs-z-index-high);
10
- }