Version not found. Please check the version and try again.

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
@@ -1,30 +1,233 @@
1
1
  import '@nextblock-cms/ui/styles/globals.css';
2
- import '@nextblock-cms/editor/styles/editor.css';
3
2
  // app/layout.tsx
4
- import { EnvVarWarning } from "@/components/env-var-warning";
5
- import { SandboxBanner } from "@/components/SandboxBanner";
6
- import { Analytics } from "@vercel/analytics/next"
7
- import { ThemeSwitcher } from '@/components/theme-switcher';
3
+
8
4
  import type { Metadata } from 'next';
9
- import Header from "@/components/Header";
10
- import FooterNavigation from "@/components/FooterNavigation";
5
+ import Script from 'next/script';
11
6
  import { Providers } from './providers';
7
+ import { DeferredCartDrawer } from '../components/DeferredCartDrawer';
8
+ import { CURRENCY_COOKIE_NAME } from '@nextblock-cms/ecommerce/currency-constants';
12
9
  import { ToasterProvider } from './ToasterProvider';
13
- 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';
14
21
  import { getActiveLanguagesServerSide } from '@nextblock-cms/db/server';
15
- import { getNavigationMenu } from '@/app/cms/navigation/actions';
16
- import { getActiveLogo } from '@/app/cms/settings/logos/actions';
17
- import { getCopyrightSettings } from '@/app/cms/settings/copyright/actions';
18
- import { getTranslations } from '@/app/cms/settings/extra-translations/actions';
19
22
  import type { Database } from '@nextblock-cms/db';
20
- 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';
21
28
 
22
- const defaultUrl = process.env.NEXT_PUBLIC_URL || "http://localhost:3000";
29
+ const defaultUrl = process.env.NEXT_PUBLIC_URL || 'http://localhost:3000';
23
30
 
