kofi-stack-template-generator 2.1.37 → 2.1.39

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 (323) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/index.js +8057 -440
  3. package/package.json +1 -1
  4. package/src/templates.generated.ts +248 -94
  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 +124 -0
  74. package/templates/marketing/payload/src/blocks/FeatureGrid/config.ts +120 -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/src/components/button.tsx.hbs +53 -0
  244. package/templates/packages/ui/src/components/card.tsx.hbs +76 -0
  245. package/templates/packages/ui/src/components/separator.tsx.hbs +26 -0
  246. package/templates/marketing/payload/src/Footer/config.ts.hbs +0 -178
  247. package/templates/marketing/payload/src/Footer/index.ts.hbs +0 -1
  248. package/templates/marketing/payload/src/Header/RowLabel.tsx.hbs +0 -21
  249. package/templates/marketing/payload/src/Header/config.ts.hbs +0 -208
  250. package/templates/marketing/payload/src/Header/index.ts.hbs +0 -1
  251. package/templates/marketing/payload/src/access/index.ts.hbs +0 -3
  252. package/templates/marketing/payload/src/app/(frontend)/layout.tsx.hbs +0 -19
  253. package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts.hbs +0 -31
  254. package/templates/marketing/payload/src/app/(frontend)/page.tsx.hbs +0 -83
  255. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/not-found.tsx.hbs +0 -24
  256. package/templates/marketing/payload/src/app/(payload)/admin/[[...segments]]/page.tsx.hbs +0 -24
  257. package/templates/marketing/payload/src/app/(payload)/admin/importMap.js.hbs +0 -1
  258. package/templates/marketing/payload/src/app/(payload)/custom.scss.hbs +0 -1
  259. package/templates/marketing/payload/src/app/(payload)/layout.tsx.hbs +0 -31
  260. package/templates/marketing/payload/src/app/globals.css.hbs +0 -83
  261. package/templates/marketing/payload/src/app/layout.tsx.hbs +0 -10
  262. package/templates/marketing/payload/src/blocks/Benefits.ts.hbs +0 -34
  263. package/templates/marketing/payload/src/blocks/CTA.ts.hbs +0 -39
  264. package/templates/marketing/payload/src/blocks/Content.ts.hbs +0 -9
  265. package/templates/marketing/payload/src/blocks/FAQ.ts.hbs +0 -18
  266. package/templates/marketing/payload/src/blocks/Features.ts.hbs +0 -34
  267. package/templates/marketing/payload/src/blocks/Hero.ts.hbs +0 -40
  268. package/templates/marketing/payload/src/blocks/LogoBanner.ts.hbs +0 -17
  269. package/templates/marketing/payload/src/blocks/Pricing.ts.hbs +0 -37
  270. package/templates/marketing/payload/src/blocks/Testimonials.ts.hbs +0 -21
  271. package/templates/marketing/payload/src/blocks/index.ts.hbs +0 -9
  272. package/templates/marketing/payload/src/collections/Categories/index.ts.hbs +0 -28
  273. package/templates/marketing/payload/src/collections/FAQs/index.ts.hbs +0 -100
  274. package/templates/marketing/payload/src/collections/Media.ts.hbs +0 -164
  275. package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts.hbs +0 -43
  276. package/templates/marketing/payload/src/collections/Pages/index.ts.hbs +0 -142
  277. package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts.hbs +0 -41
  278. package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts.hbs +0 -44
  279. package/templates/marketing/payload/src/collections/Posts/index.ts.hbs +0 -244
  280. package/templates/marketing/payload/src/collections/Users/index.ts.hbs +0 -26
  281. package/templates/marketing/payload/src/collections/index.ts.hbs +0 -6
  282. package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx.hbs +0 -89
  283. package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx.hbs +0 -69
  284. package/templates/marketing/payload/src/components/BeforeLogin/index.tsx.hbs +0 -14
  285. package/templates/marketing/payload/src/components/Link/index.tsx.hbs +0 -79
  286. package/templates/marketing/payload/src/components/Media/index.tsx.hbs +0 -67
  287. package/templates/marketing/payload/src/components/RichText/index.tsx.hbs +0 -44
  288. package/templates/marketing/payload/src/endpoints/seed/home.ts.hbs +0 -76
  289. package/templates/marketing/payload/src/endpoints/seed/image-1.ts.hbs +0 -5
  290. package/templates/marketing/payload/src/endpoints/seed/image-2.ts.hbs +0 -5
  291. package/templates/marketing/payload/src/endpoints/seed/image-hero.ts.hbs +0 -5
  292. package/templates/marketing/payload/src/endpoints/seed/index.ts.hbs +0 -235
  293. package/templates/marketing/payload/src/endpoints/seed/post-1.ts.hbs +0 -252
  294. package/templates/marketing/payload/src/fields/defaultLexical.ts.hbs +0 -73
  295. package/templates/marketing/payload/src/fields/link.ts.hbs +0 -139
  296. package/templates/marketing/payload/src/fields/linkGroup.ts.hbs +0 -28
  297. package/templates/marketing/payload/src/globals/index.ts.hbs +0 -2
  298. package/templates/marketing/payload/src/heros/HighImpact/index.tsx.hbs +0 -53
  299. package/templates/marketing/payload/src/heros/LowImpact/index.tsx.hbs +0 -48
  300. package/templates/marketing/payload/src/heros/MediumImpact/index.tsx.hbs +0 -46
  301. package/templates/marketing/payload/src/heros/PostHero/index.tsx.hbs +0 -68
  302. package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx.hbs +0 -88
  303. package/templates/marketing/payload/src/heros/config.ts.hbs +0 -112
  304. package/templates/marketing/payload/src/heros/index.ts.hbs +0 -7
  305. package/templates/marketing/payload/src/hooks/index.ts.hbs +0 -2
  306. package/templates/marketing/payload/src/hooks/populatePublishedAt.ts.hbs +0 -15
  307. package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx.hbs +0 -34
  308. package/templates/marketing/payload/src/providers/Theme/index.tsx.hbs +0 -60
  309. package/templates/marketing/payload/src/providers/Theme/shared.ts.hbs +0 -17
  310. package/templates/marketing/payload/src/providers/index.tsx.hbs +0 -18
  311. package/templates/marketing/payload/src/utilities/deepMerge.ts.hbs +0 -35
  312. package/templates/marketing/payload/src/utilities/formatAuthors.ts.hbs +0 -24
  313. package/templates/marketing/payload/src/utilities/formatDateTime.ts.hbs +0 -13
  314. package/templates/marketing/payload/src/utilities/generateMeta.ts.hbs +0 -87
  315. package/templates/marketing/payload/src/utilities/generatePreviewPath.ts.hbs +0 -33
  316. package/templates/marketing/payload/src/utilities/getURL.ts.hbs +0 -26
  317. package/templates/marketing/payload/src/utilities/index.ts.hbs +0 -8
  318. package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts.hbs +0 -26
  319. /package/templates/marketing/payload/src/access/{anyone.ts.hbs → anyone.ts} +0 -0
  320. /package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/{index.scss.hbs → index.scss} +0 -0
  321. /package/templates/marketing/payload/src/components/BeforeDashboard/{index.scss.hbs → index.scss} +0 -0
  322. /package/templates/marketing/payload/src/fields/{index.ts.hbs → index.ts} +0 -0
  323. /package/templates/marketing/payload/src/utilities/{canUseDOM.ts.hbs → canUseDOM.ts} +0 -0
