create-nextblock 0.2.77 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +191 -151
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -116
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui";
3
+ import { Card, CardContent } from "@nextblock-cms/ui";
4
+ import { MessageSquareQuote } from "lucide-react";
5
+ import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
6
+
7
+ type TestimonialBlockContent = {
8
+ quote?: string;
9
+ author_name?: string;
10
+ author_title?: string;
11
+ image_url?: string;
12
+ };
13
+
14
+ interface TestimonialBlockRendererProps {
15
+ content: TestimonialBlockContent;
16
+ visualEditAttributes?: VisualEditAttributes;
17
+ }
18
+
19
+ const TestimonialBlockRenderer: React.FC<TestimonialBlockRendererProps> = ({
20
+ content,
21
+ visualEditAttributes,
22
+ }) => {
23
+ const authorName = content.author_name || "Customer";
24
+
25
+ return (
26
+ <div className="container m-8" {...visualEditAttributes}>
27
+ <Card className="h-full">
28
+ <CardContent className="pt-6 flex flex-col gap-4 h-full">
29
+ <MessageSquareQuote className="w-8 h-8 text-primary/40" />
30
+ {content.quote && (
31
+ <blockquote className="flex-grow text-lg italic text-muted-foreground">
32
+ &quot;{content.quote}&quot;
33
+ </blockquote>
34
+ )}
35
+ <div className="flex items-center gap-3 mt-4">
36
+ <Avatar>
37
+ {content.image_url && (
38
+ <AvatarImage src={content.image_url} alt={authorName} />
39
+ )}
40
+ <AvatarFallback>{authorName.slice(0, 2).toUpperCase()}</AvatarFallback>
41
+ </Avatar>
42
+ <div>
43
+ <div className="font-semibold">{authorName}</div>
44
+ {content.author_title && (
45
+ <div className="text-sm text-muted-foreground">
46
+ {content.author_title}
47
+ </div>
48
+ )}
49
+ </div>
50
+ </div>
51
+ </CardContent>
52
+ </Card>
53
+ </div>
54
+ );
55
+ };
56
+
57
+ export default TestimonialBlockRenderer;
@@ -1,31 +1,46 @@
1
- import React from "react";
2
- import { headers } from 'next/headers';
3
- import ClientTextBlockRenderer from "./ClientTextBlockRenderer";
1
+ import React from "react";
2
+ import { headers } from 'next/headers';
3
+ import ClientTextBlockRenderer from "./ClientTextBlockRenderer";
4
+ import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
4
5
 
5
6
  export type TextBlockContent = {
6
7
  html_content?: string;
7
8
  };
8
9
 
