create-nextblock 0.2.78 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +182 -154
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -120
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -1,7 +1,7 @@
1
1
  // app/cms/pages/components/PageForm.tsx
2
2
  "use client";
3
3
 
4
- import { useEffect, useState, useTransition } from "react";
4
+ import { useEffect, useRef, useState, useTransition } from "react";
5
5
  import { useRouter, useSearchParams } from "next/navigation";
6
6
  import { Button } from "@nextblock-cms/ui";
7
7
  import { Spinner, Alert, AlertDescription } from "@nextblock-cms/ui";
@@ -16,9 +16,9 @@ import {
16
16
  } from "@nextblock-cms/ui";
17
17
  import { Textarea } from "@nextblock-cms/ui";
18
18
  import type { Database } from "@nextblock-cms/db";
19
- import { useAuth } from "@/context/AuthContext";
20
- import { useRef } from "react";
21
- import { useHotkeys } from "@/hooks/use-hotkeys";
19
+ import { useAuth } from '../../../../context/AuthContext';
20
+ import { useHotkeys } from '../../../../hooks/use-hotkeys';
21
+ import FeatureImageField from "../../components/FeatureImageField";
22
22
 
23
23
  type Page = Database['public']['Tables']['pages']['Row'];
24
24
  type PageStatus = Database['public']['Enums']['page_status'];
@@ -26,13 +26,15 @@ type Language = Database['public']['Tables']['languages']['Row'];
26
26
  // Remove: import { getActiveLanguagesClientSide } from "@nextblock-cms/db";
27
27
 
28
28
  interface PageFormProps {
29
- page?: Page | null;
29
+ page?: (Page & { feature_image_id?: string | null }) | null;
30
30
  formAction: (formData: FormData) => Promise<{ error?: string } | void>;
31
31
  actionButtonText?: string;
32
32
  isEditing?: boolean;
33
33
  availableLanguagesProp: Language[]; // New prop
34
34
  translationGroupId?: string;
35
35
  target_lang_id?: string;
36
+ initialFeatureImageUrl?: string | null;
37
+ initialFeatureImageId?: string | null;
36
38
  }
37
39
 
