create-nextblock 0.2.78 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +182 -154
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -120
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -1,16 +1,60 @@
1
1
  // components/blocks/renderers/SectionBlockRenderer.tsx
2
2
  import React from "react";
3
+ import Image from "next/image";
3
4
  import type { SectionBlockContent } from "../../../lib/blocks/blockRegistry";
4
- import { getBlockDefinition } from "../../../lib/blocks/blockRegistry";
5
- import dynamic from "next/dynamic";
5
+ import { buildVisualEditAttributes } from "../../../lib/visual-editing/edit-info";
6
+ import type {
7
+ VisualEditAttributes,
8
+ VisualEditingDocumentContext,
9
+ } from "../../../lib/visual-editing/types";
10
+ import { getPublicBlockRendererLoader } from "../publicRendererLoaders";
11
+ import SectionSlider from "./SectionSlider";
12
+ import { getCachedCustomBlockDefinitionBySlug } from "../../../lib/custom-block-definitions";
13
+ import { CachedDynamicLayoutEngine } from "../../renderers/CachedDynamicLayoutEngine";
14
+ import { resolveBlockRelations } from "../../../lib/resolve-block-relations";
15
+
16
+ // Static imports for core block renderers for LCP/performance optimization
17
+ import TextBlockRenderer from "./TextBlockRenderer";
18
+ import HeadingBlockRenderer from "./HeadingBlockRenderer";
19
+ import ImageBlockRenderer from "./ImageBlockRenderer";
20
+ import ButtonBlockRenderer from "./ButtonBlockRenderer";
6
21
 
7
22
  const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
23
+ const BACKGROUND_COMPOSITING_CLASSES =
24
+ "isolate transform-gpu [backface-visibility:hidden] [transform-style:preserve-3d]";
25
+ const ABSOLUTE_BACKGROUND_CLASSES =
26
+ "pointer-events-none absolute inset-[-1px] -z-10 transform-gpu [backface-visibility:hidden] [transform-style:preserve-3d]";
27
+ const ECOMMERCE_BLOCK_TYPES = new Set([
28
+ "product_grid",
29
+ "featured_product",
30
+ "cart",
31
+ "checkout",
32
+ "product_details",
33
+ ]);
34
+
35
+ function loadEcommerceBlockRenderer(blockType: string) {
36
+ return import("../ecommerceRendererLoaders").then((module) =>
37
+ module.loadEcommerceBlockRenderer(blockType)
38
+ );
39
+ }
8
40
 
9
41
  interface SectionBlockRendererProps {
10
42
  content: SectionBlockContent;
11
43
  languageId: number;
44
+ visualEditAttributes?: VisualEditAttributes;
45
+ visualEditing?: VisualEditingDocumentContext;
46
+ parentBlockId?: number;
47
+ parentBlockIndex?: number;
48
+ blockType?: "section";
49
+ botProtectionPublic?: BotProtectionPublicSettings;
50
+ scriptNonce?: string;
12
51
  }
13
52
 
53
+ type BotProtectionPublicSettings = {
54
+ provider: 'none' | 'turnstile' | 'recaptcha';
55
+ siteKey: string;
56
+ };
57
+
14
58
  // Container class mapping
