create-nextblock 0.2.77 → 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 +191 -151
  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 -116
  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
@@ -0,0 +1,91 @@
1
+ 'use server';
2
+
3
+ import { headers } from 'next/headers';
4
+ import { redirect } from 'next/navigation';
5
+ import { createClient } from '@nextblock-cms/db/server';
6
+ import {
7
+ createEmailChallenge,
8
+ issueTwoFactorVerifiedCookie,
9
+ verifyEmailChallenge,
10
+ } from '../../../lib/auth/twoFactor';
11
+ import { issueTrustedDevice } from '../../../lib/auth/trustedDevices';
12
+ import {
13
+ REMEMBER_INTENT_COOKIE,
14
+ clearCookie,
15
+ getCookieValue,
16
+ } from '../../../lib/auth/cookies';
17
+ import { sendTwoFactorCodeEmail } from '../../actions/twoFactorEmail';
18
+
19
+ function safeRedirect(path?: string): string {
20
+ return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
21
+ }
22
+
23
+ /** If the user opted into "remember this device" at login, mint the trust now. */
24
+ async function maybeIssueTrustedDevice(userId: string): Promise<void> {
25
+ const intent = await getCookieValue(REMEMBER_INTENT_COOKIE);
26
+ if (intent !== '1') return;
27
+ const userAgent = (await headers()).get('user-agent');
28
+ await issueTrustedDevice(userId, userAgent);
29
+ await clearCookie(REMEMBER_INTENT_COOKIE);
30
+ }
31
+
32
+ export async function verifyTotpChallenge(formData: FormData) {
33
+ const code = (formData.get('code')?.toString() ?? '').trim();
34
+ const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
35
+ const supabase = createClient();
36
+
37
+ const {
38
+ data: { user },
39
+ } = await supabase.auth.getUser();
40
+ if (!user) return { error: 'Your session expired. Please sign in again.' };
41
+ if (!/^\d{6}$/.test(code)) return { error: 'Enter the 6-digit code from your app.' };
42
+
43
+ const { data: factors } = await supabase.auth.mfa.listFactors();
44
+ const factor = factors?.totp?.find((f) => f.status === 'verified');
45
+ if (!factor) return { error: 'No authenticator is set up for this account.' };
46
+
47
+ const { data: challenge, error: challengeError } = await supabase.auth.mfa.challenge({
48
+ factorId: factor.id,
49
+ });
50
+ if (challengeError || !challenge) return { error: 'Could not start verification.' };
51
+
52
+ const { error: verifyError } = await supabase.auth.mfa.verify({
53
+ factorId: factor.id,
54
+ challengeId: challenge.id,
55
+ code,
56
+ });
57
+ if (verifyError) return { error: 'That code was not valid. Please try again.' };
58
+
59
+ await maybeIssueTrustedDevice(user.id);
60
+ redirect(redirectTo);
61
+ }
62
+
63
+ export async function verifyEmailCode(formData: FormData) {
64
+ const code = (formData.get('code')?.toString() ?? '').trim();
65
+ const redirectTo = safeRedirect(formData.get('redirect_to')?.toString());
66
+ const supabase = createClient();
67
+
68
+ const {
69
+ data: { user },
70
+ } = await supabase.auth.getUser();
71
+ if (!user) return { error: 'Your session expired. Please sign in again.' };
72
+
73
+ const ok = await verifyEmailChallenge(user.id, code);
74
+ if (!ok) return { error: 'That code was incorrect or expired. Request a new one.' };
75
+
76
+ await issueTwoFactorVerifiedCookie(user.id);
77
+ await maybeIssueTrustedDevice(user.id);
78
+ redirect(redirectTo);
79
+ }
80
+
81
+ export async function resendEmailCode() {
82
+ const supabase = createClient();
83
+ const {
84
+ data: { user },
85
+ } = await supabase.auth.getUser();
86
+ if (!user?.email) return { error: 'No email address is associated with your account.' };
87
+
88
+ const code = await createEmailChallenge(user.id);
89
+ await sendTwoFactorCodeEmail(user.email, code);
90
+ return { success: true, message: `A new code is on its way to ${user.email}.` };
91
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import { useState, useTransition } from 'react';
4
+ import { Alert, AlertDescription, Button, Input, Label, Spinner } from '@nextblock-cms/ui';
5
+ import { resendEmailCode, verifyEmailCode, verifyTotpChallenge } from '../actions';
6
+
7
+ interface TwoFactorFormProps {
8
+ type: 'totp' | 'email';
9
+ email: string;
10
+ redirectTo: string;
11
+ pendingEmailCode: boolean;
12
+ }
13
+
14
+ export default function TwoFactorForm({
15
+ type,
16
+ email,
17
+ redirectTo,
18
+ pendingEmailCode,
19
+ }: TwoFactorFormProps) {
20
+ const [isPending, startTransition] = useTransition();
21
+ const [code, setCode] = useState('');
22
+ const [error, setError] = useState<string | null>(null);
23
+ const [info, setInfo] = useState<string | null>(
24
+ type === 'email' && pendingEmailCode ? `Enter the code we sent to ${email}.` : null,
25
+ );
26
+
27
+ const submit = () => {
28
+ setError(null);
29
+ const formData = new FormData();
30
+ formData.append('code', code);
31
+ formData.append('redirect_to', redirectTo);
32
+ startTransition(async () => {
33
+ try {
34
+ const action = type === 'totp' ? verifyTotpChallenge : verifyEmailCode;
35
+ const result = await action(formData);
36
+ // A successful action redirects server-side; only failures return here.
37
+ if (result?.error) setError(result.error);
38
+ } catch (err) {
39
+ setError(err instanceof Error ? err.message : 'Verification failed.');
40
+ }
41
+ });
42
+ };
43
+
44
+ const resend = () => {
45
+ setError(null);
46
+ setInfo(null);
47
+ startTransition(async () => {
48
+ try {
49
+ const result = await resendEmailCode();
50
+ if (result?.error) setError(result.error);
51
+ else if (result?.message) setInfo(result.message);
52
+ } catch (err) {
53
+ setError(err instanceof Error ? err.message : 'Could not send a code.');
54
+ }
55
+ });
56
+ };
57
+
58
+ return (
59
+ <div className="rounded-xl border bg-card p-6 shadow-sm">
60
+ <h1 className="text-xl font-semibold">Two-step verification</h1>
61
+ <p className="mt-1 text-sm text-muted-foreground">
62
+ {type === 'totp'
63
+ ? 'Enter the 6-digit code from your authenticator app to finish signing in.'
64
+ : `For your security, enter the 6-digit code sent to ${email || 'your email'}.`}
65
+ </p>
66
+
67
+ <form
68
+ className="mt-6 space-y-4"
69
+ onSubmit={(e) => {
70
+ e.preventDefault();
71
+ submit();
72
+ }}
73
+ >
74
+ <div className="space-y-2">
75
+ <Label htmlFor="code">Verification code</Label>
76
+ <Input
77
+ id="code"
78
+ inputMode="numeric"
79
+ autoComplete="one-time-code"
80
+ autoFocus
81
+ maxLength={6}
82
+ value={code}
83
+ onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
84
+ placeholder="000000"
85
+ className="tracking-[0.5em] text-center text-lg"
86
+ />
87
+ </div>
88
+
89
+ {error && (
90
+ <Alert variant="destructive" className="py-2 px-4">
91
+ <AlertDescription>{error}</AlertDescription>
92
+ </Alert>
93
+ )}
94
+ {info && !error && (
95
+ <Alert variant="success" className="py-2 px-4">
96
+ <AlertDescription>{info}</AlertDescription>
97
+ </Alert>
98
+ )}
99
+
100
+ <Button type="submit" disabled={isPending || code.length !== 6} className="w-full">
101
+ {isPending ? <Spinner className="mr-2 h-4 w-4 animate-spin" /> : null}
102
+ Verify &amp; continue
103
+ </Button>
104
+ </form>
105
+
106
+ {type === 'email' && (
107
+ <button
108
+ type="button"
109
+ onClick={resend}
110
+ disabled={isPending}
111
+ className="mt-4 w-full text-center text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:opacity-50"
112
+ >
113
+ {pendingEmailCode ? 'Resend code' : 'Send me a code'}
114
+ </button>
115
+ )}
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,51 @@
1
+ import { redirect } from 'next/navigation';
2
+ import { createClient } from '@nextblock-cms/db/server';
3
+ import {
4
+ evaluateTwoFactor,
5
+ hasPendingEmailChallenge,
6
+ } from '../../../lib/auth/twoFactor';
7
+ import TwoFactorForm from './components/TwoFactorForm';
8
+
9
+ function safeRedirect(path?: string): string {
10
+ return path && path.startsWith('/') && !path.startsWith('//') ? path : '/cms/dashboard';
11
+ }
12
+
13
+ export default async function TwoFactorPage({
14
+ searchParams,
15
+ }: {
16
+ searchParams: Promise<{ redirect_to?: string }>;
17
+ }) {
18
+ const params = await searchParams;
19
+ const redirectTo = safeRedirect(params.redirect_to);
20
+
21
+ const evaluation = await evaluateTwoFactor();
22
+ const userId = evaluation.userId;
23
+ if (!userId) {
24
+ redirect('/sign-in');
25
+ }
26
+ // Already satisfied (or no MFA) -> straight through.
27
+ if (evaluation.status === 'satisfied' || evaluation.status === 'not_required') {
28
+ redirect(redirectTo);
29
+ }
30
+
31
+ const type = evaluation.status === 'totp_required' ? 'totp' : 'email';
32
+
33
+ const supabase = createClient();
34
+ const {
35
+ data: { user },
36
+ } = await supabase.auth.getUser();
37
+
38
+ const pendingEmailCode =
39
+ type === 'email' ? await hasPendingEmailChallenge(userId) : false;
40
+
41
+ return (
42
+ <div className="flex-1 flex flex-col w-full max-w-md mx-auto py-10">
43
+ <TwoFactorForm
44
+ type={type}
45
+ email={user?.email ?? ''}
46
+ redirectTo={redirectTo}
47
+ pendingEmailCode={pendingEmailCode}
48
+ />
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { buildUcpProfile } from '../../lib/ucp/protocol';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+ export const runtime = 'nodejs';
6
+
7
+ export async function GET(request: Request) {
8
+ const baseUrl =
9
+ process.env.NEXT_PUBLIC_URL?.replace(/\/+$/, '') || new URL(request.url).origin;
10
+
11
+ return NextResponse.json(buildUcpProfile(baseUrl), {
12
+ headers: {
13
+ 'Cache-Control': 'public, max-age=300, s-maxage=300',
14
+ },
15
+ });
16
+ }
@@ -2,18 +2,28 @@
2
2
  "use client";
3
3
 
4
4
  import React, { useState, useEffect, useMemo } from 'react';
5
+ import Image from 'next/image';
5
6
  import { useRouter } from 'next/navigation'; // For navigation on lang switch
6
7
  import type { Database } from "@nextblock-cms/db";
7
- import { useLanguage } from '@/context/LanguageContext';
8
- import { useCurrentContent } from '@/context/CurrentContentContext';
8
+ import { useLanguage } from '../../context/LanguageContext';
9
+ import { useCurrentContent } from '../../context/CurrentContentContext';
9
10
  import Link from 'next/link';
10
- import { createClient } from '@nextblock-cms/db';
11
+ import { getPublishedPageForLocale } from './pageClientActions';
11
12
 
12
13
  type PageType = Database['public']['Tables']['pages']['Row'];
13
14
  type BlockType = Database['public']['Tables']['blocks']['Row'];
14
15
 
15
16
  interface PageClientContentProps {
16
- initialPageData: (PageType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; }) | null;
17
+ initialPageData: (PageType & {
18
+ blocks: BlockType[];
19
+ language_code: string;
20
+ language_id: number;
21
+ translation_group_id: string | null;
22
+ feature_image_url?: string | null;
23
+ feature_image_blur_data_url?: string | null;
24
+ feature_image_width?: number | null;
25
+ feature_image_height?: number | null;
26
+ }) | null;
17
27
  currentSlug: string; // The slug of the currently viewed page
18
28
  children: React.ReactNode;
19
29
  translatedSlugs?: { [key: string]: string };
@@ -51,8 +61,6 @@ export default function PageClientContent({ initialPageData, currentSlug, childr
51
61
  // It's initially set by the server for the slug it resolved.
52
62
  const [currentPageData, setCurrentPageData] = useState(initialPageData);
53
63
  const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false);
54
- const supabase = useMemo(() => createClient(), []);
55
-
56
64
  // Memoize pageId and pageSlug
57
65
  const pageId = useMemo(() => currentPageData?.id, [currentPageData?.id]);
58
66
  const pageSlug = useMemo(() => currentPageData?.slug, [currentPageData?.id, currentLocale]); // include locale so updates propagate
@@ -68,24 +76,10 @@ export default function PageClientContent({ initialPageData, currentSlug, childr
68
76
  } else if (targetSlug && targetSlug === currentSlug) {
69
77
  // Same slug across languages - refetch the page in the target language and update content
70
78
  (async () => {
71
- const { data, error } = await supabase
72
- .from("pages")
73
- .select("*, languages!inner(code), blocks(*)")
74
- .eq("slug", targetSlug)
75
- .eq("languages.code", currentLocale)
76
- .eq("status", "published")
77
- .order('order', { foreignTable: 'blocks', ascending: true })
78
- .maybeSingle();
79
-
80
- if (!error && data) {
81
- const langInfo = Array.isArray(data.languages) ? data.languages[0] : (data.languages as unknown as { code?: string });
82
- setCurrentPageData({
83
- ...(data as PageType),
84
- blocks: (data as any).blocks || [],
85
- language_code: langInfo?.code || currentLocale,
86
- language_id: data.language_id,
87
- translation_group_id: data.translation_group_id || currentPageData.translation_group_id,
88
- } as typeof currentPageData);
79
+ const data = await getPublishedPageForLocale(targetSlug, currentLocale);
80
+
81
+ if (data) {
82
+ setCurrentPageData(data as typeof currentPageData);
89
83
  } else {
90
84
  // fallback to refresh if fetch fails
91
85
  router.refresh();
@@ -98,7 +92,7 @@ export default function PageClientContent({ initialPageData, currentSlug, childr
98
92
  setIsLoadingTargetLang(false);
99
93
  }
100
94
  }
101
- }, [currentLocale, currentPageData, currentSlug, router, initialPageData, translatedSlugs]); // Rerun if initialPageData changes (e.g. after revalidation)
95
+ }, [currentLocale, currentPageData, currentSlug, router, translatedSlugs]); // Rerun if route data or locale changes
102
96
 
103
97
  // Update HTML lang attribute based on the *actually displayed* content's language
104
98
  useEffect(() => {
@@ -126,11 +120,16 @@ export default function PageClientContent({ initialPageData, currentSlug, childr
126
120
  currentContent.slug !== null);
127
121
 
128
122
  if (needsUpdate) {
129
- setCurrentContent({ id: pageId, type: newType, slug: slugToSet });
123
+ setCurrentContent({
124
+ id: pageId,
125
+ type: newType,
126
+ slug: slugToSet,
127
+ translation_group_id: currentPageData?.translation_group_id
128
+ });
130
129
  } else if (needsClearing) {
131
- setCurrentContent({ id: null, type: null, slug: null });
130
+ setCurrentContent({ id: null, type: null, slug: null, translation_group_id: null });
132
131
  }
133
- }, [pageId, pageSlug, setCurrentContent, currentContent.id, currentContent.type, currentContent.slug]);
132
+ }, [pageId, pageSlug, setCurrentContent, currentContent.id, currentContent.type, currentContent.slug, currentContent.translation_group_id, currentPageData?.translation_group_id]);
134
133
 
135
134
  // Separate useEffect for cleanup
136
135
  useEffect(() => {
@@ -166,6 +165,27 @@ export default function PageClientContent({ initialPageData, currentSlug, childr
166
165
  return (
167
166
  <article className="w-full mx-auto">
168
167
  {isLoadingTargetLang && <div className="text-center py-2 text-sm text-muted-foreground">Switching language...</div>}
168
+
169
+ {currentPageData.feature_image_url ? (
170
+ <div className="relative h-48 w-full overflow-hidden bg-slate-950 sm:h-56 md:h-64 lg:h-72">
171
+ <Image
172
+ src={currentPageData.feature_image_url}
173
+ alt={`Feature image for ${currentPageData.title}`}
174
+ fill
175
+ sizes="100vw"
176
+ className="object-cover"
177
+ placeholder={currentPageData.feature_image_blur_data_url ? "blur" : "empty"}
178
+ blurDataURL={currentPageData.feature_image_blur_data_url ?? undefined}
179
+ priority
180
+ />
181
+ <div className="absolute inset-0 bg-slate-950/45" />
182
+ <div className="absolute inset-0 flex items-center justify-center px-4 text-center">
183
+ <div className="max-w-5xl break-words text-3xl font-semibold leading-tight text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.65)] sm:text-4xl md:text-5xl">
184
+ {currentPageData.title}
185
+ </div>
186
+ </div>
187
+ </div>
188
+ ) : null}
169
189
 
170
190
  {/* Render blocks passed as children */}
171
191
  {children}
@@ -6,7 +6,16 @@ import type { Metadata } from 'next';
6
6
  import PageClientContent from "./PageClientContent";
7
7
  import { getPageDataBySlug } from "./page.utils";
8
8
  import BlockRenderer from "../../components/BlockRenderer";
9
- import { cookies, headers } from "next/headers";
9
+ import { cookies, draftMode, headers } from "next/headers";
10
+ import {
11
+ resolveMetaTitle,
12
+ resolvePageMetaDescription,
13
+ stringifyJsonLd,
14
+ buildSocialMetadata,
15
+ toOpenGraphLocale,
16
+ } from "../lib/seo";
17
+ import { getSiteSettings } from "../lib/site-settings";
18
+ import { getRequestOrigin } from "../../lib/visual-editing/edit-info";
10
19
 
11
20
  export const dynamicParams = true;
12
21
  export const revalidate = 360;
@@ -68,7 +77,7 @@ export async function generateMetadata(
68
77
  return { title: "Page Not Found" };
69
78
  }
70
79
 
71
- const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "";
80
+ const siteUrl = process.env.NEXT_PUBLIC_URL || "";
72
81
  const supabase = getSsgSupabaseClient();
73
82
 
74
83
  // Parallel queries for better performance
@@ -94,9 +103,22 @@ export async function generateMetadata(
94
103
  });
95
104
  }
96
105
 
106
+ const title = resolveMetaTitle(pageData.meta_title, pageData.title);
107
+ const description = resolvePageMetaDescription(pageData.meta_description, pageData.blocks);
108
+ const { siteTitle } = await getSiteSettings();
109
+
97
110
  return {
98
- title: pageData.meta_title || pageData.title,
99
- description: pageData.meta_description || "",
111
+ title,
112
+ description,
113
+ ...buildSocialMetadata({
114
+ title,
115
+ description,
116
+ url: `${siteUrl}/${params.slug}`,
117
+ siteTitle,
118
+ imageUrl: pageData.feature_image_url,
119
+ type: 'website',
120
+ locale: toOpenGraphLocale(pageData.language_code),
121
+ }),
100
122
  alternates: {
101
123
  canonical: `${siteUrl}/${params.slug}`,
102
124
  languages: Object.keys(alternates).length > 0 ? alternates : undefined,
@@ -148,11 +170,46 @@ export default async function DynamicPage({ params: paramsPromise }: PageProps)
148
170
 
149
171
 
150
172
 
151
- const pageBlocks = pageData ? <BlockRenderer blocks={pageData.blocks} languageId={pageData.language_id} /> : null;
173
+ const requestOrigin = await getRequestOrigin();
174
+ const draft = await draftMode();
175
+ const visualEditingEnabled =
176
+ draft.isEnabled || process.env.NEXTBLOCK_VISUAL_EDITING_ENABLED === 'true';
177
+ const siteUrl = process.env.NEXT_PUBLIC_URL || "";
178
+ const nonce = (await headers()).get('x-nonce') || undefined;
179
+ const title = resolveMetaTitle(pageData.meta_title, pageData.title);
180
+ const description = resolvePageMetaDescription(pageData.meta_description, pageData.blocks);
181
+ const pageJsonLd = {
182
+ '@context': 'https://schema.org',
183
+ '@type': 'WebPage',
184
+ name: title,
185
+ description,
186
+ url: `${siteUrl}/${params.slug}`,
187
+ inLanguage: pageData.language_code,
188
+ };
189
+ const pageBlocks = pageData ? (
190
+ <BlockRenderer
191
+ blocks={pageData.blocks}
192
+ languageId={pageData.language_id}
193
+ visualEditing={{
194
+ enabled: visualEditingEnabled,
195
+ documentType: "page",
196
+ documentId: pageData.id,
197
+ slug: pageData.slug,
198
+ languageId: pageData.language_id,
199
+ draftId: pageData.draft_id ?? null,
200
+ pageOrigin: requestOrigin,
201
+ }}
202
+ />
203
+ ) : null;
152
204
 
153
205
  return (
154
206
  <>
155
-
207
+ <script
208
+ type="application/ld+json"
209
+ nonce={nonce}
210
+ suppressHydrationWarning
211
+ dangerouslySetInnerHTML={{ __html: stringifyJsonLd(pageJsonLd) }}
212
+ />
156
213
  <PageClientContent initialPageData={pageData} currentSlug={params.slug} translatedSlugs={translatedSlugs}>
157
214
  {pageBlocks}
158
215
  </PageClientContent>