38
40
  export default function PageForm({
@@ -43,6 +45,8 @@ export default function PageForm({
43
45
  availableLanguagesProp, // Use the new prop
44
46
  translationGroupId,
45
47
  target_lang_id,
48
+ initialFeatureImageUrl,
49
+ initialFeatureImageId,
46
50
  }: PageFormProps) {
47
51
  const router = useRouter();
48
52
  const searchParams = useSearchParams();
@@ -77,6 +81,9 @@ export default function PageForm({
77
81
  const [metaDescription, setMetaDescription] = useState(
78
82
  page?.meta_description || ""
79
83
  );
84
+ const [featureImageId, setFeatureImageId] = useState<string | null>(
85
+ initialFeatureImageId || page?.feature_image_id || null
86
+ );
80
87
 
81
88
  // Use the passed-in languages
82
89
  const [availableLanguages] = useState<Language[]>(availableLanguagesProp);
@@ -84,6 +91,13 @@ export default function PageForm({
84
91
  // const [languagesLoading, setLanguagesLoading] = useState(true); // Remove or set to false initially
85
92
 
86
93
  const [formMessage, setFormMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
94
+ const formRef = useRef<HTMLFormElement>(null);
95
+ const [isSaving, setIsSaving] = useState(false);
96
+ const [saveError, setSaveError] = useState<string | null>(null);
97
+ const [lastSaved, setLastSaved] = useState<Date | null>(null);
98
+ const isFirstRender = useRef(true);
99
+
100
+ useHotkeys('ctrl+s', () => formRef.current?.requestSubmit());
87
101
 
88
102
  useEffect(() => {
89
103
  const successMessage = searchParams.get('success');
@@ -95,7 +109,29 @@ export default function PageForm({
95
109
  }
96
110
  }, [searchParams]);
97
111
 
112
+ useEffect(() => {
113
+ if (!page) {
114
+ return;
115
+ }
98
116
 
117
+ setTitle(page.title || "");
118
+ setSlug(page.slug || "");
119
+ setLanguageId(page.language_id?.toString() || "");
120
+ setStatus(page.status || "draft");
121
+ setMetaTitle(page.meta_title || "");
122
+ setMetaDescription(page.meta_description || "");
123
+ setFeatureImageId(initialFeatureImageId || page.feature_image_id || null);
124
+ }, [
125
+ initialFeatureImageId,
126
+ page?.id,
127
+ page?.language_id,
128
+ page?.meta_description,
129
+ page?.meta_title,
130
+ page?.slug,
131
+ page?.status,
132
+ page?.title,
133
+ page?.updated_at,
134
+ ]);
99
135
 
100
136
  const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
101
137
  const newTitle = e.target.value;
@@ -105,19 +141,89 @@ export default function PageForm({
105
141
  }
106
142
  };
107
143
 
108
- const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
109
- event.preventDefault();
110
- setFormMessage(null);
111
- const formData = new FormData(event.currentTarget);
144
+ const saveDraft = async (customFormData?: FormData) => {
145
+ if (!title.trim() || !slug.trim()) {
146
+ return;
147
+ }
148
+ setIsSaving(true);
149
+ setSaveError(null);
150
+
151
+ const formData = customFormData || (formRef.current ? new FormData(formRef.current) : new FormData());
152
+ if (!customFormData && !formRef.current) {
153
+ formData.append("title", title);
154
+ formData.append("slug", slug);
155
+ formData.append("language_id", languageId);
156
+ formData.append("status", status);
157
+ formData.append("meta_title", metaTitle);
158
+ formData.append("meta_description", metaDescription);
159
+ formData.append("feature_image_id", featureImageId || "");
160
+ if (translationGroupId) {
161
+ formData.append("translation_group_id", translationGroupId);
162
+ }
163
+ }
112
164
 
113
- startTransition(async () => {
165
+ try {
114
166
  const result = await formAction(formData);
115
- if (result?.error) {
167
+ if (result && 'error' in result && result.error) {
168
+ setSaveError(result.error);
116
169
  setFormMessage({ type: 'error', text: result.error });
170
+ } else {
171
+ setLastSaved(new Date());
172
+ setFormMessage(null);
173
+ router.refresh();
117
174
  }
118
- });
175
+ } catch (err: any) {
176
+ const msg = err.message || "Failed to save draft";
177
+ setSaveError(msg);
178
+ setFormMessage({ type: 'error', text: msg });
179
+ } finally {
180
+ setIsSaving(false);
181
+ }
182
+ };
183
+
184
+ const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
185
+ event.preventDefault();
186
+ if (!isEditing) {
187
+ setFormMessage(null);
188
+ const formData = new FormData(event.currentTarget);
189
+
190
+ startTransition(async () => {
191
+ const result = await formAction(formData);
192
+ if (result?.error) {
193
+ setFormMessage({ type: 'error', text: result.error });
194
+ }
195
+ });
196
+ } else {
197
+ await saveDraft();
198
+ }
119
199
  };
120
200
 
201
+ useEffect(() => {
202
+ if (!isEditing) return;
203
+
204
+ if (isFirstRender.current) {
205
+ isFirstRender.current = false;
206
+ return;
207
+ }
208
+
209
+ const hasChanges =
210
+ title !== (page?.title || "") ||
211
+ slug !== (page?.slug || "") ||
212
+ languageId !== (page?.language_id?.toString() || "") ||
213
+ status !== (page?.status || "draft") ||
214
+ metaTitle !== (page?.meta_title || "") ||
215
+ metaDescription !== (page?.meta_description || "") ||
216
+ featureImageId !== (page?.feature_image_id || null);
217
+
218
+ if (!hasChanges) return;
219
+
220
+ const timer = setTimeout(() => {
221
+ saveDraft();
222
+ }, 1000);
223
+
224
+ return () => clearTimeout(timer);
225
+ }, [title, slug, languageId, status, metaTitle, metaDescription, featureImageId, page, isEditing]);
226
+
121
227
  // Removed languagesLoading from this condition
122
228
  if (authLoading) {
123
229
  return <div>Loading form...</div>;
@@ -127,12 +233,32 @@ export default function PageForm({
127
233
  return <div>Please log in to manage pages.</div>;
128
234
  }
129
235
 
130
- const formRef = useRef<HTMLFormElement>(null);
131
- useHotkeys('ctrl+s', () => formRef.current?.requestSubmit());
132
-
133
236
  return (
134
- <form ref={formRef} onSubmit={handleSubmit} className="space-y-6 w-full mx-auto px-6">
135
- {/* ... (rest of the form remains the same, but `availableLanguages` is now populated by the prop) ... */}
237
+ <form ref={formRef} onSubmit={handleSubmit} className="space-y-4 w-full mx-auto px-6">
238
+ {isEditing && (
239
+ <div className="flex items-center justify-between text-xs text-muted-foreground pb-2 border-b border-border/40 mb-2">
240
+ <span className="font-semibold text-[11px] uppercase tracking-wider text-muted-foreground/80">Page Settings</span>
241
+ <div className="flex items-center gap-1.5 min-h-[16px]">
242
+ {isSaving ? (
243
+ <>
244
+ <div className="relative flex h-2 w-2">
245
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
246
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-amber-500"></span>
247
+ </div>
248
+ <span className="text-amber-600 dark:text-amber-400 font-medium">Autosaving settings...</span>
249
+ </>
250
+ ) : saveError ? (
251
+ <span className="text-red-500 font-medium">Error saving settings: {saveError}</span>
252
+ ) : lastSaved ? (
253
+ <span className="text-emerald-600 dark:text-emerald-400 font-medium">
254
+ Settings autosaved at {lastSaved.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
255
+ </span>
256
+ ) : (
257
+ <span className="text-muted-foreground/60">Settings autosave in draft mode</span>
258
+ )}
259
+ </div>
260
+ </div>
261
+ )}
136
262
  {formMessage && (
137
263
  <Alert variant={formMessage.type === 'success' ? 'success' : 'destructive'} className="mb-4">
138
264
  <AlertDescription>{formMessage.text}</AlertDescription>
@@ -141,119 +267,144 @@ export default function PageForm({
141
267
  {translationGroupId && (
142
268
  <input type="hidden" name="translation_group_id" value={translationGroupId} />
143
269
  )}
144
- <div>
145
- <Label htmlFor="title">Title</Label>
146
- <Input
147
- id="title"
148
- name="title"
149
- value={title}
150
- onChange={handleTitleChange}
151
- required
152
- className="mt-1"
153
- />
154
- </div>
155
270
 
156
- <div>
157
- <Label htmlFor="slug">Slug</Label>
158
- <Input
159
- id="slug"
160
- name="slug"
161
- value={slug}
162
- onChange={(e) => setSlug(e.target.value)}
163
- required
164
- className="mt-1"
165
- />
166
- <p className="text-xs text-muted-foreground mt-1">URL-friendly identifier. Auto-generated from title if left empty on creation.</p>
167
- </div>
271
+ {/* Row 1: Basic Page Information */}
272
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-4">
273
+ {/* Title */}
274
+ <div className="md:col-span-4 flex flex-col gap-1">
275
+ <Label htmlFor="title" className="text-xs font-medium">Title</Label>
276
+ <Input
277
+ id="title"
278
+ name="title"
279
+ value={title}
280
+ onChange={handleTitleChange}
281
+ required
282
+ className="h-9"
283
+ />
284
+ </div>
285
+
286
+ {/* Slug */}
287
+ <div className="md:col-span-4 flex flex-col gap-1">
288
+ <Label htmlFor="slug" className="text-xs font-medium">Slug</Label>
289
+ <Input
290
+ id="slug"
291
+ name="slug"
292
+ value={slug}
293
+ onChange={(e) => setSlug(e.target.value)}
294
+ required
295
+ className="h-9"
296
+ />
297
+ <p className="text-[10px] text-muted-foreground mt-0.5 leading-tight truncate" title="URL-friendly identifier. Auto-generated from title if left empty on creation.">
298
+ URL-friendly identifier. Auto-generated from title if left empty.
299
+ </p>
300
+ </div>
168
301
 
169
- <div>
170
- <Label htmlFor="language_id">Language</Label>
171
- {availableLanguages.length > 0 ? (
302
+ {/* Language */}
303
+ <div className="md:col-span-2 flex flex-col gap-1">
304
+ <Label htmlFor="language_id" className="text-xs font-medium">Language</Label>
305
+ {availableLanguages.length > 0 ? (
306
+ <Select
307
+ name="language_id"
308
+ defaultValue={target_lang_id}
309
+ value={languageId}
310
+ onValueChange={setLanguageId}
311
+ required
312
+ >
313
+ <SelectTrigger className="h-9">
314
+ <SelectValue placeholder="Select language" />
315
+ </SelectTrigger>
316
+ <SelectContent>
317
+ {availableLanguages.map((lang) => (
318
+ <SelectItem key={lang.id} value={lang.id.toString()}>
319
+ {lang.name} ({lang.code})
320
+ </SelectItem>
321
+ ))}
322
+ </SelectContent>
323
+ </Select>
324
+ ) : (
325
+ <p className="text-xs text-muted-foreground py-2 leading-none">No languages available.</p>
326
+ )}
327
+ </div>
328
+
329
+ {/* Status */}
330
+ <div className="md:col-span-2 flex flex-col gap-1">
331
+ <Label htmlFor="status" className="text-xs font-medium">Status</Label>
172
332
  <Select
173
- name="language_id"
174
- defaultValue={target_lang_id}
175
- value={languageId}
176
- onValueChange={setLanguageId}
333
+ name="status"
334
+ value={status}
335
+ onValueChange={(value) => setStatus(value as PageStatus)}
177
336
  required
178
337
  >
179
- <SelectTrigger className="mt-1">
180
- <SelectValue placeholder="Select language" />
338
+ <SelectTrigger className="h-9">
339
+ <SelectValue placeholder="Select status" />
181
340
  </SelectTrigger>
182
341
  <SelectContent>
183
- {availableLanguages.map((lang) => (
184
- <SelectItem key={lang.id} value={lang.id.toString()}>
185
- {lang.name} ({lang.code})
186
- </SelectItem>
187
- ))}
342
+ <SelectItem value="draft">Draft</SelectItem>
343
+ <SelectItem value="published">Published</SelectItem>
344
+ <SelectItem value="archived">Archived</SelectItem>
188
345
  </SelectContent>
189
346
  </Select>
190
- ) : (
191
- <p className="text-sm text-muted-foreground mt-1">No languages available. Please add languages in CMS settings.</p>
192
- )}
347
+ </div>
193
348
  </div>
194
349
 
195
- <div>
196
- <Label htmlFor="status">Status</Label>
197
- <Select
198
- name="status"
199
- value={status}
200
- onValueChange={(value) => setStatus(value as PageStatus)}
201
- required
202
- >
203
- <SelectTrigger className="mt-1">
204
- <SelectValue placeholder="Select status" />
205
- </SelectTrigger>
206
- <SelectContent>
207
- <SelectItem value="draft">Draft</SelectItem>
208
- <SelectItem value="published">Published</SelectItem>
209
- <SelectItem value="archived">Archived</SelectItem>
210
- </SelectContent>
211
- </Select>
212
- </div>
350
+ {/* Row 2: SEO Settings */}
351
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-4">
352
+ {/* Meta Title */}
353
+ <div className="md:col-span-4 flex flex-col gap-1">
354
+ <Label htmlFor="meta_title" className="text-xs font-medium">Meta Title (SEO)</Label>
355
+ <Input
356
+ id="meta_title"
357
+ name="meta_title"
358
+ value={metaTitle}
359
+ onChange={(e) => setMetaTitle(e.target.value)}
360
+ className="h-9"
361
+ />
362
+ </div>
213
363
 
214
- <div>
215
- <Label htmlFor="meta_title">Meta Title (SEO)</Label>
216
- <Input
217
- id="meta_title"
218
- name="meta_title"
219
- value={metaTitle}
220
- onChange={(e) => setMetaTitle(e.target.value)}
221
- className="mt-1"
222
- />
364
+ {/* Meta Description */}
365
+ <div className="md:col-span-8 flex flex-col gap-1">
366
+ <Label htmlFor="meta_description" className="text-xs font-medium">Meta Description (SEO)</Label>
367
+ <Textarea
368
+ id="meta_description"
369
+ name="meta_description"
370
+ value={metaDescription}
371
+ onChange={(e) => setMetaDescription(e.target.value)}
372
+ className="min-h-[36px] h-9 py-1.5 resize-y text-sm leading-normal"
373
+ rows={1}
374
+ placeholder="Meta description for search engines..."
375
+ />
376
+ </div>
223
377
  </div>
224
378
 
225
- <div>
226
- <Label htmlFor="meta_description">Meta Description (SEO)</Label>
227
- <Textarea
228
- id="meta_description"
229
- name="meta_description"
230
- value={metaDescription}
231
- onChange={(e) => setMetaDescription(e.target.value)}
232
- className="mt-1"
233
- rows={3}
234
- />
235
- </div>
379
+ <FeatureImageField
380
+ initialImageId={initialFeatureImageId || page?.feature_image_id || null}
381
+ initialImageUrl={initialFeatureImageUrl || null}
382
+ onImageIdChange={setFeatureImageId}
383
+ uploadFolder={`pages/${(slug || 'untitled').toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-_]/g, '')}/`}
384
+ />
236
385
 
237
- <div className="flex justify-end space-x-3">
238
- <Button
239
- type="button"
240
- variant="outline"
241
- onClick={() => router.push("/cms/pages")}
242
- disabled={isPending}
243
- >
244
- Cancel
245
- </Button>
246
- {/* Ensure button is not disabled due to removed languagesLoading */}
247
- <Button type="submit" disabled={isPending || authLoading || availableLanguages.length === 0}>
248
- {isPending ? (
249
- <>
250
- <Spinner className="mr-2 h-4 w-4" /> Saving...
251
- </>
252
- ) : (
253
- actionButtonText
254
- )}
255
- </Button>
256
- </div>
386
+ {!isEditing && (
387
+ <div className="flex justify-end space-x-3">
388
+ <Button
389
+ type="button"
390
+ variant="outline"
391
+ onClick={() => router.push("/cms/pages")}
392
+ disabled={isPending}
393
+ >
394
+ Cancel
395
+ </Button>
396
+ {/* Ensure button is not disabled due to removed languagesLoading */}
397
+ <Button type="submit" disabled={isPending || authLoading || availableLanguages.length === 0}>
398
+ {isPending ? (
399
+ <>
400
+ <Spinner className="mr-2 h-4 w-4" /> Saving...
401
+ </>
402
+ ) : (
403
+ actionButtonText
404
+ )}
405
+ </Button>
406
+ </div>
407
+ )}
257
408
  </form>
258
409
  );
259
- }
410
+ }
@@ -27,12 +27,13 @@ import {
27
27
  DropdownMenuSeparator,
28
28
  } from "@nextblock-cms/ui";
29
29
  // Server action `deletePage` is used by DeletePageButtonClient
30
- import type { Database } from "@nextblock-cms/db";
31
- import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
32
-
33
- type Page = Database["public"]["Tables"]["pages"]["Row"];
34
- import LanguageFilterSelect from "@/app/cms/components/LanguageFilterSelect";
35
- import DeletePageButtonClient from "./components/DeletePageButtonClient"; // Import the client component
30
+ import type { Database } from "@nextblock-cms/db";
31
+ import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
32
+
33
+ type Page = Database["public"]["Tables"]["pages"]["Row"];
34
+ import LanguageFilterSelect from "../components/LanguageFilterSelect";
35
+ import DeletePageButtonClient from "./components/DeletePageButtonClient"; // Import the client component
36
+ import { ContentTransferControls } from "../import-export/ContentTransferControls";
36
37
 
37
38
  async function getPagesWithDetails(
38
39
  filterLanguageId?: number
@@ -95,11 +96,16 @@ export default async function CmsPagesListPage(props: CmsPagesListPageProps) {
95
96
  return (
96
97
  <div className="w-full">
97
98
  <div className="flex justify-between items-center mb-6 flex-wrap gap-4">
98
- <h1 className="text-2xl font-semibold">Manage Pages</h1>
99
- <div className="flex items-center gap-3">
100
- <LanguageFilterSelect
101
- allLanguages={allLanguages}
102
- currentFilterLangId={filterLangId}
99
+ <h1 className="text-2xl font-semibold">Manage Pages</h1>
100
+ <div className="flex items-center gap-3">
101
+ <ContentTransferControls
102
+ contentType="pages"
103
+ label="Pages"
104
+ languageId={filterLangId}
105
+ />
106
+ <LanguageFilterSelect
107
+ allLanguages={allLanguages}
108
+ currentFilterLangId={filterLangId}
103
109
  basePath="/cms/pages"
104
110
  />
105
111
  <Button variant="default" asChild>
@@ -198,13 +204,14 @@ export default async function CmsPagesListPage(props: CmsPagesListPageProps) {
198
204
  </TableCell>
199
205
  <TableCell className="text-right">
200
206
  <DropdownMenu>
201
- <DropdownMenuTrigger asChild>
202
- <Button variant="ghost" size="icon">
203
- <MoreHorizontal className="h-4 w-4" />
204
- <span className="sr-only">
205
- Page actions for {page.title}
206
- </span>
207
- </Button>
207
+ <DropdownMenuTrigger
208
+ id={`page-trigger-${page.id}`}
209
+ className="inline-flex h-10 w-10 items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
210
+ >
211
+ <MoreHorizontal className="h-4 w-4" />
212
+ <span className="sr-only">
213
+ Page actions for {page.title}
214
+ </span>
208
215
  </DropdownMenuTrigger>
209
216
  <DropdownMenuContent align="end">
210
217
  <DropdownMenuItem asChild>
@@ -0,0 +1,16 @@
1
+ import { PaymentsPage as PaymentsPageUI } from '@nextblock-cms/ecommerce/server';
2
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
+ import { redirect } from 'next/navigation';
4
+
5
+ export const metadata = {
6
+ title: 'Payments Settings | NextBlock™ CMS',
7
+ };
8
+
9
+ export default async function PaymentsPageWrapper() {
10
+ const isOnline = await verifyPackageOnline('ecommerce');
11
+ if (!isOnline) {
12
+ redirect('/cms/settings/packages');
13
+ }
14
+
15
+ return <PaymentsPageUI />;
16
+ }