create-nextblock 0.2.77 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +191 -151
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -116
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -1,4 +1,4 @@
1
- // app/cms/blocks/components/BackgroundSelector.tsx
1
+ // app/cms/blocks/components/BackgroundSelector.tsx
2
2
  "use client";
3
3
 
4
4
  import React, { useState, useEffect } from "react";
@@ -6,16 +6,14 @@ import Image from "next/image";
6
6
  import { Label, Select, SelectTrigger, SelectContent, SelectItem, SelectValue, Button, Input, Checkbox } from "@nextblock-cms/ui";
7
7
  import { CustomSelectWithInput, ColorPicker } from "@nextblock-cms/ui";
8
8
  import { TooltipProvider } from "@radix-ui/react-tooltip";
9
- import { ImageIcon, X as XIcon, Save } from "lucide-react";
10
- import { cn } from "@nextblock-cms/utils";
9
+ import { Trash } from "lucide-react";
11
10
  import type { Database } from "@nextblock-cms/db";
12
- import type { SectionBlockContent } from "@/lib/blocks/blockRegistry";
13
- import MediaPickerDialog from "@/app/cms/media/components/MediaPickerDialog";
11
+ import { SectionBlockContent } from '../../../../lib/blocks/blockRegistry';
12
+ import MediaPickerDialog from "../../media/components/MediaPickerDialog";
13
+ import { resolveMediaUrl } from "../../../../lib/media/resolveMediaUrl";
14
14
 
15
15
  type Media = Database["public"]["Tables"]["media"]["Row"];
16
16
 
17
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || "";
18
-
19
17
  interface BackgroundSelectorProps {
20
18
  background: SectionBlockContent["background"];
21
19
  onChange: (newBackground: SectionBlockContent["background"]) => void;
@@ -127,11 +125,6 @@ export default function BackgroundSelector({ background, onChange }: BackgroundS
127
125
  }
128
126
  };
129
127
 
