create-nextblock 0.2.78 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +182 -154
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -120
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -1,11 +1,12 @@
1
1
  // components/LanguageSwitcher.tsx
2
2
  'use client';
3
3
 
4
- import { useLanguage } from '@/context/LanguageContext';
5
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nextblock-cms/ui';
4
+ import { useLanguage } from '../context/LanguageContext';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nextblock-cms/ui';
6
6
  import { useRouter, usePathname } from 'next/navigation';
7
- import { getPageTranslations, getPageMetadataBySlugAndLocale } from '@/app/actions/languageActions';
8
- import type { Language } from '@/app/actions/languageActions';
7
+ import { getContentTranslations, getContentMetadataBySlugAndLocale } from '../app/actions/languageActions';
8
+ import { Language } from '../app/actions/languageActions';
9
+ import { useCurrentContent } from '../context/CurrentContentContext';
9
10
 
10
11
  interface CurrentPageInfo {
11
12
  slug: string;
@@ -17,6 +18,7 @@ interface LanguageSwitcherProps {
17
18
  }
18
19
 
19
20
  export default function LanguageSwitcher({ currentPageData }: LanguageSwitcherProps) {
21
+ const { currentContent } = useCurrentContent();
20
22
  const { currentLocale, setCurrentLocale, availableLanguages, isLoadingLanguages } = useLanguage();
21
23
  const router = useRouter();
22
24
  const pathname = usePathname();
@@ -36,47 +38,68 @@ export default function LanguageSwitcher({ currentPageData }: LanguageSwitcherPr
36
38
  if (isHomePage) {
37
39
  targetPath = '/'; // For any homepage, new language path is root
38
40
  } else {
39
- // Extract slug from pathname (remove leading slash)
40
- const currentSlug = pathname.startsWith('/') ? pathname.slice(1) : pathname;
41
-
42
- let pageMetadata = currentPageData;
43
-
44
- // If currentPageData is not provided, try to fetch it
45
- if (!pageMetadata && currentSlug) {
46
- try {
47
- const fetchedMetadata = await getPageMetadataBySlugAndLocale(currentSlug, currentLocale);
48
- if (fetchedMetadata) {
49
- pageMetadata = fetchedMetadata;
50
- }
51
- } catch (error) {
52
- console.error('Error fetching current page metadata:', error);
41
+ // Extract slug from pathname (remove leading slash)
42
+ const segments = pathname.split('/').filter(Boolean);
43
+ const currentSlug = segments[segments.length - 1] || '';
44
+
45
+ let contentMetadata = currentContent.id ? {
46
+ slug: currentContent.slug || '',
47
+ translation_group_id: currentContent.translation_group_id || null,
48
+ type: currentContent.type
49
+ } : null;
50
+
51
+ // Fallback to fetching if context is missing but we have a slug
52
+ if (!contentMetadata && currentSlug && !isHomePage) {
53
+ try {
54
+ // Try pages first, then products, then posts?
55
+ // Or determine from pathname prefix
56
+ let type: 'pages' | 'posts' | 'products' = 'pages';
57
+ if (pathname.includes('/product/')) type = 'products';
58
+ if (pathname.includes('/article/')) type = 'posts';
59
+
60
+ const fetchedMetadata = await getContentMetadataBySlugAndLocale(currentSlug, currentLocale, type);
61
+ if (fetchedMetadata) {
62
+ contentMetadata = fetchedMetadata as any;
53
63
  }
64
+ } catch (error) {
65
+ console.error('Error fetching content metadata:', error);
54
66
  }
67
+ }
55
68
 
56
- if (pageMetadata?.translation_group_id) {
57
- try {
58
- const translations = await getPageTranslations(pageMetadata.translation_group_id);
59
- const foundTranslation = translations.find(t => t.language_code === newLocaleCode);
69
+ if (contentMetadata?.translation_group_id) {
70
+ try {
71
+ const typePlural = contentMetadata.type === 'product' ? 'products' : (contentMetadata.type === 'post' ? 'posts' : 'pages');
72
+ const translations = await getContentTranslations(contentMetadata.translation_group_id, typePlural);
73
+ const foundTranslation = translations.find(t => t.language_code === newLocaleCode);
60
74
 
61
- if (foundTranslation) {
62
- targetPath = `/${foundTranslation.slug}`;
75
+ if (foundTranslation) {
76
+ // Construct target path based on type
77
+ if (contentMetadata.type === 'product') {
78
+ targetPath = `/product/${foundTranslation.slug}`;
79
+ } else if (contentMetadata.type === 'post') {
80
+ targetPath = `/article/${foundTranslation.slug}`;
63
81
  } else {
64
- // Original warning, without the [LanguageSwitcher] prefix
65
- console.warn(`No translation found for ${pageMetadata.slug} to ${newLocaleCode}. Falling back to current path.`);
82
+ targetPath = `/${foundTranslation.slug}`;
66
83
  }
67
- } catch (error) {
68
- // Original error, without the [LanguageSwitcher] prefix
69
- console.error("Error fetching page translations:", error);
84
+ } else {
85
+ console.warn(`No translation found for ${contentMetadata.slug} to ${newLocaleCode}. Falling back to current path.`);
70
86
  }
71
- } else {
72
- // Original warning, without the [LanguageSwitcher] prefix
73
- console.warn(`No translation_group_id for page: ${pageMetadata?.slug || currentSlug}. Current path will be used.`);
87
+ } catch (error) {
88
+ console.error("Error fetching translations:", error);
74
89
  }
90
+ } else if (!isHomePage) {
91
+ console.warn(`No translation_group_id for content: ${contentMetadata?.slug || currentSlug}. Current path will be used.`);
92
+ }
75
93
  }
76
94
 
77
95
  setTimeout(() => {
78
96
  if (pathname !== targetPath) {
79
97
  router.push(targetPath);
98
+ // Force Next.js to re-fetch the root layout since the language cookie changed.
99
+ // This ensures the Header/Footer navigation links update accurately.
100
+ setTimeout(() => {
101
+ router.refresh();
102
+ }, 50);
80
103
  } else {
81
104
  // If path is the same, refresh to ensure content updates for the new locale
82
105
  router.refresh();
@@ -1,17 +1,20 @@
1
1
  "use client";
2
2
 
3
3
  import Link from 'next/link';
4
+ import { usePathname } from 'next/navigation';
4
5
  import React, { useState, useEffect, useMemo, useRef } from 'react'
5
6
  import type { Database } from '@nextblock-cms/db' // Relative path from components/
6
- import { useCurrentContent } from '../context/CurrentContentContext';
7
- import { useTranslations } from '@nextblock-cms/utils';
7
+ import { useCurrentContent } from '../context/CurrentContentContext';
8
+ import { useTranslations } from '@nextblock-cms/utils';
9
+ import { DeferredGlobalSearch } from './DeferredGlobalSearch';
10
+ import { resolveMediaUrl } from '../lib/media/resolveMediaUrl';
8
11
 
9
12
  type Logo = Database['public']['Tables']['logos']['Row'] & { media: (Database['public']['Tables']['media']['Row'] & { alt_text: string | null }) | null };
10
13
  type NavigationItem = Database['public']['Tables']['navigation_items']['Row'];
11
- import Image from 'next/image'
12
-
13
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
14
- const FALLBACK_LOGO_PATH = '/images/nextblock-logo-small.webp';
14
+ import Image from 'next/image'
15
+ import { EyeOff, FilePenLine, Pencil } from 'lucide-react';
16
+
17
+ const FALLBACK_LOGO_PATH = '/images/nextblock-logo-small.webp';
15
18
 
16
19
  // Define a type for hierarchical navigation items
17
20
  interface HierarchicalNavigationItem extends NavigationItem {
@@ -66,8 +69,22 @@ interface ResponsiveNavProps {
66
69
  navItems: NavigationItem[]
67
70
  canAccessCms: boolean;
68
71
  cmsDashboardLinkHref: string;
69
- headerAuthComponent: React.ReactNode;
70
- languageSwitcherComponent: React.ReactNode;
72
+ isDraftModeEnabled: boolean;
73
+ renderHeaderAuth: () => React.ReactNode;
74
+ renderLanguageSwitcher: () => React.ReactNode;
75
+ renderCurrencySwitcher?: () => React.ReactNode;
76
+ renderCartIcon?: () => React.ReactNode;
77
+ isEcommerceActive?: boolean;
78
+ }
79
+
80
+ function ClientOnly({ children }: { children: React.ReactNode }) {
81
+ const [mounted, setMounted] = useState(false);
82
+
83
+ useEffect(() => {
84
+ setMounted(true);
85
+ }, []);
86
+
87
+ return mounted ? <>{children}</> : null;
71
88
  }
72
89
 
73
90
  export default function ResponsiveNav({
@@ -75,22 +92,39 @@ export default function ResponsiveNav({
75
92
  navItems,
76
93
  canAccessCms,
77
94
  cmsDashboardLinkHref,
78
- headerAuthComponent,
79
- languageSwitcherComponent,
95
+ isDraftModeEnabled,
96
+ renderHeaderAuth,
97
+ renderLanguageSwitcher,
98
+ renderCurrencySwitcher,
80
99
  logo,
81
100
  siteTitle,
101
+ renderCartIcon,
102
+ isEcommerceActive = false,
82
103
  }: ResponsiveNavProps) {
83
104
  const { t } = useTranslations();
105
+ const pathname = usePathname() || '/';
84
106
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
85
107
  const [expandedMobileItems, setExpandedMobileItems] = useState<Record<string, boolean>>({});
86
108
 
87
109
  const menuButtonRef = useRef<HTMLButtonElement>(null);
88
110
  const menuContainerRef = useRef<HTMLDivElement>(null);
89
111
 
90
- const hierarchicalNavItems = useMemo(() => buildHierarchy(navItems), [navItems]);
112
+ const activeNavItems = useMemo(() => {
113
+ if (isEcommerceActive) return navItems;
114
+ return navItems.filter(item => {
115
+ const href = item.url.toLowerCase();
116
+ if (href.startsWith('/product') || href.startsWith('/checkout') || href.startsWith('/shop') || href.startsWith('/cart')) {
117
+ return false;
118
+ }
119
+ return true;
120
+ });
121
+ }, [navItems, isEcommerceActive]);
122
+
123
+ const hierarchicalNavItems = useMemo(() => buildHierarchy(activeNavItems), [activeNavItems]);
91
124
  const { currentContent } = useCurrentContent();
92
125
 
93
126
  let editPathDetails: { href: string; label: string } | null = null;
127
+ let draftModeDetails: { href: string; label: string; ariaLabel: string } | null = null;
94
128
 
95
129
  if (canAccessCms && currentContent.id && currentContent.type) {
96
130
  if (currentContent.type === 'page') {
@@ -103,8 +137,32 @@ export default function ResponsiveNav({
103
137
  href: `/cms/posts/${currentContent.id}/edit`,
104
138
  label: t('edit_post'),
105
139
  };
140
+ } else if (currentContent.type === 'product') {
141
+ editPathDetails = {
142
+ href: `/cms/products/${currentContent.id}/edit`,
143
+ label: t('edit_product'),
144
+ };
106
145
  }
107
146
  }
147
+
148
+ const canToggleDraftForCurrentContent =
149
+ canAccessCms &&
150
+ Boolean(currentContent.id) &&
151
+ (currentContent.type === 'page' ||
152
+ currentContent.type === 'post' ||
153
+ currentContent.type === 'product') &&
154
+ !pathname.startsWith('/cms');
155
+
156
+ if (canToggleDraftForCurrentContent) {
157
+ const draftRoute = isDraftModeEnabled ? '/api/draft/disable' : '/api/draft/start';
158
+ draftModeDetails = {
159
+ href: `${draftRoute}?path=${encodeURIComponent(pathname)}`,
160
+ label: isDraftModeEnabled ? 'Exit Draft' : 'Draft',
161
+ ariaLabel: isDraftModeEnabled
162
+ ? 'Exit draft mode'
163
+ : `Enable draft mode for this ${currentContent.type}`,
164
+ };
165
+ }
108
166
  // The old path-based logic for determining editPathDetails is removed
109
167
  // as the context is now the source of truth for ID and type.
110
168
  // The link will only show if canAccessCms is true and context provides valid id and type.
@@ -259,17 +317,13 @@ export default function ResponsiveNav({
259
317
  href={homeLinkHref}
260
318
  className="flex items-center space-x-2 rtl:space-x-reverse"
261
319
  >
262
- {logo && logo.media ? (
263
- <Image
264
- src={
265
- logo.media.object_key.startsWith('/') || logo.media.object_key.startsWith('http')
266
- ? logo.media.object_key
267
- : `${R2_BASE_URL}/${logo.media.object_key}`
268
- }
269
- alt={logo.media.alt_text || siteTitle || 'Nextblock'}
270
- width={logo.media.width || 100}
271
- height={logo.media.height || 32}
272
- className="h-14 w-auto object-contain"
320
+ {logo && logo.media ? (
321
+ <Image
322
+ src={resolveMediaUrl(logo.media.object_key) || FALLBACK_LOGO_PATH}
323
+ alt={logo.media.alt_text || siteTitle || 'Nextblock'}
324
+ width={logo.media.width || 100}
325
+ height={logo.media.height || 32}
326
+ className="h-14 w-auto object-contain" style={{ width: 'auto', height: '56px' }}
273
327
  priority
274
328
  />
275
329
  ) : (
@@ -278,7 +332,7 @@ export default function ResponsiveNav({
278
332
  alt={siteTitle || 'Nextblock'}
279
333
  width={120}
280
334
  height={40}
281
- className="h-14 w-auto object-contain"
335
+ className="h-14 w-auto object-contain" style={{ width: 'auto', height: '56px' }}
282
336
  priority
283
337
  />
284
338
  )}
@@ -290,22 +344,44 @@ export default function ResponsiveNav({
290
344
  </div>
291
345
 
292
346
  {/* Right side: Auth, LangSwitcher (desktop), Hamburger (mobile) */}
293
- <div className="hidden md:flex items-center space-x-4">
347
+ <div className="hidden min-h-10 items-center gap-4 md:flex">
348
+ {draftModeDetails && (
349
+ <a
350
+ href={draftModeDetails.href}
351
+ aria-label={draftModeDetails.ariaLabel}
352
+ className={`flex items-center rounded-md px-3 py-2 text-sm font-semibold transition-colors ${
353
+ isDraftModeEnabled
354
+ ? 'bg-amber-500 text-white hover:bg-amber-600'
355
+ : 'text-foreground hover:bg-gray-100 dark:hover:bg-gray-800'
356
+ }`}
357
+ >
358
+ {isDraftModeEnabled ? (
359
+ <EyeOff className="mr-2 h-4 w-4" />
360
+ ) : (
361
+ <FilePenLine className="mr-2 h-4 w-4" />
362
+ )}
363
+ {draftModeDetails.label}
364
+ </a>
365
+ )}
294
366
  {canAccessCms && editPathDetails && (
295
- <Link href={editPathDetails.href} className="hover:underline font-semibold text-sm text-foreground mr-3">
367
+ <Link href={editPathDetails.href} className="hover:underline font-semibold text-sm text-foreground mr-3 flex items-center">
368
+ <Pencil className="w-4 h-4 mr-2" />
296
369
  {editPathDetails.label}
297
370
  </Link>
298
371
  )}
299
- {canAccessCms && (
300
- <Link href={cmsDashboardLinkHref} className="hover:underline font-semibold text-sm text-foreground">
301
- {t('cms_dashboard')}
302
- </Link>
303
- )}
304
- {headerAuthComponent}
305
- {languageSwitcherComponent}
372
+ <ClientOnly>
373
+ <DeferredGlobalSearch isEcommerceActive={isEcommerceActive} variant="desktop" />
374
+ {renderHeaderAuth()}
375
+ {renderLanguageSwitcher()}
376
+ {renderCurrencySwitcher?.()}
377
+ {renderCartIcon?.()}
378
+ </ClientOnly>
306
379
  </div>
307
380
 
308
- <div className="md:hidden flex items-center z-[60]">
381
+ <div className="md:hidden flex items-center gap-2 z-[60]">
382
+ <ClientOnly>
383
+ <DeferredGlobalSearch isEcommerceActive={isEcommerceActive} variant="mobile" />
384
+ </ClientOnly>
309
385
  <button
310
386
  ref={menuButtonRef}
311
387
  onClick={toggleMobileMenu}
@@ -354,31 +430,50 @@ export default function ResponsiveNav({
354
430
 
355
431
  {canAccessCms && (
356
432
  <div className="mt-auto space-y-1 border-t border-gray-200 dark:border-gray-800">
433
+ {draftModeDetails && (
434
+ <a
435
+ href={draftModeDetails.href}
436
+ aria-label={draftModeDetails.ariaLabel}
437
+ className={`flex items-center rounded-md px-3 py-2 text-base font-medium ${
438
+ isDraftModeEnabled
439
+ ? 'bg-amber-500 text-white hover:bg-amber-600'
440
+ : 'text-foreground hover:bg-gray-100 dark:hover:bg-gray-700'
441
+ }`}
442
+ onClick={() => {
443
+ toggleMobileMenu();
444
+ }}
445
+ >
446
+ {isDraftModeEnabled ? (
447
+ <EyeOff className="mr-2 h-4 w-4" />
448
+ ) : (
449
+ <FilePenLine className="mr-2 h-4 w-4" />
450
+ )}
451
+ {draftModeDetails.label}
452
+ </a>
453
+ )}
357
454
  {editPathDetails && (
358
455
  <Link
359
456
  href={editPathDetails.href}
360
- className="block px-3 py-2 rounded-md text-base font-medium text-foreground hover:bg-gray-100 dark:hover:bg-gray-700"
457
+ className="flex items-center px-3 py-2 rounded-md text-base font-medium text-foreground hover:bg-gray-100 dark:hover:bg-gray-700"
361
458
  onClick={() => {
362
459
  toggleMobileMenu();
363
460
  }}
364
461
  >
462
+ <Pencil className="w-4 h-4 mr-2" />
365
463
  {editPathDetails.label}
366
464
  </Link>
367
465
  )}
368
- <Link
369
- href={cmsDashboardLinkHref}
370
- className="block px-3 py-2 rounded-md text-base font-medium text-foreground hover:bg-gray-100 dark:hover:bg-gray-700"
371
- onClick={toggleMobileMenu}
372
- >
373
- {t('cms_dashboard')}
374
- </Link>
375
466
  </div>
376
467
  )}
377
468
  </nav>
378
469
 
379
470
  <div className="mt-auto pt-6 border-t border-foreground/20 space-y-4">
380
- <div >{headerAuthComponent}</div>
381
- <div >{languageSwitcherComponent}</div>
471
+ <ClientOnly>
472
+ <div>{renderHeaderAuth()}</div>
473
+ <div>{renderCartIcon?.()}</div>
474
+ <div>{renderCurrencySwitcher?.()}</div>
475
+ <div>{renderLanguageSwitcher()}</div>
476
+ </ClientOnly>
382
477
  </div>
383
478
  </div>
384
479
  </div>
@@ -1,17 +1,21 @@
1
- import { Skeleton } from "@nextblock-cms/ui";
1
+ import { Skeleton } from '@nextblock-cms/ui';
2
2
 
3
3
  const PostCardSkeleton = () => {
4
4
  return (
5
- <div className="border rounded-lg overflow-hidden shadow-sm bg-card text-card-foreground">
6
- <Skeleton className="h-48 w-full" />
7
- <div className="p-4 space-y-3">
8
- <Skeleton className="h-5 w-3/4" />
9
- <Skeleton className="h-4 w-full" />
10
- <Skeleton className="h-4 w-5/6" />
5
+ <div className="border rounded-lg overflow-hidden shadow-sm bg-card text-card-foreground">
6
+ <Skeleton className="h-48 w-full" />
7
+ <div className="p-4 space-y-3">
8
+ <div className="flex items-center gap-2">
9
+ <Skeleton className="h-6 w-20 rounded-full" />
10
+ <Skeleton className="h-4 w-16" />
11
+ </div>
12
+ <Skeleton className="h-5 w-3/4" />
13
+ <Skeleton className="h-4 w-full" />
14
+ <Skeleton className="h-4 w-5/6" />
11
15
  <Skeleton className="h-4 w-1/4 mt-2" />
12
16
  </div>
13
17
  </div>
14
18
  );
15
19
  };
16
20
 
17
- export default PostCardSkeleton;
21
+ export default PostCardSkeleton;
@@ -1,13 +1,11 @@
1
1
  // components/blocks/PostsGridBlock.tsx
2
- import React from 'react';
3
- import type { Database } from '@nextblock-cms/db';
4
- import { createClient } from '@nextblock-cms/db'; // Added import
5
- import type { PostWithMediaDimensions } from './types';
6
-
7
- type Block = Database['public']['Tables']['blocks']['Row'];
8
- // import Link from 'next/link'; // Unused, PostsGridClient handles links
9
- import PostsGridClient from './PostsGridClient';
10
- import { fetchPaginatedPublishedPosts } from '../../app/actions/postActions'; // fetchInitialPublishedPosts removed
2
+ import React from 'react';
3
+ import type { Database } from '@nextblock-cms/db';
4
+
5
+ type Block = Database['public']['Tables']['blocks']['Row'];
6
+ // import Link from 'next/link'; // Unused, PostsGridClient handles links
7
+ import PostsGridClient from './PostsGridClient';
8
+ import { fetchInitialPublishedPosts, fetchPaginatedPublishedPosts } from '../../app/actions/postActions';
11
9
 
12
10
  interface PostsGridBlockProps {
13
11
  block: Block;
@@ -22,52 +20,11 @@ const PostsGridBlock: React.FC<PostsGridBlockProps> = async ({ block, languageId
22
20
  showPagination = true,
23
21
  } = block.content as { title?: string, postsPerPage?: number, columns?: number, showPagination?: boolean };
24
22
 
25
- const supabase = createClient();
26
-
27
- const { data: postsData, error: queryError, count } = await supabase
28
- .from('posts')
29
- .select('id, title, slug, excerpt, published_at, language_id, status, created_at, updated_at, translation_group_id, feature_image_id, feature_media_object:media!feature_image_id(object_key, width, height)', { count: 'exact' })
30
- .eq('status', 'published')
31
- .eq('language_id', languageId)
32
- .order('published_at', { ascending: false })
33
- .limit(postsPerPage);
34
-
35
- let initialPosts: PostWithMediaDimensions[] = [];
36
- let totalCount = 0;
37
- let postsError: string | null = null;
38
-
39
- if (queryError) {
40
- console.error("Error fetching initial posts directly in PostsGridBlock:", queryError);
41
- postsError = queryError.message;
42
- } else {
43
- const buildMediaUrl = (objectKey?: string | null) => {
44
- if (!objectKey) return null;
45
- if (objectKey.startsWith('/')) return objectKey;
46
- const base = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
47
- return base ? `${base}/${objectKey}` : objectKey;
48
- };
49
-
50
- initialPosts = (postsData as any)?.map((p: any) => {
51
- // feature_media_object is an object here, not an array, due to the query structure media!feature_image_id(object_key, width, height)
52
- // Cast to 'unknown' then to the expected single object type to satisfy TypeScript, reflecting runtime reality.
53
- const mediaObject = p.feature_media_object as unknown as { object_key: string; width?: number | null; height?: number | null; blur_data_url?: string | null } | null;
54
- const imageUrl = buildMediaUrl(mediaObject?.object_key);
55
- return {
56
- ...p,
57
- // Convert feature_media_object to array format to match the type
58
- feature_media_object: mediaObject ? [{ object_key: mediaObject.object_key }] : null,
59
- feature_image_url: imageUrl,
60
- feature_image_width: mediaObject?.width || null,
61
- feature_image_height: mediaObject?.height || null,
62
- blur_data_url: mediaObject?.blur_data_url || null,
63
- };
64
- }) as PostWithMediaDimensions[] || [];
65
- totalCount = count || 0;
66
- }
67
-
68
- if (postsError) {
69
- return <div className="text-red-500">Error loading posts: {postsError}</div>;
70
- }
23
+ const { posts: initialPosts, totalCount, error: postsError } = await fetchInitialPublishedPosts(languageId, postsPerPage);
24
+
25
+ if (postsError) {
26
+ return <div className="text-red-500">Error loading posts: {postsError}</div>;
27
+ }
71
28
 
72
29
  if (!initialPosts || initialPosts.length === 0) {
73
30
  return (
@@ -1,18 +1,12 @@
1
1
  // components/blocks/PostsGridClient.tsx
2
2
  'use client';
3
3
 
4
- import React, { useState, useEffect } from 'react';
5
- import type { Database } from '@nextblock-cms/db';
6
- import Link from 'next/link';
7
-
8
- type PostWithMediaDimensions = Database['public']['Tables']['posts']['Row'] & {
9
- feature_image_url: string | null;
10
- feature_image_width: number | null;
11
- feature_image_height: number | null;
12
- blur_data_url: string | null;
13
- };
4
+ import React, { useState, useEffect } from 'react';
5
+ import Link from 'next/link';
6
+ import { useLanguage } from '../../context/LanguageContext';
7
+ import type { PostWithMediaDimensions } from './types';
14
8
  import Image from 'next/image';
15
- import { Button } from '@nextblock-cms/ui'; // Adjusted path
9
+ import { Button } from '@nextblock-cms/ui';
16
10
  import PostCardSkeleton from './PostCardSkeleton'; // Added import
17
11
 
18
12
  interface PostsGridClientProps {
@@ -26,10 +20,10 @@ interface PostsGridClientProps {
26
20
  fetchAction: (languageId: number, page: number, limit: number) => Promise<{ posts: PostWithMediaDimensions[], totalCount: number, error?: string }>;
27
21
  }
28
22
 
29
- const DEFAULT_FEATURE_IMAGE_WIDTH = 1600;
30
- const DEFAULT_FEATURE_IMAGE_HEIGHT = 900;
31
-
32
- const PostsGridClient: React.FC<PostsGridClientProps> = ({
23
+ const DEFAULT_FEATURE_IMAGE_WIDTH = 1600;
24
+ const DEFAULT_FEATURE_IMAGE_HEIGHT = 900;
25
+
26
+ const PostsGridClient: React.FC<PostsGridClientProps> = ({
33
27
  initialPosts,
34
28
  initialPage,
35
29
  postsPerPage,
@@ -39,6 +33,7 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
39
33
  showPagination,
40
34
  fetchAction,
41
35
  }) => {
36
+ const { currentLocale } = useLanguage();
42
37
  const [currentPage, setCurrentPage] = useState(initialPage);
43
38
  const [posts, setPosts] = useState<PostWithMediaDimensions[]>(initialPosts);
44
39
  const [isLoading, setIsLoading] = useState(false);
@@ -117,30 +112,40 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
117
112
  <PostCardSkeleton key={`skeleton-${index}`} />
118
113
  ))
119
114
  ) : posts.length > 0 ? (
120
- posts.map((post, index) => (
121
- <Link href={`/article/${post.slug}`} key={post.id} className="block group">
122
- <div className="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow bg-card text-card-foreground">
115
+ posts.map((post) => (
116
+ <Link href={`/article/${post.slug}`} key={post.id} className="block group h-full">
117
+ <div className="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow bg-card text-card-foreground h-full flex flex-col">
123
118
  {/* Basic Post Card Structure - Enhanced with Feature Image */}
124
- {post.feature_image_url ? (
125
- <div className="aspect-video overflow-hidden">
126
- <Image
127
- src={post.feature_image_url}
128
- alt={`Feature image for ${post.title}`}
129
- width={post.feature_image_width && post.feature_image_width > 0 ? post.feature_image_width : DEFAULT_FEATURE_IMAGE_WIDTH}
130
- height={post.feature_image_height && post.feature_image_height > 0 ? post.feature_image_height : DEFAULT_FEATURE_IMAGE_HEIGHT}
131
- sizes={imageSizes}
132
- priority={index === 0}
133
- placeholder={post.blur_data_url ? 'blur' : 'empty'}
134
- blurDataURL={post.blur_data_url ?? undefined}
135
- quality={60}
136
- className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
137
- />
138
- </div>
139
- ) : null}
140
- <div className="p-4">
119
+ {post.feature_image_url ? (
120
+ <div className="aspect-video overflow-hidden">
121
+ <Image
122
+ src={post.feature_image_url}
123
+ alt={`Feature image for ${post.title}`}
124
+ width={post.feature_image_width && post.feature_image_width > 0 ? post.feature_image_width : DEFAULT_FEATURE_IMAGE_WIDTH}
125
+ height={post.feature_image_height && post.feature_image_height > 0 ? post.feature_image_height : DEFAULT_FEATURE_IMAGE_HEIGHT}
126
+ sizes={imageSizes}
127
+ loading="lazy"
128
+ placeholder={post.blur_data_url ? 'blur' : 'empty'}
129
+ blurDataURL={post.blur_data_url ?? undefined}
130
+ quality={60}
131
+ className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
132
+ />
133
+ </div>
134
+ ) : null}
135
+ <div className="p-4 flex flex-1 flex-col">
136
+ <div className="mb-3 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
137
+ <span className="inline-flex items-center rounded-full border border-slate-200 bg-slate-50 px-2.5 py-1 font-semibold text-slate-700">
138
+ {post.label?.trim() || 'Article'}
139
+ </span>
140
+ <span>
141
+ {post.estimated_read_time_minutes} {currentLocale === 'fr' ? 'min de lecture' : 'min read'}
142
+ </span>
143
+ </div>
141
144
  <h3 className="text-lg font-semibold mb-2 group-hover:text-primary">{post.title}</h3>
142
145
  {post.excerpt && <p className="text-sm text-muted-foreground mb-3 line-clamp-3">{post.excerpt}</p>}
143
- <span className="text-xs text-primary group-hover:underline">Read more</span>
146
+ <div className="mt-auto pt-2">
147
+ <span className="text-xs text-primary group-hover:underline">Read more</span>
148
+ </div>
144
149
  </div>
145
150
  </div>
146
151
  </Link>
@@ -176,4 +181,4 @@ const PostsGridClient: React.FC<PostsGridClientProps> = ({
176
181
  );
177
182
  };
178
183
 
179
- export default PostsGridClient;
184
+ export default PostsGridClient;
@@ -1,7 +1,11 @@
1
1
  import React from 'react';
2
- import { z } from 'zod';
2
+ import { z } from '../../lib/zod-config';
3
3
  import { BlockConfig, BlockProps, BlockEditorProps } from '@nextblock-cms/sdk';
4
- import { Card, CardContent, Avatar, AvatarImage, AvatarFallback, Input, Label, Textarea } from '@nextblock-cms/ui';
4
+ import { Avatar, AvatarFallback, AvatarImage } from '@nextblock-cms/ui';
5
+ import { Card, CardContent } from '@nextblock-cms/ui';
6
+ import { Input } from '@nextblock-cms/ui';
7
+ import { Label } from '@nextblock-cms/ui';
8
+ import { Textarea } from '@nextblock-cms/ui';
5
9
  import { MessageSquareQuote } from 'lucide-react';
6
10
 
7
11
  // 1. Define the Schema