create-nextblock 0.2.78 → 0.8.1

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 +793 -472
  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
@@ -2,23 +2,27 @@
2
2
  import React from "react";
3
3
  import { Separator } from "@nextblock-cms/ui";
4
4
  import { createClient } from "@nextblock-cms/db/server";
5
- import PostForm from "../../components/PostForm"; // Adjusted path
5
+ import PostForm from "../../components/PostForm";
6
6
  import { updatePost } from "../../actions";
7
- import type { Database } from "@nextblock-cms/db"; // Ensure Language and Media are imported
7
+ import type { Database } from "@nextblock-cms/db";
8
8
  import { notFound, redirect } from "next/navigation";
9
+ import BlockEditorArea from "../../../blocks/components/BlockEditorArea";
10
+ import Link from "next/link";
11
+ import { Button } from "@nextblock-cms/ui";
12
+ import { ArrowLeft, Eye, FilePenLine } from "lucide-react";
13
+ import ContentLanguageSwitcher from "../../../components/ContentLanguageSwitcher";
14
+ import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server";
15
+ import { normalizeContentDraftRow } from "../../../../../lib/visual-editing/draft-content";
16
+ import CopyContentFromLanguage from "../../../components/CopyContentFromLanguage";
17
+ import { UploadFolderProvider } from '../../../media/UploadFolderContext';
18
+ import RevisionHistoryButton from "../../../revisions/RevisionHistoryButton";
19
+ import { resolveMediaUrl } from "../../../../../lib/media/resolveMediaUrl";
20
+ import { CortexAiPageContextRegistrar } from "../../../components/CortexAiPageContext";
21
+ import DraftStatusActions from "../../../components/DraftStatusActions";
9
22
 
10
23
  type PostType = Database['public']['Tables']['posts']['Row'];
11
24
  type BlockType = Database['public']['Tables']['blocks']['Row'];
12
25
  type Language = Database['public']['Tables']['languages']['Row'];
13
- import BlockEditorArea from "@/app/cms/blocks/components/BlockEditorArea";
14
- import Link from "next/link";
15
- import { Button } from "@nextblock-cms/ui";
16
- import { Eye, ArrowLeft } from "lucide-react"; // Removed SeparatorVertical, use <Separator />
17
- import ContentLanguageSwitcher from "@/app/cms/components/ContentLanguageSwitcher";
18
- import { getActiveLanguagesServerSide } from "@nextblock-cms/db/server"; // Correct server-side fetch
19
- import CopyContentFromLanguage from "@/app/cms/components/CopyContentFromLanguage";
20
- import { UploadFolderProvider } from "@/app/cms/media/UploadFolderContext";
21
- import RevisionHistoryButton from "@/app/cms/revisions/RevisionHistoryButton";
22
26
 