130
- const handleBackgroundPropertyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
131
- const { name, value } = e.target;
132
- onChange({ ...background, [name]: value });
133
- };
134
-
135
128
  const handleOverlayGradientChange = (e: React.ChangeEvent<HTMLInputElement>) => {
136
129
  const { name, value } = e.target;
137
130
  if (background?.type === "image" && background.image) {
@@ -161,17 +154,36 @@ export default function BackgroundSelector({ background, onChange }: BackgroundS
161
154
  }
162
155
  };
163
156
 
164
- const hasMinHeightChanged = (background?.min_height || "") !== minHeight;
165
- const imageSizeClass = selectedImage?.size === "contain" ? "object-contain" : "object-cover";
166
- const hasOverlayDirectionChanged = (selectedImage?.overlay?.gradient?.direction || "to bottom") !== overlayDirection;
157
+ const handleBackgroundGradientChange = (e: React.ChangeEvent<HTMLInputElement>) => {
158
+ const { name, value } = e.target as any;
159
+ if (backgroundType !== 'gradient') return;
160
+ const current = background.gradient || { type: 'linear' as const, direction: 'to right', stops: [ { color: '#3b82f6', position: 0 }, { color: '#8b5cf6', position: 100 } ] };
161
+ if (name === 'direction') {
162
+ onChange({ type: 'gradient', gradient: { ...current, direction: value } });
163
+ return;
164
+ }
165
+ if (name === 'startColor' || name === 'endColor') {
166
+ const updatedStops = (current.stops || [ { color: '#3b82f6', position: 0 }, { color: '#8b5cf6', position: 100 } ]).map((s) => {
167
+ if (name === 'startColor' && s.position === 0) return { ...s, color: value };
168
+ if (name === 'endColor' && s.position === 100) return { ...s, color: value };
169
+ return s;
170
+ });
171
+ onChange({ type: 'gradient', gradient: { ...current, stops: updatedStops } });
172
+ }
173
+ };
174
+
175
+ const selectedImageUrl = resolveMediaUrl(selectedImage?.object_key);
167
176
 
168
177
  return (
169
178
  <TooltipProvider>
170
- <div className="space-y-4">
171
- <div className="grid gap-2">
172
- <Label>Background Type</Label>
179
+ <div className="flex flex-wrap items-end gap-3.5 pt-1">
180
+ {/* Background Type */}
181
+ <div className="space-y-1.5 w-[140px]">
182
+ <Label className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Background Type</Label>
173
183
  <Select value={backgroundType} onValueChange={(v) => handleTypeChange(v as any)}>
174
- <SelectTrigger className="w-full max-w-[250px]"><SelectValue placeholder="Select type" /></SelectTrigger>
184
+ <SelectTrigger className="h-9 text-sm">
185
+ <SelectValue placeholder="Select type" />
186
+ </SelectTrigger>
175
187
  <SelectContent>
176
188
  <SelectItem value="none">None</SelectItem>
177
189
  <SelectItem value="gradient">Gradient</SelectItem>
@@ -180,169 +192,218 @@ export default function BackgroundSelector({ background, onChange }: BackgroundS
180
192
  </Select>
181
193
  </div>
182
194
 
183
- <div className="grid gap-2">
184
- <Label htmlFor="min_height">Minimum Height (e.g., 250px)</Label>
185
- <div className="flex items-center gap-2">
186
- <Input id="min_height" name="min_height" value={minHeight} onChange={(e) => setMinHeight(e.target.value)} placeholder="e.g., 250px" className="max-w-[200px]" />
187
- <Button type="button" variant="ghost" size="icon" onClick={() => handleBackgroundPropertyChange({ target: { name: "min_height", value: minHeight } } as any)} disabled={!hasMinHeightChanged} title="Save Minimum Height">
188
- <Save className={cn("h-5 w-5", hasMinHeightChanged && "text-green-600")} />
189
- </Button>
190
- </div>
195
+ {/* Minimum Height */}
196
+ <div className="space-y-1.5 w-[110px]">
197
+ <Label htmlFor="min_height" className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Min Height</Label>
198
+ <Input
199
+ id="min_height"
200
+ name="min_height"
201
+ value={minHeight}
202
+ onChange={(e) => setMinHeight(e.target.value)}
203
+ onBlur={() => {
204
+ onChange({ ...background, min_height: minHeight });
205
+ }}
206
+ onKeyDown={(e) => {
207
+ if (e.key === "Enter") {
208
+ e.currentTarget.blur();
209
+ }
210
+ }}
211
+ placeholder="e.g., 250px"
212
+ className="h-9 text-sm"
213
+ />
191
214
  </div>
192
215
 
193
- {backgroundType === "image" && (
216
+ {/* Gradient Background configuration (rendered inline) */}
217
+ {backgroundType === "gradient" && (
194
218
  <>
195
- <div className="mt-3 p-3 border rounded-md bg-muted/30 min-h-[120px] flex flex-col items-center justify-center">
196
- {selectedImage?.object_key ? (
197
- <div className="relative group w-full" style={{ height: background?.min_height || "250px", overflow: "hidden" }}>
198
- <Image src={`${R2_BASE_URL}/${selectedImage.object_key}`} alt="Selected background image" width={selectedImage.width || 500} height={selectedImage.height || 300} sizes="100vw" className={`w-full h-full ${imageSizeClass}`} style={{ objectPosition: selectedImage.position }} />
219
+ <div className="w-[200px]">
220
+ <CustomSelectWithInput
221
+ label="Gradient Direction"
222
+ tooltipContent="Select a preset or enter a custom angle like '45deg' or 'to top left'."
223
+ value={background.gradient?.direction || "to right"}
224
+ onChange={(value: string) => handleBackgroundGradientChange({ target: { name: "direction", value } } as any)}
225
+ options={[
226
+ { value: "to right", label: "To Right" },
227
+ { value: "to left", label: "To Left" },
228
+ { value: "to top", label: "To Top" },
229
+ { value: "to bottom", label: "To Bottom" },
230
+ { value: "to bottom right", label: "To Bottom Right" },
231
+ { value: "to top left", label: "To Top Left" },
232
+ ]}
233
+ />
234
+ </div>
235
+ <div className="w-[150px]">
236
+ <ColorPicker
237
+ label="Start Color"
238
+ color={background.gradient?.stops?.[0]?.color || "#3b82f6"}
239
+ onChange={(color) => handleBackgroundGradientChange({ target: { name: "startColor", value: color } } as any)}
240
+ />
241
+ </div>
242
+ <div className="w-[150px]">
243
+ <ColorPicker
244
+ label="End Color"
245
+ color={background.gradient?.stops?.[1]?.color || "#8b5cf6"}
246
+ onChange={(color) => handleBackgroundGradientChange({ target: { name: "endColor", value: color } } as any)}
247
+ />
248
+ </div>
249
+ </>
250
+ )}
251
+
252
+ {/* Image Size (only if image type) */}
253
+ {backgroundType === "image" && (
254
+ <div className="space-y-1.5 w-[110px]">
255
+ <Label className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Image Size</Label>
256
+ <Select value={selectedImage?.size || "cover"} onValueChange={(v) => handleImagePropertyChange("size", v)}>
257
+ <SelectTrigger className="h-9 text-sm">
258
+ <SelectValue />
259
+ </SelectTrigger>
260
+ <SelectContent>
261
+ <SelectItem value="cover">Cover</SelectItem>
262
+ <SelectItem value="contain">Contain</SelectItem>
263
+ </SelectContent>
264
+ </Select>
265
+ </div>
266
+ )}
267
+
268
+ {/* Image Position (only if image type) */}
269
+ {backgroundType === "image" && (
270
+ <div className="space-y-1.5 w-[130px]">
271
+ <Label className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Image Position</Label>
272
+ <Select value={imagePosition} onValueChange={(v) => { setImagePosition(v); handleImagePropertyChange("position", v); }}>
273
+ <SelectTrigger className="h-9 text-sm">
274
+ <SelectValue />
275
+ </SelectTrigger>
276
+ <SelectContent>
277
+ <SelectItem value="center">Center</SelectItem>
278
+ <SelectItem value="top">Top</SelectItem>
279
+ <SelectItem value="bottom">Bottom</SelectItem>
280
+ <SelectItem value="left">Left</SelectItem>
281
+ <SelectItem value="right">Right</SelectItem>
282
+ <SelectItem value="top left">Top Left</SelectItem>
283
+ <SelectItem value="top right">Top Right</SelectItem>
284
+ <SelectItem value="bottom left">Bottom Left</SelectItem>
285
+ <SelectItem value="bottom right">Bottom Right</SelectItem>
286
+ </SelectContent>
287
+ </Select>
288
+ </div>
289
+ )}
290
+
291
+ {/* Image Picker Trigger / Thumbnail */}
292
+ {backgroundType === "image" && (
293
+ <div className="space-y-1.5">
294
+ <Label className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Image</Label>
295
+ {selectedImage?.object_key ? (
296
+ <div className="flex items-center gap-2 h-9">
297
+ <div className="relative w-9 h-9 rounded border bg-muted overflow-hidden flex-shrink-0 shadow-sm">
298
+ {selectedImageUrl ? (
299
+ <Image
300
+ src={selectedImageUrl}
301
+ alt="Thumbnail"
302
+ fill
303
+ sizes="36px"
304
+ className="object-cover"
305
+ style={{ objectPosition: selectedImage.position }}
306
+ />
307
+ ) : null}
199
308
  {selectedImage.overlay && (
200
309
  <div className="absolute inset-0" style={{ background: generateGradientCss(selectedImage.overlay.gradient) }} />
201
310
  )}
202
- <Button type="button" variant="destructive" size="icon" className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity h-6 w-6" onClick={handleRemoveImage} title="Remove Image">
203
- <XIcon className="h-3 w-3" />
311
+ </div>
312
+ <div className="flex items-center gap-1.5">
313
+ <MediaPickerDialog
314
+ triggerLabel="Change"
315
+ triggerVariant="outline"
316
+ onSelect={handleSelectMediaFromLibrary}
317
+ accept={(m) => !!m.file_type?.startsWith("image/")}
318
+ title="Select Background Image"
319
+ >
320
+ <Button type="button" variant="outline" size="sm" className="h-9 px-2.5 text-xs">
321
+ Change
322
+ </Button>
323
+ </MediaPickerDialog>
324
+ <Button
325
+ type="button"
326
+ variant="outline"
327
+ size="icon"
328
+ className="h-9 w-9 text-destructive border-destructive/20 hover:bg-destructive/10 hover:text-destructive flex-shrink-0"
329
+ onClick={handleRemoveImage}
330
+ title="Remove Image"
331
+ >
332
+ <Trash className="h-4 w-4" />
204
333
  </Button>
205
334
  </div>
206
- ) : (
207
- <ImageIcon className="h-16 w-16 text-muted-foreground" />
208
- )}
209
-
210
- <div className="mt-3">
335
+ </div>
336
+ ) : (
337
+ <div className="h-9 flex items-center">
211
338
  <MediaPickerDialog
212
- triggerLabel={selectedImage?.object_key ? "Change Image" : "Select from Library"}
339
+ triggerLabel="Select Image"
340
+ triggerVariant="outline"
213
341
  onSelect={handleSelectMediaFromLibrary}
214
342
  accept={(m) => !!m.file_type?.startsWith("image/")}
215
- title="Select or Upload Background Image"
216
- />
217
- </div>
218
- </div>
219
-
220
- <div className="grid gap-2">
221
- <Label>Image Size</Label>
222
- <Select value={selectedImage?.size || "cover"} onValueChange={(v) => handleImagePropertyChange("size", v)}>
223
- <SelectTrigger className="w-full max-w-[250px]"><SelectValue /></SelectTrigger>
224
- <SelectContent>
225
- <SelectItem value="cover">Cover</SelectItem>
226
- <SelectItem value="contain">Contain</SelectItem>
227
- </SelectContent>
228
- </Select>
229
- </div>
230
-
231
- <div className="grid gap-2">
232
- <Label>Image Position</Label>
233
- <Select value={imagePosition} onValueChange={(v) => { setImagePosition(v); handleImagePropertyChange("position", v); }}>
234
- <SelectTrigger className="w-full max-w-[250px]"><SelectValue /></SelectTrigger>
235
- <SelectContent>
236
- <SelectItem value="center">Center</SelectItem>
237
- <SelectItem value="top">Top</SelectItem>
238
- <SelectItem value="bottom">Bottom</SelectItem>
239
- <SelectItem value="left">Left</SelectItem>
240
- <SelectItem value="right">Right</SelectItem>
241
- <SelectItem value="top left">Top Left</SelectItem>
242
- <SelectItem value="top right">Top Right</SelectItem>
243
- <SelectItem value="bottom left">Bottom Left</SelectItem>
244
- <SelectItem value="bottom right">Bottom Right</SelectItem>
245
- </SelectContent>
246
- </Select>
247
- </div>
248
-
249
- <div className="flex items-center space-x-2 mt-2">
250
- <Checkbox id="gradientOverlay" checked={!!selectedImage?.overlay} onCheckedChange={(c) => handleOverlayToggle(!!c)} />
251
- <div className="grid gap-1.5 leading-none">
252
- <label htmlFor="gradientOverlay" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Add Gradient Overlay</label>
253
- </div>
254
- </div>
255
-
256
- {selectedImage?.overlay && (
257
- <div className="mt-3 p-3 border rounded-md bg-muted/30 space-y-4">
258
- <div className="flex items-center gap-2">
259
- <div className="flex-grow">
260
- <CustomSelectWithInput
261
- label="Direction"
262
- tooltipContent="Select a preset or enter a custom angle like '45deg' or 'to top left'. See MDN's linear-gradient docs for more options."
263
- value={overlayDirection}
264
- onChange={setOverlayDirection}
265
- options={[
266
- { value: "to bottom", label: "To Bottom" },
267
- { value: "to top", label: "To Top" },
268
- { value: "to left", label: "To Left" },
269
- { value: "to right", label: "To Right" },
270
- { value: "to bottom right", label: "To Bottom Right" },
271
- { value: "to top left", label: "To Top Left" },
272
- ]}
273
- />
274
- </div>
275
- <Button size="icon" variant="ghost" onClick={() => handleOverlayGradientChange({ target: { name: "direction", value: overlayDirection } } as any)} disabled={!hasOverlayDirectionChanged} title="Save Overlay Direction">
276
- <Save className={cn("h-5 w-5 mt-[1.3rem]", hasOverlayDirectionChanged && "text-green-600")} />
343
+ title="Select Background Image"
344
+ >
345
+ <Button type="button" variant="outline" size="sm" className="h-9 px-3 text-xs">
346
+ Select Image
277
347
  </Button>
278
- </div>
279
- <div className="flex items-center gap-4">
280
- <ColorPicker
281
- label="Start Color"
282
- color={selectedImage.overlay.gradient?.stops?.[0]?.color || "rgba(0,0,0,0.5)"}
283
- onChange={(color) => handleOverlayGradientChange({ target: { name: "startColor", value: color } } as any)}
284
- />
285
- <ColorPicker
286
- label="End Color"
287
- color={selectedImage.overlay.gradient?.stops?.[1]?.color || "rgba(0,0,0,0)"}
288
- onChange={(color) => handleOverlayGradientChange({ target: { name: "endColor", value: color } } as any)}
289
- />
290
- </div>
348
+ </MediaPickerDialog>
291
349
  </div>
292
350
  )}
293
- </>
351
+ </div>
294
352
  )}
295
353
 
296
- {backgroundType === "gradient" && (
297
- <div className="mt-3 p-3 border rounded-md bg-muted/30 space-y-4">
298
- <div>
354
+ {/* Overlay Checkbox */}
355
+ {backgroundType === "image" && selectedImage?.object_key && (
356
+ <div className="space-y-1.5">
357
+ <Label className="text-xs uppercase font-bold text-muted-foreground tracking-wider leading-none">Overlay</Label>
358
+ <div className="flex items-center justify-center h-9 border border-input rounded-md px-3 bg-background">
359
+ <Checkbox
360
+ id="gradientOverlay"
361
+ checked={!!selectedImage?.overlay}
362
+ onCheckedChange={(c) => handleOverlayToggle(!!c)}
363
+ />
364
+ </div>
365
+ </div>
366
+ )}
367
+
368
+ {/* Gradient Overlay configuration fields (inline) */}
369
+ {backgroundType === "image" && selectedImage?.overlay && (
370
+ <>
371
+ <div className="w-[200px]">
299
372
  <CustomSelectWithInput
300
- label="Direction"
301
- tooltipContent="Select a preset or enter a custom angle like '45deg' or 'to top left'. See MDN's linear-gradient docs for more options."
302
- value={background.gradient?.direction || "to right"}
303
- onChange={(value: string) => handleBackgroundGradientChange({ target: { name: "direction", value } } as any)}
373
+ label="Overlay Direction"
374
+ tooltipContent="Select a preset or enter a custom angle like '45deg' or 'to top left'."
375
+ value={overlayDirection}
376
+ onChange={(val) => {
377
+ setOverlayDirection(val);
378
+ handleOverlayGradientChange({ target: { name: "direction", value: val } } as any);
379
+ }}
304
380
  options={[
305
- { value: "to right", label: "To Right" },
306
- { value: "to left", label: "To Left" },
307
- { value: "to top", label: "To Top" },
308
381
  { value: "to bottom", label: "To Bottom" },
382
+ { value: "to top", label: "To Top" },
383
+ { value: "to left", label: "To Left" },
384
+ { value: "to right", label: "To Right" },
309
385
  { value: "to bottom right", label: "To Bottom Right" },
310
386
  { value: "to top left", label: "To Top Left" },
311
387
  ]}
312
388
  />
313
389
  </div>
314
- <div className="flex items-center gap-4">
390
+ <div className="w-[150px]">
315
391
  <ColorPicker
316
- label="Start Color"
317
- color={background.gradient?.stops?.[0]?.color || "#3b82f6"}
318
- onChange={(color) => handleBackgroundGradientChange({ target: { name: "startColor", value: color } } as any)}
392
+ label="Overlay Start"
393
+ color={selectedImage.overlay.gradient?.stops?.[0]?.color || "rgba(0,0,0,0.5)"}
394
+ onChange={(color) => handleOverlayGradientChange({ target: { name: "startColor", value: color } } as any)}
319
395
  />
396
+ </div>
397
+ <div className="w-[150px]">
320
398
  <ColorPicker
321
- label="End Color"
322
- color={background.gradient?.stops?.[1]?.color || "#8b5cf6"}
323
- onChange={(color) => handleBackgroundGradientChange({ target: { name: "endColor", value: color } } as any)}
399
+ label="Overlay End"
400
+ color={selectedImage.overlay.gradient?.stops?.[1]?.color || "rgba(0,0,0,0)"}
401
+ onChange={(color) => handleOverlayGradientChange({ target: { name: "endColor", value: color } } as any)}
324
402
  />
325
403
  </div>
326
- </div>
404
+ </>
327
405
  )}
328
406
  </div>
329
407
  </TooltipProvider>
330
408
  );
331
- const handleBackgroundGradientChange = (e: React.ChangeEvent<HTMLInputElement>) => {
332
- const { name, value } = e.target as any;
333
- if (backgroundType !== 'gradient') return;
334
- const current = background.gradient || { type: 'linear' as const, direction: 'to right', stops: [ { color: '#3b82f6', position: 0 }, { color: '#8b5cf6', position: 100 } ] };
335
- if (name === 'direction') {
336
- onChange({ type: 'gradient', gradient: { ...current, direction: value } });
337
- return;
338
- }
339
- if (name === 'startColor' || name === 'endColor') {
340
- const updatedStops = (current.stops || [ { color: '#3b82f6', position: 0 }, { color: '#8b5cf6', position: 100 } ]).map((s) => {
341
- if (name === 'startColor' && s.position === 0) return { ...s, color: value };
342
- if (name === 'endColor' && s.position === 100) return { ...s, color: value };
343
- return s;
344
- });
345
- onChange({ type: 'gradient', gradient: { ...current, stops: updatedStops } });
346
- }
347
- };
348
409
  }
@@ -2,13 +2,14 @@
2
2
  "use client";
3
3
 
4
4
  import React, { useState, useTransition, useEffect, ComponentType, useCallback, useRef } from "react";
5
+ import { useRouter } from "next/navigation";
5
6
  import dynamic from 'next/dynamic';
6
7
  import debounce from 'lodash.debounce';
7
8
  import type { Database, Json } from "@nextblock-cms/db";
8
- import { type BlockType } from "@/lib/blocks/blockRegistry";
9
+ import { BlockType } from '../../../../lib/blocks/blockRegistry';
9
10
 
10
11
  type Block = Database["public"]["Tables"]["blocks"]["Row"];
11
- import { getBlockDefinition, type SectionBlockContent } from "@/lib/blocks/blockRegistry";
12
+ import { getBlockDefinition, SectionBlockContent } from '../../../../lib/blocks/blockRegistry';
12
13
  import { Button } from "@nextblock-cms/ui";
13
14
  import { PlusCircle } from "lucide-react";
14
15
  import {
@@ -23,6 +24,7 @@ import BlockTypeSelector from "./BlockTypeSelector";
23
24
  import {
24
25
  createBlockForPage,
25
26
  createBlockForPost,
27
+ createBlockForProduct,
26
28
  updateBlock,
27
29
  updateMultipleBlockOrders,
28
30
  } from "../actions";
@@ -47,8 +49,8 @@ import { SortableBlockItem } from "./SortableBlockItem";
47
49
  import EditableBlock from "./EditableBlock";
48
50
 
49
51
  interface BlockEditorAreaProps {
50
- parentId: number;
51
- parentType: "page" | "post";
52
+ parentId: number | string;
53
+ parentType: "page" | "post" | "product";
52
54
  initialBlocks: Block[];
53
55
  languageId: number;
54
56
  }
@@ -76,6 +78,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
76
78
 
77
79
  const [blocks, setBlocks] = useState<Block[]>(() => initialBlocks.sort((a, b) => a.order - b.order));
78
80
  const lastSavedBlocks = useRef(blocks);
81
+ const router = useRouter();
79
82
  const [isPending, startTransition] = useTransition();
80
83
  const [isSavingNested, startSavingNestedTransition] = useTransition();
81
84
  const [isBlockSelectorOpen, setIsBlockSelectorOpen] = useState(false);
@@ -95,13 +98,15 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
95
98
  const result = await updateBlock(
96
99
  blockToSave.id,
97
100
  blockToSave.content,
98
- parentType === "page" ? parentId : null,
99
- parentType === "post" ? parentId : null
101
+ parentType === "page" ? parentId as number : null,
102
+ parentType === "post" ? parentId as number : null,
103
+ parentType === "product" ? parentId as string : null
100
104
  );
101
105
 
102
106
  if (result.success && result.updatedBlock) {
103
107
  // On success, update the last saved state ref
104
108
  lastSavedBlocks.current = blocks;
109
+ router.refresh();
105
110
  } else {
106
111
  // On failure, revert the UI to the last known good state
107
112
  alert(`Failed to save changes: ${result.error}. Reverting.`);
@@ -250,13 +255,15 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
250
255
  const result = await updateBlock(
251
256
  parentSectionBlock.id,
252
257
  parentSectionBlock.content,
253
- parentType === "page" ? parentId : null,
254
- parentType === "post" ? parentId : null
258
+ parentType === "page" ? parentId as number : null,
259
+ parentType === "post" ? parentId as number : null,
260
+ parentType === "product" ? parentId as string : null
255
261
  );
256
262
 
257
263
  if (result.success && result.updatedBlock) {
258
264
  lastSavedBlocks.current = blocks;
259
265
  setEditingNestedBlockInfo(null);
266
+ router.refresh();
260
267
  } else {
261
268
  alert(`Error saving nested block changes: ${result.error}`);
262
269
  setBlocks(lastSavedBlocks.current);
@@ -293,8 +300,9 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
293
300
  if (blocksToUpdate.length > 0) {
294
301
  const updateResult = await updateMultipleBlockOrders(
295
302
  blocksToUpdate,
296
- parentType === "page" ? parentId : null,
297
- parentType === "post" ? parentId : null
303
+ parentType === "page" ? parentId as number : null,
304
+ parentType === "post" ? parentId as number : null,
305
+ parentType === "product" ? parentId as string : null
298
306
  );
299
307
 
300
308
  if (updateResult?.error) {
@@ -306,14 +314,21 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
306
314
  let createResult;
307
315
  if (parentType === "page") {
308
316
  createResult = await createBlockForPage(
309
- parentId,
317
+ parentId as number,
310
318
  languageId,
311
319
  blockType,
312
320
  newOrder
313
321
  );
314
- } else {
322
+ } else if (parentType === "post") {
315
323
  createResult = await createBlockForPost(
316
- parentId,
324
+ parentId as number,
325
+ languageId,
326
+ blockType,
327
+ newOrder
328
+ );
329
+ } else {
330
+ createResult = await createBlockForProduct(
331
+ parentId as string,
317
332
  languageId,
318
333
  blockType,
319
334
  newOrder
@@ -333,6 +348,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
333
348
 
334
349
  setBlocks(finalBlocks);
335
350
  lastSavedBlocks.current = finalBlocks;
351
+ router.refresh();
336
352
  } else {
337
353
  alert(`Error adding block: ${createResult?.error}`);
338
354
  // TODO: Revert order changes if creation fails
@@ -380,8 +396,9 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
380
396
  startTransition(async () => {
381
397
  const result = await updateMultipleBlockOrders(
382
398
  itemsToUpdateDb,
383
- parentType === "page" ? parentId : null,
384
- parentType === "post" ? parentId : null
399
+ parentType === "page" ? parentId as number : null,
400
+ parentType === "post" ? parentId as number : null,
401
+ parentType === "product" ? parentId as string : null
385
402
  );
386
403
 
387
404
  if (result?.error) {
@@ -389,6 +406,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
389
406
  setBlocks(originalBlocks);
390
407
  } else {
391
408
  lastSavedBlocks.current = finalItemsWithUpdatedOrder;
409
+ router.refresh();
392
410
  }
393
411
  });
394
412
  }
@@ -407,7 +425,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
407
425
  parentBlockId: parentBlockIdStr,
408
426
  columnIndex,
409
427
  blockIndexInColumn,
410
- blockData: nestedBlockData,
428
+ blockData: nestedBlockData as unknown as NestedBlockData,
411
429
  });
412
430
  } else {
413
431
  console.error("Nested block not found at specified indices:", { parentBlockIdStr, columnIndex, blockIndexInColumn });
@@ -465,14 +483,16 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
465
483
  const result = await import("../actions").then(({ deleteBlock }) =>
466
484
  deleteBlock(
467
485
  blockIdToDelete,
468
- parentType === "page" ? parentId : null,
469
- parentType === "post" ? parentId : null
486
+ parentType === "page" ? parentId as number : null,
487
+ parentType === "post" ? parentId as number : null,
488
+ parentType === "product" ? parentId as string : null
470
489
  )
471
490
  );
472
491
  if (result && result.success) {
473
492
  const newBlocks = blocks.filter((b) => b.id !== blockIdToDelete);
474
493
  setBlocks(newBlocks);
475
494
  lastSavedBlocks.current = newBlocks;
495
+ router.refresh();
476
496
  } else if (result?.error) {
477
497
  alert(`Error deleting block: ${result.error}`);
478
498
  }
@@ -546,8 +566,9 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
546
566
  order: 0, // Temporary order for nested blocks
547
567
  created_at: new Date().toISOString(),
548
568
  updated_at: new Date().toISOString(),
549
- page_id: parentType === 'page' ? parentId : null,
550
- post_id: parentType === 'post' ? parentId : null,
569
+ page_id: parentType === 'page' ? parentId as number : null,
570
+ post_id: parentType === 'post' ? parentId as number : null,
571
+ product_id: parentType === 'product' ? parentId as string : null,
551
572
  };
552
573
  return <NestedBlockEditorComponent
553
574
  block={fullBlockForEditor}