create-nextblock 0.2.78 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +182 -154
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -120
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -0,0 +1,410 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import {
5
+ Alert,
6
+ AlertDescription,
7
+ AlertTitle,
8
+ Badge,
9
+ Button,
10
+ Card,
11
+ CardContent,
12
+ CardDescription,
13
+ CardHeader,
14
+ CardTitle,
15
+ Input,
16
+ Label,
17
+ SearchableSelect,
18
+ } from '@nextblock-cms/ui';
19
+ import {
20
+ AlertTriangle,
21
+ Brain,
22
+ CheckCircle2,
23
+ Cpu,
24
+ KeyRound,
25
+ ServerCog,
26
+ ShieldCheck,
27
+ Trash2,
28
+ } from 'lucide-react';
29
+
30
+ import type { CortexAiStoredModelSelection } from '../../../../lib/ai-model-registry';
31
+ import {
32
+ clearCortexAiModelSelectionAction,
33
+ clearOpenRouterApiKeyAction,
34
+ saveCortexAiModelSelectionAction,
35
+ saveOpenRouterApiKeyAction,
36
+ } from './actions';
37
+
38
+ const CORTEX_AI_SETTINGS_CHANGED_EVENT = 'nextblock:cortex-ai-settings-changed';
39
+
40
+ type StoredCortexAiSettingsClientProps = {
41
+ compatibleModels: Array<{
42
+ id: string;
43
+ name: string;
44
+ pricing: Record<string, string>;
45
+ context_length: number | null;
46
+ }>;
47
+ isPackageActive: boolean;
48
+ hasEnvOpenRouterKey: boolean;
49
+ maskedEnvOpenRouterKey: string | null;
50
+ hasStoredOpenRouterKey: boolean;
51
+ maskedStoredOpenRouterKey: string | null;
52
+ storedKeyUpdatedAt: string | null;
53
+ selectedModel: CortexAiStoredModelSelection | null;
54
+ selectedModelUpdatedAt: string | null;
55
+ hasEncryptionKey: boolean;
56
+ modelCatalogError: string | null;
57
+ successMessage?: string;
58
+ errorMessage?: string;
59
+ };
60
+
61
+ function formatTokenPrice(value: string | undefined) {
62
+ const amount = Number(value);
63
+ if (!Number.isFinite(amount)) return null;
64
+ if (amount === 0) return '$0';
65
+ const perMillion = amount * 1_000_000;
66
+ return `$${perMillion < 0.01 ? perMillion.toFixed(4) : perMillion.toFixed(2)}`;
67
+ }
68
+
69
+ function formatModelPricing(pricing: Record<string, string>) {
70
+ const promptPrice = formatTokenPrice(pricing.prompt);
71
+ const completionPrice = formatTokenPrice(pricing.completion);
72
+ if (promptPrice === '$0' && completionPrice === '$0') return 'Free';
73
+ if (promptPrice && completionPrice) return `${promptPrice}/1M input - ${completionPrice}/1M output`;
74
+ return 'Pricing varies';
75
+ }
76
+
77
+ function notifyCortexAiSettingsChanged() {
78
+ window.dispatchEvent(new Event(CORTEX_AI_SETTINGS_CHANGED_EVENT));
79
+ }
80
+
81
+ export function StoredCortexAiSettingsClient({
82
+ compatibleModels,
83
+ isPackageActive,
84
+ hasEnvOpenRouterKey,
85
+ maskedEnvOpenRouterKey,
86
+ hasStoredOpenRouterKey,
87
+ maskedStoredOpenRouterKey,
88
+ storedKeyUpdatedAt,
89
+ selectedModel,
90
+ selectedModelUpdatedAt,
91
+ hasEncryptionKey,
92
+ modelCatalogError,
93
+ successMessage,
94
+ errorMessage,
95
+ }: StoredCortexAiSettingsClientProps) {
96
+ const [apiKeyInput, setApiKeyInput] = useState('');
97
+ const [modelInput, setModelInput] = useState<string>(selectedModel?.modelId || '');
98
+
99
+ const isKeyDirty = apiKeyInput.trim().length > 0;
100
+ const isModelDirty = modelInput !== (selectedModel?.modelId || '');
101
+
102
+ const selectedModelIsInCatalog = compatibleModels.some(
103
+ (model) => model.id === selectedModel?.modelId
104
+ );
105
+ const modelOptions =
106
+ selectedModel && !selectedModelIsInCatalog
107
+ ? [
108
+ {
109
+ context_length: selectedModel.contextLength,
110
+ id: selectedModel.modelId,
111
+ name: `${selectedModel.name} (saved)`,
112
+ pricing: selectedModel.pricing,
113
+ },
114
+ ...compatibleModels,
115
+ ]
116
+ : compatibleModels;
117
+
118
+ const canSelectModel = hasStoredOpenRouterKey && compatibleModels.length > 0;
119
+
120
+ const searchableOptions = modelOptions.map((model) => ({
121
+ value: model.id,
122
+ label: model.name,
123
+ description: `${model.id} - ${formatModelPricing(model.pricing as any)}`,
124
+ }));
125
+
126
+ return (
127
+ <div className="mx-auto w-full max-w-6xl space-y-6 px-6 py-8">
128
+ <div>
129
+ <div className="flex items-center gap-3">
130
+ <Brain className="h-7 w-7 text-primary" />
131
+ <h1 className="text-2xl font-semibold">NextBlock Cortex AI</h1>
132
+ </div>
133
+ <p className="mt-2 text-sm text-muted-foreground">
134
+ Manage premium activation and the OpenRouter key used by Cortex AI.
135
+ </p>
136
+ </div>
137
+
138
+ {successMessage && (
139
+ <Alert variant="success">
140
+ <CheckCircle2 className="h-4 w-4" />
141
+ <AlertTitle>Saved</AlertTitle>
142
+ <AlertDescription>{successMessage}</AlertDescription>
143
+ </Alert>
144
+ )}
145
+
146
+ {errorMessage && (
147
+ <Alert variant="destructive">
148
+ <AlertTriangle className="h-4 w-4" />
149
+ <AlertTitle>Unable to save</AlertTitle>
150
+ <AlertDescription>{errorMessage}</AlertDescription>
151
+ </Alert>
152
+ )}
153
+
154
+ <div className="grid gap-4 md:grid-cols-4">
155
+ <Card>
156
+ <CardHeader>
157
+ <CardTitle className="flex items-center gap-2 text-base">
158
+ <ShieldCheck className="h-4 w-4" />
159
+ Package
160
+ </CardTitle>
161
+ <CardDescription>Premium access</CardDescription>
162
+ </CardHeader>
163
+ <CardContent>
164
+ <Badge variant={isPackageActive ? 'default' : 'outline'}>
165
+ {isPackageActive ? 'Active' : 'Inactive'}
166
+ </Badge>
167
+ </CardContent>
168
+ </Card>
169
+
170
+ <Card>
171
+ <CardHeader>
172
+ <CardTitle className="flex items-center gap-2 text-base">
173
+ <ServerCog className="h-4 w-4" />
174
+ Environment
175
+ </CardTitle>
176
+ <CardDescription>OPENROUTER_API_KEY</CardDescription>
177
+ </CardHeader>
178
+ <CardContent className="space-y-2">
179
+ <Badge variant={hasEnvOpenRouterKey ? 'default' : 'outline'}>
180
+ {hasEnvOpenRouterKey ? 'Configured' : 'Not set'}
181
+ </Badge>
182
+ {maskedEnvOpenRouterKey && (
183
+ <p className="font-mono text-xs text-muted-foreground">
184
+ {maskedEnvOpenRouterKey}
185
+ </p>
186
+ )}
187
+ </CardContent>
188
+ </Card>
189
+
190
+ <Card>
191
+ <CardHeader>
192
+ <CardTitle className="flex items-center gap-2 text-base">
193
+ <KeyRound className="h-4 w-4" />
194
+ Stored BYOK
195
+ </CardTitle>
196
+ <CardDescription>Encrypted database key</CardDescription>
197
+ </CardHeader>
198
+ <CardContent className="space-y-2">
199
+ <Badge variant={hasStoredOpenRouterKey ? 'default' : 'outline'}>
200
+ {hasStoredOpenRouterKey ? 'Stored' : 'Empty'}
201
+ </Badge>
202
+ {maskedStoredOpenRouterKey && (
203
+ <p className="font-mono text-xs text-muted-foreground">
204
+ {maskedStoredOpenRouterKey}
205
+ </p>
206
+ )}
207
+ {storedKeyUpdatedAt && (
208
+ <p className="text-xs text-muted-foreground">{storedKeyUpdatedAt}</p>
209
+ )}
210
+ </CardContent>
211
+ </Card>
212
+
213
+ <Card>
214
+ <CardHeader>
215
+ <CardTitle className="flex items-center gap-2 text-base">
216
+ <Cpu className="h-4 w-4" />
217
+ Model
218
+ </CardTitle>
219
+ <CardDescription>Stored BYOK routing</CardDescription>
220
+ </CardHeader>
221
+ <CardContent className="space-y-2">
222
+ <Badge variant={selectedModel ? 'default' : 'outline'}>
223
+ {selectedModel ? 'Selected' : 'Free registry'}
224
+ </Badge>
225
+ {selectedModel && (
226
+ <>
227
+ <p className="text-xs font-medium">{selectedModel.name}</p>
228
+ <p className="break-all font-mono text-xs text-muted-foreground">
229
+ {selectedModel.modelId}
230
+ </p>
231
+ {selectedModelUpdatedAt && (
232
+ <p className="text-xs text-muted-foreground">{selectedModelUpdatedAt}</p>
233
+ )}
234
+ </>
235
+ )}
236
+ </CardContent>
237
+ </Card>
238
+ </div>
239
+
240
+ {hasEnvOpenRouterKey && !hasStoredOpenRouterKey && (
241
+ <Alert>
242
+ <ServerCog className="h-4 w-4" />
243
+ <AlertTitle>Sandbox free-model lock active</AlertTitle>
244
+ <AlertDescription>
245
+ Cortex AI will only use the three configured free OpenRouter models until a
246
+ stored BYOK is saved.
247
+ </AlertDescription>
248
+ </Alert>
249
+ )}
250
+
251
+ {hasEnvOpenRouterKey && hasStoredOpenRouterKey && (
252
+ <Alert>
253
+ <KeyRound className="h-4 w-4" />
254
+ <AlertTitle>Stored BYOK active</AlertTitle>
255
+ <AlertDescription>
256
+ Cortex AI will use the stored BYOK before the server environment key, so the
257
+ selected compatible OpenRouter model can run across the website.
258
+ </AlertDescription>
259
+ </Alert>
260
+ )}
261
+
262
+ {!hasEncryptionKey && (
263
+ <Alert variant="warning">
264
+ <AlertTriangle className="h-4 w-4" />
265
+ <AlertTitle>Encryption key missing</AlertTitle>
266
+ <AlertDescription>
267
+ Set CORTEX_AI_ENCRYPTION_KEY before saving an OpenRouter key here.
268
+ </AlertDescription>
269
+ </Alert>
270
+ )}
271
+
272
+ <Card>
273
+ <CardHeader className="flex flex-row items-start justify-between">
274
+ <div>
275
+ <CardTitle className="text-lg">OpenRouter BYOK</CardTitle>
276
+ <CardDescription>
277
+ The saved key is encrypted and masked after submission.
278
+ </CardDescription>
279
+ </div>
280
+ {hasStoredOpenRouterKey && (
281
+ <form action={clearOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
282
+ <Button
283
+ type="submit"
284
+ variant="outline"
285
+ size="sm"
286
+ className="text-destructive hover:text-destructive"
287
+ >
288
+ <Trash2 className="mr-2 h-4 w-4" />
289
+ Clear
290
+ </Button>
291
+ </form>
292
+ )}
293
+ </CardHeader>
294
+ <CardContent>
295
+ <form action={saveOpenRouterApiKeyAction} onSubmit={notifyCortexAiSettingsChanged}>
296
+ <div className="flex items-end gap-3">
297
+ <div className="flex-1 space-y-2">
298
+ <Label htmlFor="openrouter_api_key">OpenRouter API key</Label>
299
+ <Input
300
+ id="openrouter_api_key"
301
+ name="openrouter_api_key"
302
+ type="password"
303
+ autoComplete="off"
304
+ minLength={12}
305
+ placeholder={hasStoredOpenRouterKey ? 'Enter new key to overwrite...' : 'sk-or-v1-...'}
306
+ value={apiKeyInput}
307
+ onChange={(e) => setApiKeyInput(e.target.value)}
308
+ required
309
+ />
310
+ </div>
311
+ <Button type="submit" disabled={!isKeyDirty}>
312
+ {isKeyDirty ? (
313
+ <>
314
+ <KeyRound className="mr-2 h-4 w-4" />
315
+ Save Key
316
+ </>
317
+ ) : (
318
+ <>
319
+ <CheckCircle2 className="mr-2 h-4 w-4" />
320
+ Saved
321
+ </>
322
+ )}
323
+ </Button>
324
+ </div>
325
+ </form>
326
+ </CardContent>
327
+ </Card>
328
+
329
+ <Card>
330
+ <CardHeader className="flex flex-row items-start justify-between">
331
+ <div>
332
+ <CardTitle className="text-lg">OpenRouter Model</CardTitle>
333
+ <CardDescription>
334
+ Stored BYOK can use compatible text models that support structured outputs and
335
+ tool calling.
336
+ </CardDescription>
337
+ </div>
338
+ {selectedModel && (
339
+ <form action={clearCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
340
+ <Button
341
+ type="submit"
342
+ variant="outline"
343
+ size="sm"
344
+ className="text-destructive hover:text-destructive"
345
+ >
346
+ <Trash2 className="mr-2 h-4 w-4" />
347
+ Clear
348
+ </Button>
349
+ </form>
350
+ )}
351
+ </CardHeader>
352
+ <CardContent className="space-y-4">
353
+ {!hasStoredOpenRouterKey && (
354
+ <Alert>
355
+ <KeyRound className="h-4 w-4" />
356
+ <AlertTitle>Stored BYOK required</AlertTitle>
357
+ <AlertDescription>
358
+ Save an encrypted OpenRouter key before choosing a paid model.
359
+ </AlertDescription>
360
+ </Alert>
361
+ )}
362
+
363
+ {modelCatalogError && (
364
+ <Alert variant="warning">
365
+ <AlertTriangle className="h-4 w-4" />
366
+ <AlertTitle>Model catalog unavailable</AlertTitle>
367
+ <AlertDescription>{modelCatalogError}</AlertDescription>
368
+ </Alert>
369
+ )}
370
+
371
+ <form action={saveCortexAiModelSelectionAction} onSubmit={notifyCortexAiSettingsChanged}>
372
+ {/* We need a hidden input because SearchableSelect isn't a native form element that automatically passes 'name' inside form submission natively (unless we do) */}
373
+ <input type="hidden" name="openrouter_model_id" value={modelInput} />
374
+
375
+ <div className="flex items-end gap-3">
376
+ <div className="flex-1 space-y-2">
377
+ <Label htmlFor="openrouter_model_id_select">Model</Label>
378
+ <SearchableSelect
379
+ options={searchableOptions}
380
+ value={modelInput}
381
+ onChange={(val) => setModelInput(val)}
382
+ disabled={!canSelectModel}
383
+ placeholder="Select a compatible model..."
384
+ />
385
+ <p className="text-xs text-muted-foreground mt-1">
386
+ {canSelectModel
387
+ ? `${compatibleModels.length} compatible models available`
388
+ : 'Cortex AI will use the free registry until model selection is available.'}
389
+ </p>
390
+ </div>
391
+ <Button type="submit" disabled={!canSelectModel || !isModelDirty}>
392
+ {isModelDirty ? (
393
+ <>
394
+ <Cpu className="mr-2 h-4 w-4" />
395
+ Save Model
396
+ </>
397
+ ) : (
398
+ <>
399
+ <CheckCircle2 className="mr-2 h-4 w-4" />
400
+ Saved
401
+ </>
402
+ )}
403
+ </Button>
404
+ </div>
405
+ </form>
406
+ </CardContent>
407
+ </Card>
408
+ </div>
409
+ );
410
+ }
@@ -0,0 +1,248 @@
1
+ 'use server';
2
+
3
+ import { revalidatePath } from 'next/cache';
4
+ import { redirect } from 'next/navigation';
5
+
6
+ import { createClient, verifyPackageOnline } from '@nextblock-cms/db/server';
7
+
8
+ import {
9
+ CORTEX_AI_OPENROUTER_MODEL_SELECTION_SETTING_KEY,
10
+ CORTEX_AI_OPENROUTER_SETTING_KEY,
11
+ CORTEX_AI_PACKAGE_ID,
12
+ encryptStoredOpenRouterApiKey,
13
+ getCortexAiEnvConfig,
14
+ getEnvOpenRouterKeyStatus,
15
+ getStoredOpenRouterKeyStatus,
16
+ } from '../../../../lib/ai-config';
17
+ import { listCortexAiCompatibleOpenRouterModels } from '../../../../lib/ai-model-catalog';
18
+ import {
19
+ createCortexAiStoredModelSelection,
20
+ safeParseCortexAiModelSelection,
21
+ type CortexAiStoredModelSelection,
22
+ } from '../../../../lib/ai-model-registry';
23
+
24
+ const CORTEX_AI_SETTINGS_PATH = '/cms/settings/cortex-ai';
25
+
26
+ type CortexAiSettingsStatus = {
27
+ activeKeySource: 'env' | 'stored' | 'none';
28
+ hasEncryptionKey: boolean;
29
+ hasEnvOpenRouterKey: boolean;
30
+ hasStoredOpenRouterKey: boolean;
31
+ isPackageActive: boolean;
32
+ maskedEnvOpenRouterKey: string | null;
33
+ maskedStoredOpenRouterKey: string | null;
34
+ selectedModel: CortexAiStoredModelSelection | null;
35
+ storedOpenRouterKeyUpdatedAt: string | null;
36
+ };
37
+
38
+ function redirectWithStatus(status: 'success' | 'error', message: string): never {
39
+ redirect(`${CORTEX_AI_SETTINGS_PATH}?${status}=${encodeURIComponent(message)}`);
40
+ }
41
+
42
+ async function requireAdminSupabaseClient() {
43
+ const supabase = createClient();
44
+ const {
45
+ data: { user },
46
+ error: userError,
47
+ } = await supabase.auth.getUser();
48
+
49
+ if (userError || !user) {
50
+ throw new Error('You must be logged in to manage Cortex AI settings.');
51
+ }
52
+
53
+ const { data: profile, error: profileError } = await supabase
54
+ .from('profiles')
55
+ .select('role')
56
+ .eq('id', user.id)
57
+ .single();
58
+
59
+ if (profileError || !profile || profile.role !== 'ADMIN') {
60
+ throw new Error('You do not have permission to manage Cortex AI settings.');
61
+ }
62
+
63
+ return supabase;
64
+ }
65
+
66
+ export async function getCortexAiSettingsStatus(): Promise<CortexAiSettingsStatus> {
67
+ const supabase = await requireAdminSupabaseClient();
68
+ const env = getCortexAiEnvConfig();
69
+ const envKeyStatus = getEnvOpenRouterKeyStatus();
70
+
71
+ const [{ data: storedKeyRow }, { data: selectedModelRow }, isPackageActive] = await Promise.all([
72
+ supabase
73
+ .from('site_settings')
74
+ .select('value')
75
+ .eq('key', CORTEX_AI_OPENROUTER_SETTING_KEY)
76
+ .maybeSingle(),
77
+ supabase
78
+ .from('site_settings')
79
+ .select('value')
80
+ .eq('key', CORTEX_AI_OPENROUTER_MODEL_SELECTION_SETTING_KEY)
81
+ .maybeSingle(),
82
+ verifyPackageOnline(CORTEX_AI_PACKAGE_ID).catch(() => false),
83
+ ]);
84
+
85
+ const storedKeyStatus = getStoredOpenRouterKeyStatus(storedKeyRow?.value);
86
+ const selectedModel = safeParseCortexAiModelSelection(selectedModelRow?.value);
87
+
88
+ return {
89
+ activeKeySource: storedKeyStatus.hasStoredKey
90
+ ? 'stored'
91
+ : env.hasOpenRouterEnvKey
92
+ ? 'env'
93
+ : 'none',
94
+ hasEncryptionKey: env.hasEncryptionKey,
95
+ hasEnvOpenRouterKey: env.hasOpenRouterEnvKey,
96
+ hasStoredOpenRouterKey: storedKeyStatus.hasStoredKey,
97
+ isPackageActive,
98
+ maskedEnvOpenRouterKey: envKeyStatus.maskedEnvOpenRouterKey,
99
+ maskedStoredOpenRouterKey: storedKeyStatus.maskedKey,
100
+ selectedModel: storedKeyStatus.hasStoredKey ? selectedModel : null,
101
+ storedOpenRouterKeyUpdatedAt: storedKeyStatus.updatedAt,
102
+ };
103
+ }
104
+
105
+ export async function saveOpenRouterApiKeyAction(formData: FormData) {
106
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
107
+ throw new Error('Sandbox environment cannot save keys to the database.');
108
+ }
109
+
110
+ try {
111
+ const supabase = await requireAdminSupabaseClient();
112
+ const apiKey = String(formData.get('openrouter_api_key') || '').trim();
113
+
114
+ if (!apiKey) {
115
+ throw new Error('OpenRouter API key is required.');
116
+ }
117
+
118
+ const encryptedKey = encryptStoredOpenRouterApiKey(apiKey);
119
+ const { error } = await supabase.from('site_settings').upsert({
120
+ key: CORTEX_AI_OPENROUTER_SETTING_KEY,
121
+ value: encryptedKey,
122
+ });
123
+
124
+ if (error) {
125
+ throw new Error(error.message);
126
+ }
127
+ } catch (error) {
128
+ redirectWithStatus(
129
+ 'error',
130
+ error instanceof Error ? error.message : 'Failed to save OpenRouter key.'
131
+ );
132
+ }
133
+
134
+ revalidatePath(CORTEX_AI_SETTINGS_PATH);
135
+ redirectWithStatus('success', 'OpenRouter key saved.');
136
+ }
137
+
138
+ export async function clearOpenRouterApiKeyAction() {
139
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
140
+ throw new Error('Sandbox environment cannot clear keys from the database.');
141
+ }
142
+
143
+ try {
144
+ const supabase = await requireAdminSupabaseClient();
145
+ const { error } = await supabase
146
+ .from('site_settings')
147
+ .delete()
148
+ .in('key', [
149
+ CORTEX_AI_OPENROUTER_SETTING_KEY,
150
+ CORTEX_AI_OPENROUTER_MODEL_SELECTION_SETTING_KEY,
151
+ ]);
152
+
153
+ if (error) {
154
+ throw new Error(error.message);
155
+ }
156
+ } catch (error) {
157
+ redirectWithStatus(
158
+ 'error',
159
+ error instanceof Error ? error.message : 'Failed to clear OpenRouter key.'
160
+ );
161
+ }
162
+
163
+ revalidatePath(CORTEX_AI_SETTINGS_PATH);
164
+ redirectWithStatus('success', 'Stored OpenRouter key cleared.');
165
+ }
166
+
167
+ export async function saveCortexAiModelSelectionAction(formData: FormData) {
168
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
169
+ throw new Error('Sandbox environment cannot save model selection to the database.');
170
+ }
171
+
172
+ try {
173
+ const supabase = await requireAdminSupabaseClient();
174
+ const modelId = String(formData.get('openrouter_model_id') || '').trim();
175
+
176
+ if (!modelId) {
177
+ throw new Error('Choose an OpenRouter model before saving.');
178
+ }
179
+
180
+ const { data: storedKeyRow, error: storedKeyError } = await supabase
181
+ .from('site_settings')
182
+ .select('value')
183
+ .eq('key', CORTEX_AI_OPENROUTER_SETTING_KEY)
184
+ .maybeSingle();
185
+
186
+ if (storedKeyError) {
187
+ throw new Error(storedKeyError.message);
188
+ }
189
+
190
+ const storedKeyStatus = getStoredOpenRouterKeyStatus(storedKeyRow?.value);
191
+
192
+ if (!storedKeyStatus.hasStoredKey) {
193
+ throw new Error('Save a stored OpenRouter BYOK before choosing a paid model.');
194
+ }
195
+
196
+ const compatibleModels = await listCortexAiCompatibleOpenRouterModels();
197
+ const selectedModel = compatibleModels.find((model) => model.id === modelId);
198
+
199
+ if (!selectedModel) {
200
+ throw new Error(
201
+ 'The selected model is no longer eligible for Cortex AI structured output and tool calling.'
202
+ );
203
+ }
204
+
205
+ const { error } = await supabase.from('site_settings').upsert({
206
+ key: CORTEX_AI_OPENROUTER_MODEL_SELECTION_SETTING_KEY,
207
+ value: createCortexAiStoredModelSelection(selectedModel),
208
+ });
209
+
210
+ if (error) {
211
+ throw new Error(error.message);
212
+ }
213
+ } catch (error) {
214
+ redirectWithStatus(
215
+ 'error',
216
+ error instanceof Error ? error.message : 'Failed to save Cortex AI model selection.'
217
+ );
218
+ }
219
+
220
+ revalidatePath(CORTEX_AI_SETTINGS_PATH);
221
+ redirectWithStatus('success', 'Cortex AI model selection saved.');
222
+ }
223
+
224
+ export async function clearCortexAiModelSelectionAction() {
225
+ if (process.env.NEXT_PUBLIC_IS_SANDBOX === 'true') {
226
+ throw new Error('Sandbox environment cannot clear model selection from the database.');
227
+ }
228
+
229
+ try {
230
+ const supabase = await requireAdminSupabaseClient();
231
+ const { error } = await supabase
232
+ .from('site_settings')
233
+ .delete()
234
+ .eq('key', CORTEX_AI_OPENROUTER_MODEL_SELECTION_SETTING_KEY);
235
+
236
+ if (error) {
237
+ throw new Error(error.message);
238
+ }
239
+ } catch (error) {
240
+ redirectWithStatus(
241
+ 'error',
242
+ error instanceof Error ? error.message : 'Failed to clear Cortex AI model selection.'
243
+ );
244
+ }
245
+
246
+ revalidatePath(CORTEX_AI_SETTINGS_PATH);
247
+ redirectWithStatus('success', 'Cortex AI model selection cleared.');
248
+ }