9
- interface TextBlockRendererProps {
10
- content: TextBlockContent;
11
- languageId: number;
10
+ interface TextBlockRendererProps {
11
+ content: TextBlockContent;
12
+ languageId: number;
13
+ visualEditAttributes?: VisualEditAttributes;
14
+ renderContext?: 'prose' | 'section';
15
+ }
16
+
17
+ function addNonceToInlineScripts(html: string, nonce: string): string {
18
+ if (!html || !nonce) return html || '';
19
+ // Add nonce to <script> tags that do not already have a nonce
20
+ // and do not have a src attribute (inline scripts)
21
+ return html.replace(/<script(?![^>]*\bsrc=)([^>]*)(?<!nonce=["'][^"']*["'])>/gi, (_m, attrs) => {
22
+ return `<script nonce="${nonce}"${attrs}>`;
23
+ });
12
24
  }
13
25
 
14
- function addNonceToInlineScripts(html: string, nonce: string): string {
15
- if (!html || !nonce) return html || '';
16
- // Add nonce to <script> tags that do not already have a nonce
17
- // and do not have a src attribute (inline scripts)
18
- return html.replace(/<script(?![^>]*\bsrc=)([^>]*)(?<!nonce=["'][^"']*["'])>/gi, (_m, attrs) => {
19
- return `<script nonce="${nonce}"${attrs}>`;
20
- });
21
- }
22
-
23
- const TextBlockRenderer: React.FC<TextBlockRendererProps> = async ({ content, languageId }) => {
24
- const hdrs = await headers();
25
- const nonce = hdrs.get('x-nonce') || '';
26
- const htmlWithNonce = content.html_content ? addNonceToInlineScripts(content.html_content, nonce) : '';
27
- const patchedContent = { ...content, html_content: htmlWithNonce };
28
- return <ClientTextBlockRenderer content={patchedContent} languageId={languageId} />;
26
+ const TextBlockRenderer: React.FC<TextBlockRendererProps> = async ({
27
+ content,
28
+ languageId,
29
+ visualEditAttributes,
30
+ renderContext = 'prose',
31
+ }) => {
32
+ const hdrs = await headers();
33
+ const nonce = hdrs.get('x-nonce') || '';
34
+ const htmlWithNonce = content.html_content ? addNonceToInlineScripts(content.html_content, nonce) : '';
35
+ const patchedContent = { ...content, html_content: htmlWithNonce };
36
+ return (
37
+ <ClientTextBlockRenderer
38
+ content={patchedContent}
39
+ languageId={languageId}
40
+ visualEditAttributes={visualEditAttributes}
41
+ renderContext={renderContext}
42
+ />
43
+ );
29
44
  };
30
45
 
31
- export default TextBlockRenderer;
46
+ export default TextBlockRenderer;
@@ -1,24 +1,32 @@
1
1
  import React from "react";
2
- import { validateBlockContent, VideoEmbedBlockContent } from "@/lib/blocks/blockRegistry";
2
+ import type { VideoEmbedBlockContent } from '../../../lib/blocks/blockRegistry';
3
+ import type { VisualEditAttributes } from "../../../lib/visual-editing/types";
3
4
 
4
5
  interface VideoEmbedBlockRendererProps {
5
6
  content: VideoEmbedBlockContent;
6
7
  languageId: number;
8
+ visualEditAttributes?: VisualEditAttributes;
7
9
  }
8
10
 
9
- const VideoEmbedBlockRenderer: React.FC<VideoEmbedBlockRendererProps> = ({
10
- content,
11
- languageId,
12
- }) => {
13
- void languageId;
14
- // Optional: Validate content against registry schema
15
- const validation = validateBlockContent("video_embed", content);
16
- if (!validation.isValid) {
17
- console.warn("Invalid video embed content:", validation.errors);
18
- }
19
-
11
+ const VideoEmbedBlockRenderer: React.FC<VideoEmbedBlockRendererProps> = ({
12
+ content,
13
+ languageId,
14
+ visualEditAttributes,
15
+ }) => {
16
+ void languageId;
20
17
  if (!content.url) {
21
- return null;
18
+ if (!visualEditAttributes) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <div
24
+ className="my-4 p-4 border rounded text-center text-muted-foreground italic"
25
+ {...visualEditAttributes}
26
+ >
27
+ (Video block: URL missing)
28
+ </div>
29
+ );
22
30
  }
23
31
 
24
32
  // Convert YouTube URLs to embed format
@@ -39,7 +47,7 @@ const VideoEmbedBlockRenderer: React.FC<VideoEmbedBlockRendererProps> = ({
39
47
  };
40
48
 
41
49
  return (
42
- <div className="my-4">
50
+ <div className="my-4" {...visualEditAttributes}>
43
51
  {content.title && (
44
52
  <h3 className="text-lg font-semibold mb-2">{content.title}</h3>
45
53
  )}
@@ -56,4 +64,4 @@ const VideoEmbedBlockRenderer: React.FC<VideoEmbedBlockRendererProps> = ({
56
64
  );
57
65
  };
58
66
 
59
- export default VideoEmbedBlockRenderer;
67
+ export default VideoEmbedBlockRenderer;
@@ -1,5 +1,3 @@
1
- 'use client';
2
-
3
1
  interface AlertWidgetRendererProps {
4
2
  type: 'info' | 'warning' | 'notification' | 'danger';
5
3
  title: string;
@@ -48,4 +46,4 @@ const AlertWidgetRenderer = ({ type, title, message, align, size, textAlign }: A
48
46
  );
49
47
  };
50
48
 
51
- export default AlertWidgetRenderer;
49
+ export default AlertWidgetRenderer;
@@ -1,5 +1,3 @@
1
- 'use client';
2
-
3
1
  import Link from 'next/link';
4
2
 
5
3
  interface CtaWidgetRendererProps {
@@ -37,4 +35,4 @@ const CtaWidgetRenderer = ({ text, url, style, size, textAlign }: CtaWidgetRende
37
35
  );
38
36
  };
39
37
 
40
- export default CtaWidgetRenderer;
38
+ export default CtaWidgetRenderer;
@@ -1,8 +1,9 @@
1
1
  import type { Database } from '@nextblock-cms/db';
2
2
 
3
- export type PostWithMediaDimensions = Database['public']['Tables']['posts']['Row'] & {
4
- feature_image_url: string | null;
5
- feature_image_width: number | null;
6
- feature_image_height: number | null;
7
- blur_data_url: string | null;
8
- };
3
+ export type PostWithMediaDimensions = Database['public']['Tables']['posts']['Row'] & {
4
+ feature_image_url: string | null;
5
+ feature_image_width: number | null;
6
+ feature_image_height: number | null;
7
+ blur_data_url: string | null;
8
+ estimated_read_time_minutes: number;
9
+ };
@@ -1,6 +1,6 @@
1
- import Link from "next/link";
2
- import { Badge } from "@nextblock-cms/ui";
3
- import { Button } from "@nextblock-cms/ui";
1
+ import Link from "next/link";
2
+ import { Badge } from "@nextblock-cms/ui";
3
+ import { Button } from "@nextblock-cms/ui";
4
4
 
5
5
  export function EnvVarWarning() {
6
6
  return (
@@ -1,26 +1,32 @@
1
- export type Message =
2
- | { success: string }
3
- | { error: string }
4
- | { message: string };
5
-
6
- export function FormMessage({ message }: { message?: Message }) {
7
- if (!message) return null;
8
-
9
- return (
10
- <div className="flex flex-col gap-2 w-full max-w-md text-sm">
11
- {"success" in message && message.success && (
12
- <div className="text-foreground border-l-2 border-foreground px-4">
13
- {message.success}
14
- </div>
15
- )}
16
- {"error" in message && message.error && (
17
- <div className="text-destructive-foreground border-l-2 border-destructive-foreground px-4">
18
- {message.error}
19
- </div>
20
- )}
21
- {"message" in message && message.message && (
22
- <div className="text-foreground border-l-2 px-4">{message.message}</div>
23
- )}
24
- </div>
25
- );
26
- }
1
+ 'use client';
2
+
3
+ import { useTranslations } from "@nextblock-cms/utils";
4
+
5
+ export type Message =
6
+ | { success: string }
7
+ | { error: string }
8
+ | { message: string };
9
+
10
+ export function FormMessage({ message }: { message?: Message }) {
11
+ const { t } = useTranslations();
12
+
13
+ if (!message) return null;
14
+
15
+ return (
16
+ <div className="flex flex-col gap-2 w-full max-w-md text-sm">
17
+ {"success" in message && message.success && (
18
+ <div className="text-foreground border-l-2 border-foreground px-4">
19
+ {t(message.success)}
20
+ </div>
21
+ )}
22
+ {"error" in message && message.error && (
23
+ <div className="text-destructive border-l-2 border-destructive px-4">
24
+ {t(message.error)}
25
+ </div>
26
+ )}
27
+ {"message" in message && message.message && (
28
+ <div className="text-foreground border-l-2 px-4">{t(message.message)}</div>
29
+ )}
30
+ </div>
31
+ );
32
+ }
@@ -1,17 +1,33 @@
1
1
  'use client';
2
2
 
3
- import { signOutAction } from "@/app/actions";
4
- import { hasPublicEnvVars } from "@nextblock-cms/utils";
5
- import Link from "next/link";
6
- import { Badge } from "@nextblock-cms/ui";
7
- import { Button } from "@nextblock-cms/ui";
8
- import { useAuth } from "@/context/AuthContext";
9
- import { useTranslations } from "@nextblock-cms/utils";
3
+ import { hasPublicEnvVars } from "@nextblock-cms/utils";
4
+ import Link from "next/link";
5
+ import { Avatar, AvatarFallback, AvatarImage } from "@nextblock-cms/ui";
6
+ import { Badge } from "@nextblock-cms/ui";
7
+ import { Button } from "@nextblock-cms/ui";
8
+ import { signOutAction } from "../app/actions";
9
+ import { useAuth } from "../context/AuthContext";
10
+ import { useTranslations } from "@nextblock-cms/utils";
11
+
12
+ import {
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuLabel,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuTrigger,
19
+ } from "@nextblock-cms/ui";
20
+ import { User, LogOut, LayoutDashboard } from "lucide-react";
10
21
 
11
- export default function AuthButton() {
12
- const { user, profile } = useAuth();
13
- const { t } = useTranslations();
14
- const username = profile?.username || null;
22
+ export default function AuthButton() {
23
+ const { user, profile, isAdmin, isWriter } = useAuth();
24
+ const { t } = useTranslations();
25
+ const displayName = profile?.full_name || profile?.github_username || user?.email || null;
26
+ const showAdminLink = isAdmin || isWriter;
27
+
28
+ const handleSignOut = async () => {
29
+ await signOutAction();
30
+ };
15
31
 
16
32
  if (!hasPublicEnvVars) {
17
33
  return (
@@ -51,12 +67,48 @@ export default function AuthButton() {
51
67
  }
52
68
  return user ? (
53
69
  <div className="flex items-center gap-4">
54
- {t('greeting', { username: username || user.email || 'User' })}
55
- <form action={signOutAction}>
56
- <Button type="submit" variant={"outline"}>
57
- {t('sign_out')}
58
- </Button>
59
- </form>
70
+ <DropdownMenu>
71
+ <DropdownMenuTrigger asChild>
72
+ <Button variant="ghost" className="relative h-8 w-8 rounded-full p-0">
73
+ <Avatar className="h-8 w-8 transition-all hover:ring-1 hover:ring-primary">
74
+ <AvatarImage src={profile?.avatar_url || undefined} alt={displayName || 'User'} />
75
+ <AvatarFallback className="bg-muted">
76
+ {displayName ? displayName.charAt(0).toUpperCase() : <User className="h-4 w-4" />}
77
+ </AvatarFallback>
78
+ </Avatar>
79
+ </Button>
80
+ </DropdownMenuTrigger>
81
+ <DropdownMenuContent className="w-56" align="end" forceMount>
82
+ <DropdownMenuLabel className="font-normal">
83
+ <div className="flex flex-col space-y-1">
84
+ <p className="text-sm font-medium leading-none">{displayName || 'User'}</p>
85
+ <p className="text-xs leading-none text-muted-foreground">
86
+ {user.email}
87
+ </p>
88
+ </div>
89
+ </DropdownMenuLabel>
90
+ <DropdownMenuSeparator />
91
+ <DropdownMenuItem asChild>
92
+ <Link href="/profile" className="cursor-pointer">
93
+ <User className="mr-2 h-4 w-4" />
94
+ <span>{t('profile') || 'Profile'}</span>
95
+ </Link>
96
+ </DropdownMenuItem>
97
+ {showAdminLink && (
98
+ <DropdownMenuItem asChild>
99
+ <Link href="/cms/dashboard" className="cursor-pointer">
100
+ <LayoutDashboard className="mr-2 h-4 w-4" />
101
+ <span>{t('cms_dashboard')}</span>
102
+ </Link>
103
+ </DropdownMenuItem>
104
+ )}
105
+ <DropdownMenuSeparator />
106
+ <DropdownMenuItem onClick={handleSignOut} className="cursor-pointer">
107
+ <LogOut className="mr-2 h-4 w-4" />
108
+ <span>{t('sign_out')}</span>
109
+ </DropdownMenuItem>
110
+ </DropdownMenuContent>
111
+ </DropdownMenu>
60
112
  </div>
61
113
  ) : (
62
114
  <div className="flex gap-2">
@@ -0,0 +1,127 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { logConsentDecision } from '../../app/actions/consent';
6
+ import { readConsent, writeConsent } from '../../lib/privacy/consent-client';
7
+
8
+ /**
9
+ * Minimal floating consent pill, bottom-right. Renders nothing once a decision
10
+ * exists (the nb_consent_preference cookie). Decisions persist client-side and
11
+ * are mirrored to privacy_consent_logs for accountability.
12
+ */
13
+ export function ConsentBanner() {
14
+ const [visible, setVisible] = useState(false);
15
+ const [managing, setManaging] = useState(false);
16
+ const [analytics, setAnalytics] = useState(true);
17
+ const [marketing, setMarketing] = useState(false);
18
+
19
+ useEffect(() => {
20
+ if (!readConsent()) setVisible(true);
21
+ }, []);
22
+
23
+ const decide = (choice: { analytics: boolean; marketing: boolean }) => {
24
+ const pref = writeConsent(choice);
25
+ void logConsentDecision({
26
+ token: pref.token,
27
+ analytics: choice.analytics,
28
+ marketing: choice.marketing,
29
+ });
30
+ setVisible(false);
31
+ };
32
+
33
+ if (!visible) return null;
34
+
35
+ return (
36
+ <div
37
+ role="dialog"
38
+ aria-label="Privacy consent"
39
+ className="fixed bottom-4 right-4 z-[70] w-[min(92vw,360px)] animate-in fade-in slide-in-from-bottom-2 duration-300"
40
+ >
41
+ <div className="rounded-2xl border border-slate-200/70 bg-white/95 p-4 shadow-[0_8px_30px_rgba(15,23,42,0.12)] backdrop-blur-md dark:border-slate-700/60 dark:bg-slate-900/95 dark:shadow-[0_8px_30px_rgba(0,0,0,0.5)]">
42
+ <p className="text-sm font-medium text-slate-900 dark:text-slate-100">
43
+ We value your privacy
44
+ </p>
45
+ <p className="mt-1 text-xs leading-relaxed text-slate-500 dark:text-slate-400">
46
+ We use only essential cookies by default. With your consent we also use
47
+ analytics to improve the site. See our{' '}
48
+ <Link href="/privacy-policy" className="underline underline-offset-2 hover:text-slate-700 dark:hover:text-slate-200">
49
+ Privacy Policy
50
+ </Link>
51
+ .
52
+ </p>
53
+
54
+ {managing && (
55
+ <div className="mt-3 space-y-2 rounded-lg bg-slate-50 p-3 dark:bg-slate-800/60">
56
+ <label className="flex items-center justify-between gap-3 text-xs text-slate-600 dark:text-slate-300">
57
+ <span>
58
+ <span className="font-medium text-slate-800 dark:text-slate-200">Necessary</span>
59
+ <span className="block text-[11px] text-slate-400">Always on</span>
60
+ </span>
61
+ <input type="checkbox" checked disabled className="h-4 w-4 accent-slate-400" />
62
+ </label>
63
+ <label className="flex cursor-pointer items-center justify-between gap-3 text-xs text-slate-600 dark:text-slate-300">
64
+ <span>
65
+ <span className="font-medium text-slate-800 dark:text-slate-200">Analytics</span>
66
+ <span className="block text-[11px] text-slate-400">Usage insights</span>
67
+ </span>
68
+ <input
69
+ type="checkbox"
70
+ checked={analytics}
71
+ onChange={(e) => setAnalytics(e.target.checked)}
72
+ className="h-4 w-4 accent-slate-900 dark:accent-slate-100"
73
+ />
74
+ </label>
75
+ <label className="flex cursor-pointer items-center justify-between gap-3 text-xs text-slate-600 dark:text-slate-300">
76
+ <span>
77
+ <span className="font-medium text-slate-800 dark:text-slate-200">Marketing</span>
78
+ <span className="block text-[11px] text-slate-400">Personalized content</span>
79
+ </span>
80
+ <input
81
+ type="checkbox"
82
+ checked={marketing}
83
+ onChange={(e) => setMarketing(e.target.checked)}
84
+ className="h-4 w-4 accent-slate-900 dark:accent-slate-100"
85
+ />
86
+ </label>
87
+ </div>
88
+ )}
89
+
90
+ <div className="mt-3 flex items-center gap-2">
91
+ {managing ? (
92
+ <button
93
+ type="button"
94
+ onClick={() => decide({ analytics, marketing })}
95
+ className="flex-1 rounded-lg bg-slate-900 px-3 py-2 text-xs font-medium text-white transition hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white"
96
+ >
97
+ Save choices
98
+ </button>
99
+ ) : (
100
+ <button
101
+ type="button"
102
+ onClick={() => decide({ analytics: true, marketing: true })}
103
+ className="flex-1 rounded-lg bg-slate-900 px-3 py-2 text-xs font-medium text-white transition hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white"
104
+ >
105
+ Accept all
106
+ </button>
107
+ )}
108
+ <button
109
+ type="button"
110
+ onClick={() => decide({ analytics: false, marketing: false })}
111
+ className="rounded-lg border border-slate-200 px-3 py-2 text-xs font-medium text-slate-600 transition hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800"
112
+ >
113
+ Reject all
114
+ </button>
115
+ </div>
116
+
117
+ <button
118
+ type="button"
119
+ onClick={() => setManaging((prev) => !prev)}
120
+ className="mt-2 w-full text-center text-[11px] text-slate-400 underline underline-offset-2 transition hover:text-slate-600 dark:hover:text-slate-300"
121
+ >
122
+ {managing ? 'Hide options' : 'Manage options'}
123
+ </button>
124
+ </div>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ // Gates third-party analytics behind explicit consent. Until the visitor opts
4
+ // in, this renders null -> the GTM chunk is never imported and the network tab
5
+ // shows zero analytics bytes, preserving the Lighthouse budget.
6
+ import { useEffect, useRef, useState } from 'react';
7
+ import { DeferredGoogleTagManager } from '../DeferredGoogleTagManager';
8
+ import { CONSENT_CHANGE_EVENT, readConsent } from '../../lib/privacy/consent-client';
9
+
10
+ interface ConsentGatedAnalyticsProps {
11
+ gtmId?: string;
12
+ customScripts?: string;
13
+ nonce?: string;
14
+ }
15
+
16
+ /** Recreate <script> elements from admin-provided markup so they actually run. */
17
+ function injectCustomScripts(markup: string, nonce?: string) {
18
+ const parsed = new DOMParser().parseFromString(markup, 'text/html');
19
+ parsed.querySelectorAll('script').forEach((source) => {
20
+ const script = document.createElement('script');
21
+ for (const attr of Array.from(source.attributes)) {
22
+ script.setAttribute(attr.name, attr.value);
23
+ }
24
+ if (nonce) script.setAttribute('nonce', nonce);
25
+ if (source.textContent) script.textContent = source.textContent;
26
+ document.head.appendChild(script);
27
+ });
28
+ }
29
+
30
+ export function ConsentGatedAnalytics({
31
+ gtmId,
32
+ customScripts,
33
+ nonce,
34
+ }: ConsentGatedAnalyticsProps) {
35
+ const [analyticsAllowed, setAnalyticsAllowed] = useState(false);
36
+ const injectedRef = useRef(false);
37
+
38
+ useEffect(() => {
39
+ const sync = () => setAnalyticsAllowed(Boolean(readConsent()?.analytics));
40
+ sync();
41
+ window.addEventListener(CONSENT_CHANGE_EVENT, sync);
42
+ return () => window.removeEventListener(CONSENT_CHANGE_EVENT, sync);
43
+ }, []);
44
+
45
+ useEffect(() => {
46
+ if (!analyticsAllowed || injectedRef.current) return;
47
+ const markup = customScripts?.trim();
48
+ if (!markup) return;
49
+ injectedRef.current = true;
50
+ try {
51
+ injectCustomScripts(markup, nonce);
52
+ } catch (error) {
53
+ console.error('Failed to inject consented custom scripts:', error);
54
+ }
55
+ }, [analyticsAllowed, customScripts, nonce]);
56
+
57
+ if (!analyticsAllowed || !gtmId) return null;
58
+ return <DeferredGoogleTagManager gtmId={gtmId} nonce={nonce} />;
59
+ }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { cacheLife, cacheTag } from 'next/cache';
3
+ import {
4
+ DynamicLayoutEngine,
5
+ type DynamicLayoutEngineProps,
6
+ DYNAMIC_LAYOUT_ENGINE_CACHE_TAG,
7
+ getDynamicLayoutDefinitionCacheTag,
8
+ } from './DynamicLayoutEngine';
9
+
10
+ export async function CachedDynamicLayoutEngine(props: DynamicLayoutEngineProps) {
11
+ // 'use cache';
12
+ // cacheLife('minutes');
13
+ // cacheTag(DYNAMIC_LAYOUT_ENGINE_CACHE_TAG);
14
+
15
+ // for (const tag of props.cacheTags ?? []) {
16
+ // cacheTag(tag);
17
+ // }
18
+
19
+ // if (props.definition?.id) {
20
+ // cacheTag(getDynamicLayoutDefinitionCacheTag(props.definition.id));
21
+ // }
22
+
23
+ // if (props.definition?.slug) {
24
+ // cacheTag(getDynamicLayoutDefinitionCacheTag(props.definition.slug));
25
+ // }
26
+
27
+ return <DynamicLayoutEngine {...props} />;
28
+ }