@@ -0,0 +1,250 @@
1
+ "use client"
2
+
3
+ import { Check, X } from "lucide-react"
4
+ import React from "react"
5
+
6
+ import { cn } from "@/utilities/ui"
7
+
8
+ /**
9
+ * Feature type from Stripe Entitlements
10
+ */
11
+ interface PlanFeature {
12
+ id: string
13
+ lookupKey: string
14
+ name: string
15
+ metadata?: Record<string, string>
16
+ }
17
+
18
+ /**
19
+ * All features type for comparison table
20
+ */
21
+ interface AllFeature {
22
+ id: string
23
+ lookupKey: string
24
+ name: string
25
+ category?: string
26
+ metadata?: Record<string, string>
27
+ }
28
+
29
+ /**
30
+ * Plan type for comparison
31
+ */
32
+ interface Plan {
33
+ planId: string
34
+ name: string
35
+ features: PlanFeature[]
36
+ popular?: boolean
37
+ }
38
+
39
+ /**
40
+ * Props for the comparison table
41
+ */
42
+ interface PricingComparisonTableProps {
43
+ plans: Plan[]
44
+ allFeatures: AllFeature[]
45
+ className?: string
46
+ }
47
+
48
+ /**
49
+ * Feature category labels for display
50
+ */
51
+ const CATEGORY_LABELS: Record<string, string> = {
52
+ limits: "Directories & Listings",
53
+ templates: "Templates",
54
+ core: "Core Features",
55
+ infrastructure: "Infrastructure",
56
+ content: "Content & Customization",
57
+ branding: "Branding",
58
+ monetization: "Monetization",
59
+ developer: "Developer Tools",
60
+ analytics: "Analytics",
61
+ collaboration: "Team & Collaboration",
62
+ support: "Support",
63
+ }
64
+
65
+ /**
66
+ * Category display order
67
+ */
68
+ const CATEGORY_ORDER = [
69
+ "limits",
70
+ "templates",
71
+ "core",
72
+ "infrastructure",
73
+ "content",
74
+ "branding",
75
+ "monetization",
76
+ "developer",
77
+ "analytics",
78
+ "collaboration",
79
+ "support",
80
+ ]
81
+
82
+ /**
83
+ * Get the feature value to display (e.g., "5" for directory limit)
84
+ */
85
+ function getFeatureValue(plan: Plan, feature: AllFeature): string | boolean {
86
+ const planFeature = plan.features.find((f) => f.lookupKey === feature.lookupKey)
87
+ if (!planFeature) return false
88
+
89
+ // Check for limit metadata
90
+ const limit = planFeature.metadata?.limit || feature.metadata?.limit
91
+ if (limit) {
92
+ if (limit === "-1") return "Unlimited"
93
+ return limit
94
+ }
95
+
96
+ return true
97
+ }
98
+
99
+ /**
100
+ * Group features by category
101
+ */
102
+ function groupFeaturesByCategory(features: AllFeature[]): Map<string, AllFeature[]> {
103
+ const grouped = new Map<string, AllFeature[]>()
104
+
105
+ for (const feature of features) {
106
+ const category = feature.category || "other"
107
+ const existing = grouped.get(category) || []
108
+ existing.push(feature)
109
+ grouped.set(category, existing)
110
+ }
111
+
112
+ return grouped
113
+ }
114
+
115
+ /**
116
+ * Feature cell component
117
+ */
118
+ function FeatureCell({
119
+ value,
120
+ isPopular,
121
+ }: {
122
+ value: string | boolean
123
+ isPopular?: boolean
124
+ }) {
125
+ if (value === false) {
126
+ return (
127
+ <td className={cn("px-4 py-3 text-center", isPopular && "bg-primary/5")}>
128
+ <X className="mx-auto size-5 text-muted-foreground/40" />
129
+ </td>
130
+ )
131
+ }
132
+
133
+ if (value === true) {
134
+ return (
135
+ <td className={cn("px-4 py-3 text-center", isPopular && "bg-primary/5")}>
136
+ <Check className="mx-auto size-5 text-green-500" />
137
+ </td>
138
+ )
139
+ }
140
+
141
+ // String value (e.g., "5" or "Unlimited")
142
+ return (
143
+ <td className={cn("px-4 py-3 text-center font-medium", isPopular && "bg-primary/5")}>
144
+ {value}
145
+ </td>
146
+ )
147
+ }
148
+
149
+ /**
150
+ * Pricing comparison table component
151
+ * Shows a grid of features (rows) vs plans (columns)
152
+ */
153
+ export const PricingComparisonTable: React.FC<PricingComparisonTableProps> = ({
154
+ plans,
155
+ allFeatures,
156
+ className,
157
+ }) => {
158
+ if (!plans.length || !allFeatures.length) {
159
+ return null
160
+ }
161
+
162
+ // Find the index of the first plan marked as popular (only show badge on ONE plan)
163
+ const popularPlanIndex = plans.findIndex((p) => p.popular)
164
+
165
+ // Group features by category
166
+ const groupedFeatures = groupFeaturesByCategory(allFeatures)
167
+
168
+ // Get sorted categories
169
+ const sortedCategories = [...groupedFeatures.keys()].sort((a, b) => {
170
+ const aIndex = CATEGORY_ORDER.indexOf(a)
171
+ const bIndex = CATEGORY_ORDER.indexOf(b)
172
+ if (aIndex === -1 && bIndex === -1) return a.localeCompare(b)
173
+ if (aIndex === -1) return 1
174
+ if (bIndex === -1) return -1
175
+ return aIndex - bIndex
176
+ })
177
+
178
+ return (
179
+ <div className={cn("mt-16", className)}>
180
+ <h3 className="text-2xl font-bold text-center mb-8">Full Feature Comparison</h3>
181
+
182
+ <div className="overflow-x-auto">
183
+ <table className="w-full border-collapse">
184
+ {/* Header with plan names */}
185
+ <thead>
186
+ <tr className="border-b">
187
+ <th className="py-4 px-4 text-left font-semibold">Features</th>
188
+ {plans.map((plan, index) => {
189
+ const isPopular = index === popularPlanIndex
190
+ return (
191
+ <th
192
+ key={plan.planId}
193
+ className={cn(
194
+ "py-4 px-4 text-center font-semibold min-w-[120px]",
195
+ isPopular && "bg-primary/5",
196
+ )}
197
+ >
198
+ <span className="block">{plan.name}</span>
199
+ {isPopular && (
200
+ <span className="inline-block mt-1 px-2 py-0.5 text-xs font-medium bg-primary text-primary-foreground rounded-full">
201
+ Popular
202
+ </span>
203
+ )}
204
+ </th>
205
+ )
206
+ })}
207
+ </tr>
208
+ </thead>
209
+
210
+ <tbody>
211
+ {sortedCategories.map((category) => {
212
+ const categoryFeatures = groupedFeatures.get(category) || []
213
+ const categoryLabel = CATEGORY_LABELS[category] || category
214
+
215
+ return (
216
+ <React.Fragment key={`category-${category}`}>
217
+ {/* Category header row */}
218
+ <tr className="bg-muted/50">
219
+ <td
220
+ colSpan={plans.length + 1}
221
+ className="py-3 px-4 font-semibold text-sm uppercase tracking-wide text-muted-foreground"
222
+ >
223
+ {categoryLabel}
224
+ </td>
225
+ </tr>
226
+
227
+ {/* Feature rows */}
228
+ {categoryFeatures.map((feature) => (
229
+ <tr key={feature.id} className="border-b border-border/50 hover:bg-muted/30">
230
+ <td className="py-3 px-4 text-sm">{feature.name}</td>
231
+ {plans.map((plan, index) => (
232
+ <FeatureCell
233
+ key={`${plan.planId}-${feature.id}`}
234
+ value={getFeatureValue(plan, feature)}
235
+ isPopular={index === popularPlanIndex}
236
+ />
237
+ ))}
238
+ </tr>
239
+ ))}
240
+ </React.Fragment>
241
+ )
242
+ })}
243
+ </tbody>
244
+ </table>
245
+ </div>
246
+ </div>
247
+ )
248
+ }
249
+
250
+ export default PricingComparisonTable
@@ -0,0 +1,443 @@
1
+ "use client"
2
+
3
+ import { Check, Loader2, X } from "lucide-react"
4
+ import Link from "next/link"
5
+ import type React from "react"
6
+ import { useEffect, useState } from "react"
7
+
8
+ import type { PricingTableBlock as PricingTableBlockProps } from "@/payload-types"
9
+
10
+ import { CTATracker, PricingViewTracker } from "@/components/Analytics"
11
+ import { CMSLink } from "@/components/Link"
12
+ import { cn } from "@/utilities/ui"
13
+
14
+ import { PricingComparisonTable } from "./ComparisonTable"
15
+
16
+ type BillingPeriod = "monthly" | "annual"
17
+
18
+ /**
19
+ * Feature type from Stripe Entitlements
20
+ */
21
+ interface PlanFeature {
22
+ id: string
23
+ lookupKey: string
24
+ name: string
25
+ metadata?: Record<string, string>
26
+ }
27
+
28
+ /**
29
+ * All features type for comparison table
30
+ */
31
+ interface AllFeature {
32
+ id: string
33
+ lookupKey: string
34
+ name: string
35
+ category?: string
36
+ metadata?: Record<string, string>
37
+ }
38
+
39
+ /**
40
+ * Dynamic plan type with features from Stripe
41
+ */
42
+ interface DynamicPlan {
43
+ planId: string
44
+ stripeProductId: string
45
+ name: string
46
+ description: string
47
+ features: PlanFeature[]
48
+ monthlyAmount: number
49
+ annualAmount: number
50
+ annualDiscount: number
51
+ popular: boolean
52
+ order: number
53
+ }
54
+
55
+ interface PricingApiResponse {
56
+ plans: DynamicPlan[]
57
+ allFeatures: AllFeature[]
58
+ lastFetched: number
59
+ error?: string
60
+ }
61
+
62
+ /**
63
+ * Format price in cents to display string
64
+ */
65
+ function formatPrice(cents: number): string {
66
+ const dollars = cents / 100
67
+ if (dollars === 0) return "$0"
68
+ if (dollars % 1 === 0) {
69
+ return `$${dollars.toLocaleString()}`
70
+ }
71
+ return dollars.toLocaleString("en-US", {
72
+ style: "currency",
73
+ currency: "USD",
74
+ minimumFractionDigits: 0,
75
+ maximumFractionDigits: 2,
76
+ })
77
+ }
78
+
79
+ /**
80
+ * Billing period toggle component
81
+ */
82
+ function BillingToggle({
83
+ billingPeriod,
84
+ onBillingPeriodChange,
85
+ discountPercentage,
86
+ }: {
87
+ billingPeriod: BillingPeriod
88
+ onBillingPeriodChange: (period: BillingPeriod) => void
89
+ discountPercentage?: number
90
+ }) {
91
+ const isAnnual = billingPeriod === "annual"
92
+
93
+ return (
94
+ <div className="flex items-center justify-center gap-3 mb-12">
95
+ <button
96
+ type="button"
97
+ onClick={() => onBillingPeriodChange("monthly")}
98
+ className={cn(
99
+ "text-sm font-medium transition-colors",
100
+ !isAnnual ? "text-foreground" : "text-muted-foreground hover:text-foreground",
101
+ )}
102
+ >
103
+ Monthly
104
+ </button>
105
+
106
+ {/* Toggle Switch */}
107
+ <button
108
+ type="button"
109
+ role="switch"
110
+ aria-checked={isAnnual}
111
+ aria-label={`Switch to ${isAnnual ? "monthly" : "annual"} billing`}
112
+ onClick={() => onBillingPeriodChange(isAnnual ? "monthly" : "annual")}
113
+ className={cn(
114
+ "relative h-7 w-14 rounded-full border-2 border-primary transition-colors",
115
+ isAnnual ? "bg-primary" : "bg-muted",
116
+ )}
117
+ >
118
+ <div
119
+ className={cn(
120
+ "absolute top-0.5 size-5 rounded-full shadow-sm transition-all duration-200",
121
+ isAnnual ? "left-[calc(100%-24px)] bg-white" : "left-0.5 bg-primary",
122
+ )}
123
+ />
124
+ </button>
125
+
126
+ <div className="flex items-center gap-2">
127
+ <button
128
+ type="button"
129
+ onClick={() => onBillingPeriodChange("annual")}
130
+ className={cn(
131
+ "text-sm font-medium transition-colors",
132
+ isAnnual ? "text-foreground" : "text-muted-foreground hover:text-foreground",
133
+ )}
134
+ >
135
+ Annual
136
+ </button>
137
+
138
+ {discountPercentage && discountPercentage > 0 && (
139
+ <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400">
140
+ Save {discountPercentage}%
141
+ </span>
142
+ )}
143
+ </div>
144
+ </div>
145
+ )
146
+ }
147
+
148
+ /**
149
+ * Dynamic pricing card component with Stripe features
150
+ */
151
+ function DynamicPricingCard({
152
+ plan,
153
+ billingPeriod,
154
+ ctaLink,
155
+ isPopular,
156
+ maxFeatures = 4,
157
+ }: {
158
+ plan: DynamicPlan
159
+ billingPeriod: BillingPeriod
160
+ ctaLink?: NonNullable<PricingTableBlockProps["plans"]>[number]["link"]
161
+ isPopular: boolean
162
+ maxFeatures?: number
163
+ }) {
164
+ const isAnnual = billingPeriod === "annual"
165
+ const price = isAnnual ? plan.annualAmount : plan.monthlyAmount
166
+ const monthlyEquivalent = isAnnual ? Math.round(plan.annualAmount / 12) : plan.monthlyAmount
167
+
168
+ // Get CTA label based on plan
169
+ const ctaLabel = plan.planId === "free" ? "Get Started Free" : `Get ${plan.name}`
170
+
171
+ // Limit features shown on card
172
+ const displayFeatures = plan.features.slice(0, maxFeatures)
173
+ const hasMoreFeatures = plan.features.length > maxFeatures
174
+
175
+ return (
176
+ <div
177
+ className={cn(
178
+ "relative flex flex-col p-8 rounded-xl border transition-all",
179
+ isPopular ? "border-primary bg-primary/5 shadow-lg scale-105" : "border-border bg-card",
180
+ )}
181
+ >
182
+ {isPopular && (
183
+ <div className="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-primary text-primary-foreground text-sm font-medium rounded-full">
184
+ Most Popular
185
+ </div>
186
+ )}
187
+
188
+ <div className="mb-6">
189
+ <h3 className="text-xl font-semibold mb-2">{plan.name}</h3>
190
+ <div className="flex items-baseline gap-1">
191
+ <span className="text-4xl font-bold">{formatPrice(monthlyEquivalent)}</span>
192
+ <span className="text-muted-foreground">/month</span>
193
+ </div>
194
+ {isAnnual && plan.monthlyAmount > 0 && (
195
+ <p className="text-sm text-muted-foreground mt-1">{formatPrice(price)} billed annually</p>
196
+ )}
197
+ {isAnnual && plan.annualDiscount > 0 && (
198
+ <span className="inline-flex items-center mt-2 px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400">
199
+ Save {plan.annualDiscount}%
200
+ </span>
201
+ )}
202
+ <p className="text-muted-foreground text-sm mt-2">{plan.description}</p>
203
+ </div>
204
+
205
+ <ul className="space-y-3 mb-8 flex-grow">
206
+ {displayFeatures.map((feature) => (
207
+ <li key={feature.id} className="flex items-center gap-3">
208
+ <Check className="w-5 h-5 text-green-500 flex-shrink-0" />
209
+ <span className="text-sm">{feature.name}</span>
210
+ </li>
211
+ ))}
212
+ {hasMoreFeatures && (
213
+ <li className="text-sm text-muted-foreground">
214
+ +{plan.features.length - maxFeatures} more features
215
+ </li>
216
+ )}
217
+ </ul>
218
+
219
+ <CTATracker location="pricing" variant={plan.name}>
220
+ {ctaLink ? (
221
+ <CMSLink
222
+ {...ctaLink}
223
+ label={ctaLabel}
224
+ className="w-full"
225
+ appearance={isPopular ? "default" : "outline"}
226
+ />
227
+ ) : (
228
+ <Link
229
+ href="/sign-up"
230
+ className={cn(
231
+ "inline-flex items-center justify-center w-full px-4 py-2 rounded-md text-sm font-medium transition-colors",
232
+ isPopular
233
+ ? "bg-primary text-primary-foreground hover:bg-primary/90"
234
+ : "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
235
+ )}
236
+ >
237
+ {ctaLabel}
238
+ </Link>
239
+ )}
240
+ </CTATracker>
241
+ </div>
242
+ )
243
+ }
244
+
245
+ export const PricingTableBlock: React.FC<PricingTableBlockProps> = ({
246
+ heading,
247
+ subheading,
248
+ useDynamicPricing = true,
249
+ showComparisonTable = true,
250
+ showViewAllLink = false,
251
+ maxFeaturesOnCard = 4,
252
+ plans: cmsPlans,
253
+ }) => {
254
+ const [dynamicPlans, setDynamicPlans] = useState<DynamicPlan[] | null>(null)
255
+ const [allFeatures, setAllFeatures] = useState<AllFeature[]>([])
256
+ const [isLoading, setIsLoading] = useState(useDynamicPricing)
257
+ const [billingPeriod, setBillingPeriod] = useState<BillingPeriod>("monthly")
258
+
259
+ // Fetch dynamic pricing from API (only if enabled)
260
+ useEffect(() => {
261
+ if (!useDynamicPricing) {
262
+ setIsLoading(false)
263
+ return
264
+ }
265
+
266
+ const fetchPricing = async () => {
267
+ try {
268
+ const response = await fetch("/api/pricing")
269
+ if (response.ok) {
270
+ const data: PricingApiResponse = await response.json()
271
+ if (data.plans && data.plans.length > 0) {
272
+ setDynamicPlans(data.plans)
273
+ setAllFeatures(data.allFeatures || [])
274
+ }
275
+ }
276
+ } catch (error) {
277
+ console.error("Failed to fetch pricing:", error)
278
+ // Fall back to CMS plans
279
+ } finally {
280
+ setIsLoading(false)
281
+ }
282
+ }
283
+
284
+ fetchPricing()
285
+ }, [useDynamicPricing])
286
+
287
+ // Calculate max discount for display
288
+ const maxDiscount = dynamicPlans ? Math.max(...dynamicPlans.map((p) => p.annualDiscount)) : 0
289
+
290
+ // Map CMS plan links to dynamic plans
291
+ const getCmsLinkForPlan = (planName: string) => {
292
+ const cmsPlan = cmsPlans?.find((p) => p.name?.toLowerCase() === planName.toLowerCase())
293
+ return cmsPlan?.link
294
+ }
295
+
296
+ // Find the index of the first plan marked as popular (only show badge on ONE plan)
297
+ const popularPlanIndex = dynamicPlans?.findIndex((p) => p.popular) ?? -1
298
+
299
+ return (
300
+ <PricingViewTracker>
301
+ <section className="py-20 md:py-28">
302
+ <div className="container">
303
+ {(heading || subheading) && (
304
+ <div className="text-center mb-12 max-w-3xl mx-auto">
305
+ {heading && <h2 className="text-3xl md:text-4xl font-bold mb-4">{heading}</h2>}
306
+ {subheading && <p className="text-lg text-muted-foreground">{subheading}</p>}
307
+ </div>
308
+ )}
309
+
310
+ {/* Loading State */}
311
+ {isLoading && (
312
+ <div className="flex justify-center py-12">
313
+ <Loader2 className="size-8 animate-spin text-muted-foreground" />
314
+ </div>
315
+ )}
316
+
317
+ {/* Dynamic Pricing with Toggle */}
318
+ {!isLoading && dynamicPlans && dynamicPlans.length > 0 && (
319
+ <>
320
+ <BillingToggle
321
+ billingPeriod={billingPeriod}
322
+ onBillingPeriodChange={setBillingPeriod}
323
+ discountPercentage={maxDiscount > 0 ? maxDiscount : undefined}
324
+ />
325
+
326
+ <div
327
+ className={cn(
328
+ "grid gap-8 max-w-5xl mx-auto",
329
+ dynamicPlans.length === 1 && "max-w-md",
330
+ dynamicPlans.length === 2 && "md:grid-cols-2 max-w-3xl",
331
+ dynamicPlans.length >= 3 && "md:grid-cols-2 lg:grid-cols-3",
332
+ )}
333
+ >
334
+ {dynamicPlans.map((plan, index) => (
335
+ <DynamicPricingCard
336
+ key={plan.planId}
337
+ plan={plan}
338
+ billingPeriod={billingPeriod}
339
+ ctaLink={getCmsLinkForPlan(plan.name)}
340
+ isPopular={index === popularPlanIndex}
341
+ maxFeatures={maxFeaturesOnCard ?? 4}
342
+ />
343
+ ))}
344
+ </div>
345
+
346
+ {/* View Full Comparison Link (for home page) */}
347
+ {showViewAllLink && !showComparisonTable && (
348
+ <div className="text-center mt-8">
349
+ <Link
350
+ href="/pricing"
351
+ className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
352
+ >
353
+ View full feature comparison
354
+ <span aria-hidden="true">→</span>
355
+ </Link>
356
+ </div>
357
+ )}
358
+
359
+ {/* Comparison Table */}
360
+ {showComparisonTable && allFeatures.length > 0 && (
361
+ <PricingComparisonTable
362
+ plans={dynamicPlans}
363
+ allFeatures={allFeatures}
364
+ className="max-w-5xl mx-auto"
365
+ />
366
+ )}
367
+ </>
368
+ )}
369
+
370
+ {/* Fallback to CMS Plans (if dynamic fetch fails) */}
371
+ {!isLoading && !dynamicPlans && Array.isArray(cmsPlans) && cmsPlans.length > 0 && (
372
+ <div
373
+ className={cn(
374
+ "grid gap-8 max-w-5xl mx-auto",
375
+ cmsPlans.length === 1 && "max-w-md",
376
+ cmsPlans.length === 2 && "md:grid-cols-2 max-w-3xl",
377
+ cmsPlans.length >= 3 && "md:grid-cols-2 lg:grid-cols-3",
378
+ )}
379
+ >
380
+ {cmsPlans.map((plan, index) => (
381
+ <div
382
+ key={plan.name || `plan-${index}`}
383
+ className={cn(
384
+ "relative flex flex-col p-8 rounded-xl border",
385
+ plan.featured
386
+ ? "border-primary bg-primary/5 shadow-lg scale-105"
387
+ : "border-border bg-card",
388
+ )}
389
+ >
390
+ {plan.featured && (
391
+ <div className="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-primary text-primary-foreground text-sm font-medium rounded-full">
392
+ Most Popular
393
+ </div>
394
+ )}
395
+
396
+ <div className="mb-6">
397
+ {plan.name && <h3 className="text-xl font-semibold mb-2">{plan.name}</h3>}
398
+ {plan.price && <div className="text-4xl font-bold mb-2">{plan.price}</div>}
399
+ {plan.description && (
400
+ <p className="text-muted-foreground text-sm">{plan.description}</p>
401
+ )}
402
+ </div>
403
+
404
+ {Array.isArray(plan.features) && plan.features.length > 0 && (
405
+ <ul className="space-y-3 mb-8 flex-grow">
406
+ {plan.features.map((item) => (
407
+ <li key={item.feature} className="flex items-center gap-3">
408
+ {item.included ? (
409
+ <Check className="w-5 h-5 text-green-500 flex-shrink-0" />
410
+ ) : (
411
+ <X className="w-5 h-5 text-muted-foreground flex-shrink-0" />
412
+ )}
413
+ <span
414
+ className={cn(
415
+ "text-sm",
416
+ !item.included && "text-muted-foreground line-through",
417
+ )}
418
+ >
419
+ {item.feature}
420
+ </span>
421
+ </li>
422
+ ))}
423
+ </ul>
424
+ )}
425
+
426
+ {plan.link && (
427
+ <CTATracker location="pricing" variant={plan.name || `plan_${index}`}>
428
+ <CMSLink
429
+ {...plan.link}
430
+ className="w-full"
431
+ appearance={plan.featured ? "default" : "outline"}
432
+ />
433
+ </CTATracker>
434
+ )}
435
+ </div>
436
+ ))}
437
+ </div>
438
+ )}
439
+ </div>
440
+ </section>
441
+ </PricingViewTracker>
442
+ )
443
+ }