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,33 +1,233 @@
1
1
  import '@nextblock-cms/ui/styles/globals.css';
2
- // eslint-disable-next-line @nx/enforce-module-boundaries
3
- import '@nextblock-cms/editor/styles/editor.css';
4
2
  // app/layout.tsx
5
- import { EnvVarWarning } from "../components/env-var-warning";
6
- import { SandboxBanner } from "../components/SandboxBanner";
7
- import { SpeedInsights } from "@vercel/speed-insights/next"
8
- // eslint-disable-next-line @nx/enforce-module-boundaries
9
- import { GoogleTagManager } from '@next/third-parties/google'
10
- import { ThemeSwitcher } from '../components/theme-switcher';
3
+
11
4
  import type { Metadata } from 'next';
12
- import Header from "../components/Header";
13
- import FooterNavigation from "../components/FooterNavigation";
5
+ import Script from 'next/script';
14
6
  import { Providers } from './providers';
7
+ import { DeferredCartDrawer } from '../components/DeferredCartDrawer';
8
+ import { CURRENCY_COOKIE_NAME } from '@nextblock-cms/ecommerce/currency-constants';
15
9
  import { ToasterProvider } from './ToasterProvider';
16
- import { createClient as createSupabaseServerClient, getProfileWithRoleServerSide } from '@nextblock-cms/db/server';
10
+ import { AppShell } from '../components/AppShell';
11
+ import { ConsentGatedAnalytics } from '../components/privacy/ConsentGatedAnalytics';
12
+ import { ConsentBanner } from '../components/privacy/ConsentBanner';
13
+ import { getPrivacySettings } from '../lib/privacy/settings';
14
+ import { DEFAULT_PRIVACY_SETTINGS } from '../lib/privacy/types';
15
+ import { DeferredSpeedInsights } from '../components/DeferredSpeedInsights';
16
+ import { DeferredVisualEditing } from '../components/visual-editing/DeferredVisualEditing';
17
+ import {
18
+ createClient as createSupabaseServerClient,
19
+ getProfileWithRoleServerSide,
20
+ } from '@nextblock-cms/db/server';
17
21
  import { getActiveLanguagesServerSide } from '@nextblock-cms/db/server';
18
- import { getNavigationMenu } from './cms/navigation/actions';
19
- import { getActiveLogo } from './cms/settings/logos/actions';
20
- import { getCopyrightSettings } from './cms/settings/copyright/actions';
21
- import { getTranslations } from './cms/settings/extra-translations/actions';
22
22
  import type { Database } from '@nextblock-cms/db';
23
- import { headers, cookies } from 'next/headers';
23
+ import { headers, cookies, draftMode } from 'next/headers';
24
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
25
+ import { unstable_cache } from 'next/cache';
26
+ import { createStaticSupabaseClient, getSiteSettings } from './lib/site-settings';
27
+ import { DEFAULT_OG_IMAGE } from './lib/seo';
24
28
 
25
- const defaultUrl = process.env.NEXT_PUBLIC_URL || "http://localhost:3000";
29
+ const defaultUrl = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';
26
30
 
27
31
  const DEFAULT_LOCALE_FOR_LAYOUT = 'en';
32
+ const PUBLIC_LAYOUT_REVALIDATE_SECONDS = 60;
33
+ const PUBLIC_LAYOUT_LOGO_CACHE_TAG = 'public-layout-logo';
34
+ const TRUSTED_TYPES_SCRIPT_STRATEGY =
35
+ process.env.NODE_ENV === 'production' ? 'beforeInteractive' : 'afterInteractive';
36
+ const TRUSTED_TYPES_BOOTSTRAP = `
37
+ (function () {
38
+ if (!window.trustedTypes || window.__nextblockTrustedTypesPolicy) return;
39
+ try {
40
+ window.__nextblockTrustedTypesPolicy = window.trustedTypes.createPolicy('default', {
41
+ createHTML: function (value) { return value; },
42
+ createScript: function (value) { return value; },
43
+ createScriptURL: function (value) { return value; }
44
+ });
45
+ } catch (error) {
46
+ window.__nextblockTrustedTypesPolicy = true;
47
+ }
48
+ })();
49
+ `;
28
50
 