15
59
  const containerClasses = {
16
60
  'full-width': 'w-full',
@@ -54,7 +98,38 @@ const verticalAlignmentClasses = {
54
98
  stretch: 'items-stretch'
55
99
  };
56
100
 
57
- // Background style generator
101
+ function formatMinHeight(value?: string) {
102
+ if (!value) return undefined;
103
+ const trimmed = value.trim();
104
+ if (/^\d+$/.test(trimmed)) {
105
+ return `${trimmed}px`;
106
+ }
107
+ return trimmed;
108
+ }
109
+
110
+ function formatStopPosition(position: number) {
111
+ return Number.isInteger(position) ? String(position) : position.toFixed(1);
112
+ }
113
+
114
+ function formatGradientStops(stops: Array<{ color: string; position: number }>) {
115
+ return stops
116
+ .map((stop, index) => {
117
+ const previous = stops[index - 1];
118
+ const next = stops[index + 1];
119
+ let position = stop.position;
120
+
121
+ if (next && next.position === stop.position && stop.position > 0) {
122
+ position = stop.position - 0.1;
123
+ } else if (previous && previous.position === stop.position && stop.position < 100) {
124
+ position = stop.position + 0.1;
125
+ }
126
+
127
+ return `${stop.color} ${formatStopPosition(position)}%`;
128
+ })
129
+ .join(', ');
130
+ }
131
+
132
+ // Background style generator (handles solid, theme and gradients; image handled in JSX)
58
133
  function generateBackgroundStyles(background: SectionBlockContent['background']) {
59
134
  const styles: React.CSSProperties = {};
60
135
  let className = '';
@@ -80,63 +155,155 @@ function generateBackgroundStyles(background: SectionBlockContent['background'])
80
155
  case 'gradient':
81
156
  if (background.gradient) {
82
157
  const { type, direction, stops } = background.gradient;
83
- const gradientStops = stops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
158
+ const gradientStops = formatGradientStops(stops);
84
159
  styles.background = `${type}-gradient(${direction || 'to right'}, ${gradientStops})`;
85
160
  }
86
161
  break;
87
162
 
88
163
  case 'image':
89
- if (background.image) {
90
- const imageUrl = `${R2_BASE_URL}/${background.image.object_key}`;
91
- styles.backgroundSize = background.image.size || 'cover';
92
- styles.backgroundPosition = background.image.position || 'center';
93
-
94
- let finalBackgroundImage = `url(${imageUrl})`;
95
-
96
- if (background.image.overlay && background.image.overlay.gradient) {
97
- const { type, direction, stops } = background.image.overlay.gradient;
98
- const gradientStops = stops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
99
- const gradient = `${type}-gradient(${direction || 'to right'}, ${gradientStops})`;
100
- finalBackgroundImage = `${gradient}, ${finalBackgroundImage}`;
101
- }
102
-
103
- styles.backgroundImage = finalBackgroundImage;
104
- }
164
+ // Handled via absolute next/image in the render tree for LCP & sizing optimization
105
165
  break;
106
166
 
107
167
  default:
108
- // No background
109
168
  break;
110
169
  }
111
170
 
112
171
  return { styles, className };
113
172
  }
114
173
 