23
27
  interface PostWithBlocks extends PostType {
24
28
  blocks: BlockType[];
@@ -26,7 +30,7 @@ interface PostWithBlocks extends PostType {
26
30
  translation_group_id: string;
27
31
  }
28
32
 
29
- async function getPostDataWithBlocks(id: number): Promise<PostWithBlocks | null> {
33
+ async function getPostDataWithBlocks(id: number): Promise<{ post: PostWithBlocks; hasDraft: boolean } | null> {
30
34
  const supabase = createClient();
31
35
  const { data: postData, error: postError } = await supabase
32
36
  .from("posts")
@@ -44,14 +48,45 @@ async function getPostDataWithBlocks(id: number): Promise<PostWithBlocks | null>
44
48
  return null;
45
49
  }
46
50
 
47
- const langCode = (postData.languages as unknown as Language)?.code;
51
+ const langCode = Array.isArray(postData.languages)
52
+ ? postData.languages[0]?.code
53
+ : (postData.languages as unknown as Language)?.code;
48
54
 
49
- return {
55
+ let postWithBlocks: PostWithBlocks = {
50
56
  ...postData,
51
57
  blocks: postData.blocks || [],
52
58
  language_code: langCode,
53
- translation_group_id: postData.translation_group_id,
59
+ translation_group_id: postData.translation_group_id ?? "",
54
60
  } as PostWithBlocks;
61
+
62
+ const { data: draftData } = await supabase
63
+ .from("content_drafts")
64
+ .select("*")
65
+ .eq("parent_type", "post")
66
+ .eq("parent_id", id)
67
+ .maybeSingle();
68
+
69
+ const hasDraft = draftData !== null;
70
+ if (draftData) {
71
+ const draft = normalizeContentDraftRow(draftData);
72
+ postWithBlocks = {
73
+ ...postWithBlocks,
74
+ title: (draft.meta.title as string) ?? postWithBlocks.title,
75
+ slug: (draft.meta.slug as string) ?? postWithBlocks.slug,
76
+ language_id: (draft.meta.language_id as number) ?? postWithBlocks.language_id,
77
+ status: (draft.meta.status as any) ?? postWithBlocks.status,
78
+ meta_title: (draft.meta.meta_title as string) ?? postWithBlocks.meta_title,
79
+ meta_description: (draft.meta.meta_description as string) ?? postWithBlocks.meta_description,
80
+ label: (draft.meta.label as string) ?? postWithBlocks.label,
81
+ excerpt: (draft.meta.excerpt as string) ?? postWithBlocks.excerpt,
82
+ subtitle: (draft.meta.subtitle as string) ?? postWithBlocks.subtitle,
83
+ published_at: (draft.meta.published_at as string) ?? postWithBlocks.published_at,
84
+ feature_image_id: (draft.meta.feature_image_id as any) ?? postWithBlocks.feature_image_id,
85
+ blocks: draft.blocks as any[],
86
+ };
87
+ }
88
+
89
+ return { post: postWithBlocks, hasDraft };
55
90
  }
56
91
 
57
92
  export default async function EditPostPage(props: { params: Promise<{ id: string }> }) {
@@ -71,113 +106,120 @@ export default async function EditPostPage(props: { params: Promise<{ id: string
71
106
  }
72
107
 
73
108
  // Fetch post data and all site languages concurrently
74
- const [postWithBlocks, allSiteLanguages] = await Promise.all([
75
- getPostDataWithBlocks(postId),
76
- getActiveLanguagesServerSide() // Fetch languages on the server
77
- ]);
109
+ const postDataResult = await getPostDataWithBlocks(postId);
110
+ const allSiteLanguages = await getActiveLanguagesServerSide();
78
111
 
79
- if (!postWithBlocks) {
112
+ if (!postDataResult) {
80
113
  return notFound();
81
114
  }
82
115
 
116
+ const { post: postWithBlocks, hasDraft } = postDataResult;
117
+
83
118
  let initialFeatureImageUrl: string | null = null;
84
119
  let initialFeatureImageIdProp: string | null = null;
85
120
 
86
- // The PostType defines feature_image_id as number | null.
87
- // However, Media.id is a string (uuid), and PostForm expects a string UUID.
88
- // This assumes that postWithBlocks.feature_image_id, despite its 'number' typing,
89
- // actually holds a value that can be used to identify a media item by its UUID,
90
- // or that the type definition for Post.feature_image_id is outdated.
91
- // Casting to `unknown` then `string` for the query.
92
121
  const featureImageIdFromDb = postWithBlocks.feature_image_id as unknown as (string | number | null);
93
122
 
94
123
  if (featureImageIdFromDb) {
95
124
  const { data: mediaItem, error: mediaError } = await supabase
96
125
  .from("media")
97
- .select("id, object_key")
126
+ .select("id, object_key, file_path")
98
127
  .eq("id", String(featureImageIdFromDb)) // Query using the ID as string
99
128
  .single();
100
129
 
101
130
  if (mediaError) {
102
131
  console.error(`Error fetching media item for feature_image_id '${featureImageIdFromDb}':`, mediaError.message);
103
- // Not critical enough to notFound(), form will just not have initial image.
104
132
  } else if (mediaItem) {
105
133
  initialFeatureImageIdProp = mediaItem.id; // string UUID from media table
106
- const r2BaseUrl = process.env.NEXT_PUBLIC_R2_BASE_URL;
107
- if (r2BaseUrl && mediaItem.object_key) {
108
- initialFeatureImageUrl = `${r2BaseUrl}/${mediaItem.object_key}`;
109
- } else if (!r2BaseUrl) {
110
- console.warn("NEXT_PUBLIC_R2_PUBLIC_URL is not set. Cannot construct feature image URL for edit page.");
111
- }
134
+ initialFeatureImageUrl = resolveMediaUrl(mediaItem.file_path || mediaItem.object_key);
112
135
  }
113
136
  }
114
137
 
115
138
  const updatePostWithId = updatePost.bind(null, postId);
116
- const publicPostUrl = `/article/${postWithBlocks.slug}`;
139
+ const publicPostUrl = `/article/${postWithBlocks.slug}`;
140
+ const draftModeUrl = `/api/draft/start?path=${encodeURIComponent(publicPostUrl)}`;
117
141
 
118
142
  return (
119
143
  <UploadFolderProvider defaultFolder={`posts/${postWithBlocks.slug}/`}>
120
- <div className="space-y-8 w-full mx-auto px-6">
121
- <div className="flex justify-between items-center flex-wrap gap-4 w-full">
122
- <div className="flex items-center gap-3">
123
- <Button variant="outline" size="icon" aria-label="Back to posts" asChild>
124
- <Link href="/cms/posts">
125
- <ArrowLeft className="h-4 w-4" />
144
+ <CortexAiPageContextRegistrar
145
+ context={{
146
+ contentType: "post",
147
+ entityId: postWithBlocks.id,
148
+ languageId: postWithBlocks.language_id,
149
+ slug: postWithBlocks.slug,
150
+ title: postWithBlocks.title,
151
+ translationGroupId: postWithBlocks.translation_group_id,
152
+ }}
153
+ />
154
+ <div className="space-y-8 w-full mx-auto px-6">
155
+ <div className="flex justify-between items-center flex-wrap gap-4 w-full">
156
+ <div className="flex items-center gap-3">
157
+ <Button variant="outline" size="icon" aria-label="Back to posts" asChild>
158
+ <Link href="/cms/posts">
159
+ <ArrowLeft className="h-4 w-4" />
160
+ </Link>
161
+ </Button>
162
+ <div>
163
+ <h1 className="text-2xl font-bold">Edit Post</h1>
164
+ <p className="text-sm text-muted-foreground truncate max-w-md" title={postWithBlocks.title}>{postWithBlocks.title}</p>
165
+ </div>
166
+ </div>
167
+ <div className="flex items-center gap-3 flex-wrap">
168
+ {allSiteLanguages.length > 0 && (
169
+ <ContentLanguageSwitcher
170
+ currentItem={postWithBlocks}
171
+ itemType="post"
172
+ allSiteLanguages={allSiteLanguages}
173
+ />
174
+ )}
175
+ {postWithBlocks.translation_group_id && allSiteLanguages.length > 1 && (
176
+ <CopyContentFromLanguage
177
+ parentId={postId}
178
+ parentType="post"
179
+ currentLanguageId={postWithBlocks.language_id}
180
+ translationGroupId={postWithBlocks.translation_group_id}
181
+ allSiteLanguages={allSiteLanguages}
182
+ />
183
+ )}
184
+ <Button variant="outline" asChild>
185
+ <Link href={publicPostUrl} target="_blank" rel="noopener noreferrer">
186
+ <Eye className="mr-2 h-4 w-4" /> View Live Post
126
187
  </Link>
127
- </Button>
128
- <div>
129
- <h1 className="text-2xl font-bold">Edit Post</h1>
130
- <p className="text-sm text-muted-foreground truncate max-w-md" title={postWithBlocks.title}>{postWithBlocks.title}</p>
131
- </div>
132
- </div>
133
- <div className="flex items-center gap-3 flex-wrap"> {/* Added flex-wrap for responsiveness */}
134
- {allSiteLanguages.length > 0 && (
135
- <ContentLanguageSwitcher
136
- currentItem={postWithBlocks}
137
- itemType="post"
138
- allSiteLanguages={allSiteLanguages}
139
- />
140
- )}
141
- {postWithBlocks.translation_group_id && allSiteLanguages.length > 1 && (
142
- <CopyContentFromLanguage
143
- parentId={postId}
144
- parentType="post"
145
- currentLanguageId={postWithBlocks.language_id}
146
- translationGroupId={postWithBlocks.translation_group_id}
147
- allSiteLanguages={allSiteLanguages}
148
- />
149
- )}
150
- <Button variant="outline" asChild>
151
- <Link href={publicPostUrl} target="_blank" rel="noopener noreferrer">
152
- <Eye className="mr-2 h-4 w-4" /> View Live Post
153
- </Link>
154
- </Button>
155
- <RevisionHistoryButton parentType="post" parentId={postId} />
188
+ </Button>
189
+ <Button variant="secondary" asChild>
190
+ <a href={draftModeUrl} target="_blank" rel="noopener noreferrer">
191
+ <FilePenLine className="mr-2 h-4 w-4" />
192
+ Preview
193
+ </a>
194
+ </Button>
195
+ <RevisionHistoryButton parentType="post" parentId={postId} />
196
+ </div>
156
197
  </div>
157
- </div>
158
-
159
- <PostForm
160
- post={postWithBlocks as PostType & { feature_image_id?: string | null }} // Asserting feature_image_id might be string for PostForm
161
- formAction={updatePostWithId}
162
- actionButtonText="Update Post Metadata"
163
- isEditing={true}
164
- availableLanguagesProp={allSiteLanguages}
165
- initialFeatureImageUrl={initialFeatureImageUrl}
166
- initialFeatureImageId={initialFeatureImageIdProp}
167
- />
168
198
 
169
- <Separator className="my-8" />
199
+ <DraftStatusActions parentId={postId} parentType="post" hasDraft={hasDraft} />
170
200
 
171
- <div className="w-full mx-auto px-6">
172
- <h2 className="text-xl font-semibold mb-4">Post Content Blocks</h2>
173
- <BlockEditorArea
174
- parentId={postWithBlocks.id}
175
- parentType="post"
176
- initialBlocks={postWithBlocks.blocks}
177
- languageId={postWithBlocks.language_id}
201
+ <PostForm
202
+ post={postWithBlocks as PostType & { feature_image_id?: string | null }}
203
+ formAction={updatePostWithId}
204
+ actionButtonText="Update Post Metadata"
205
+ isEditing={true}
206
+ availableLanguagesProp={allSiteLanguages}
207
+ initialFeatureImageUrl={initialFeatureImageUrl}
208
+ initialFeatureImageId={initialFeatureImageIdProp}
178
209
  />
210
+
211
+ <Separator className="my-8" />
212
+
213
+ <div className="w-full mx-auto px-6">
214
+ <h2 className="text-xl font-semibold mb-4">Post Content Blocks</h2>
215
+ <BlockEditorArea
216
+ parentId={postWithBlocks.id}
217
+ parentType="post"
218
+ initialBlocks={postWithBlocks.blocks}
219
+ languageId={postWithBlocks.language_id}
220
+ />
221
+ </div>
179
222
  </div>
180
- </div>
181
223
  </UploadFolderProvider>
182
224
  );
183
225
  }
@@ -6,19 +6,17 @@ import { revalidatePath } from "next/cache";
6
6
  import { redirect } from "next/navigation";
7
7
  import type { Database } from "@nextblock-cms/db";
8
8
  import { v4 as uuidv4 } from 'uuid';
9
+ import { getOrCreateContentDraft } from "../../../lib/visual-editing/draft-content";
9
10
 
10
11
  type PageStatus = Database['public']['Enums']['page_status'];
11
12
  import { encodedRedirect } from "@nextblock-cms/utils/server"; // Ensure this is correctly imported
12
- import { getFullPostContent } from "../revisions/utils";
13
- import { createPostRevision } from "../revisions/service";
14
-
15
- // --- createPost and updatePost functions to be updated similarly for error returns ---
16
-
17
- export async function createPost(formData: FormData) {
18
- const supabase = createClient();
19
- const { data: { user } } = await supabase.auth.getUser();
20
- if (!user) {
21
- return encodedRedirect("error", "/cms/posts/new", "User not authenticated.");
13
+ // --- createPost and updatePost functions to be updated similarly for error returns ---
14
+
15
+ export async function createPost(formData: FormData) {
16
+ const supabase = createClient();
17
+ const { data: { user } } = await supabase.auth.getUser();
18
+ if (!user) {
19
+ return encodedRedirect("error", "/cms/posts/new", "User not authenticated.");
22
20
  }
23
21
 
24
22
  const featureImageIdStr_create = formData.get("feature_image_id") as string;
@@ -31,8 +29,10 @@ export async function createPost(formData: FormData) {
31
29
  title: formData.get("title") as string,
32
30
  slug: formData.get("slug") as string,
33
31
  language_id: parseInt(formData.get("language_id") as string, 10),
32
+ label: formData.get("label") as string || null,
34
33
  status: formData.get("status") as PageStatus,
35
34
  excerpt: formData.get("excerpt") as string || null,
35
+ subtitle: formData.get("subtitle") as string || null,
36
36
  published_at: formData.get("published_at") as string || null,
37
37
  meta_title: formData.get("meta_title") as string || null,
38
38
  meta_description: formData.get("meta_description") as string || null,
@@ -63,7 +63,7 @@ export async function createPost(formData: FormData) {
63
63
  const { data: newPost, error: createError } = await supabase
64
64
  .from("posts")
65
65
  .insert(postData)
66
- .select("id, title, slug, language_id, translation_group_id, excerpt, feature_image_id") // Added excerpt, feature_image_id
66
+ .select("id, title, slug, language_id, translation_group_id, label, excerpt, subtitle, feature_image_id")
67
67
  .single();
68
68
 
69
69
  if (createError) {
@@ -92,9 +92,11 @@ export async function createPost(formData: FormData) {
92
92
  language_id: lang.id,
93
93
  title: `[${lang.code.toUpperCase()}] ${newPost.title}`,
94
94
  slug: placeholderSlug,
95
+ label: newPost.label || null,
95
96
  status: 'draft',
96
97
  published_at: null,
97
98
  excerpt: `Placeholder for ${lang.code.toUpperCase()} translation. Original excerpt: ${newPost.excerpt || ''}`.substring(0, 250),
99
+ subtitle: `Placeholder for ${lang.code.toUpperCase()} translation. Original subtitle: ${newPost.subtitle || ''}`.substring(0, 500),
98
100
  meta_title: null,
99
101
  meta_description: null,
100
102
  translation_group_id: newPost.translation_group_id,
@@ -111,16 +113,16 @@ export async function createPost(formData: FormData) {
111
113
  successMessage += ` ${placeholderCreations} placeholder version(s) also created (draft status, please edit their slugs and content).`;
112
114
  }
113
115
  }
114
- }
115
-
116
- revalidatePath("/cms/posts");
117
- if (newPost?.slug) revalidatePath(`/article/${newPost.slug}`);
118
- revalidatePath("/articles");
119
-
120
- if (newPost?.id) {
121
- redirect(`/cms/posts/${newPost.id}/edit?success=${encodeURIComponent(successMessage)}`);
122
- } else {
123
- redirect(`/cms/posts?success=${encodeURIComponent(successMessage)}`);
116
+ }
117
+
118
+ revalidatePath("/cms/posts");
119
+ if (newPost?.slug) revalidatePath(`/article/${newPost.slug}`);
120
+ revalidatePath("/articles");
121
+
122
+ if (newPost?.id) {
123
+ redirect(`/cms/posts/${newPost.id}/edit?success=${encodeURIComponent(successMessage)}`);
124
+ } else {
125
+ redirect(`/cms/posts?success=${encodeURIComponent(successMessage)}`);
124
126
  }
125
127
  }
126
128
 
@@ -129,7 +131,7 @@ export async function updatePost(postId: number, formData: FormData) {
129
131
  const { data: { user } } = await supabase.auth.getUser();
130
132
  const postEditPath = `/cms/posts/${postId}/edit`;
131
133
 
132
- if (!user) return encodedRedirect("error", postEditPath, "User not authenticated.");
134
+ if (!user) return { error: "User not authenticated." };
133
135
 
134
136
  const { data: existingPost, error: fetchError } = await supabase
135
137
  .from("posts")
@@ -138,7 +140,7 @@ export async function updatePost(postId: number, formData: FormData) {
138
140
  .single();
139
141
 
140
142
  if (fetchError || !existingPost) {
141
- return encodedRedirect("error", "/cms/posts", "Original post not found or error fetching it.");
143
+ return { error: "Original post not found or error fetching it." };
142
144
  }
143
145
 
144
146
  const featureImageIdStr_update = formData.get("feature_image_id") as string;
@@ -151,8 +153,10 @@ export async function updatePost(postId: number, formData: FormData) {
151
153
  title: formData.get("title") as string,
152
154
  slug: formData.get("slug") as string,
153
155
  language_id: existingPost.language_id, // Use existing post's language_id
156
+ label: formData.get("label") as string || null,
154
157
  status: formData.get("status") as PageStatus,
155
158
  excerpt: formData.get("excerpt") as string || null,
159
+ subtitle: formData.get("subtitle") as string || null,
156
160
  published_at: formData.get("published_at") as string || null,
157
161
  meta_title: formData.get("meta_title") as string || null,
158
162
  meta_description: formData.get("meta_description") as string || null,
@@ -160,10 +164,10 @@ export async function updatePost(postId: number, formData: FormData) {
160
164
  };
161
165
 
162
166
  if (!rawFormData.title || !rawFormData.slug || isNaN(rawFormData.language_id) || !rawFormData.status) {
163
- return encodedRedirect("error", postEditPath, "Missing required fields: title, slug, language, or status.");
167
+ return { error: "Missing required fields: title, slug, language, or status." };
164
168
  }
165
169
  if (rawFormData.language_id !== existingPost.language_id) {
166
- return encodedRedirect("error", postEditPath, "Changing the language of an existing post version is not allowed. Create a new translation instead.");
170
+ return { error: "Changing the language of an existing post version is not allowed. Create a new translation instead." };
167
171
  }
168
172
 
169
173
  let publishedAtISO: string | null = null;
@@ -177,7 +181,9 @@ export async function updatePost(postId: number, formData: FormData) {
177
181
  title: rawFormData.title,
178
182
  slug: rawFormData.slug,
179
183
  language_id: rawFormData.language_id,
184
+ label: rawFormData.label,
180
185
  excerpt: rawFormData.excerpt,
186
+ subtitle: rawFormData.subtitle,
181
187
  status: rawFormData.status,
182
188
  published_at: publishedAtISO,
183
189
  meta_title: rawFormData.meta_title,
@@ -185,40 +191,31 @@ export async function updatePost(postId: number, formData: FormData) {
185
191
  feature_image_id: rawFormData.feature_image_id,
186
192
  };
187
193
 
188
- // capture previous full content
189
- const previousContent = await getFullPostContent(postId);
190
-
191
- const { error: updateError } = await supabase
192
- .from("posts")
193
- .update(postUpdateData)
194
- .eq("id", postId);
195
-
196
- if (updateError) {
197
- console.error("Error updating post:", updateError);
198
- if (updateError.code === '23505' && updateError.message.includes('posts_language_id_slug_key')) {
199
- return encodedRedirect("error", postEditPath, `The slug "${postUpdateData.slug}" already exists for the selected language. Please use a unique slug.`);
194
+ try {
195
+ const draft = await getOrCreateContentDraft(supabase, "post", postId, user.id);
196
+ const updatedMeta = {
197
+ ...draft.meta,
198
+ ...postUpdateData,
199
+ };
200
+
201
+ const { error: updateError } = await supabase
202
+ .from("content_drafts")
203
+ .update({ meta: updatedMeta as any })
204
+ .eq("id", draft.id);
205
+
206
+ if (updateError) {
207
+ console.error("Error updating post draft:", updateError);
208
+ return { error: `Failed to update draft: ${updateError.message}` };
200
209
  }
201
- return encodedRedirect("error", postEditPath, `Failed to update post: ${updateError.message}`);
210
+ } catch (err: any) {
211
+ console.error("Error loading/creating draft for post metadata update:", err);
212
+ return { error: `Failed to load draft: ${err.message || err}` };
202
213
  }
203
214
 
204
- // create revision after update
205
- if (previousContent && user) {
206
- const newContent = await getFullPostContent(postId);
207
- if (newContent) {
208
- await createPostRevision(postId, user.id, previousContent, newContent);
209
- }
210
- }
211
-
212
- revalidatePath("/cms/posts");
213
- if (existingPost.slug) revalidatePath(`/article/${existingPost.slug}`);
214
- if (rawFormData.slug && rawFormData.slug !== existingPost.slug) {
215
- revalidatePath(`/article/${rawFormData.slug}`);
216
- }
217
- revalidatePath("/articles");
218
- revalidatePath(postEditPath);
219
- redirect(`${postEditPath}?success=Post updated successfully`);
220
- }
221
-
215
+ revalidatePath(postEditPath);
216
+ return { success: true };
217
+ }
218
+
222
219
 
223
220
  export async function deletePost(postId: number) {
224
221
  const supabase = createClient();
@@ -248,11 +245,11 @@ export async function deletePost(postId: number) {
248
245
 
249
246
  // 3. Delete All Associated Navigation Links
250
247
  if (relatedPosts && relatedPosts.length > 0) {
251
- const urlsToDelete = relatedPosts.map(p => `/article/${p.slug}`);
252
- const { error: navError } = await supabase
253
- .from("navigation_items")
254
- .delete()
255
- .in("url", urlsToDelete);
248
+ const urlsToDelete = relatedPosts.map(p => `/article/${p.slug}`);
249
+ const { error: navError } = await supabase
250
+ .from("navigation_items")
251
+ .delete()
252
+ .in("url", urlsToDelete);
256
253
 
257
254
  if (navError) {
258
255
  console.error("Error deleting navigation links:", navError);
@@ -270,17 +267,17 @@ export async function deletePost(postId: number) {
270
267
  return encodedRedirect("error", "/cms/posts", `Failed to delete posts: ${deletePostsError.message}`);
271
268
  }
272
269
 
273
- // Revalidate paths
274
- revalidatePath("/cms/posts");
275
- revalidatePath("/cms/navigation");
276
- if (relatedPosts) {
277
- relatedPosts.forEach(p => {
278
- if (p.slug) {
279
- revalidatePath(`/article/${p.slug}`);
280
- }
281
- });
282
- }
283
- revalidatePath("/articles");
270
+ // Revalidate paths
271
+ revalidatePath("/cms/posts");
272
+ revalidatePath("/cms/navigation");
273
+ if (relatedPosts) {
274
+ relatedPosts.forEach(p => {
275
+ if (p.slug) {
276
+ revalidatePath(`/article/${p.slug}`);
277
+ }
278
+ });
279
+ }
280
+ revalidatePath("/articles");
284
281
 
285
282
  // 5. Update Redirect Message
286
283
  redirect(`/cms/posts?success=${encodeURIComponent("Post and all its translations were deleted successfully.")}`);
@@ -302,7 +299,9 @@ type UpsertPostPayload = {
302
299
  author_id: string | null;
303
300
  title: string;
304
301
  slug: string;
302
+ label?: string | null;
305
303
  excerpt?: string | null;
304
+ subtitle?: string | null;
306
305
  status: PageStatus;
307
306
  published_at?: string | null;
308
307
  meta_title?: string | null;
@@ -5,7 +5,7 @@ import React, { useState, useRef } from 'react';
5
5
  import { DropdownMenuItem } from "@nextblock-cms/ui";
6
6
  import { Trash2 } from "lucide-react";
7
7
  import { deletePost } from "../actions";
8
- import { ConfirmationModal } from '@/app/cms/components/ConfirmationModal';
8
+ import { ConfirmationModal } from '../../components/ConfirmationModal';
9
9
 
10
10
  interface DeletePostButtonClientProps {
11
11
  postId: number;