29
51
  type Language = Database['public']['Tables']['languages']['Row'];
52
+ type StoreCurrency = Database['public']['Tables']['currencies']['Row'];
30
53
  type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
54
+ type MenuLocation = Database['public']['Enums']['menu_location'];
55
+ type HeaderLogo = Database['public']['Tables']['logos']['Row'] & {
56
+ media: (Database['public']['Tables']['media']['Row'] & { alt_text: string | null }) | null;
57
+ };
58
+
59
+ const getCachedLanguages = unstable_cache(
60
+ async (): Promise<Language[]> => {
61
+ const supabase = createStaticSupabaseClient();
62
+ const { data, error } = await supabase
63
+ .from('languages')
64
+ .select('id, code, name, is_default, is_active, created_at, updated_at')
65
+ .order('name', { ascending: true });
66
+
67
+ if (error) {
68
+ console.error('Error fetching cached languages:', error.message);
69
+ return [];
70
+ }
71
+
72
+ return data || [];
73
+ },
74
+ ['public-layout-languages'],
75
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS }
76
+ );
77
+
78
+ const getCachedCopyrightSettings = unstable_cache(
79
+ async (): Promise<Record<string, string>> => {
80
+ const supabase = createStaticSupabaseClient();
81
+ const { data, error } = await supabase
82
+ .from('site_settings')
83
+ .select('value')
84
+ .eq('key', 'footer_copyright')
85
+ .single();
86
+
87
+ if (error || !data) {
88
+ console.error('Error fetching cached copyright settings:', error);
89
+ return { en: '(c) {year} Nextblock CMS. All rights reserved.' };
90
+ }
91
+
92
+ return data.value as Record<string, string>;
93
+ },
94
+ ['public-layout-copyright'],
95
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS }
96
+ );
97
+
98
+ const getCachedGlobalCss = unstable_cache(
99
+ async (): Promise<string> => {
100
+ const supabase = createStaticSupabaseClient();
101
+ const { data, error } = await supabase
102
+ .from('site_settings')
103
+ .select('value')
104
+ .eq('key', 'global_css')
105
+ .single();
106
+
107
+ if (error || !data || !data.value) {
108
+ return '';
109
+ }
110
+ if (typeof data.value === 'string') {
111
+ if (data.value.startsWith('"') && data.value.endsWith('"')) {
112
+ try { return JSON.parse(data.value); } catch { return data.value; }
113
+ }
114
+ return data.value;
115
+ }
116
+ return String(data.value);
117
+ },
118
+ ['public-layout-global-css'],
119
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS }
120
+ );
121
+
122
+ const getCachedTranslations = unstable_cache(
123
+ async () => {
124
+ const supabase = createStaticSupabaseClient();
125
+ const { data, error } = await supabase
126
+ .from('translations')
127
+ .select('key, translations, created_at, updated_at')
128
+ .order('key');
129
+
130
+ if (error) {
131
+ console.error('Error fetching cached translations:', error.message);
132
+ return [];
133
+ }
134
+
135
+ return data || [];
136
+ },
137
+ ['public-layout-translations'],
138
+ {
139
+ revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS,
140
+ tags: ['public-layout-translations'],
141
+ }
142
+ );
143
+
144
+ const getCachedCurrencies = unstable_cache(
145
+ async (): Promise<StoreCurrency[]> => {
146
+ const supabase = createStaticSupabaseClient();
147
+ const { data, error } = await supabase
148
+ .from('currencies')
149
+ .select(
150
+ 'id, code, symbol, exchange_rate, is_default, is_active, auto_sync_product_prices, auto_update_exchange_rate, exchange_rate_source, exchange_rate_updated_at, rounding_mode, rounding_increment, rounding_charm_amount, created_at, updated_at'
151
+ )
152
+ .eq('is_active', true)
153
+ .order('code', { ascending: true });
154
+
155
+ if (error) {
156
+ console.error('Error fetching cached currencies:', error.message);
157
+ return [];
158
+ }
159
+
160
+ return data || [];
161
+ },
162
+ ['public-layout-currencies'],
163
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS }
164
+ );
165
+
166
+ const getCachedNavigationMenu = unstable_cache(
167
+ async (menuKey: MenuLocation, languageCode: string): Promise<NavigationItem[]> => {
168
+ const supabase = createStaticSupabaseClient();
169
+
170
+ const { data: language, error: langError } = await supabase
171
+ .from('languages')
172
+ .select('id')
173
+ .eq('code', languageCode)
174
+ .single();
175
+
176
+ if (langError || !language) {
177
+ console.error(
178
+ `Error fetching cached navigation language ${languageCode} for ${menuKey}:`,
179
+ langError
180
+ );
181
+ return [];
182
+ }
183
+
184
+ const { data: items, error: itemsError } = await supabase
185
+ .from('navigation_items')
186
+ .select('*, pages(slug)')
187
+ .eq('menu_key', menuKey)
188
+ .eq('language_id', language.id)
189
+ .order('parent_id', { nullsFirst: true })
190
+ .order('order');
191
+
192
+ if (itemsError) {
193
+ console.error(
194
+ `Error fetching cached navigation items for ${menuKey} (${languageCode}):`,
195
+ itemsError
196
+ );
197
+ return [];
198
+ }
199
+
200
+ return (items || []).map((item) => ({ ...item, id: Number(item.id) }));
201
+ },
202
+ ['public-layout-navigation'],
203
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS }
204
+ );
205
+
206
+ const getCachedActiveLogo = unstable_cache(
207
+ async (): Promise<HeaderLogo | null> => {
208
+ const supabase = createStaticSupabaseClient();
209
+ const { data, error } = await supabase
210
+ .from('logos')
211
+ .select(
212
+ `
213
+ *,
214
+ media:media_id (*)
215
+ `
216
+ )
217
+ .order('created_at', { ascending: false })
218
+ .limit(1)
219
+ .maybeSingle();
220
+
221
+ if (error) {
222
+ console.error('Error fetching cached active logo:', error.message);
223
+ return null;
224
+ }
225
+
226
+ return data as HeaderLogo | null;
227
+ },
228
+ ['public-layout-logo'],
229
+ { revalidate: PUBLIC_LAYOUT_REVALIDATE_SECONDS, tags: [PUBLIC_LAYOUT_LOGO_CACHE_TAG] }
230
+ );
31
231
 
