kofi-stack-template-generator 2.1.36 → 2.1.38

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 (324) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/index.js +8059 -441
  3. package/package.json +1 -1
  4. package/src/templates.generated.ts +250 -95
  5. package/templates/integrations/posthog/src/components/providers/posthog-provider.tsx.hbs +4 -1
  6. package/templates/marketing/payload/package.json.hbs +41 -26
  7. package/templates/marketing/payload/src/Footer/Component.client.tsx +288 -0
  8. package/templates/marketing/payload/src/Footer/Component.tsx +11 -0
  9. package/templates/marketing/payload/src/Footer/RowLabel.tsx +15 -0
  10. package/templates/marketing/payload/src/Footer/config.ts +178 -0
  11. package/templates/marketing/payload/src/Footer/hooks/{revalidateFooter.ts.hbs → revalidateFooter.ts} +5 -5
  12. package/templates/marketing/payload/src/Header/Component.client.tsx +94 -0
  13. package/templates/marketing/payload/src/Header/Component.tsx +10 -0
  14. package/templates/marketing/payload/src/Header/MegaMenu/index.tsx +197 -0
  15. package/templates/marketing/payload/src/Header/MobileMenu/HamburgerIcon.tsx +48 -0
  16. package/templates/marketing/payload/src/Header/MobileMenu/index.tsx +299 -0
  17. package/templates/marketing/payload/src/Header/Nav/index.tsx +76 -0
  18. package/templates/marketing/payload/src/Header/RowLabel.tsx +21 -0
  19. package/templates/marketing/payload/src/Header/config.ts +208 -0
  20. package/templates/marketing/payload/src/Header/hooks/{revalidateHeader.ts.hbs → revalidateHeader.ts} +5 -5
  21. package/templates/marketing/payload/src/access/{authenticated.ts.hbs → authenticated.ts} +1 -1
  22. package/templates/marketing/payload/src/access/{authenticatedOrPublished.ts.hbs → authenticatedOrPublished.ts} +8 -8
  23. package/templates/marketing/payload/src/app/(docs)/docs/[[...slug]]/page.tsx +117 -0
  24. package/templates/marketing/payload/src/app/(docs)/docs/layout.tsx +39 -0
  25. package/templates/marketing/payload/src/app/(docs)/layout.tsx +44 -0
  26. package/templates/marketing/payload/src/app/(frontend)/(sitemaps)/pages-sitemap.xml/route.ts +68 -0
  27. package/templates/marketing/payload/src/app/(frontend)/(sitemaps)/posts-sitemap.xml/route.ts +55 -0
  28. package/templates/marketing/payload/src/app/(frontend)/[slug]/page.client.tsx +15 -0
  29. package/templates/marketing/payload/src/app/(frontend)/[slug]/page.tsx +114 -0
  30. package/templates/marketing/payload/src/app/(frontend)/api/docs-search/route.ts +67 -0
  31. package/templates/marketing/payload/src/app/(frontend)/api/newsletter/route.ts +260 -0
  32. package/templates/marketing/payload/src/app/(frontend)/api/pricing/route.ts +266 -0
  33. package/templates/marketing/payload/src/app/(frontend)/globals.css +1019 -0
  34. package/templates/marketing/payload/src/app/(frontend)/layout.tsx +114 -0
  35. package/templates/marketing/payload/src/app/(frontend)/next/exit-preview/route.ts +7 -0
  36. package/templates/marketing/payload/src/app/(frontend)/next/preview/route.ts +56 -0
  37. package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts +31 -0
  38. package/templates/marketing/payload/src/app/(frontend)/not-found.tsx +17 -0
  39. package/templates/marketing/payload/src/app/(frontend)/page.tsx +5 -0
  40. package/templates/marketing/payload/src/app/(frontend)/posts/BlogPageClient.tsx +190 -0
  41. package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/BlogPostContent.tsx +67 -0
  42. package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/page.client.tsx +15 -0
  43. package/templates/marketing/payload/src/app/(frontend)/posts/[slug]/page.tsx +118 -0
  44. package/templates/marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.client.tsx +15 -0
  45. package/templates/marketing/payload/src/app/(frontend)/posts/page/[pageNumber]/page.tsx +87 -0
  46. package/templates/marketing/payload/src/app/(frontend)/posts/page.tsx +49 -0
  47. package/templates/marketing/payload/src/app/(frontend)/search/page.client.tsx +15 -0
  48. package/templates/marketing/payload/src/app/(frontend)/search/page.tsx +87 -0
  49. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx +24 -0
  50. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx +24 -0
  51. package/templates/marketing/payload/src/app/(payload)/admin/importMap.js +83 -0
  52. package/templates/marketing/payload/src/app/(payload)/api/[...slug]/{route.ts.hbs → route.ts} +13 -9
  53. package/templates/marketing/payload/src/app/(payload)/api/graphql/{route.ts.hbs → route.ts} +2 -2
  54. package/templates/marketing/payload/src/app/(payload)/api/graphql-playground/{route.ts.hbs → route.ts} +3 -3
  55. package/templates/marketing/payload/src/app/(payload)/custom.scss +0 -0
  56. package/templates/marketing/payload/src/app/(payload)/layout.tsx +31 -0
  57. package/templates/marketing/payload/src/blocks/ArchiveBlock/Component.tsx +65 -0
  58. package/templates/marketing/payload/src/blocks/ArchiveBlock/config.ts +120 -0
  59. package/templates/marketing/payload/src/blocks/Banner/Component.tsx +26 -0
  60. package/templates/marketing/payload/src/blocks/Banner/config.ts +67 -0
  61. package/templates/marketing/payload/src/blocks/BentoFeatures/Component.tsx +243 -0
  62. package/templates/marketing/payload/src/blocks/BentoFeatures/config.ts +147 -0
  63. package/templates/marketing/payload/src/blocks/CallToAction/Component.tsx +31 -0
  64. package/templates/marketing/payload/src/blocks/CallToAction/config.ts +68 -0
  65. package/templates/marketing/payload/src/blocks/Code/Component.client.tsx +33 -0
  66. package/templates/marketing/payload/src/blocks/Code/Component.tsx +21 -0
  67. package/templates/marketing/payload/src/blocks/Code/CopyButton.tsx +33 -0
  68. package/templates/marketing/payload/src/blocks/Code/config.ts +33 -0
  69. package/templates/marketing/payload/src/blocks/Content/Component.tsx +41 -0
  70. package/templates/marketing/payload/src/blocks/Content/config.ts +105 -0
  71. package/templates/marketing/payload/src/blocks/FAQAccordion/Component.tsx +90 -0
  72. package/templates/marketing/payload/src/blocks/FAQAccordion/config.ts +75 -0
  73. package/templates/marketing/payload/src/blocks/FeatureGrid/Component.tsx +108 -0
  74. package/templates/marketing/payload/src/blocks/FeatureGrid/config.ts +109 -0
  75. package/templates/marketing/payload/src/blocks/FeatureShowcase/Component.tsx +107 -0
  76. package/templates/marketing/payload/src/blocks/FeatureShowcase/config.ts +111 -0
  77. package/templates/marketing/payload/src/blocks/FinalCTA/Component.tsx +117 -0
  78. package/templates/marketing/payload/src/blocks/FinalCTA/config.ts +50 -0
  79. package/templates/marketing/payload/src/blocks/Form/Checkbox/index.tsx +45 -0
  80. package/templates/marketing/payload/src/blocks/Form/Component.tsx +170 -0
  81. package/templates/marketing/payload/src/blocks/Form/Country/index.tsx +65 -0
  82. package/templates/marketing/payload/src/blocks/Form/Country/options.ts +982 -0
  83. package/templates/marketing/payload/src/blocks/Form/Email/index.tsx +38 -0
  84. package/templates/marketing/payload/src/blocks/Form/Error/index.tsx +13 -0
  85. package/templates/marketing/payload/src/blocks/Form/Message/index.tsx +13 -0
  86. package/templates/marketing/payload/src/blocks/Form/Number/index.tsx +36 -0
  87. package/templates/marketing/payload/src/blocks/Form/Select/index.tsx +63 -0
  88. package/templates/marketing/payload/src/blocks/Form/State/index.tsx +64 -0
  89. package/templates/marketing/payload/src/blocks/Form/State/options.ts +52 -0
  90. package/templates/marketing/payload/src/blocks/Form/Text/index.tsx +32 -0
  91. package/templates/marketing/payload/src/blocks/Form/Textarea/index.tsx +40 -0
  92. package/templates/marketing/payload/src/blocks/Form/Width/index.tsx +13 -0
  93. package/templates/marketing/payload/src/blocks/Form/config.ts +77 -0
  94. package/templates/marketing/payload/src/blocks/Form/fields.tsx +21 -0
  95. package/templates/marketing/payload/src/blocks/HowItWorks/Component.tsx +59 -0
  96. package/templates/marketing/payload/src/blocks/HowItWorks/config.ts +88 -0
  97. package/templates/marketing/payload/src/blocks/IndustryTabs/Component.tsx +132 -0
  98. package/templates/marketing/payload/src/blocks/IndustryTabs/config.ts +77 -0
  99. package/templates/marketing/payload/src/blocks/LogoBanner/Component.tsx +95 -0
  100. package/templates/marketing/payload/src/blocks/LogoBanner/config.ts +48 -0
  101. package/templates/marketing/payload/src/blocks/MediaBlock/Component.tsx +67 -0
  102. package/templates/marketing/payload/src/blocks/MediaBlock/config.ts +14 -0
  103. package/templates/marketing/payload/src/blocks/Personas/Component.tsx +69 -0
  104. package/templates/marketing/payload/src/blocks/Personas/config.ts +96 -0
  105. package/templates/marketing/payload/src/blocks/PricingTable/ComparisonTable.tsx +250 -0
  106. package/templates/marketing/payload/src/blocks/PricingTable/Component.tsx +443 -0
  107. package/templates/marketing/payload/src/blocks/PricingTable/config.ts +142 -0
  108. package/templates/marketing/payload/src/blocks/ProofBanner/Component.tsx +65 -0
  109. package/templates/marketing/payload/src/blocks/ProofBanner/config.ts +42 -0
  110. package/templates/marketing/payload/src/blocks/RelatedPosts/Component.tsx +32 -0
  111. package/templates/marketing/payload/src/blocks/RenderBlocks.tsx +92 -0
  112. package/templates/marketing/payload/src/blocks/TestimonialsGrid/Component.tsx +107 -0
  113. package/templates/marketing/payload/src/blocks/TestimonialsGrid/config.ts +76 -0
  114. package/templates/marketing/payload/src/blocks/TrustColumns/Component.tsx +83 -0
  115. package/templates/marketing/payload/src/blocks/TrustColumns/config.ts +70 -0
  116. package/templates/marketing/payload/src/collections/Categories.ts +28 -0
  117. package/templates/marketing/payload/src/collections/FAQs/index.ts +100 -0
  118. package/templates/marketing/payload/src/collections/Media.ts +160 -0
  119. package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts +43 -0
  120. package/templates/marketing/payload/src/collections/Pages/index.ts +168 -0
  121. package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts +41 -0
  122. package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts +44 -0
  123. package/templates/marketing/payload/src/collections/Posts/index.ts +259 -0
  124. package/templates/marketing/payload/src/collections/Users/index.ts +26 -0
  125. package/templates/marketing/payload/src/components/AdminBar/index.scss +7 -0
  126. package/templates/marketing/payload/src/components/AdminBar/index.tsx +89 -0
  127. package/templates/marketing/payload/src/components/Analytics/CTATracker.tsx +33 -0
  128. package/templates/marketing/payload/src/components/Analytics/FeatureSectionTracker.tsx +47 -0
  129. package/templates/marketing/payload/src/components/Analytics/PricingViewTracker.tsx +46 -0
  130. package/templates/marketing/payload/src/components/Analytics/index.tsx +3 -0
  131. package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx +89 -0
  132. package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx +69 -0
  133. package/templates/marketing/payload/src/components/BeforeLogin/index.tsx +14 -0
  134. package/templates/marketing/payload/src/components/BlogCTA/index.tsx +77 -0
  135. package/templates/marketing/payload/src/components/Card/index.tsx +85 -0
  136. package/templates/marketing/payload/src/components/CollectionArchive/index.tsx +32 -0
  137. package/templates/marketing/payload/src/components/JsonLd/index.tsx +138 -0
  138. package/templates/marketing/payload/src/components/Link/index.tsx +66 -0
  139. package/templates/marketing/payload/src/components/LivePreviewListener/index.tsx +10 -0
  140. package/templates/marketing/payload/src/components/Logo/Logo.tsx +46 -0
  141. package/templates/marketing/payload/src/components/Media/ImageMedia/index.tsx +80 -0
  142. package/templates/marketing/payload/src/components/Media/VideoMedia/index.tsx +47 -0
  143. package/templates/marketing/payload/src/components/Media/index.tsx +26 -0
  144. package/templates/marketing/payload/src/components/Media/types.ts +22 -0
  145. package/templates/marketing/payload/src/components/PageRange/index.tsx +57 -0
  146. package/templates/marketing/payload/src/components/Pagination/index.tsx +101 -0
  147. package/templates/marketing/payload/src/components/PayloadRedirects/index.tsx +48 -0
  148. package/templates/marketing/payload/src/components/RichText/index.tsx +152 -0
  149. package/templates/marketing/payload/src/components/TableOfContents/index.tsx +128 -0
  150. package/templates/marketing/payload/src/components/ui/accordion.tsx +64 -0
  151. package/templates/marketing/payload/src/components/ui/button.tsx +52 -0
  152. package/templates/marketing/payload/src/components/ui/card.tsx +48 -0
  153. package/templates/marketing/payload/src/components/ui/checkbox.tsx +27 -0
  154. package/templates/marketing/payload/src/components/ui/input.tsx +22 -0
  155. package/templates/marketing/payload/src/components/ui/label.tsx +19 -0
  156. package/templates/marketing/payload/src/components/ui/pagination.tsx +92 -0
  157. package/templates/marketing/payload/src/components/ui/select.tsx +144 -0
  158. package/templates/marketing/payload/src/components/ui/textarea.tsx +21 -0
  159. package/templates/marketing/payload/src/endpoints/seed/contact-form.ts +111 -0
  160. package/templates/marketing/payload/src/endpoints/seed/contact-page.ts +56 -0
  161. package/templates/marketing/payload/src/endpoints/seed/directoryhub/about.ts +281 -0
  162. package/templates/marketing/payload/src/endpoints/seed/directoryhub/faqs.ts +224 -0
  163. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/automation.ts +229 -0
  164. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/custom-fields.ts +229 -0
  165. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/dashboard.ts +228 -0
  166. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/index.ts +6 -0
  167. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/monetization.ts +230 -0
  168. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/seo.ts +229 -0
  169. package/templates/marketing/payload/src/endpoints/seed/directoryhub/features/templates.ts +218 -0
  170. package/templates/marketing/payload/src/endpoints/seed/directoryhub/home.ts +555 -0
  171. package/templates/marketing/payload/src/endpoints/seed/directoryhub/index.ts +767 -0
  172. package/templates/marketing/payload/src/endpoints/seed/directoryhub/posts.ts +623 -0
  173. package/templates/marketing/payload/src/endpoints/seed/directoryhub/pricing.ts +251 -0
  174. package/templates/marketing/payload/src/endpoints/seed/directoryhub/privacy.ts +457 -0
  175. package/templates/marketing/payload/src/endpoints/seed/directoryhub/richtext-helper.ts +88 -0
  176. package/templates/marketing/payload/src/endpoints/seed/directoryhub/terms.ts +478 -0
  177. package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/b2b-vendor-hubs.ts +229 -0
  178. package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/communities.ts +230 -0
  179. package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/index.ts +4 -0
  180. package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/local-services.ts +230 -0
  181. package/templates/marketing/payload/src/endpoints/seed/directoryhub/use-cases/marketplaces.ts +230 -0
  182. package/templates/marketing/payload/src/endpoints/seed/home-static.ts +691 -0
  183. package/templates/marketing/payload/src/endpoints/seed/home.ts +675 -0
  184. package/templates/marketing/payload/src/endpoints/seed/image-1.ts +67 -0
  185. package/templates/marketing/payload/src/endpoints/seed/image-2.ts +67 -0
  186. package/templates/marketing/payload/src/endpoints/seed/image-3.ts +67 -0
  187. package/templates/marketing/payload/src/endpoints/seed/image-hero-1.ts +5 -0
  188. package/templates/marketing/payload/src/endpoints/seed/image-hero1.webp +0 -0
  189. package/templates/marketing/payload/src/endpoints/seed/image-post1.webp +0 -0
  190. package/templates/marketing/payload/src/endpoints/seed/image-post2.webp +0 -0
  191. package/templates/marketing/payload/src/endpoints/seed/image-post3.webp +0 -0
  192. package/templates/marketing/payload/src/endpoints/seed/index.ts +335 -0
  193. package/templates/marketing/payload/src/endpoints/seed/post-1.ts +315 -0
  194. package/templates/marketing/payload/src/endpoints/seed/post-2.ts +232 -0
  195. package/templates/marketing/payload/src/endpoints/seed/post-3.ts +268 -0
  196. package/templates/marketing/payload/src/fields/defaultLexical.ts +73 -0
  197. package/templates/marketing/payload/src/fields/link.ts +139 -0
  198. package/templates/marketing/payload/src/fields/linkGroup.ts +28 -0
  199. package/templates/marketing/payload/src/heros/HighImpact/index.tsx +56 -0
  200. package/templates/marketing/payload/src/heros/LowImpact/index.tsx +48 -0
  201. package/templates/marketing/payload/src/heros/MediumImpact/index.tsx +50 -0
  202. package/templates/marketing/payload/src/heros/PostHero/index.tsx +73 -0
  203. package/templates/marketing/payload/src/heros/ProductShowcase/AnimatedMockup.tsx +241 -0
  204. package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx +108 -0
  205. package/templates/marketing/payload/src/heros/{RenderHero.tsx.hbs → RenderHero.tsx} +9 -9
  206. package/templates/marketing/payload/src/heros/config.ts +121 -0
  207. package/templates/marketing/payload/src/hooks/populatePublishedAt.ts +15 -0
  208. package/templates/marketing/payload/src/hooks/{revalidateRedirects.ts.hbs → revalidateRedirects.ts} +3 -3
  209. package/templates/marketing/payload/src/lib/convex.ts +13 -0
  210. package/templates/marketing/payload/src/lib/docs-source.ts +138 -0
  211. package/templates/marketing/payload/src/lib/mdx.tsx +191 -0
  212. package/templates/marketing/payload/src/payload.config.ts.hbs +95 -145
  213. package/templates/marketing/payload/src/plugins/index.ts +107 -0
  214. package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx +34 -0
  215. package/templates/marketing/payload/src/providers/PostHogProvider.tsx +33 -0
  216. package/templates/marketing/payload/src/providers/Theme/InitTheme/{index.tsx.hbs → index.tsx} +11 -10
  217. package/templates/marketing/payload/src/providers/Theme/ThemeSelector/index.tsx +133 -0
  218. package/templates/marketing/payload/src/providers/Theme/ThemeSelector/types.ts +7 -0
  219. package/templates/marketing/payload/src/providers/Theme/index.tsx +60 -0
  220. package/templates/marketing/payload/src/providers/Theme/shared.ts +17 -0
  221. package/templates/marketing/payload/src/providers/Theme/{types.ts.hbs → types.ts} +3 -3
  222. package/templates/marketing/payload/src/providers/index.tsx +17 -0
  223. package/templates/marketing/payload/src/search/Component.tsx +42 -0
  224. package/templates/marketing/payload/src/search/beforeSync.ts +56 -0
  225. package/templates/marketing/payload/src/search/fieldOverrides.ts +61 -0
  226. package/templates/marketing/payload/src/utilities/deepMerge.ts +35 -0
  227. package/templates/marketing/payload/src/utilities/extractHeadings.ts +78 -0
  228. package/templates/marketing/payload/src/utilities/formatAuthors.ts +24 -0
  229. package/templates/marketing/payload/src/utilities/formatDateTime.ts +20 -0
  230. package/templates/marketing/payload/src/utilities/generateMeta.ts +93 -0
  231. package/templates/marketing/payload/src/utilities/generatePreviewPath.ts +33 -0
  232. package/templates/marketing/payload/src/utilities/getDocument.ts +32 -0
  233. package/templates/marketing/payload/src/utilities/getGlobals.ts +26 -0
  234. package/templates/marketing/payload/src/utilities/getMeUser.ts +43 -0
  235. package/templates/marketing/payload/src/utilities/getMediaUrl.ts +24 -0
  236. package/templates/marketing/payload/src/utilities/getRedirects.ts +26 -0
  237. package/templates/marketing/payload/src/utilities/getURL.ts +26 -0
  238. package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts +26 -0
  239. package/templates/marketing/payload/src/utilities/toKebabCase.ts +5 -0
  240. package/templates/marketing/payload/src/utilities/ui.ts +12 -0
  241. package/templates/marketing/payload/src/utilities/useClickableCard.ts +108 -0
  242. package/templates/marketing/payload/src/utilities/useDebounce.ts +17 -0
  243. package/templates/packages/ui/package.json.hbs +4 -0
  244. package/templates/packages/ui/src/components/button.tsx.hbs +53 -0
  245. package/templates/packages/ui/src/components/card.tsx.hbs +76 -0
  246. package/templates/packages/ui/src/components/separator.tsx.hbs +26 -0
  247. package/templates/{marketing/payload/src/app/globals.css.hbs → packages/ui/src/styles.css.hbs} +39 -1
  248. package/templates/marketing/payload/src/Footer/config.ts.hbs +0 -178
  249. package/templates/marketing/payload/src/Footer/index.ts.hbs +0 -1
  250. package/templates/marketing/payload/src/Header/RowLabel.tsx.hbs +0 -21
  251. package/templates/marketing/payload/src/Header/config.ts.hbs +0 -208
  252. package/templates/marketing/payload/src/Header/index.ts.hbs +0 -1
  253. package/templates/marketing/payload/src/access/index.ts.hbs +0 -3
  254. package/templates/marketing/payload/src/app/(frontend)/layout.tsx.hbs +0 -19
  255. package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts.hbs +0 -31
  256. package/templates/marketing/payload/src/app/(frontend)/page.tsx.hbs +0 -83
  257. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx.hbs +0 -24
  258. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx.hbs +0 -24
  259. package/templates/marketing/payload/src/app/(payload)/admin/importMap.js.hbs +0 -1
  260. package/templates/marketing/payload/src/app/(payload)/custom.scss.hbs +0 -1
  261. package/templates/marketing/payload/src/app/(payload)/layout.tsx.hbs +0 -31
  262. package/templates/marketing/payload/src/app/layout.tsx.hbs +0 -10
  263. package/templates/marketing/payload/src/blocks/Benefits.ts.hbs +0 -34
  264. package/templates/marketing/payload/src/blocks/CTA.ts.hbs +0 -39
  265. package/templates/marketing/payload/src/blocks/Content.ts.hbs +0 -9
  266. package/templates/marketing/payload/src/blocks/FAQ.ts.hbs +0 -18
  267. package/templates/marketing/payload/src/blocks/Features.ts.hbs +0 -32
  268. package/templates/marketing/payload/src/blocks/Hero.ts.hbs +0 -40
  269. package/templates/marketing/payload/src/blocks/LogoBanner.ts.hbs +0 -17
  270. package/templates/marketing/payload/src/blocks/Pricing.ts.hbs +0 -37
  271. package/templates/marketing/payload/src/blocks/Testimonials.ts.hbs +0 -21
  272. package/templates/marketing/payload/src/blocks/index.ts.hbs +0 -9
  273. package/templates/marketing/payload/src/collections/Categories/index.ts.hbs +0 -28
  274. package/templates/marketing/payload/src/collections/FAQs/index.ts.hbs +0 -100
  275. package/templates/marketing/payload/src/collections/Media.ts.hbs +0 -164
  276. package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts.hbs +0 -43
  277. package/templates/marketing/payload/src/collections/Pages/index.ts.hbs +0 -142
  278. package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts.hbs +0 -41
  279. package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts.hbs +0 -44
  280. package/templates/marketing/payload/src/collections/Posts/index.ts.hbs +0 -244
  281. package/templates/marketing/payload/src/collections/Users/index.ts.hbs +0 -26
  282. package/templates/marketing/payload/src/collections/index.ts.hbs +0 -6
  283. package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx.hbs +0 -89
  284. package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx.hbs +0 -69
  285. package/templates/marketing/payload/src/components/BeforeLogin/index.tsx.hbs +0 -14
  286. package/templates/marketing/payload/src/components/Link/index.tsx.hbs +0 -79
  287. package/templates/marketing/payload/src/components/Media/index.tsx.hbs +0 -67
  288. package/templates/marketing/payload/src/components/RichText/index.tsx.hbs +0 -44
  289. package/templates/marketing/payload/src/endpoints/seed/home.ts.hbs +0 -76
  290. package/templates/marketing/payload/src/endpoints/seed/image-1.ts.hbs +0 -5
  291. package/templates/marketing/payload/src/endpoints/seed/image-2.ts.hbs +0 -5
  292. package/templates/marketing/payload/src/endpoints/seed/image-hero.ts.hbs +0 -5
  293. package/templates/marketing/payload/src/endpoints/seed/index.ts.hbs +0 -235
  294. package/templates/marketing/payload/src/endpoints/seed/post-1.ts.hbs +0 -252
  295. package/templates/marketing/payload/src/fields/defaultLexical.ts.hbs +0 -73
  296. package/templates/marketing/payload/src/fields/link.ts.hbs +0 -139
  297. package/templates/marketing/payload/src/fields/linkGroup.ts.hbs +0 -28
  298. package/templates/marketing/payload/src/globals/index.ts.hbs +0 -2
  299. package/templates/marketing/payload/src/heros/HighImpact/index.tsx.hbs +0 -53
  300. package/templates/marketing/payload/src/heros/LowImpact/index.tsx.hbs +0 -48
  301. package/templates/marketing/payload/src/heros/MediumImpact/index.tsx.hbs +0 -46
  302. package/templates/marketing/payload/src/heros/PostHero/index.tsx.hbs +0 -68
  303. package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx.hbs +0 -88
  304. package/templates/marketing/payload/src/heros/config.ts.hbs +0 -112
  305. package/templates/marketing/payload/src/heros/index.ts.hbs +0 -7
  306. package/templates/marketing/payload/src/hooks/index.ts.hbs +0 -2
  307. package/templates/marketing/payload/src/hooks/populatePublishedAt.ts.hbs +0 -15
  308. package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx.hbs +0 -34
  309. package/templates/marketing/payload/src/providers/Theme/index.tsx.hbs +0 -60
  310. package/templates/marketing/payload/src/providers/Theme/shared.ts.hbs +0 -17
  311. package/templates/marketing/payload/src/providers/index.tsx.hbs +0 -18
  312. package/templates/marketing/payload/src/utilities/deepMerge.ts.hbs +0 -35
  313. package/templates/marketing/payload/src/utilities/formatAuthors.ts.hbs +0 -24
  314. package/templates/marketing/payload/src/utilities/formatDateTime.ts.hbs +0 -13
  315. package/templates/marketing/payload/src/utilities/generateMeta.ts.hbs +0 -87
  316. package/templates/marketing/payload/src/utilities/generatePreviewPath.ts.hbs +0 -33
  317. package/templates/marketing/payload/src/utilities/getURL.ts.hbs +0 -26
  318. package/templates/marketing/payload/src/utilities/index.ts.hbs +0 -8
  319. package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts.hbs +0 -26
  320. /package/templates/marketing/payload/src/access/{anyone.ts.hbs → anyone.ts} +0 -0
  321. /package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/{index.scss.hbs → index.scss} +0 -0
  322. /package/templates/marketing/payload/src/components/BeforeDashboard/{index.scss.hbs → index.scss} +0 -0
  323. /package/templates/marketing/payload/src/fields/{index.ts.hbs → index.ts} +0 -0
  324. /package/templates/marketing/payload/src/utilities/{canUseDOM.ts.hbs → canUseDOM.ts} +0 -0