115
- // Dynamic block renderer component
116
- const DynamicNestedBlockRenderer: React.FC<{
174
+ function generateGradientOverlayStyle(gradient: any): React.CSSProperties {
175
+ const { type, direction, stops } = gradient;
176
+ const gradientStops = formatGradientStops(stops);
177
+ return {
178
+ background: `${type}-gradient(${direction || 'to right'}, ${gradientStops})`,
179
+ };
180
+ }
181
+
182
+ interface NestedBlockRendererProps {
117
183
  block: SectionBlockContent['column_blocks'][0][0];
118
184
  languageId: number;
119
- }> = ({ block, languageId }) => {
120
- const blockDefinition = getBlockDefinition(block.block_type);
121
-
122
- if (!blockDefinition) {
185
+ parentBlockId?: number;
186
+ parentBlockIndex?: number;
187
+ visualEditing?: VisualEditingDocumentContext;
188
+ visualEditAttributes?: VisualEditAttributes;
189
+ botProtectionPublic?: BotProtectionPublicSettings;
190
+ scriptNonce?: string;
191
+ priority?: boolean;
192
+ }
193
+
194
+ async function renderNestedBlock({
195
+ block,
196
+ languageId,
197
+ parentBlockId,
198
+ parentBlockIndex,
199
+ visualEditing,
200
+ visualEditAttributes,
201
+ botProtectionPublic,
202
+ scriptNonce,
203
+ priority = false,
204
+ }: NestedBlockRendererProps) {
205
+ // Statically resolve core block types first to avoid dynamic imports overhead
206
+ if (block.block_type === 'text') {
207
+ return (
208
+ <TextBlockRenderer
209
+ content={block.content as any}
210
+ languageId={languageId}
211
+ visualEditAttributes={visualEditAttributes}
212
+ renderContext="section"
213
+ />
214
+ );
215
+ }
216
+
217
+ if (block.block_type === 'heading') {
218
+ return (
219
+ <HeadingBlockRenderer
220
+ content={block.content as any}
221
+ languageId={languageId}
222
+ visualEditAttributes={visualEditAttributes}
223
+ />
224
+ );
225
+ }
226
+
227
+ if (block.block_type === 'button') {
228
+ return (
229
+ <ButtonBlockRenderer
230
+ content={block.content as any}
231
+ languageId={languageId}
232
+ visualEditAttributes={visualEditAttributes}
233
+ />
234
+ );
235
+ }
236
+
237
+ if (block.block_type === 'image') {
238
+ return (
239
+ <ImageBlockRenderer
240
+ content={block.content as any}
241
+ languageId={languageId}
242
+ priority={priority}
243
+ visualEditAttributes={visualEditAttributes}
244
+ />
245
+ );
246
+ }
247
+
248
+ // Fallback to dynamic loader for custom/other block types
249
+ const rendererLoader = getPublicBlockRendererLoader(block.block_type);
250
+
251
+ if (!rendererLoader) {
252
+ if (ECOMMERCE_BLOCK_TYPES.has(block.block_type)) {
253
+ const { default: EcommerceRendererComponent } = await loadEcommerceBlockRenderer(
254
+ block.block_type
255
+ );
256
+
257
+ return (
258
+ <EcommerceRendererComponent
259
+ content={block.content}
260
+ languageId={languageId}
261
+ visualEditAttributes={visualEditAttributes}
262
+ />
263
+ );
264
+ }
265
+
266
+ // Custom block definitions are data-rendered the same way as at the top
267
+ // level. A custom block's block_type is the definition slug.
268
+ const definition = await getCachedCustomBlockDefinitionBySlug(block.block_type);
269
+ if (definition) {
270
+ const resolvedBlock = (await resolveBlockRelations({
271
+ data: block.content as Record<string, any>,
272
+ fields: definition.fields,
273
+ })) as any;
274
+
275
+ return (
276
+ <div {...visualEditAttributes}>
277
+ <CachedDynamicLayoutEngine
278
+ definition={definition}
279
+ layoutSchema={definition.layout_schema}
280
+ fields={definition.fields}
281
+ data={{
282
+ ...(resolvedBlock.data || {}),
283
+ resolved_relations: resolvedBlock.resolved_relations || {},
284
+ }}
285
+ />
286
+ </div>
287
+ );
288
+ }
289
+
290
+ // Unknown type with no matching definition: keep the diagnostic out of the
291
+ // public-facing page (only show it inside the live editor).
292
+ if (!visualEditing?.enabled) {
293
+ return null;
294
+ }
295
+
123
296
  return (
124
- <div className="p-2 border rounded bg-destructive/10 text-destructive text-sm">
297
+ <div
298
+ className="p-2 border rounded bg-destructive/10 text-destructive text-sm"
299
+ {...visualEditAttributes}
300
+ >
125
301
  <strong>Unsupported block type:</strong> {block.block_type}
126
302
  </div>
127
303
  );
128
304
  }
129
305
 
130
- // Create dynamic component with proper SSR handling
131
- const RendererComponent = dynamic(
132
- () => import(`./${blockDefinition.rendererComponentFilename}`),
133
- {
134
- loading: () => (
135
- <div className="animate-pulse bg-muted rounded h-8"></div>
136
- ),
137
- ssr: true,
138
- }
139
- ) as React.ComponentType<any>;
306
+ const { default: RendererComponent } = await rendererLoader();
140
307
 
141
308
  // Handle different prop requirements for different renderers
142
309
  if (block.block_type === 'posts_grid') {
@@ -145,6 +312,7 @@ const DynamicNestedBlockRenderer: React.FC<{
145
312
  content={block.content}
146
313
  languageId={languageId}
147
314
  block={{ ...block, id: 0, language_id: languageId, order: 0, created_at: '', updated_at: '' }}
315
+ visualEditAttributes={visualEditAttributes}
148
316
  />
149
317
  );
150
318
  }
@@ -153,16 +321,29 @@ const DynamicNestedBlockRenderer: React.FC<{
153
321
  <RendererComponent
154
322
  content={block.content}
155
323
  languageId={languageId}
324
+ visualEditAttributes={visualEditAttributes}
325
+ visualEditing={visualEditing}
326
+ parentBlockId={parentBlockId}
327
+ parentBlockIndex={parentBlockIndex}
328
+ botProtectionPublic={botProtectionPublic}
329
+ scriptNonce={scriptNonce}
156
330
  />
157
331
  );
158
- };
332
+ }
159
333
 
160
- const SectionBlockRenderer: React.FC<SectionBlockRendererProps> = ({
334
+ export default async function SectionBlockRenderer({
161
335
  content,
162
336
  languageId,
163
- }) => {
164
- const { styles, className: backgroundClassName } = generateBackgroundStyles(content.background);
165
-
337
+ visualEditAttributes,
338
+ visualEditing,
339
+ parentBlockId,
340
+ parentBlockIndex,
341
+ blockType,
342
+ botProtectionPublic,
343
+ scriptNonce,
344
+ }: SectionBlockRendererProps) {
345
+ const isHero = content.is_hero === true;
346
+
166
347
  // Build CSS classes
167
348
  const containerClass = containerClasses[content.container_type] || containerClasses.container;
168
349
  const gridClass = columnClasses[content.responsive_columns.desktop] || columnClasses[3];
@@ -171,21 +352,211 @@ const SectionBlockRenderer: React.FC<SectionBlockRendererProps> = ({
171
352
  const paddingBottomClass = paddingClasses[content.padding.bottom] || paddingClasses.md;
172
353
  const alignmentClass = content.vertical_alignment ? verticalAlignmentClasses[content.vertical_alignment] : 'items-start';
173
354
 
355
+ // --- Render Slide-based Carousel Section ---
356
+ if (content.slider && content.slides && content.slides.length > 0) {
357
+ const renderedSlides = await Promise.all(
358
+ content.slides.map(async (slide, slideIndex) => {
359
+ const isFirstSlide = slideIndex === 0;
360
+ // Priority loading for the first slide of a hero carousel
361
+ const slidePriority = isHero && isFirstSlide;
362
+
363
+ const slideBackground = slide.background || { type: "none" };
364
+ const { styles: slideBgStyles, className: slideBgClassName } = generateBackgroundStyles(slideBackground);
365
+
366
+ const renderedColumns = await Promise.all(
367
+ slide.column_blocks.map(async (columnBlocks, columnIndex) => {
368
+ const blocks = Array.isArray(columnBlocks) ? columnBlocks : [];
369
+ const renderedBlocks = await Promise.all(
370
+ blocks.map(async (block, blockIndex) => ({
371
+ key: `${block.block_type}-${columnIndex}-${blockIndex}`,
372
+ node: await renderNestedBlock({
373
+ block,
374
+ languageId,
375
+ parentBlockId,
376
+ parentBlockIndex,
377
+ visualEditing,
378
+ botProtectionPublic,
379
+ scriptNonce,
380
+ priority: slidePriority, // Pass down priority
381
+ visualEditAttributes:
382
+ typeof parentBlockId === "number" && typeof parentBlockIndex === "number"
383
+ ? buildVisualEditAttributes(visualEditing, {
384
+ kind: "nested",
385
+ parentBlockId,
386
+ parentBlockIndex,
387
+ parentBlockType: "section",
388
+ columnIndex,
389
+ blockIndex,
390
+ blockType: block.block_type,
391
+ })
392
+ : undefined,
393
+ }),
394
+ }))
395
+ );
396
+
397
+ return { columnIndex, renderedBlocks };
398
+ })
399
+ );
400
+
401
+ return (
402
+ <div
403
+ key={`slide-${slideIndex}`}
404
+ className={`relative w-full flex items-center ${BACKGROUND_COMPOSITING_CLASSES} ${paddingTopClass} ${paddingBottomClass} ${slideBgClassName}`}
405
+ style={{
406
+ ...slideBgStyles,
407
+ minHeight: formatMinHeight(slideBackground.min_height) || '400px'
408
+ }}
409
+ >
410
+ {/* Background image Layer for slide */}
411
+ {slideBackground.type === 'image' && slideBackground.image && (
412
+ <div className={ABSOLUTE_BACKGROUND_CLASSES}>
413
+ <Image
414
+ src={`${R2_BASE_URL}/${slideBackground.image.object_key}`}
415
+ alt={slideBackground.image.alt_text || ""}
416
+ fill
417
+ priority={slidePriority}
418
+ fetchPriority={slidePriority ? "high" : "auto"}
419
+ placeholder={slideBackground.image.blur_data_url ? "blur" : "empty"}
420
+ blurDataURL={slideBackground.image.blur_data_url || undefined}
421
+ quality={slideBackground.image.quality || 80}
422
+ sizes="100vw"
423
+ style={{
424
+ objectFit: slideBackground.image.size === 'contain' ? 'contain' : 'cover',
425
+ objectPosition: slideBackground.image.position || 'center',
426
+ }}
427
+ />
428
+ {slideBackground.image.overlay && slideBackground.image.overlay.gradient && (
429
+ <div
430
+ className="absolute inset-0 transform-gpu [backface-visibility:hidden]"
431
+ style={generateGradientOverlayStyle(slideBackground.image.overlay.gradient)}
432
+ />
433
+ )}
434
+ </div>
435
+ )}
436
+
437
+ <div className={`${containerClass} w-full`}>
438
+ <div className={`grid ${gridClass} ${gapClass} ${alignmentClass}`}>
439
+ {renderedColumns.map(({ columnIndex, renderedBlocks }) => (
440
+ <div key={`column-${columnIndex}`} className="min-h-0 space-y-4">
441
+ {renderedBlocks.map(({ key, node }) => (
442
+ <React.Fragment key={key}>{node}</React.Fragment>
443
+ ))}
444
+ </div>
445
+ ))}
446
+ </div>
447
+ </div>
448
+ </div>
449
+ );
450
+ })
451
+ );
452
+
453
+ // Determine the slider's container min-height from configured slides
454
+ let sliderMinHeight = '400px';
455
+ if (content.slides && content.slides.length > 0) {
456
+ const configuredHeight = content.slides
457
+ .map(s => s.background?.min_height)
458
+ .find(Boolean);
459
+ if (configuredHeight) {
460
+ sliderMinHeight = formatMinHeight(configuredHeight) || '400px';
461
+ }
462
+ }
463
+
464
+ return (
465
+ <section
466
+ className={`relative w-full overflow-hidden ${BACKGROUND_COMPOSITING_CLASSES}`}
467
+ {...visualEditAttributes}
468
+ >
469
+ <SectionSlider
470
+ autoplay={content.autoplay}
471
+ timeframe={content.timeframe}
472
+ minHeight={sliderMinHeight}
473
+ >
474
+ {renderedSlides}
475
+ </SectionSlider>
476
+ </section>
477
+ );
478
+ }
479
+
480
+ // --- Render Standard Single Layout Section ---
481
+ const { styles, className: backgroundClassName } = generateBackgroundStyles(content.background);
482
+
483
+ const renderedColumns = await Promise.all(
484
+ content.column_blocks.map(async (columnBlocks, columnIndex) => {
485
+ const blocks = Array.isArray(columnBlocks) ? columnBlocks : [];
486
+ const renderedBlocks = await Promise.all(
487
+ blocks.map(async (block, blockIndex) => ({
488
+ key: `${block.block_type}-${columnIndex}-${blockIndex}`,
489
+ node: await renderNestedBlock({
490
+ block,
491
+ languageId,
492
+ parentBlockId,
493
+ parentBlockIndex,
494
+ visualEditing,
495
+ botProtectionPublic,
496
+ scriptNonce,
497
+ priority: isHero, // Pass priority down to nested image blocks
498
+ visualEditAttributes:
499
+ typeof parentBlockId === "number" && typeof parentBlockIndex === "number"
500
+ ? buildVisualEditAttributes(visualEditing, {
501
+ kind: "nested",
502
+ parentBlockId,
503
+ parentBlockIndex,
504
+ parentBlockType: "section",
505
+ columnIndex,
506
+ blockIndex,
507
+ blockType: block.block_type,
508
+ })
509
+ : undefined,
510
+ }),
511
+ }))
512
+ );
513
+
514
+ return { columnIndex, renderedBlocks };
515
+ })
516
+ );
517
+
174
518
  return (
175
519
  <section
176
- className={`w-full ${paddingTopClass} ${paddingBottomClass} ${backgroundClassName}`.trim()}
177
- style={styles}
520
+ className={`relative w-full ${BACKGROUND_COMPOSITING_CLASSES} ${paddingTopClass} ${paddingBottomClass} ${backgroundClassName}`.trim()}
521
+ style={{
522
+ ...styles,
523
+ minHeight: formatMinHeight(content.background?.min_height)
524
+ }}
525
+ {...visualEditAttributes}
178
526
  >
527
+ {/* Background image Layer */}
528
+ {content.background?.type === 'image' && content.background.image && (
529
+ <div className={ABSOLUTE_BACKGROUND_CLASSES}>
530
+ <Image
531
+ src={`${R2_BASE_URL}/${content.background.image.object_key}`}
532
+ alt={content.background.image.alt_text || ""}
533
+ fill
534
+ priority={isHero}
535
+ fetchPriority={isHero ? "high" : "auto"}
536
+ placeholder={content.background.image.blur_data_url ? "blur" : "empty"}
537
+ blurDataURL={content.background.image.blur_data_url || undefined}
538
+ quality={content.background.image.quality || 80}
539
+ sizes="100vw"
540
+ style={{
541
+ objectFit: content.background.image.size === 'contain' ? 'contain' : 'cover',
542
+ objectPosition: content.background.image.position || 'center',
543
+ }}
544
+ />
545
+ {content.background.image.overlay && content.background.image.overlay.gradient && (
546
+ <div
547
+ className="absolute inset-0 transform-gpu [backface-visibility:hidden]"
548
+ style={generateGradientOverlayStyle(content.background.image.overlay.gradient)}
549
+ />
550
+ )}
551
+ </div>
552
+ )}
553
+
179
554
  <div className={containerClass}>
180
555
  <div className={`grid ${gridClass} ${gapClass} ${alignmentClass}`}>
181
- {content.column_blocks.map((columnBlocks, columnIndex) => (
556
+ {renderedColumns.map(({ columnIndex, renderedBlocks }) => (
182
557
  <div key={`column-${columnIndex}`} className="min-h-0 space-y-4">
183
- {(Array.isArray(columnBlocks) ? columnBlocks : []).map((block, blockIndex) => (
184
- <DynamicNestedBlockRenderer
185
- key={`${block.block_type}-${columnIndex}-${blockIndex}`}
186
- block={block}
187
- languageId={languageId}
188
- />
558
+ {renderedBlocks.map(({ key, node }) => (
559
+ <React.Fragment key={key}>{node}</React.Fragment>
189
560
  ))}
190
561
  </div>
191
562
  ))}
@@ -193,6 +564,4 @@ const SectionBlockRenderer: React.FC<SectionBlockRendererProps> = ({
193
564
  </div>
194
565
  </section>
195
566
  );
196
- };
197
-
198
- export default SectionBlockRenderer;
567
+ }
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import React, { useState, useEffect, useRef } from "react";
4
+ import { ChevronLeft, ChevronRight } from "lucide-react";
5
+
6
+ interface SectionSliderProps {
7
+ autoplay?: boolean;
8
+ timeframe?: number; // In seconds
9
+ children: React.ReactNode[];
10
+ minHeight?: string;
11
+ }
12
+
13
+ export default function SectionSlider({
14
+ autoplay = false,
15
+ timeframe = 5,
16
+ children,
17
+ minHeight = '400px',
18
+ }: SectionSliderProps) {
19
+ const [currentIndex, setCurrentIndex] = useState(0);
20
+ const [isPaused, setIsPaused] = useState(false);
21
+ const totalSlides = children.length;
22
+ const timerRef = useRef<NodeJS.Timeout | null>(null);
23
+
24
+ // Reset to first slide if children count changes
25
+ useEffect(() => {
26
+ setCurrentIndex(0);
27
+ }, [totalSlides]);
28
+
29
+ useEffect(() => {
30
+ if (!autoplay || totalSlides <= 1 || isPaused) {
31
+ if (timerRef.current) {
32
+ clearInterval(timerRef.current);
33
+ }
34
+ return;
35
+ }
36
+
37
+ const intervalMs = timeframe * 1000;
38
+
39
+ timerRef.current = setInterval(() => {
40
+ setCurrentIndex((prevIndex) => (prevIndex + 1) % totalSlides);
41
+ }, intervalMs);
42
+
43
+ return () => {
44
+ if (timerRef.current) {
45
+ clearInterval(timerRef.current);
46
+ }
47
+ };
48
+ }, [autoplay, timeframe, totalSlides, isPaused]);
49
+
50
+ const handlePrev = (e: React.MouseEvent) => {
51
+ e.stopPropagation();
52
+ setCurrentIndex((prev) => (prev - 1 + totalSlides) % totalSlides);
53
+ };
54
+
55
+ const handleNext = (e: React.MouseEvent) => {
56
+ e.stopPropagation();
57
+ setCurrentIndex((prev) => (prev + 1) % totalSlides);
58
+ };
59
+
60
+ const handleDotClick = (index: number, e: React.MouseEvent) => {
61
+ e.stopPropagation();
62
+ setCurrentIndex(index);
63
+ };
64
+
65
+ if (totalSlides === 0) {
66
+ return null;
67
+ }
68
+
69
+ return (
70
+ <div
71
+ className="relative w-full overflow-hidden group"
72
+ onMouseEnter={() => setIsPaused(true)}
73
+ onMouseLeave={() => setIsPaused(false)}
74
+ style={{ minHeight }}
75
+ >
76
+ {/* Slides Container */}
77
+ <div className="relative w-full" style={{ minHeight }}>
78
+ {children.map((child, index) => {
79
+ const isActive = index === currentIndex;
80
+ return (
81
+ <div
82
+ key={index}
83
+ className={`w-full transition-opacity duration-700 ease-in-out ${
84
+ isActive
85
+ ? "relative opacity-100 z-10 pointer-events-auto"
86
+ : "absolute inset-x-0 top-0 opacity-0 z-0 pointer-events-none"
87
+ }`}
88
+ >
89
+ {child}
90
+ </div>
91
+ );
92
+ })}
93
+ </div>
94
+
95
+ {/* Navigation Chevrons */}
96
+ {totalSlides > 1 && (
97
+ <>
98
+ <button
99
+ onClick={handlePrev}
100
+ className="absolute left-4 top-1/2 -translate-y-1/2 z-20 flex items-center justify-center w-12 h-12 rounded-full border border-white/20 bg-white/10 dark:bg-black/20 hover:bg-white/25 dark:hover:bg-black/45 backdrop-blur-md text-white shadow-lg opacity-0 group-hover:opacity-100 transition-all duration-300 hover:scale-105 active:scale-95"
101
+ aria-label="Previous slide"
102
+ >
103
+ <ChevronLeft className="w-6 h-6 text-current" />
104
+ </button>
105
+ <button
106
+ onClick={handleNext}
107
+ className="absolute right-4 top-1/2 -translate-y-1/2 z-20 flex items-center justify-center w-12 h-12 rounded-full border border-white/20 bg-white/10 dark:bg-black/20 hover:bg-white/25 dark:hover:bg-black/45 backdrop-blur-md text-white shadow-lg opacity-0 group-hover:opacity-100 transition-all duration-300 hover:scale-105 active:scale-95"
108
+ aria-label="Next slide"
109
+ >
110
+ <ChevronRight className="w-6 h-6 text-current" />
111
+ </button>
112
+ </>
113
+ )}
114
+
115
+ {/* Navigation Dots */}
116
+ {totalSlides > 1 && (
117
+ <div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 flex items-center justify-center space-x-2">
118
+ {children.map((_, index) => {
119
+ const isActive = index === currentIndex;
120
+ return (
121
+ <button
122
+ key={index}
123
+ onClick={(e) => handleDotClick(index, e)}
124
+ className={`h-2.5 rounded-full transition-all duration-300 border border-white/10 ${
125
+ isActive
126
+ ? "w-7 bg-white shadow-md"
127
+ : "w-2.5 bg-white/40 hover:bg-white/70"
128
+ }`}
129
+ aria-label={`Go to slide ${index + 1}`}
130
+ />
131
+ );
132
+ })}
133
+ </div>
134
+ )}
135
+ </div>
136
+ );
137
+ }