32
232
  async function loadLayoutData() {
33
233
  const supabase = createSupabaseServerClient();
@@ -38,6 +238,7 @@ async function loadLayoutData() {
38
238
 
39
239
  const xUserLocaleHeader = headerList.get('x-user-locale');
40
240
  const nextUserLocaleCookie = cookieStore.get('NEXT_USER_LOCALE')?.value;
241
+ const serverCurrencyCode = cookieStore.get(CURRENCY_COOKIE_NAME)?.value ?? null;
41
242
 
42
243
  let serverDeterminedLocale =
43
244
  xUserLocaleHeader ??
@@ -45,19 +246,31 @@ async function loadLayoutData() {
45
246
  DEFAULT_LOCALE_FOR_LAYOUT;
46
247
 
47
248
  const [
48
- { data: { user } },
249
+ {
250
+ data: { user },
251
+ },
49
252
  availableLanguagesResult,
253
+ currenciesResult,
50
254
  copyrightSettingsResult,
255
+ globalCssResult,
51
256
  translationsResult,
257
+ isEcommerceActive,
258
+ privacySettings,
52
259
  ] = await Promise.all([
53
260
  supabase.auth.getUser(),
54
- getActiveLanguagesServerSide().catch(() => []),
55
- getCopyrightSettings().catch(() => ({ en: '© {year} Nextblock CMS. All rights reserved.' })),
56
- getTranslations().catch(() => []),
261
+ getCachedLanguages().catch(() => getActiveLanguagesServerSide().catch(() => [])),
262
+ getCachedCurrencies().catch(() => []),
263
+ getCachedCopyrightSettings().catch(() => ({
264
+ en: '(c) {year} Nextblock CMS. All rights reserved.',
265
+ })),
266
+ getCachedGlobalCss().catch(() => ''),
267
+ getCachedTranslations().catch(() => []),
268
+ verifyPackageOnline('ecommerce').catch(() => false),
269
+ getPrivacySettings().catch(() => DEFAULT_PRIVACY_SETTINGS),
57
270
  ]);
58
271
 
59
- const profile = user ? await getProfileWithRoleServerSide(user.id) : null;
60
272
  const availableLanguages: Language[] = availableLanguagesResult;
273
+ const availableCurrencies: StoreCurrency[] = currenciesResult;
61
274
  const defaultLanguage: Language | null =
62
275
  availableLanguages.find((lang) => lang.is_default) ?? availableLanguages[0] ?? null;
63
276
 
@@ -69,28 +282,34 @@ async function loadLayoutData() {
69
282
 
70
283
  const copyrightSettings = copyrightSettingsResult as Record<string, string>;
71
284
  const fallbackTemplate =
72
- copyrightSettings['en'] ?? '© {year} Nextblock CMS. All rights reserved.';
73
- const templateForLocale =
74
- copyrightSettings[serverDeterminedLocale] ?? fallbackTemplate;
285
+ copyrightSettings.en ?? '(c) {year} Nextblock CMS. All rights reserved.';
286
+ const templateForLocale = copyrightSettings[serverDeterminedLocale] ?? fallbackTemplate;
75
287
  const copyrightText = templateForLocale.replace('{year}', new Date().getFullYear().toString());
76
288
 
289
+ const globalCss = typeof globalCssResult === 'string' ? globalCssResult : '';
77
290
  const translations = Array.isArray(translationsResult) ? translationsResult : [];
78
291
 
79
- const hasSupabaseEnv =
80
- Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
292
+ const hasSupabaseEnv = Boolean(
293
+ process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
294
+ );
81
295
 
82
- const headerNavItems: NavigationItem[] = await getNavigationMenu('HEADER', serverDeterminedLocale).catch(() => []);
83
- const footerNavItems: NavigationItem[] = await getNavigationMenu('FOOTER', serverDeterminedLocale).catch(() => []);
84
- const logo = await getActiveLogo().catch(() => null);
296
+ const [profile, headerNavItems, footerNavItems, logo] = await Promise.all([
297
+ user ? getProfileWithRoleServerSide(user.id) : Promise.resolve(null),
298
+ getCachedNavigationMenu('HEADER', serverDeterminedLocale).catch(() => []),
299
+ getCachedNavigationMenu('FOOTER', serverDeterminedLocale).catch(() => []),
300
+ getCachedActiveLogo().catch(() => null),
301
+ ]);
85
302
 
86
303
  const role = profile?.role ?? null;
87
304
  const canAccessCms = role === 'ADMIN' || role === 'WRITER';
88
- const siteTitle = logo?.site_title ?? 'Nextblock';
305
+ const { siteTitle } = await getSiteSettings();
89
306
 
90
307
  return {
91
308
  user,
92
309
  profile,
93
310
  serverDeterminedLocale,
311
+ availableCurrencies,
312
+ serverCurrencyCode,
94
313
  availableLanguages,
95
314
  defaultLanguage,
96
315
  translations,
@@ -102,47 +321,63 @@ async function loadLayoutData() {
102
321
  logo,
103
322
  canAccessCms,
104
323
  siteTitle,
324
+ isEcommerceActive,
325
+ globalCss,
326
+ privacySettings,
105
327
  };
106
328
  }
107
329
 
108
- export const metadata: Metadata = {
109
- metadataBase: new URL(defaultUrl),
110
- title: 'Nextblock CMS',
111
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
112
- openGraph: {
113
- title: 'Nextblock CMS',
114
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
115
- url: defaultUrl,
116
- siteName: 'Nextblock CMS',
117
- images: [
118
- {
119
- url: '/images/metadata_image.webp',
120
- width: 1200,
121
- height: 630,
122
- alt: 'Nextblock CMS',
123
- },
124
- ],
125
- locale: 'en_US',
126
- type: 'website',
127
- },
128
- twitter: {
129
- card: 'summary_large_image',
130
- title: 'Nextblock CMS',
131
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
132
- images: ['/images/metadata_image.webp'],
133
- },
134
- icons: {
135
- icon: [
136
- { url: '/favicon/favicon.ico' },
137
- { url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
138
- { url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
139
- ],
140
- apple: [
141
- { url: '/favicon/apple-touch-icon.png' },
142
- ],
143
- },
144
- manifest: '/favicon/site.webmanifest',
145
- };
330
+ export async function generateMetadata(): Promise<Metadata> {
331
+ const { siteTitle, siteDescription, siteKeywords } = await getSiteSettings();
332
+ const isSandbox = process.env.NEXT_PUBLIC_IS_SANDBOX === 'true';
333
+
334
+ return {
335
+ metadataBase: new URL(defaultUrl),
336
+ title: {
337
+ default: siteTitle,
338
+ template: `%s | ${siteTitle}`,
339
+ },
340
+ description: siteDescription,
341
+ keywords: siteKeywords,
342
+ applicationName: siteTitle,
343
+ openGraph: {
344
+ title: siteTitle,
345
+ description: siteDescription,
346
+ url: defaultUrl,
347
+ siteName: siteTitle,
348
+ images: [
349
+ {
350
+ url: DEFAULT_OG_IMAGE,
351
+ width: 1200,
352
+ height: 630,
353
+ alt: siteTitle,
354
+ },
355
+ ],
356
+ locale: 'en_US',
357
+ type: 'website',
358
+ },
359
+ twitter: {
360
+ card: 'summary_large_image',
361
+ title: siteTitle,
362
+ description: siteDescription,
363
+ images: [DEFAULT_OG_IMAGE],
364
+ },
365
+ icons: {
366
+ icon: [
367
+ { url: '/favicon/favicon.ico' },
368
+ { url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
369
+ { url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
370
+ ],
371
+ apple: [{ url: '/favicon/apple-touch-icon.png' }],
372
+ },
373
+ manifest: '/favicon/site.webmanifest',
374
+ // Sandbox is a copy of production, so keep it out of the index. Use
375
+ // `noindex, follow` (not nofollow) so Googlebot still follows internal links,
376
+ // recrawls every page, and drops them all — paired with an allow-crawl
377
+ // robots.txt (see app/robots.txt/route.ts) so the noindex is actually seen.
378
+ robots: isSandbox ? { index: false, follow: true } : { index: true, follow: true },
379
+ };
380
+ }
146
381
 
147
382
  export default async function RootLayout({
148
383
  children,
@@ -152,75 +387,92 @@ export default async function RootLayout({
152
387
  const {
153
388
  user,
154
389
  profile,
155
- serverDeterminedLocale,
156
- availableLanguages,
157
- defaultLanguage,
158
- translations,
159
- copyrightText,
160
- nonce,
161
- hasSupabaseEnv,
162
- headerNavItems,
163
- logo,
390
+ serverDeterminedLocale,
391
+ availableCurrencies,
392
+ serverCurrencyCode,
393
+ availableLanguages,
394
+ defaultLanguage,
395
+ translations,
396
+ copyrightText,
397
+ nonce,
398
+ hasSupabaseEnv,
399
+ headerNavItems,
400
+ logo,
164
401
  footerNavItems,
165
- canAccessCms,
166
- siteTitle,
167
- } = await loadLayoutData();
402
+ canAccessCms,
403
+ siteTitle,
404
+ isEcommerceActive,
405
+ globalCss,
406
+ privacySettings,
407
+ } = await loadLayoutData();
408
+ const draft = await draftMode();
409
+ const resolvedGtmId = privacySettings.gtm_id || process.env.NEXT_PUBLIC_GTM_ID || '';
410
+ const visualEditingEnabled =
411
+ draft.isEnabled || process.env.NEXTBLOCK_VISUAL_EDITING_ENABLED === 'true';
412
+ const isVercelDeployment = process.env.VERCEL === '1';
413
+ const toolbarEnabled =
414
+ process.env.NEXTBLOCK_VERCEL_TOOLBAR_ENABLED === 'true' ||
415
+ (isVercelDeployment && visualEditingEnabled);
416
+ const Toolbar = toolbarEnabled
417
+ ? (await import('@vercel/toolbar/next')).VercelToolbar
418
+ : null;
168
419
 
169
420
  return (
170
421
  <html lang={serverDeterminedLocale} suppressHydrationWarning>
171
422
  <head>
172
- <title>{metadata.title as string}</title>
173
- <meta name="description" content={metadata.description as string} />
174
- <link rel="preconnect" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
175
- <link rel="dns-prefetch" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
176
- <link rel="dns-prefetch" href="https://aws-0-us-east-1.pooler.supabase.com" />
177
- <link rel="dns-prefetch" href="https://db.ppcppwsfnrptznvbxnsz.supabase.co" />
178
- <link rel="dns-prefetch" href="https://realtime.supabase.com" />
179
423
  <meta name="viewport" content="width=device-width, initial-scale=1" />
180
- <SpeedInsights/>
424
+ {globalCss && <style dangerouslySetInnerHTML={{ __html: globalCss }} />}
181
425
  </head>
182
- <body className="bg-background text-foreground min-h-screen flex flex-col">
426
+ <body className="min-h-screen">
427
+ {/* In development this loads after hydration to avoid browser-hidden nonce comparisons. */}
428
+ <Script
429
+ id="trusted-types-bootstrap"
430
+ strategy={TRUSTED_TYPES_SCRIPT_STRATEGY}
431
+ nonce={nonce}
432
+ dangerouslySetInnerHTML={{ __html: TRUSTED_TYPES_BOOTSTRAP }}
433
+ />
183
434
  <Providers
184
435
  serverUser={user}
185
436
  serverProfile={profile}
186
437
  serverLocale={serverDeterminedLocale}
438
+ initialCurrencies={availableCurrencies}
439
+ initialCurrencyCode={serverCurrencyCode}
187
440
  initialAvailableLanguages={availableLanguages}
188
441
  initialDefaultLanguage={defaultLanguage}
189
442
  translations={translations}
190
443
  nonce={nonce}
191
444
  >
192
- {process.env.NEXT_PUBLIC_IS_SANDBOX === 'true' && <SandboxBanner />}
193
445
  <ToasterProvider />
194
- <div className="flex-1 w-full flex flex-col items-center">
195
- <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
196
- <div className="w-full max-w-7xl flex justify-between items-center p-3 px-5 text-sm">
197
- {!hasSupabaseEnv ? (
198
- <EnvVarWarning />
199
- ) : (
200
- <Header
201
- navItems={headerNavItems}
202
- canAccessCms={canAccessCms}
203
- logo={logo}
204
- siteTitle={siteTitle}
205
- />
206
- )}
207
- </div>
208
- </nav>
209
- <main className="flex-grow w-full">
210
- {children}
211
- </main>
212
- <footer className="w-full border-t py-8">
213
- <div className="mx-auto flex flex-col items-center justify-center gap-6 text-center text-xs">
214
- <FooterNavigation navItems={footerNavItems} />
215
- <div className="flex flex-row items-center gap-2">
216
- <p className="text-muted-foreground">{copyrightText}</p>
217
- <ThemeSwitcher />
218
- </div>
219
- </div>
220
- </footer>
221
- </div>
446
+ <AppShell
447
+ canAccessCms={canAccessCms}
448
+ copyrightText={copyrightText}
449
+ corporateFooter={{
450
+ legalName: privacySettings.corporate.legal_name,
451
+ address: privacySettings.corporate.address,
452
+ supportEmail: privacySettings.corporate.support_email,
453
+ }}
454
+ footerNavItems={footerNavItems}
455
+ hasSupabaseEnv={hasSupabaseEnv}
456
+ headerNavItems={headerNavItems}
457
+ isDraftModeEnabled={draft.isEnabled}
458
+ isEcommerceActive={isEcommerceActive}
459
+ logo={logo}
460
+ siteTitle={siteTitle}
461
+ >
462
+ {children}
463
+ </AppShell>
464
+
465
+ {isEcommerceActive && <DeferredCartDrawer />}
466
+ {visualEditingEnabled && <DeferredVisualEditing />}
467
+ {Toolbar && <Toolbar nonce={nonce} />}
468
+ {privacySettings.banner_enabled && <ConsentBanner />}
222
469
  </Providers>
223
- <GoogleTagManager gtmId={process.env.NEXT_PUBLIC_GTM_ID || ""} />
470
+ <DeferredSpeedInsights />
471
+ <ConsentGatedAnalytics
472
+ gtmId={resolvedGtmId}
473
+ customScripts={privacySettings.custom_scripts}
474
+ nonce={nonce}
475
+ />
224
476
  </body>
225
477
  </html>
226
478
  );
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ extractIntroExcerptFromBlocks,
4
+ resolveMetaTitle,
5
+ resolvePageMetaDescription,
6
+ resolvePostMetaDescription,
7
+ resolveProductMetaDescription,
8
+ stringifyJsonLd,
9
+ } from "./seo";
10
+
11
+ describe("seo helpers", () => {
12
+ it("uses manual title only when it contains text", () => {
13
+ expect(resolveMetaTitle(" Custom SEO Title ", "Fallback Title")).toBe("Custom SEO Title");
14
+ expect(resolveMetaTitle(" ", "Fallback Title")).toBe("Fallback Title");
15
+ });
16
+
17
+ it("resolves page descriptions from the first meaningful paragraph", () => {
18
+ const blocks = [
19
+ {
20
+ block_type: "text",
21
+ content: {
22
+ html_content: "<h1>Hero heading</h1>",
23
+ },
24
+ },
25
+ {
26
+ block_type: "text",
27
+ content: {
28
+ html_content:
29
+ "<p>NextBlock gives teams a fast CMS foundation with editable content, commerce, and production-ready metadata.</p>",
30
+ },
31
+ },
32
+ ];
33
+
34
+ expect(extractIntroExcerptFromBlocks(blocks)).toBe(
35
+ "NextBlock gives teams a fast CMS foundation with editable content, commerce, and production-ready metadata."
36
+ );
37
+ expect(resolvePageMetaDescription(null, blocks)).toBe(
38
+ "NextBlock gives teams a fast CMS foundation with editable content, commerce, and production-ready metadata."
39
+ );
40
+ });
41
+
42
+ it("uses type-specific description fallbacks", () => {
43
+ expect(resolvePostMetaDescription(null, "Post subtitle")).toBe("Post subtitle");
44
+ expect(resolveProductMetaDescription(null, "<p>Short product description.</p>")).toBe(
45
+ "Short product description."
46
+ );
47
+ });
48
+
49
+ it("escapes JSON-LD closing tags", () => {
50
+ expect(stringifyJsonLd({ name: "</script>" })).toContain("\\u003c/script>");
51
+ });
52
+ });