@@ -0,0 +1,114 @@
1
+ import type { Metadata } from "next"
2
+
3
+ import { cn } from "@/utilities/ui"
4
+ import { GeistMono } from "geist/font/mono"
5
+ import { Inter } from "next/font/google"
6
+ import type React from "react"
7
+
8
+ import { Footer } from "@/Footer/Component"
9
+ import { Header } from "@/Header/Component"
10
+ import { AdminBar } from "@/components/AdminBar"
11
+ import { JsonLdSchemas } from "@/components/JsonLd"
12
+ import { Providers } from "@/providers"
13
+ import { InitTheme } from "@/providers/Theme/InitTheme"
14
+ import { mergeOpenGraph } from "@/utilities/mergeOpenGraph"
15
+ import { draftMode } from "next/headers"
16
+
17
+ import { getServerSideURL } from "@/utilities/getURL"
18
+ import "./globals.css"
19
+
20
+ const inter = Inter({
21
+ subsets: ["latin"],
22
+ variable: "--font-inter",
23
+ display: "swap",
24
+ })
25
+
26
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
27
+ const { isEnabled } = await draftMode()
28
+
29
+ return (
30
+ <html className={cn(inter.variable, GeistMono.variable)} lang="en" suppressHydrationWarning>
31
+ <head>
32
+ <InitTheme />
33
+ {/* Favicon */}
34
+ <link href="/favicon.ico" rel="icon" sizes="32x32" />
35
+ <link href="/favicon.svg" rel="icon" type="image/svg+xml" />
36
+
37
+ {/* Preconnect to external resources for performance */}
38
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
39
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
40
+ {/* Preconnect to PostHog proxy for faster analytics initialization */}
41
+ <link rel="preconnect" href="/ingest" />
42
+
43
+ {/* DNS prefetch for common external services */}
44
+ <link rel="dns-prefetch" href="https://www.google-analytics.com" />
45
+ <link rel="dns-prefetch" href="https://www.googletagmanager.com" />
46
+
47
+ {/* JSON-LD Structured Data */}
48
+ <JsonLdSchemas />
49
+ </head>
50
+ <body className="font-sans antialiased" suppressHydrationWarning>
51
+ <Providers>
52
+ <AdminBar
53
+ adminBarProps={{
54
+ preview: isEnabled,
55
+ }}
56
+ />
57
+
58
+ <Header />
59
+ <main id="main-content" className="flex-1">
60
+ {children}
61
+ </main>
62
+ <Footer />
63
+ </Providers>
64
+ </body>
65
+ </html>
66
+ )
67
+ }
68
+
69
+ export const metadata: Metadata = {
70
+ metadataBase: new URL(getServerSideURL()),
71
+ title: {
72
+ default: "DirectoryHub - Build Directories That Generate Real Business",
73
+ template: "%s | DirectoryHub",
74
+ },
75
+ description:
76
+ "Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.",
77
+ keywords: [
78
+ "directory builder",
79
+ "no-code directory",
80
+ "directory website",
81
+ "niche directory",
82
+ "business directory software",
83
+ "directory platform",
84
+ "monetize directory",
85
+ ],
86
+ authors: [{ name: "DirectoryHub", url: getServerSideURL() }],
87
+ creator: "DirectoryHub",
88
+ publisher: "DirectoryHub",
89
+ robots: {
90
+ index: true,
91
+ follow: true,
92
+ googleBot: {
93
+ index: true,
94
+ follow: true,
95
+ "max-video-preview": -1,
96
+ "max-image-preview": "large",
97
+ "max-snippet": -1,
98
+ },
99
+ },
100
+ openGraph: mergeOpenGraph(),
101
+ twitter: {
102
+ card: "summary_large_image",
103
+ creator: "@directoryhub",
104
+ site: "@directoryhub",
105
+ title: "DirectoryHub - Build Directories That Generate Real Business",
106
+ description:
107
+ "Launch a profitable directory business in minutes. The no-code platform to build, manage, and monetize niche directory websites.",
108
+ },
109
+ verification: {
110
+ // Add your verification codes here when available
111
+ // google: 'your-google-verification-code',
112
+ // yandex: 'your-yandex-verification-code',
113
+ },
114
+ }
@@ -0,0 +1,7 @@
1
+ import { draftMode } from "next/headers"
2
+
3
+ export async function GET(): Promise<Response> {
4
+ const draft = await draftMode()
5
+ draft.disable()
6
+ return new Response("Draft mode is disabled")
7
+ }
@@ -0,0 +1,56 @@
1
+ import type { CollectionSlug, PayloadRequest } from "payload"
2
+ import { getPayload } from "payload"
3
+
4
+ import { draftMode } from "next/headers"
5
+ import { redirect } from "next/navigation"
6
+ import type { NextRequest } from "next/server"
7
+
8
+ import configPromise from "@payload-config"
9
+
10
+ export async function GET(req: NextRequest): Promise<Response> {
11
+ const payload = await getPayload({ config: configPromise })
12
+
13
+ const { searchParams } = new URL(req.url)
14
+
15
+ const path = searchParams.get("path")
16
+ const collection = searchParams.get("collection") as CollectionSlug
17
+ const slug = searchParams.get("slug")
18
+ const previewSecret = searchParams.get("previewSecret")
19
+
20
+ if (previewSecret !== process.env.PREVIEW_SECRET) {
21
+ return new Response("You are not allowed to preview this page", { status: 403 })
22
+ }
23
+
24
+ if (!path || !collection || !slug) {
25
+ return new Response("Insufficient search params", { status: 404 })
26
+ }
27
+
28
+ if (!path.startsWith("/")) {
29
+ return new Response("This endpoint can only be used for relative previews", { status: 500 })
30
+ }
31
+
32
+ let user
33
+
34
+ try {
35
+ user = await payload.auth({
36
+ req: req as unknown as PayloadRequest,
37
+ headers: req.headers,
38
+ })
39
+ } catch (error) {
40
+ payload.logger.error({ err: error }, "Error verifying token for live preview")
41
+ return new Response("You are not allowed to preview this page", { status: 403 })
42
+ }
43
+
44
+ const draft = await draftMode()
45
+
46
+ if (!user) {
47
+ draft.disable()
48
+ return new Response("You are not allowed to preview this page", { status: 403 })
49
+ }
50
+
51
+ // You can add additional checks here to see if the user is allowed to preview this page
52
+
53
+ draft.enable()
54
+
55
+ redirect(path)
56
+ }
@@ -0,0 +1,31 @@
1
+ import { seed } from "@/endpoints/seed"
2
+ import config from "@payload-config"
3
+ import { headers } from "next/headers"
4
+ import { createLocalReq, getPayload } from "payload"
5
+
6
+ export const maxDuration = 60 // This function can run for a maximum of 60 seconds
7
+
8
+ export async function POST(): Promise<Response> {
9
+ const payload = await getPayload({ config })
10
+ const requestHeaders = await headers()
11
+
12
+ // Authenticate by passing request headers
13
+ const { user } = await payload.auth({ headers: requestHeaders })
14
+
15
+ if (!user) {
16
+ return new Response("Action forbidden.", { status: 403 })
17
+ }
18
+
19
+ try {
20
+ // Create a Payload request object to pass to the Local API for transactions
21
+ // At this point you should pass in a user, locale, and any other context you need for the Local API
22
+ const payloadReq = await createLocalReq({ user }, payload)
23
+
24
+ await seed({ payload, req: payloadReq })
25
+
26
+ return Response.json({ success: true })
27
+ } catch (e) {
28
+ payload.logger.error({ err: e, message: "Error seeding data" })
29
+ return new Response("Error seeding data.", { status: 500 })
30
+ }
31
+ }
@@ -0,0 +1,17 @@
1
+ import Link from "next/link"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+
5
+ export default function NotFound() {
6
+ return (
7
+ <div className="container py-28">
8
+ <div className="prose max-w-none">
9
+ <h1 style={{ marginBottom: 0 }}>404</h1>
10
+ <p className="mb-4">This page could not be found.</p>
11
+ </div>
12
+ <Button asChild variant="default">
13
+ <Link href="/">Go home</Link>
14
+ </Button>
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,5 @@
1
+ import PageTemplate, { generateMetadata } from "./[slug]/page"
2
+
3
+ export default PageTemplate
4
+
5
+ export { generateMetadata }
@@ -0,0 +1,190 @@
1
+ "use client"
2
+
3
+ import { Card, type CardPostData } from "@/components/Card"
4
+ import { Button } from "@/components/ui/button"
5
+ import { Input } from "@/components/ui/input"
6
+ import type { Category } from "@/payload-types"
7
+ import { useHeaderTheme } from "@/providers/HeaderTheme"
8
+ import { Search, X } from "lucide-react"
9
+ import { useEffect, useMemo, useState } from "react"
10
+
11
+ interface BlogPageClientProps {
12
+ initialPosts: CardPostData[]
13
+ categories: Category[]
14
+ totalPosts: number
15
+ }
16
+
17
+ export const BlogPageClient: React.FC<BlogPageClientProps> = ({
18
+ initialPosts,
19
+ categories,
20
+ totalPosts,
21
+ }) => {
22
+ const { setHeaderTheme } = useHeaderTheme()
23
+ const [searchQuery, setSearchQuery] = useState("")
24
+ const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
25
+
26
+ useEffect(() => {
27
+ setHeaderTheme("light")
28
+ }, [setHeaderTheme])
29
+
30
+ // Filter posts based on search query and selected category
31
+ const filteredPosts = useMemo(() => {
32
+ return initialPosts.filter((post) => {
33
+ // Search filter
34
+ const matchesSearch =
35
+ searchQuery === "" ||
36
+ post.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||
37
+ post.meta?.description?.toLowerCase().includes(searchQuery.toLowerCase())
38
+
39
+ // Category filter
40
+ const matchesCategory =
41
+ !selectedCategory ||
42
+ (Array.isArray(post.categories) &&
43
+ post.categories.some((cat) => {
44
+ if (typeof cat === "object" && cat !== null) {
45
+ return String(cat.id) === selectedCategory
46
+ }
47
+ return String(cat) === selectedCategory
48
+ }))
49
+
50
+ return matchesSearch && matchesCategory
51
+ })
52
+ }, [initialPosts, searchQuery, selectedCategory])
53
+
54
+ // Get featured post (first post when no filters)
55
+ const featuredPost = searchQuery === "" && !selectedCategory ? filteredPosts[0] : null
56
+ const regularPosts = featuredPost ? filteredPosts.slice(1) : filteredPosts
57
+
58
+ const clearFilters = () => {
59
+ setSearchQuery("")
60
+ setSelectedCategory(null)
61
+ }
62
+
63
+ const hasActiveFilters = searchQuery !== "" || selectedCategory !== null
64
+
65
+ return (
66
+ <div className="pt-24 pb-24">
67
+ {/* Hero Section */}
68
+ <div className="container mb-12">
69
+ <div className="max-w-4xl">
70
+ <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight mb-4">
71
+ Blog & Resources
72
+ </h1>
73
+ <p className="text-lg md:text-xl text-muted-foreground mb-8">
74
+ Learn how to build, grow, and monetize directory websites. Strategies, tutorials, and
75
+ success stories to help you succeed.
76
+ </p>
77
+
78
+ {/* Search Bar */}
79
+ <div className="relative max-w-xl">
80
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
81
+ <Input
82
+ type="text"
83
+ placeholder="Search articles..."
84
+ value={searchQuery}
85
+ onChange={(e) => setSearchQuery(e.target.value)}
86
+ className="pl-10 pr-10 h-12 text-base"
87
+ />
88
+ {searchQuery && (
89
+ <button
90
+ type="button"
91
+ onClick={() => setSearchQuery("")}
92
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
93
+ aria-label="Clear search"
94
+ >
95
+ <X className="h-5 w-5" aria-hidden="true" />
96
+ </button>
97
+ )}
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ {/* Category Filters */}
103
+ <div className="container mb-8">
104
+ <div className="flex flex-wrap gap-2">
105
+ <Button
106
+ variant={selectedCategory === null ? "default" : "outline"}
107
+ size="sm"
108
+ onClick={() => setSelectedCategory(null)}
109
+ >
110
+ All Posts
111
+ </Button>
112
+ {categories.map((category) => (
113
+ <Button
114
+ type="button"
115
+ key={category.id}
116
+ variant={selectedCategory === String(category.id) ? "default" : "outline"}
117
+ size="sm"
118
+ onClick={() => setSelectedCategory(String(category.id))}
119
+ >
120
+ {category.title}
121
+ </Button>
122
+ ))}
123
+ </div>
124
+ </div>
125
+
126
+ {/* Results Count & Clear Filters */}
127
+ <div className="container mb-8">
128
+ <div className="flex items-center justify-between">
129
+ <p className="text-sm text-muted-foreground">
130
+ {hasActiveFilters ? (
131
+ <>
132
+ Showing {filteredPosts.length} of {totalPosts} articles
133
+ </>
134
+ ) : (
135
+ <>{totalPosts} articles</>
136
+ )}
137
+ </p>
138
+ {hasActiveFilters && (
139
+ <Button variant="ghost" size="sm" onClick={clearFilters}>
140
+ Clear filters
141
+ </Button>
142
+ )}
143
+ </div>
144
+ </div>
145
+
146
+ {/* Featured Post */}
147
+ {featuredPost && (
148
+ <div className="container mb-12">
149
+ <div className="relative">
150
+ <span className="absolute -top-3 left-4 bg-primary text-primary-foreground text-xs font-medium px-2 py-1 rounded z-10">
151
+ Featured
152
+ </span>
153
+ <Card
154
+ doc={featuredPost}
155
+ relationTo="posts"
156
+ showCategories
157
+ className="lg:grid lg:grid-cols-2 lg:gap-8"
158
+ />
159
+ </div>
160
+ </div>
161
+ )}
162
+
163
+ {/* Posts Grid */}
164
+ <div className="container">
165
+ {regularPosts.length > 0 ? (
166
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
167
+ {regularPosts.map((post, index) => (
168
+ <Card
169
+ key={post.slug || index}
170
+ doc={post}
171
+ relationTo="posts"
172
+ showCategories
173
+ className="h-full"
174
+ />
175
+ ))}
176
+ </div>
177
+ ) : (
178
+ <div className="text-center py-16">
179
+ <p className="text-lg text-muted-foreground mb-4">
180
+ No articles found matching your criteria.
181
+ </p>
182
+ <Button variant="outline" onClick={clearFilters}>
183
+ Clear filters
184
+ </Button>
185
+ </div>
186
+ )}
187
+ </div>
188
+ </div>
189
+ )
190
+ }
@@ -0,0 +1,67 @@
1
+ "use client"
2
+
3
+ import type { HeadingItem } from "@/utilities/extractHeadings"
4
+ import type { DefaultTypedEditorState } from "@payloadcms/richtext-lexical"
5
+
6
+ import RichText from "@/components/RichText"
7
+ import { TableOfContents } from "@/components/TableOfContents"
8
+ import { slugify } from "@/utilities/extractHeadings"
9
+ import { useEffect, useRef } from "react"
10
+
11
+ interface BlogPostContentProps {
12
+ content: DefaultTypedEditorState
13
+ headings: HeadingItem[]
14
+ }
15
+
16
+ export function BlogPostContent({ content, headings }: BlogPostContentProps) {
17
+ const contentRef = useRef<HTMLDivElement>(null)
18
+
19
+ // Add IDs to headings after mount for TOC linking
20
+ useEffect(() => {
21
+ if (!contentRef.current) return
22
+
23
+ const headingElements = contentRef.current.querySelectorAll("h1, h2, h3, h4, h5, h6")
24
+ for (const heading of headingElements) {
25
+ const text = heading.textContent || ""
26
+ const id = slugify(text)
27
+ if (!heading.id) {
28
+ heading.id = id
29
+ }
30
+ }
31
+ }, [])
32
+
33
+ return (
34
+ <div className="container py-12">
35
+ <div className="flex flex-col lg:flex-row gap-8 lg:gap-12">
36
+ {/* Sticky TOC Sidebar - Hidden on mobile */}
37
+ <aside className="hidden lg:block lg:w-64 xl:w-72 flex-shrink-0">
38
+ <div className="sticky top-24">
39
+ <TableOfContents
40
+ headings={headings}
41
+ signUpCta={{
42
+ title: "Experience DirectoryHub",
43
+ description: "Start building your directory today",
44
+ buttonText: "Sign up for free",
45
+ buttonLink: "https://app.directoryhub.app/sign-up",
46
+ imageSrc: "/media/hero-dashboard-500x500.webp",
47
+ imageAlt: "DirectoryHub dashboard feature",
48
+ }}
49
+ />
50
+ </div>
51
+ </aside>
52
+
53
+ {/* Main Content */}
54
+ <main className="flex-1 min-w-0" ref={contentRef}>
55
+ {content && (
56
+ <RichText
57
+ className="max-w-none prose-headings:scroll-mt-24"
58
+ data={content}
59
+ enableGutter={false}
60
+ enableProse={true}
61
+ />
62
+ )}
63
+ </main>
64
+ </div>
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,15 @@
1
+ "use client"
2
+ import { useHeaderTheme } from "@/providers/HeaderTheme"
3
+ import React, { useEffect } from "react"
4
+
5
+ const PageClient: React.FC = () => {
6
+ /* Force the header to be dark mode while we have an image behind it */
7
+ const { setHeaderTheme } = useHeaderTheme()
8
+
9
+ useEffect(() => {
10
+ setHeaderTheme("dark")
11
+ }, [setHeaderTheme])
12
+ return <React.Fragment />
13
+ }
14
+
15
+ export default PageClient
@@ -0,0 +1,118 @@
1
+ import type { Metadata } from "next"
2
+
3
+ import { RelatedPosts } from "@/blocks/RelatedPosts/Component"
4
+ import { BlogCTA } from "@/components/BlogCTA"
5
+ import { PayloadRedirects } from "@/components/PayloadRedirects"
6
+ import configPromise from "@payload-config"
7
+ import { draftMode } from "next/headers"
8
+ import { getPayload } from "payload"
9
+ import { cache } from "react"
10
+
11
+ import { LivePreviewListener } from "@/components/LivePreviewListener"
12
+ import { PostHero } from "@/heros/PostHero"
13
+ import { extractHeadingsFromLexical } from "@/utilities/extractHeadings"
14
+ import { generateMeta } from "@/utilities/generateMeta"
15
+ import { BlogPostContent } from "./BlogPostContent"
16
+ import PageClient from "./page.client"
17
+
18
+ export async function generateStaticParams() {
19
+ const payload = await getPayload({ config: configPromise })
20
+ const posts = await payload.find({
21
+ collection: "posts",
22
+ draft: false,
23
+ limit: 1000,
24
+ overrideAccess: false,
25
+ pagination: false,
26
+ select: {
27
+ slug: true,
28
+ },
29
+ })
30
+
31
+ const params = posts.docs.map(({ slug }) => {
32
+ return { slug }
33
+ })
34
+
35
+ return params
36
+ }
37
+
38
+ type Args = {
39
+ params: Promise<{
40
+ slug?: string
41
+ }>
42
+ }
43
+
44
+ export default async function Post({ params: paramsPromise }: Args) {
45
+ const { isEnabled: draft } = await draftMode()
46
+ const { slug = "" } = await paramsPromise
47
+ // Decode to support slugs with special characters
48
+ const decodedSlug = decodeURIComponent(slug)
49
+ const url = `/posts/${decodedSlug}`
50
+ const post = await queryPostBySlug({ slug: decodedSlug })
51
+
52
+ if (!post) return <PayloadRedirects url={url} />
53
+
54
+ // Extract headings for table of contents
55
+ const headings = extractHeadingsFromLexical(post.content)
56
+
57
+ return (
58
+ <article className="pb-0">
59
+ <PageClient />
60
+
61
+ {/* Allows redirects for valid pages too */}
62
+ <PayloadRedirects disableNotFound url={url} />
63
+
64
+ {draft && <LivePreviewListener />}
65
+
66
+ {/* Hero Section */}
67
+ <PostHero post={post} />
68
+
69
+ {/* Two-column layout with TOC and content */}
70
+ <BlogPostContent content={post.content} headings={headings} />
71
+
72
+ {/* Related Posts */}
73
+ {post.relatedPosts && post.relatedPosts.length > 0 && (
74
+ <div className="container py-12 border-t border-border">
75
+ <h2 className="text-2xl font-bold mb-8">Related Articles</h2>
76
+ <RelatedPosts
77
+ className="max-w-none"
78
+ docs={post.relatedPosts.filter((post) => typeof post === "object")}
79
+ />
80
+ </div>
81
+ )}
82
+
83
+ {/* Bottom CTA */}
84
+ <BlogCTA />
85
+ </article>
86
+ )
87
+ }
88
+
89
+ export async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {
90
+ const { slug = "" } = await paramsPromise
91
+ // Decode to support slugs with special characters
92
+ const decodedSlug = decodeURIComponent(slug)
93
+ const post = await queryPostBySlug({ slug: decodedSlug })
94
+
95
+ return generateMeta({ doc: post })
96
+ }
97
+
98
+ const queryPostBySlug = cache(async ({ slug }: { slug: string }) => {
99
+ const { isEnabled: draft } = await draftMode()
100
+
101
+ const payload = await getPayload({ config: configPromise })
102
+
103
+ const result = await payload.find({
104
+ collection: "posts",
105
+ depth: 2, // Populate relationships in rich text content
106
+ draft,
107
+ limit: 1,
108
+ overrideAccess: draft,
109
+ pagination: false,
110
+ where: {
111
+ slug: {
112
+ equals: slug,
113
+ },
114
+ },
115
+ })
116
+
117
+ return result.docs?.[0] || null
118
+ })
@@ -0,0 +1,15 @@
1
+ "use client"
2
+ import { useHeaderTheme } from "@/providers/HeaderTheme"
3
+ import React, { useEffect } from "react"
4
+
5
+ const PageClient: React.FC = () => {
6
+ /* Force the header to be dark mode while we have an image behind it */
7
+ const { setHeaderTheme } = useHeaderTheme()
8
+
9
+ useEffect(() => {
10
+ setHeaderTheme("light")
11
+ }, [setHeaderTheme])
12
+ return <React.Fragment />
13
+ }
14
+
15
+ export default PageClient