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
@@ -0,0 +1,13 @@
1
+ // app/cms/settings/security/page.tsx
2
+ import { getSecurityPanelData } from './actions';
3
+ import SecurityPanel from './components/SecurityPanel';
4
+
5
+ export default async function SecuritySettingsPage() {
6
+ const data = await getSecurityPanelData();
7
+
8
+ return (
9
+ <div className="max-w-4xl mx-auto space-y-6">
10
+ <SecurityPanel data={data} />
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,21 @@
1
+ import { redirect } from 'next/navigation';
2
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
+ import { TaxesPage as TaxesPageUI } from '@nextblock-cms/ecommerce/server';
4
+
5
+ export const metadata = {
6
+ title: 'Tax Settings | NextBlock™ CMS',
7
+ };
8
+
9
+ export default async function TaxesPageWrapper({
10
+ searchParams,
11
+ }: {
12
+ searchParams?: Promise<{ success?: string }>;
13
+ }) {
14
+ const isOnline = await verifyPackageOnline('ecommerce');
15
+
16
+ if (!isOnline) {
17
+ redirect('/cms/settings/packages');
18
+ }
19
+
20
+ return <TaxesPageUI searchParams={await searchParams} />;
21
+ }
@@ -0,0 +1,20 @@
1
+ import { redirect } from 'next/navigation';
2
+ import { verifyPackageOnline } from '@nextblock-cms/db/server';
3
+ import { ShippingPage as ShippingPageUI } from '@nextblock-cms/ecommerce/server';
4
+
5
+ export default async function ShippingPage({
6
+ searchParams,
7
+ }: {
8
+ searchParams: Promise<{ success?: string }>;
9
+ }) {
10
+ const [isOnline, resolvedSearchParams] = await Promise.all([
11
+ verifyPackageOnline('ecommerce'),
12
+ searchParams,
13
+ ]);
14
+
15
+ if (!isOnline) {
16
+ redirect('/cms/settings/packages');
17
+ }
18
+
19
+ return <ShippingPageUI searchParams={resolvedSearchParams} />;
20
+ }
@@ -1,20 +1,23 @@
1
1
  // app/cms/users/[id]/edit/page.tsx
2
- import { createClient } from "@nextblock-cms/db/server";
3
- import UserForm from "../../components/UserForm";
4
- import { updateUserProfile } from "../../actions";
5
- import type { Database } from "@nextblock-cms/db";
6
- import { notFound } from "next/navigation";
7
-
8
- type Profile = Database['public']['Tables']['profiles']['Row'];
9
- type AuthUser = {
10
- id: string;
11
- email: string | undefined;
2
+ import UserForm from "../../components/UserForm";
3
+ import { updateUserProfile } from "../../actions";
4
+ import type { Database } from "@nextblock-cms/db";
5
+ import { notFound } from "next/navigation";
6
+ import { getDefaultUserAddresses } from "@nextblock-cms/ecommerce/server";
7
+
8
+ type Profile = Database['public']['Tables']['profiles']['Row'];
9
+ type AuthUser = {
10
+ id: string;
11
+ email: string | undefined;
12
12
  created_at: string | undefined;
13
13
  last_sign_in_at: string | undefined;
14
14
  };
15
15
 
16
- async function getUserAndProfileData(userId: string): Promise<{ authUser: AuthUser; profile: Profile | null } | null> {
17
- const supabase = createClient();
16
+ async function getUserAndProfileData(userId: string): Promise<{
17
+ authUser: AuthUser;
18
+ profile: Profile | null;
19
+ addresses: Awaited<ReturnType<typeof getDefaultUserAddresses>>;
20
+ } | null> {
18
21
 
19
22
  // Fetch user from auth.users
20
23
  // For admin operations, you might need a service_role client to fetch any user.
@@ -40,8 +43,8 @@ async function getUserAndProfileData(userId: string): Promise<{ authUser: AuthUs
40
43
  return null;
41
44
  }
42
45
 
43
- // Fetch profile from public.profiles
44
- const { data: profileData, error: profileError } = await supabase
46
+ // Fetch profile from public.profiles using Service Role to bypass RLS
47
+ const { data: profileData, error: profileError } = await serviceSupabase
45
48
  .from("profiles")
46
49
  .select("*")
47
50
  .eq("id", userId)
@@ -59,8 +62,10 @@ async function getUserAndProfileData(userId: string): Promise<{ authUser: AuthUs
59
62
  last_sign_in_at: authUserData.last_sign_in_at,
60
63
  };
61
64
 
62
- return { authUser: simplifiedAuthUser, profile: profileData as Profile | null };
63
- }
65
+ const addresses = await getDefaultUserAddresses(userId, serviceSupabase as any);
66
+
67
+ return { authUser: simplifiedAuthUser, profile: profileData as Profile | null, addresses };
68
+ }
64
69
 
65
70
  export default async function EditUserPage(props: { params: Promise<{ id: string }> }) {
66
71
  const params = await props.params;
@@ -78,14 +83,14 @@ export default async function EditUserPage(props: { params: Promise<{ id: string
78
83
  const updateUserActionWithId = updateUserProfile.bind(null, userId);
79
84
 
80
85
  return (
81
- <div className="max-w-xl mx-auto">
86
+ <div className="max-w-4xl mx-auto">
82
87
  <h1 className="text-2xl font-bold mb-6">Edit User: {userData.authUser.email}</h1>
83
- <UserForm
84
- userToEditAuth={userData.authUser}
85
- userToEditProfile={userData.profile}
86
- formAction={updateUserActionWithId}
87
- actionButtonText="Update User Profile"
88
- />
88
+ <UserForm
89
+ userToEditAuth={userData.authUser}
90
+ userToEditProfile={userData.profile}
91
+ userToEditAddresses={userData.addresses}
92
+ formAction={updateUserActionWithId}
93
+ />
89
94
  </div>
90
95
  );
91
96
  }
@@ -1,10 +1,13 @@
1
1
  // app/cms/users/actions.ts
2
- "use server";
3
-
4
- import { createClient } from "@nextblock-cms/db/server";
5
- import { revalidatePath } from "next/cache";
6
- import { redirect } from "next/navigation";
7
- import type { Database } from "@nextblock-cms/db";
2
+ "use server";
3
+
4
+ import { createClient } from "@nextblock-cms/db/server";
5
+ import { revalidatePath } from "next/cache";
6
+ import { redirect } from "next/navigation";
7
+ import type { Database } from "@nextblock-cms/db";
8
+ import { normalizeCustomerAddress } from "@nextblock-cms/ecommerce";
9
+ import { upsertDefaultUserAddresses } from "@nextblock-cms/ecommerce/server";
10
+ import { createClient as createSupabaseClient } from '@supabase/supabase-js';
8
11
 
9
12
  type UserRole = Database['public']['Enums']['user_role'];
10
13
 
@@ -29,25 +32,73 @@ async function verifyAdmin(supabase: ReturnType<typeof createClient>): Promise<{
29
32
  return { isAdmin: true, userId: user.id };
30
33
  }
31
34
 
32
- type UpdateUserProfilePayload = {
33
- role: UserRole;
34
- username?: string | null;
35
- full_name?: string | null;
36
- // Add other editable profile fields here if needed
37
- };
38
-
39
- export async function updateUserProfile(userIdToUpdate: string, formData: FormData) {
40
- const supabase = createClient();
41
- const adminCheck = await verifyAdmin(supabase);
35
+ type UpdateUserProfilePayload = {
36
+ role: UserRole;
37
+ full_name?: string | null;
38
+ avatar_url?: string | null;
39
+ website?: string | null;
40
+ github_username?: string | null;
41
+ phone?: string | null;
42
+ };
43
+
44
+ function createServiceRoleClient() {
45
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
46
+ const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
47
+
48
+ if (!supabaseUrl || !serviceRoleKey) {
49
+ throw new Error('Missing required environment variables');
50
+ }
51
+
52
+ return createSupabaseClient(supabaseUrl, serviceRoleKey, {
53
+ auth: {
54
+ autoRefreshToken: false,
55
+ persistSession: false,
56
+ },
57
+ });
58
+ }
59
+
60
+ export async function updateUserProfile(userIdToUpdate: string, formData: FormData) {
61
+ const supabase = createClient();
62
+ const adminCheck = await verifyAdmin(supabase);
42
63
  if (!adminCheck.isAdmin) {
43
64
  return { error: adminCheck.error || "Unauthorized" };
44
65
  }
45
66
 
46
- const rawFormData = {
47
- role: formData.get("role") as UserRole,
48
- username: formData.get("username") as string || null,
49
- full_name: formData.get("full_name") as string || null,
50
- };
67
+ const parseAddressField = (fieldName: string) => {
68
+ const rawValue = formData.get(fieldName) as string | null;
69
+ if (!rawValue) {
70
+ return null;
71
+ }
72
+
73
+ try {
74
+ return normalizeCustomerAddress(JSON.parse(rawValue));
75
+ } catch {
76
+ throw new Error(`Invalid JSON for ${fieldName}.`);
77
+ }
78
+ };
79
+
80
+ let billingAddressJSON = null;
81
+ let shippingAddressJSON = null;
82
+
83
+ try {
84
+ billingAddressJSON = parseAddressField("billing_address");
85
+ shippingAddressJSON = parseAddressField("shipping_address");
86
+ } catch (error: any) {
87
+ return { error: error.message };
88
+ }
89
+
90
+ if (formData.get("use_billing_for_shipping") === "true") {
91
+ shippingAddressJSON = billingAddressJSON;
92
+ }
93
+
94
+ const rawFormData = {
95
+ role: formData.get("role") as UserRole,
96
+ full_name: formData.get("full_name") as string || null,
97
+ avatar_url: formData.get("avatar_url") as string || null,
98
+ website: formData.get("website") as string || null,
99
+ github_username: formData.get("github_username") as string || null,
100
+ phone: formData.get("phone") as string || null,
101
+ };
51
102
 
52
103
  if (!rawFormData.role) {
53
104
  return { error: "Role is a required field." };
@@ -69,26 +120,40 @@ export async function updateUserProfile(userIdToUpdate: string, formData: FormDa
69
120
  }
70
121
 
71
122
 
72
- const profileData: UpdateUserProfilePayload = {
73
- role: rawFormData.role,
74
- username: rawFormData.username,
75
- full_name: rawFormData.full_name,
76
- };
77
-
78
- const { error } = await supabase
79
- .from("profiles")
80
- .update(profileData)
81
- .eq("id", userIdToUpdate);
123
+ const profileData: UpdateUserProfilePayload = {
124
+ role: rawFormData.role,
125
+ full_name: rawFormData.full_name,
126
+ avatar_url: rawFormData.avatar_url,
127
+ website: rawFormData.website,
128
+ github_username: rawFormData.github_username,
129
+ phone: rawFormData.phone,
130
+ };
131
+
132
+ const adminSupabase = createServiceRoleClient();
133
+
134
+ const { error } = await adminSupabase
135
+ .from("profiles")
136
+ .update(profileData)
137
+ .eq("id", userIdToUpdate);
82
138
 
83
139
  if (error) {
84
- console.error("Error updating user profile:", error);
85
- return { error: `Failed to update profile: ${error.message}` };
86
- }
87
-
88
- revalidatePath("/cms/users");
89
- revalidatePath(`/cms/users/${userIdToUpdate}/edit`);
90
- redirect(`/cms/users/${userIdToUpdate}/edit?success=User profile updated successfully`);
91
- }
140
+ console.error("Error updating user profile:", error);
141
+ return { error: `Failed to update profile: ${error.message}` };
142
+ }
143
+
144
+ await upsertDefaultUserAddresses({
145
+ userId: userIdToUpdate,
146
+ billingAddress: billingAddressJSON,
147
+ shippingAddress: shippingAddressJSON,
148
+ client: adminSupabase,
149
+ });
150
+
151
+ revalidatePath("/cms/users");
152
+ revalidatePath(`/cms/users/${userIdToUpdate}/edit`);
153
+ revalidatePath("/profile");
154
+ revalidatePath("/checkout");
155
+ redirect(`/cms/users/${userIdToUpdate}/edit?success=User profile updated successfully`);
156
+ }
92
157
 
93
158
  export async function deleteUserAndProfile(userIdToDelete: string) {
94
159
 
@@ -153,4 +218,4 @@ export async function deleteUserAndProfile(userIdToDelete: string) {
153
218
  // The `profiles` table has ON DELETE CASCADE for the user ID, so it should be deleted automatically.
154
219
  revalidatePath("/cms/users");
155
220
  redirect("/cms/users?success=User deleted successfully");
156
- }
221
+ }
@@ -5,7 +5,7 @@ import React, { useState, useTransition } from "react";
5
5
  import { DropdownMenuItem } from "@nextblock-cms/ui";
6
6
  import { Trash2, ShieldAlert } from "lucide-react";
7
7
  import { deleteUserAndProfile } from "../actions";
8
- import { ConfirmationModal } from "@/app/cms/components/ConfirmationModal";
8
+ import { ConfirmationModal } from '../../components/ConfirmationModal';
9
9
 
10
10
  export function DeleteUserButtonClient({
11
11
  userId,
@@ -1,158 +1,71 @@
1
- // app/cms/users/components/UserForm.tsx
2
- "use client";
3
-
4
- import { useEffect, useState, useTransition, useRef } from "react";
5
- import { useHotkeys } from "@/hooks/use-hotkeys";
6
- import { useRouter, useSearchParams } from "next/navigation";
7
- import { Button } from "@nextblock-cms/ui";
8
- import { Spinner, Alert, AlertDescription, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@nextblock-cms/ui";
9
- import { Info } from "lucide-react";
10
- import { Input } from "@nextblock-cms/ui";
11
- import { Label } from "@nextblock-cms/ui";
12
- import {
13
- Select,
14
- SelectContent,
15
- SelectItem,
16
- SelectTrigger,
17
- SelectValue,
18
- } from "@nextblock-cms/ui";
19
- import type { Database } from "@nextblock-cms/db";
20
- import { useAuth } from "@/context/AuthContext";
21
-
1
+ "use client";
2
+
3
+ import { CustomerProfileForm, ExtendedProfileUpdateData, addressesMatch, type CustomerAddressInput } from "@nextblock-cms/ecommerce";
4
+ import MediaPickerDialog from "../../media/components/MediaPickerDialog";
5
+ import type { Database } from "@nextblock-cms/db";
6
+ import { useSearchParams } from "next/navigation";
7
+
8
+
22
9
  type Profile = Database['public']['Tables']['profiles']['Row'];
23
- type UserRole = Database['public']['Enums']['user_role'];
24
- type AuthUser = {
25
- id: string;
26
- email: string | undefined;
27
- created_at: string | undefined;
28
- last_sign_in_at: string | undefined;
29
- };
30
-
31
- interface UserFormProps {
32
- userToEditAuth: AuthUser; // Auth details (email, id) - email usually not editable here
33
- userToEditProfile: Profile | null; // Profile details (role, username, etc.)
34
- formAction: (formData: FormData) => Promise<{ error?: string } | void>;
35
- actionButtonText?: string;
36
- }
37
-
38
- export default function UserForm({
39
- userToEditAuth,
40
- userToEditProfile,
41
- formAction,
42
- actionButtonText = "Save Changes",
43
- }: UserFormProps) {
44
- const router = useRouter();
45
- const searchParams = useSearchParams();
46
- const [isPending, startTransition] = useTransition();
47
- const { isAdmin, isLoading: authLoading } = useAuth(); // For client-side guard
48
-
49
- const [role, setRole] = useState<UserRole>(userToEditProfile?.role || "USER");
50
- const [username, setUsername] = useState(userToEditProfile?.username || "");
51
- const [fullName, setFullName] = useState(userToEditProfile?.full_name || "");
52
- // Email is typically not changed here by an admin, it's part of auth.users managed by user or super-admin
53
- const email = userToEditAuth.email || "N/A";
54
-
55
- const [formMessage, setFormMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
56
-
57
- useEffect(() => {
58
- const successMessage = searchParams.get('success');
59
- const errorMessage = searchParams.get('error');
60
- if (successMessage) {
61
- setFormMessage({ type: 'success', text: successMessage });
62
- // Optionally clear the query param from URL
63
- // router.replace(pathname, undefined, { shallow: true }); // if using next/router
64
- } else if (errorMessage) {
65
- setFormMessage({ type: 'error', text: errorMessage });
66
- }
67
- }, [searchParams, router]);
68
-
69
10
 
70
- const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
71
- event.preventDefault();
72
- setFormMessage(null);
73
- const formData = new FormData(event.currentTarget);
74
- // Add user ID to form data if needed by action, or pass it directly
75
- // formData.append("userId", userToEditAuth.id);
76
-
77
- startTransition(async () => {
78
- const result = await formAction(formData); // The action is already bound with userId
79
- if (result?.error) {
80
- setFormMessage({ type: 'error', text: result.error });
81
- }
82
- // Success is handled by redirect with query param in server action
83
- });
84
- };
85
-
86
- if (authLoading) return <div>Loading...</div>;
87
- if (!isAdmin) return <div>Access Denied. Admin role required.</div>;
88
-
89
- const userRoles: UserRole[] = ['USER', 'WRITER', 'ADMIN'];
90
-
91
- const formRef = useRef<HTMLFormElement>(null);
92
- useHotkeys('ctrl+s', () => formRef.current?.requestSubmit());
11
+ interface UserFormProps {
12
+ userToEditAuth: { email: string | undefined; id: string };
13
+ userToEditProfile: Profile | null;
14
+ userToEditAddresses: {
15
+ billingAddress: CustomerAddressInput | null;
16
+ shippingAddress: CustomerAddressInput | null;
17
+ };
18
+ formAction: (formData: FormData) => Promise<{ error?: string } | void>;
19
+ }
20
+
21
+ export default function UserForm({ userToEditAuth, userToEditProfile, userToEditAddresses, formAction }: UserFormProps) {
22
+ const searchParams = useSearchParams();
23
+ const successMsg = searchParams.get('success');
24
+
25
+ const handleAdminSave = async (data: ExtendedProfileUpdateData) => {
26
+ const formData = new FormData();
27
+ // Only append if defined to avoid null string issues if server doesn't handle them,
28
+ // but server action expects strings or checks presence.
29
+ // Standard practice: append everything that has a value.
30
+ if (data.full_name !== undefined) formData.append('full_name', data.full_name || '');
31
+ if (data.avatar_url !== undefined) formData.append('avatar_url', data.avatar_url || '');
32
+ if (data.website !== undefined) formData.append('website', data.website || '');
33
+ if (data.github_username !== undefined) formData.append('github_username', data.github_username || '');
34
+ if (data.phone !== undefined) formData.append('phone', data.phone || '');
35
+ if (data.role !== undefined) formData.append('role', data.role);
36
+
37
+ formData.append('billing_address', JSON.stringify(data.billing_address ?? null));
38
+ formData.append('shipping_address', JSON.stringify(data.shipping_address ?? null));
39
+ formData.append('use_billing_for_shipping', data.use_billing_for_shipping ? 'true' : 'false');
40
+
41
+ return await formAction(formData);
42
+ };
93
43
 
94
44
  return (
95
- <form ref={formRef} onSubmit={handleSubmit} className="space-y-6">
96
- {formMessage && (
97
- <Alert variant={formMessage.type === 'success' ? 'success' : 'destructive'}>
98
- <AlertDescription>{formMessage.text}</AlertDescription>
99
- </Alert>
100
- )}
101
- <div>
102
- <Label htmlFor="email">Email (Read-only)</Label>
103
- <Input id="email" name="email" value={email} readOnly disabled className="mt-1 bg-muted/50" />
104
- </div>
105
-
106
- <div>
107
- <Label htmlFor="username">Username</Label>
108
- <Input id="username" name="username" value={username} onChange={(e) => setUsername(e.target.value)} className="mt-1" />
109
- </div>
110
-
111
- <div>
112
- <Label htmlFor="full_name">Full Name</Label>
113
- <Input id="full_name" name="full_name" value={fullName} onChange={(e) => setFullName(e.target.value)} className="mt-1" />
114
- </div>
115
-
116
- <div>
117
- <div className="flex items-center gap-2 mb-2">
118
- <Label htmlFor="role">Role</Label>
119
- <TooltipProvider>
120
- <Tooltip>
121
- <TooltipTrigger asChild>
122
- <Info className="h-4 w-4 text-muted-foreground opacity-70 cursor-pointer" />
123
- </TooltipTrigger>
124
- <TooltipContent className="max-w-xs">
125
- <p><strong>ADMIN:</strong> Full access to settings and content.</p>
126
- <p><strong>WRITER:</strong> Can create/edit content, no settings access.</p>
127
- <p><strong>USER:</strong> Read-only access.</p>
128
- </TooltipContent>
129
- </Tooltip>
130
- </TooltipProvider>
131
- </div>
132
- <Select name="role" value={role} onValueChange={(value) => setRole(value as UserRole)} required>
133
- <SelectTrigger className="mt-1"><SelectValue placeholder="Select role" /></SelectTrigger>
134
- <SelectContent>
135
- {userRoles.map((r) => (
136
- <SelectItem key={r} value={r}>{r.charAt(0) + r.slice(1).toLowerCase()}</SelectItem>
137
- ))}
138
- </SelectContent>
139
- </Select>
140
- </div>
141
-
142
- <div className="flex justify-end space-x-3 pt-4">
143
- <Button type="button" variant="outline" onClick={() => router.push("/cms/users")} disabled={isPending}>
144
- Cancel
145
- </Button>
146
- <Button type="submit" disabled={isPending || authLoading}>
147
- {isPending ? (
148
- <>
149
- <Spinner className="mr-2 h-4 w-4" /> Saving...
150
- </>
151
- ) : (
152
- actionButtonText
153
- )}
154
- </Button>
155
- </div>
156
- </form>
45
+ <div className="max-w-4xl mx-auto">
46
+ <CustomerProfileForm
47
+ initialData={{
48
+ full_name: userToEditProfile?.full_name || '',
49
+ avatar_url: userToEditProfile?.avatar_url || '',
50
+ website: userToEditProfile?.website || '',
51
+ github_username: userToEditProfile?.github_username || '',
52
+ phone: userToEditProfile?.phone || '',
53
+ role: userToEditProfile?.role || 'USER',
54
+ billing_address: userToEditAddresses.billingAddress,
55
+ shipping_address: userToEditAddresses.shippingAddress,
56
+ use_billing_for_shipping:
57
+ !userToEditAddresses.shippingAddress ||
58
+ addressesMatch(
59
+ userToEditAddresses.billingAddress,
60
+ userToEditAddresses.shippingAddress
61
+ ),
62
+ }}
63
+ MediaPickerComponent={MediaPickerDialog}
64
+ isAdmin={true}
65
+ email={userToEditAuth.email}
66
+ onAction={handleAdminSave}
67
+ initialSuccessMessage={successMsg}
68
+ />
69
+ </div>
157
70
  );
158
71
  }
@@ -44,7 +44,12 @@ async function getUsersData(currentAdminId: string): Promise<UserWithProfile[]>
44
44
  throw new Error('Missing required environment variables');
45
45
  }
46
46
 
47
- const supabaseAdmin = createServiceRoleClient(supabaseUrl, serviceRoleKey);
47
+ const supabaseAdmin = createServiceRoleClient(supabaseUrl, serviceRoleKey, {
48
+ auth: {
49
+ autoRefreshToken: false,
50
+ persistSession: false
51
+ }
52
+ });
48
53
 
49
54
  const { data: { users: authUsers }, error: usersError } = await supabaseAdmin.auth.admin.listUsers({
50
55
  page: 1,
@@ -52,19 +57,19 @@ async function getUsersData(currentAdminId: string): Promise<UserWithProfile[]>
52
57
  });
53
58
 
54
59
  if (usersError) {
55
- console.error("Error fetching auth users:", usersError);
60
+ console.error("Error fetching auth users:", JSON.stringify(usersError, null, 2));
56
61
  return [];
57
62
  }
58
63
  if (!authUsers) return [];
59
64
 
60
65
  // Fetch all profiles
61
- const supabase = createClient(); // Standard client for profile fetching (RLS applies)
62
- const { data: profiles, error: profilesError } = await supabase
66
+ // Use admin client to bypass RLS policies so we can see all user profiles
67
+ const { data: profiles, error: profilesError } = await supabaseAdmin
63
68
  .from("profiles")
64
69
  .select("*");
65
70
 
66
71
  if (profilesError) {
67
- console.error("Error fetching profiles:", profilesError);
72
+ console.error("Error fetching profiles:", JSON.stringify(profilesError, null, 2));
68
73
  // Continue without profiles if there's an error, or handle differently
69
74
  }
70
75
 
@@ -82,7 +87,7 @@ async function getUsersData(currentAdminId: string): Promise<UserWithProfile[]>
82
87
  authUser: simplifiedAuthUser,
83
88
  profile: profilesMap.get(authUser.id) || null,
84
89
  };
85
- }).filter(user => user.authUser.id !== currentAdminId); // Filter out the current admin from the list
90
+ });
86
91
  }
87
92
 
88
93
  export default async function CmsUsersListPage() {
@@ -123,8 +128,8 @@ export default async function CmsUsersListPage() {
123
128
  <TableRow>
124
129
  <TableHead className="w-[80px]">Avatar</TableHead>
125
130
  <TableHead>Email</TableHead>
126
- <TableHead>Username</TableHead>
127
131
  <TableHead>Full Name</TableHead>
132
+ <TableHead>GitHub</TableHead>
128
133
  <TableHead>Role</TableHead>
129
134
  <TableHead>Joined</TableHead>
130
135
  <TableHead className="text-right w-[80px]">Actions</TableHead>
@@ -135,13 +140,13 @@ export default async function CmsUsersListPage() {
135
140
  <TableRow key={authUser.id}>
136
141
  <TableCell>
137
142
  <Avatar className="h-9 w-9">
138
- <AvatarImage src={profile?.avatar_url || undefined} alt={profile?.username || authUser.email} />
143
+ <AvatarImage src={profile?.avatar_url || undefined} alt={profile?.full_name || authUser.email} />
139
144
  <AvatarFallback>{authUser.email?.charAt(0).toUpperCase() || 'U'}</AvatarFallback>
140
145
  </Avatar>
141
146
  </TableCell>
142
147
  <TableCell className="font-medium">{authUser.email}</TableCell>
143
- <TableCell className="text-muted-foreground">{profile?.username || "N/A"}</TableCell>
144
148
  <TableCell className="text-muted-foreground">{profile?.full_name || "N/A"}</TableCell>
149
+ <TableCell className="text-muted-foreground">{profile?.github_username || "-"}</TableCell>
145
150
  <TableCell>
146
151
  <Badge variant={
147
152
  profile?.role === "ADMIN" ? "destructive" :
@@ -156,7 +161,7 @@ export default async function CmsUsersListPage() {
156
161
  <TableCell className="text-right">
157
162
  <DropdownMenu>
158
163
  <DropdownMenuTrigger asChild>
159
- <Button variant="ghost" size="icon" disabled={authUser.id === currentAdmin.id}>
164
+ <Button id={`user-trigger-${authUser.id}`} variant="ghost" size="icon">
160
165
  <MoreHorizontal className="h-4 w-4" />
161
166
  <span className="sr-only">User actions</span>
162
167
  </Button>
@@ -3,6 +3,15 @@
3
3
  @import 'tailwindcss/theme';
4
4
  @import 'tailwindcss/utilities';
5
5
 
6
+ /*─────────────────────────────────────────────────────────────────────────────
7
+ Dynamic class safelist for custom blocks.
8
+ Custom block layouts store Tailwind classes in the database (authored by
9
+ Cortex AI or by hand), which Tailwind cannot discover by scanning source.
10
+ The color utility families are force-generated by listing them as literal
11
+ tokens in apps/nextblock/lib/custom-block-safelist.ts (picked up by the Tailwind
12
+ content scanner). Regenerate via: node tools/scripts/gen-custom-block-safelist.js
13
+ ─────────────────────────────────────────────────────────────────────────────*/
14
+
6
15
  /*─────────────────────────────────────────────────────────────────────────────
7
16
  globals.css — modularized
8
17
  ─────────────────────────────────────────────────────────────────────────────*/