24
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
+ `;
25
50
 
26
51
  type Language = Database['public']['Tables']['languages']['Row'];
52
+ type StoreCurrency = Database['public']['Tables']['currencies']['Row'];
27
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
+ );
28
231
 
29
232
  async function loadLayoutData() {
30
233
  const supabase = createSupabaseServerClient();
@@ -35,6 +238,7 @@ async function loadLayoutData() {
35
238
 
36
239
  const xUserLocaleHeader = headerList.get('x-user-locale');
37
240
  const nextUserLocaleCookie = cookieStore.get('NEXT_USER_LOCALE')?.value;
241
+ const serverCurrencyCode = cookieStore.get(CURRENCY_COOKIE_NAME)?.value ?? null;
38
242
 
39
243
  let serverDeterminedLocale =
40
244
  xUserLocaleHeader ??
@@ -42,19 +246,31 @@ async function loadLayoutData() {
42
246
  DEFAULT_LOCALE_FOR_LAYOUT;
43
247
 
44
248
  const [
45
- { data: { user } },
249
+ {
250
+ data: { user },
251
+ },
46
252
  availableLanguagesResult,
253
+ currenciesResult,
47
254
  copyrightSettingsResult,
255
+ globalCssResult,
48
256
  translationsResult,
257
+ isEcommerceActive,
258
+ privacySettings,
49
259
  ] = await Promise.all([
50
260
  supabase.auth.getUser(),
51
- getActiveLanguagesServerSide().catch(() => []),
52
- getCopyrightSettings().catch(() => ({ en: '© {year} Nextblock CMS. All rights reserved.' })),
53
- 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),
54
270
  ]);
55
271
 
56
- const profile = user ? await getProfileWithRoleServerSide(user.id) : null;
57
272
  const availableLanguages: Language[] = availableLanguagesResult;
273
+ const availableCurrencies: StoreCurrency[] = currenciesResult;
58
274
  const defaultLanguage: Language | null =
59
275
  availableLanguages.find((lang) => lang.is_default) ?? availableLanguages[0] ?? null;
60
276
 
@@ -66,28 +282,34 @@ async function loadLayoutData() {
66
282
 
67
283
  const copyrightSettings = copyrightSettingsResult as Record<string, string>;
68
284
  const fallbackTemplate =
69
- copyrightSettings['en'] ?? '© {year} Nextblock CMS. All rights reserved.';
70
- const templateForLocale =
71
- copyrightSettings[serverDeterminedLocale] ?? fallbackTemplate;
285
+ copyrightSettings.en ?? '(c) {year} Nextblock CMS. All rights reserved.';
286
+ const templateForLocale = copyrightSettings[serverDeterminedLocale] ?? fallbackTemplate;
72
287
  const copyrightText = templateForLocale.replace('{year}', new Date().getFullYear().toString());
73
288
 
289
+ const globalCss = typeof globalCssResult === 'string' ? globalCssResult : '';
74
290
  const translations = Array.isArray(translationsResult) ? translationsResult : [];
75
291
 
76
- const hasSupabaseEnv =
77
- 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
+ );
78
295
 
79
- const headerNavItems: NavigationItem[] = await getNavigationMenu('HEADER', serverDeterminedLocale).catch(() => []);
80
- const footerNavItems: NavigationItem[] = await getNavigationMenu('FOOTER', serverDeterminedLocale).catch(() => []);
81
- 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
+ ]);
82
302
 
83
303
  const role = profile?.role ?? null;
84
304
  const canAccessCms = role === 'ADMIN' || role === 'WRITER';
85
- const siteTitle = logo?.site_title ?? 'Nextblock';
305
+ const { siteTitle } = await getSiteSettings();
86
306
 
87
307
  return {
88
308
  user,
89
309
  profile,
90
310
  serverDeterminedLocale,
311
+ availableCurrencies,
312
+ serverCurrencyCode,
91
313
  availableLanguages,
92
314
  defaultLanguage,
93
315
  translations,
@@ -99,47 +321,63 @@ async function loadLayoutData() {
99
321
  logo,
100
322
  canAccessCms,
101
323
  siteTitle,
324
+ isEcommerceActive,
325
+ globalCss,
326
+ privacySettings,
102
327
  };
103
328
  }
104
329
 
105
- export const metadata: Metadata = {
106
- metadataBase: new URL(defaultUrl),
107
- title: 'Nextblock CMS',
108
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
109
- openGraph: {
110
- title: 'Nextblock CMS',
111
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
112
- url: defaultUrl,
113
- siteName: 'Nextblock CMS',
114
- images: [
115
- {
116
- url: '/images/metadata_image.webp',
117
- width: 1200,
118
- height: 630,
119
- alt: 'Nextblock CMS',
120
- },
121
- ],
122
- locale: 'en_US',
123
- type: 'website',
124
- },
125
- twitter: {
126
- card: 'summary_large_image',
127
- title: 'Nextblock CMS',
128
- description: 'Nextblock CMS pairs a visual block editor with a blazing-fast Next.js + Supabase architecture.',
129
- images: ['/images/metadata_image.webp'],
130
- },
131
- icons: {
132
- icon: [
133
- { url: '/favicon/favicon.ico' },
134
- { url: '/favicon/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
135
- { url: '/favicon/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
136
- ],
137
- apple: [
138
- { url: '/favicon/apple-touch-icon.png' },
139
- ],
140
- },
141
- manifest: '/favicon/site.webmanifest',
142
- };
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
+ }
143
381
 
144
382
  export default async function RootLayout({
145
383
  children,
@@ -149,74 +387,92 @@ export default async function RootLayout({
149
387
  const {
150
388
  user,
151
389
  profile,
152
- serverDeterminedLocale,
153
- availableLanguages,
154
- defaultLanguage,
155
- translations,
156
- copyrightText,
157
- nonce,
158
- hasSupabaseEnv,
159
- headerNavItems,
160
- logo,
390
+ serverDeterminedLocale,
391
+ availableCurrencies,
392
+ serverCurrencyCode,
393
+ availableLanguages,
394
+ defaultLanguage,
395
+ translations,
396
+ copyrightText,
397
+ nonce,
398
+ hasSupabaseEnv,
399
+ headerNavItems,
400
+ logo,
161
401
  footerNavItems,
162
- canAccessCms,
163
- siteTitle,
164
- } = 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;
165
419
 
166
420
  return (
167
421
  <html lang={serverDeterminedLocale} suppressHydrationWarning>
168
422
  <head>
169
- <title>{metadata.title as string}</title>
170
- <meta name="description" content={metadata.description as string} />
171
- <link rel="preconnect" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
172
- <link rel="dns-prefetch" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
173
- <link rel="dns-prefetch" href="https://aws-0-us-east-1.pooler.supabase.com" />
174
- <link rel="dns-prefetch" href="https://db.ppcppwsfnrptznvbxnsz.supabase.co" />
175
- <link rel="dns-prefetch" href="https://realtime.supabase.com" />
176
423
  <meta name="viewport" content="width=device-width, initial-scale=1" />
177
- <Analytics/>
424
+ {globalCss && <style dangerouslySetInnerHTML={{ __html: globalCss }} />}
178
425
  </head>
179
- <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
+ />
180
434
  <Providers
181
435
  serverUser={user}
182
436
  serverProfile={profile}
183
437
  serverLocale={serverDeterminedLocale}
438
+ initialCurrencies={availableCurrencies}
439
+ initialCurrencyCode={serverCurrencyCode}
184
440
  initialAvailableLanguages={availableLanguages}
185
441
  initialDefaultLanguage={defaultLanguage}
186
442
  translations={translations}
187
443
  nonce={nonce}
188
444
  >
189
- {process.env.NEXT_PUBLIC_IS_SANDBOX === 'true' && <SandboxBanner />}
190
445
  <ToasterProvider />
191
- <div className="flex-1 w-full flex flex-col items-center">
192
- <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
193
- <div className="w-full max-w-7xl flex justify-between items-center p-3 px-5 text-sm">
194
- {!hasSupabaseEnv ? (
195
- <EnvVarWarning />
196
- ) : (
197
- <Header
198
- navItems={headerNavItems}
199
- canAccessCms={canAccessCms}
200
- logo={logo}
201
- siteTitle={siteTitle}
202
- />
203
- )}
204
- </div>
205
- </nav>
206
- <main className="flex-grow w-full">
207
- {children}
208
- </main>
209
- <footer className="w-full border-t py-8">
210
- <div className="mx-auto flex flex-col items-center justify-center gap-6 text-center text-xs">
211
- <FooterNavigation navItems={footerNavItems} />
212
- <div className="flex flex-row items-center gap-2">
213
- <p className="text-muted-foreground">{copyrightText}</p>
214
- <ThemeSwitcher />
215
- </div>
216
- </div>
217
- </footer>
218
- </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 />}
219
469
  </Providers>
470
+ <DeferredSpeedInsights />
471
+ <ConsentGatedAnalytics
472
+ gtmId={resolvedGtmId}
473
+ customScripts={privacySettings.custom_scripts}
474
+ nonce={nonce}
475
+ />
220
476
  </body>
221
477
  </html>
222
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
+ });