create-nextblock 0.2.77 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +191 -151
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -116
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -1,33 +1,230 @@
1
1
  // app/article/[slug]/PostClientContent.tsx
2
- "use client";
3
-
4
- import React, { useState, useEffect, useMemo } from 'react';
5
- import { useRouter } from 'next/navigation';
6
- import Image from 'next/image';
7
- import type { Database } from "@nextblock-cms/db";
8
- import { useLanguage } from '@/context/LanguageContext';
9
-
10
- type PostType = Database['public']['Tables']['posts']['Row'];
11
- type BlockType = Database['public']['Tables']['blocks']['Row'];
12
-
13
- export type ImageBlockContent = {
14
- media_id: string | null;
15
- object_key?: string;
16
- };
17
- import { useCurrentContent } from '@/context/CurrentContentContext';
18
- import Link from 'next/link';
19
-
2
+ "use client";
3
+
4
+ import React, { useState, useEffect, useMemo, useRef } from 'react';
5
+ import { useRouter } from 'next/navigation';
6
+ import type { Database } from "@nextblock-cms/db";
7
+ import { useLanguage } from '../../../context/LanguageContext';
8
+ import { useCurrentContent } from '../../../context/CurrentContentContext';
9
+ import Link from 'next/link';
10
+ import { estimateReadTimeMinutesFromBlocks } from '../../../lib/posts/readTime';
11
+ import FeatureImageHero from '../../../components/FeatureImageHero';
12
+
13
+ type PostType = Database['public']['Tables']['posts']['Row'];
14
+ type BlockType = Database['public']['Tables']['blocks']['Row'];
15
+
16
+ export type ImageBlockContent = {
17
+ media_id: string | null;
18
+ object_key?: string;
19
+ };
20
+
20
21
  interface PostClientContentProps {
21
- initialPostData: (PostType & { blocks: BlockType[]; language_code: string; language_id: number; translation_group_id: string; feature_image_url?: string | null; }) | null;
22
+ initialPostData: (PostType & {
23
+ blocks: BlockType[];
24
+ language_code: string;
25
+ language_id: number;
26
+ translation_group_id: string;
27
+ feature_image_url?: string | null;
28
+ feature_image_blur_data_url?: string | null;
29
+ feature_image_width?: number | null;
30
+ feature_image_height?: number | null;
31
+ }) | null;
22
32
  currentSlug: string; // The slug of the currently viewed page/post
23
33
  children: React.ReactNode;
24
34
  translatedSlugs?: { [key: string]: string };
25
35
  }
26
36
 
37
+ type PostPresentation = {
38
+ badgeClassName: string;
39
+ featureFrameClassName: string;
40
+ featureImageClassName: string;
41
+ articleClassName?: string;
42
+ };
43
+
44
+ const editorialCategories: Record<string, PostPresentation> = {
45
+ 'how-nextblock-works': {
46
+ badgeClassName:
47
+ 'border-sky-200/80 bg-sky-50 text-sky-700 dark:border-sky-500/20 dark:bg-sky-500/10 dark:text-sky-200',
48
+ featureFrameClassName:
49
+ 'bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.28),_transparent_48%),linear-gradient(135deg,#020617,#0f172a_55%,#1e293b)]',
50
+ featureImageClassName: 'h-full w-full object-cover',
51
+ },
52
+ 'comment-nextblock-fonctionne': {
53
+ badgeClassName:
54
+ 'border-sky-200/80 bg-sky-50 text-sky-700 dark:border-sky-500/20 dark:bg-sky-500/10 dark:text-sky-200',
55
+ featureFrameClassName:
56
+ 'bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.28),_transparent_48%),linear-gradient(135deg,#020617,#0f172a_55%,#1e293b)]',
57
+ featureImageClassName: 'h-full w-full object-cover',
58
+ },
59
+ 'how-to-setup-nextblock': {
60
+ badgeClassName:
61
+ 'border-indigo-200/80 bg-indigo-50 text-indigo-700 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-indigo-200',
62
+ featureFrameClassName:
63
+ 'bg-[radial-gradient(circle_at_top,_rgba(99,102,241,0.32),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#172554)]',
64
+ featureImageClassName: 'h-full w-full object-cover',
65
+ articleClassName: 'post-article--tutorial',
66
+ },
67
+ 'comment-configurer-nextblock': {
68
+ badgeClassName:
69
+ 'border-indigo-200/80 bg-indigo-50 text-indigo-700 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-indigo-200',
70
+ featureFrameClassName:
71
+ 'bg-[radial-gradient(circle_at_top,_rgba(99,102,241,0.32),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#172554)]',
72
+ featureImageClassName: 'h-full w-full object-cover',
73
+ articleClassName: 'post-article--tutorial',
74
+ },
75
+ 'nextblock-commerce-guide': {
76
+ badgeClassName:
77
+ 'border-emerald-200/80 bg-emerald-50 text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-200',
78
+ featureFrameClassName:
79
+ 'bg-[radial-gradient(circle_at_top,_rgba(16,185,129,0.28),_transparent_45%),linear-gradient(135deg,#021c17,#052e2b_55%,#0f172a)]',
80
+ featureImageClassName: 'h-full w-full object-cover',
81
+ },
82
+ 'guide-commerce-nextblock': {
83
+ badgeClassName:
84
+ 'border-emerald-200/80 bg-emerald-50 text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-200',
85
+ featureFrameClassName:
86
+ 'bg-[radial-gradient(circle_at_top,_rgba(16,185,129,0.28),_transparent_45%),linear-gradient(135deg,#021c17,#052e2b_55%,#0f172a)]',
87
+ featureImageClassName: 'h-full w-full object-cover',
88
+ },
89
+ };
90
+
91
+ function getPostPresentation(slug: string | undefined): PostPresentation {
92
+ if (!slug) {
93
+ return {
94
+ badgeClassName:
95
+ 'border-slate-200/80 bg-slate-50 text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-slate-200',
96
+ featureFrameClassName:
97
+ 'bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.18),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#1f2937)]',
98
+ featureImageClassName: 'h-full w-full object-cover',
99
+ };
100
+ }
101
+
102
+ const matched = editorialCategories[slug];
103
+ if (matched) {
104
+ return matched;
105
+ }
106
+
107
+ return {
108
+ badgeClassName:
109
+ 'border-slate-200/80 bg-slate-50 text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-slate-200',
110
+ featureFrameClassName:
111
+ 'bg-[radial-gradient(circle_at_top,_rgba(59,130,246,0.18),_transparent_45%),linear-gradient(135deg,#020617,#111827_55%,#1f2937)]',
112
+ featureImageClassName: 'h-full w-full object-cover',
113
+ };
114
+ }
115
+
116
+ function getFallbackDescription(locale: string | undefined) {
117
+ return locale === 'fr'
118
+ ? 'Notes techniques, guides de mise en route et apercus produit de l equipe NextBlock.'
119
+ : 'Technical breakdowns, launch guides, and product notes from the NextBlock™ team.';
120
+ }
121
+
122
+ function getFallbackLabel(slug: string | undefined, locale: string | undefined) {
123
+ if (!slug) {
124
+ return locale === 'fr' ? 'Journal' : 'Journal';
125
+ }
126
+
127
+ return locale === 'fr' ? 'Article' : 'Article';
128
+ }
129
+
130
+ function enhanceTutorialCodeBlocks(root: HTMLDivElement | null) {
131
+ if (!root) return;
132
+
133
+ const codeBlocks = root.querySelectorAll('pre code');
134
+
135
+ codeBlocks.forEach((codeBlock) => {
136
+ if (!(codeBlock instanceof HTMLElement) || codeBlock.dataset.terminalized === 'true') {
137
+ return;
138
+ }
139
+
140
+ const pre = codeBlock.parentElement;
141
+ if (!(pre instanceof HTMLElement)) {
142
+ return;
143
+ }
144
+
145
+ const normalizedText = (codeBlock.textContent ?? '').replace(/\r\n?/g, '\n');
146
+ const lines = normalizedText.split('\n');
147
+ const meaningfulLines = lines.filter((line) => line.trim().length > 0);
148
+
149
+ if (meaningfulLines.length === 0) {
150
+ return;
151
+ }
152
+
153
+ const isEnvBlock = meaningfulLines.every((line) => /^[A-Z][A-Z0-9_]*=/.test(line.trim()));
154
+
155
+ pre.classList.add('terminal-block');
156
+ pre.classList.add(isEnvBlock ? 'terminal-block--config' : 'terminal-block--shell');
157
+
158
+ codeBlock.textContent = '';
159
+
160
+ lines.forEach((line) => {
161
+ const terminalLine = document.createElement('span');
162
+ terminalLine.classList.add('terminal-line');
163
+
164
+ if (!line.trim()) {
165
+ terminalLine.classList.add('terminal-line--spacer');
166
+ terminalLine.innerHTML = '&nbsp;';
167
+ codeBlock.appendChild(terminalLine);
168
+ return;
169
+ }
170
+
171
+ if (!isEnvBlock && /^\s*#/.test(line)) {
172
+ terminalLine.classList.add('terminal-line--comment');
173
+ terminalLine.textContent = line.replace(/^\s*#\s?/, '');
174
+ codeBlock.appendChild(terminalLine);
175
+ return;
176
+ }
177
+
178
+ if (isEnvBlock) {
179
+ terminalLine.classList.add('terminal-line--env');
180
+
181
+ const envMatch = line.match(/^([A-Z][A-Z0-9_]*)(=)(.*)$/);
182
+ if (envMatch) {
183
+ const [, key, separator, value] = envMatch;
184
+
185
+ const keySpan = document.createElement('span');
186
+ keySpan.classList.add('terminal-env-key');
187
+ keySpan.textContent = key;
188
+
189
+ const separatorSpan = document.createElement('span');
190
+ separatorSpan.classList.add('terminal-env-separator');
191
+ separatorSpan.textContent = separator;
192
+
193
+ const valueSpan = document.createElement('span');
194
+ valueSpan.classList.add('terminal-env-value');
195
+ valueSpan.textContent = value;
196
+
197
+ terminalLine.append(keySpan, separatorSpan, valueSpan);
198
+ } else {
199
+ terminalLine.textContent = line;
200
+ }
201
+
202
+ codeBlock.appendChild(terminalLine);
203
+ return;
204
+ }
205
+
206
+ terminalLine.classList.add('terminal-line--command');
207
+ const promptSpan = document.createElement('span');
208
+ promptSpan.classList.add('terminal-prompt');
209
+ promptSpan.textContent = '>';
210
+
211
+ const commandTextSpan = document.createElement('span');
212
+ commandTextSpan.classList.add('terminal-command-text');
213
+ commandTextSpan.textContent = line;
214
+
215
+ terminalLine.append(promptSpan, commandTextSpan);
216
+ codeBlock.appendChild(terminalLine);
217
+ });
218
+
219
+ codeBlock.dataset.terminalized = 'true';
220
+ });
221
+ }
222
+
27
223
  export default function PostClientContent({ initialPostData, currentSlug, children, translatedSlugs }: PostClientContentProps) {
28
224
  const { currentLocale, isLoadingLanguages } = useLanguage();
29
225
  const { currentContent, setCurrentContent } = useCurrentContent();
30
226
  const router = useRouter();
227
+ const articleBodyRef = useRef<HTMLDivElement | null>(null);
31
228
 
32
229
  const currentPrefix = "articles";
33
230
 
@@ -35,13 +232,47 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
35
232
  // It's initially set by the server. It only changes if the URL itself changes (which happens on language switch).
36
233
  const [currentPostData, setCurrentPostData] = useState(initialPostData);
37
234
  const [isLoadingTargetLang, setIsLoadingTargetLang] = useState(false); // For feedback during navigation
38
-
39
- // Memoize postId and postSlug
40
- const postId = useMemo(() => currentPostData?.id, [currentPostData?.id]);
41
- const postSlug = useMemo(() => currentPostData?.slug, [currentPostData?.slug]);
42
-
43
- // This effect handles navigation when the language context changes
44
- useEffect(() => {
235
+
236
+ // Memoize postId and postSlug
237
+ const postId = useMemo(() => currentPostData?.id, [currentPostData?.id]);
238
+ const postSlug = useMemo(() => currentPostData?.slug, [currentPostData?.slug]);
239
+ const postPresentation = useMemo(
240
+ () => getPostPresentation(postSlug),
241
+ [postSlug]
242
+ );
243
+ const estimatedReadTime = useMemo(
244
+ () => estimateReadTimeMinutesFromBlocks(currentPostData?.blocks),
245
+ [currentPostData?.blocks]
246
+ );
247
+ const fallbackDescription = useMemo(
248
+ () => getFallbackDescription(currentPostData?.language_code),
249
+ [currentPostData?.language_code]
250
+ );
251
+ const displayLabel = useMemo(() => {
252
+ const label = currentPostData?.label?.trim();
253
+ return label || getFallbackLabel(postSlug, currentPostData?.language_code);
254
+ }, [currentPostData?.label, currentPostData?.language_code, postSlug]);
255
+ const displaySummary = useMemo(() => {
256
+ const summary = currentPostData?.excerpt?.trim();
257
+ return summary || fallbackDescription;
258
+ }, [currentPostData?.excerpt, fallbackDescription]);
259
+ const displaySubtitle = useMemo(() => {
260
+ return currentPostData?.subtitle?.trim() || null;
261
+ }, [currentPostData?.subtitle]);
262
+ const publishedLabel = useMemo(() => {
263
+ if (!currentPostData?.published_at) {
264
+ return null;
265
+ }
266
+
267
+ return new Date(currentPostData.published_at).toLocaleDateString(currentPostData.language_code, {
268
+ year: 'numeric',
269
+ month: 'long',
270
+ day: 'numeric',
271
+ });
272
+ }, [currentPostData?.language_code, currentPostData?.published_at]);
273
+
274
+ // This effect handles navigation when the language context changes
275
+ useEffect(() => {
45
276
  if (!isLoadingLanguages && currentLocale && initialPostData && initialPostData.language_code !== currentLocale && translatedSlugs) {
46
277
  // The current page's language (from initialPostData.language_code)
47
278
  // does not match the user's selected language (currentLocale).
@@ -56,66 +287,71 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
56
287
  // Optionally, provide user feedback here (e.g., a toast message)
57
288
  // For now, the user remains on the current page.
58
289
  }
59
- // If targetSlug === currentSlug, we are already on the correct page for the selected language.
60
- setIsLoadingTargetLang(false);
61
- }
62
- }, [currentLocale, isLoadingLanguages, initialPostData, currentSlug, router, translatedSlugs]);
63
-
64
- // This effect updates the document based on the currently displayed data
65
- useEffect(() => {
66
- if (currentPostData?.language_code) {
67
- document.documentElement.lang = currentPostData.language_code;
68
- if (currentPostData.meta_title || currentPostData.title) {
69
- document.title = currentPostData.meta_title || currentPostData.title;
70
- }
71
- }
72
- }, [currentPostData]);
73
-
74
- // Update currentPostData if initialPostData changes (e.g., after ISR revalidation of the current slug)
75
- useEffect(() => {
76
- setCurrentPostData(initialPostData);
77
- }, [initialPostData]);
78
-
79
- // Effect for setting or updating the context
80
- useEffect(() => {
81
- const newType = 'post' as const;
82
- const slugToSet = postSlug ?? null; // Ensures slug is string or null
83
-
84
- const needsUpdate = postId &&
85
- (currentContent.id !== postId ||
86
- currentContent.type !== newType ||
87
- currentContent.slug !== slugToSet);
88
-
89
- const needsClearing = !postId &&
90
- (currentContent.id !== null ||
91
- currentContent.type !== null ||
92
- currentContent.slug !== null);
93
-
94
- if (needsUpdate) {
95
- setCurrentContent({ id: postId, type: newType, slug: slugToSet });
96
- } else if (needsClearing) {
97
- setCurrentContent({ id: null, type: null, slug: null });
98
- }
99
- }, [postId, postSlug, setCurrentContent, currentContent.id, currentContent.type, currentContent.slug]);
100
-
101
- // Separate useEffect for cleanup
102
- useEffect(() => {
103
- const idToClean = postId; // Capture the postId when this effect runs
104
-
105
- return () => {
106
- // Cleanup logic: only clear context if the current context ID matches the ID this instance was managing
107
- if (idToClean && currentContent.id === idToClean) {
108
- setCurrentContent({ id: null, type: null, slug: null });
109
- }
110
- };
111
- }, [postId, setCurrentContent, currentContent.id]);
112
-
113
- if (!currentPostData && !isLoadingLanguages && !isLoadingTargetLang) {
114
- // This state means the initial slug from the URL didn't resolve to any data.
115
- // The server component (page.tsx) would have already called notFound().
116
- // This is a fallback or could indicate an issue if reached.
117
- return (
118
- <div className="container mx-auto px-4 py-8 text-center">
290
+ // If targetSlug === currentSlug, we are already on the correct page for the selected language.
291
+ setIsLoadingTargetLang(false);
292
+ }
293
+ }, [currentLocale, isLoadingLanguages, initialPostData, currentSlug, router, translatedSlugs]);
294
+
295
+ // This effect updates the document based on the currently displayed data
296
+ useEffect(() => {
297
+ if (currentPostData?.language_code) {
298
+ document.documentElement.lang = currentPostData.language_code;
299
+ if (currentPostData.meta_title || currentPostData.title) {
300
+ document.title = currentPostData.meta_title || currentPostData.title;
301
+ }
302
+ }
303
+ }, [currentPostData]);
304
+
305
+ // Update currentPostData if initialPostData changes (e.g., after ISR revalidation of the current slug)
306
+ useEffect(() => {
307
+ setCurrentPostData(initialPostData);
308
+ }, [initialPostData]);
309
+
310
+ useEffect(() => {
311
+ if (postPresentation.articleClassName !== 'post-article--tutorial') {
312
+ return;
313
+ }
314
+
315
+ enhanceTutorialCodeBlocks(articleBodyRef.current);
316
+ }, [currentPostData?.id, postPresentation.articleClassName]);
317
+
318
+ // Effect for setting or updating the context
319
+ useEffect(() => {
320
+ const newType = 'post' as const;
321
+ const slugToSet = postSlug ?? null; // Ensures slug is string or null
322
+
323
+ const needsUpdate = postId &&
324
+ (currentContent.id !== postId ||
325
+ currentContent.type !== newType ||
326
+ currentContent.slug !== slugToSet);
327
+
328
+ const needsClearing = !postId &&
329
+ (currentContent.id !== null ||
330
+ currentContent.type !== null ||
331
+ currentContent.slug !== null);
332
+
333
+ if (needsUpdate) {
334
+ setCurrentContent({ id: postId, type: newType, slug: slugToSet, translation_group_id: currentPostData?.translation_group_id });
335
+ } else if (needsClearing) {
336
+ setCurrentContent({ id: null, type: null, slug: null, translation_group_id: null });
337
+ }
338
+ }, [postId, postSlug, currentPostData?.translation_group_id, setCurrentContent, currentContent.id, currentContent.type, currentContent.slug]);
339
+
340
+ // Separate useEffect for cleanup
341
+ useEffect(() => {
342
+ const idToClean = postId; // Capture the postId when this effect runs
343
+
344
+ return () => {
345
+ // Cleanup logic: only clear context if the current context ID matches the ID this instance was managing
346
+ if (idToClean && currentContent.id === idToClean) {
347
+ setCurrentContent({ id: null, type: null, slug: null, translation_group_id: null });
348
+ }
349
+ };
350
+ }, [postId, setCurrentContent, currentContent.id]);
351
+
352
+ if (!currentPostData && !isLoadingLanguages && !isLoadingTargetLang) {
353
+ return (
354
+ <div className="container mx-auto px-4 py-8 text-center">
119
355
  <h1 className="text-2xl font-bold mb-4">Article Not Found</h1>
120
356
  <p className="text-muted-foreground">The article for slug &quot;{currentSlug}&quot; could not be loaded.</p>
121
357
  <p className="mt-4">
@@ -125,47 +361,75 @@ export default function PostClientContent({ initialPostData, currentSlug, childr
125
361
  </p>
126
362
  </div>
127
363
  );
128
- }
129
-
130
- // If initialPostData was null but we are still loading language context or trying to navigate
131
- if (!currentPostData && (isLoadingLanguages || isLoadingTargetLang)) {
364
+ }
365
+
366
+ if (!currentPostData && (isLoadingLanguages || isLoadingTargetLang)) {
132
367
  return <div className="container mx-auto px-4 py-20 text-center"><p>Loading article content...</p></div>;
133
- }
134
-
135
- // If after all attempts, currentPostData is still null (should be caught by notFound in server component ideally)
136
- if (!currentPostData) {
368
+ }
369
+
370
+ if (!currentPostData) {
137
371
  return <div className="container mx-auto px-4 py-20 text-center"><p>Could not load article content for &quot;{currentSlug}&quot;.</p></div>;
138
- }
139
-
140
- return (
141
- <article className="w-full mx-auto">
142
- {isLoadingTargetLang && <div className="text-center py-2 text-sm text-muted-foreground">Switching language...</div>}
143
-
144
- {currentPostData?.feature_image_url && (
145
- <div className="mb-8 relative"> {/* Adjust negative margins for full-bleed effect if container has padding */}
146
- <Image
147
- src={currentPostData.feature_image_url}
148
- alt={`Hero image for ${currentPostData.title}`}
149
- width={800}
150
- height={400}
151
- className="w-full h-auto max-h-[400px] md:max-h-[500px] object-cover shadow-lg" // Adjust max-h as needed, add rounded corners/shadow
152
- priority
153
- />
154
- </div>
155
- )}
156
- <header className="mb-8 text-center border-b pb-6 dark:border-slate-700">
157
- <h1 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-2 text-slate-900 dark:text-slate-100">{currentPostData.title}</h1>
158
- {currentPostData.published_at && (
159
- <p className="text-sm text-slate-500 dark:text-slate-400">
160
- Published on {new Date(currentPostData.published_at).toLocaleDateString(currentPostData.language_code, { year: 'numeric', month: 'long', day: 'numeric' })}
161
- </p>
162
- )}
163
- {currentPostData.excerpt && <p className="mt-4 text-lg text-slate-600 dark:text-slate-300 max-w-2xl mx-auto">{currentPostData.excerpt}</p>}
164
- </header>
165
-
166
- <div className="prose dark:prose-invert lg:prose-xl max-w-none mx-auto">
167
- {children}
168
- </div>
169
- </article>
170
- );
171
- }
372
+ }
373
+
374
+ return (
375
+ <article className={`post-article w-full mx-auto pb-16 md:pb-24 ${postPresentation.articleClassName ?? ''}`}>
376
+ {isLoadingTargetLang && <div className="text-center py-2 text-sm text-muted-foreground">Switching language...</div>}
377
+
378
+ <div className="mx-auto max-w-6xl px-4 pt-6 md:pt-10">
379
+ <div className="mb-4 flex items-center justify-between gap-4 text-sm">
380
+ <Link
381
+ href={`/${currentPrefix}`}
382
+ className="inline-flex items-center gap-2 text-slate-600 transition-colors hover:text-slate-900 dark:text-slate-300 dark:hover:text-white"
383
+ >
384
+ <span aria-hidden="true">←</span>
385
+ {currentPostData.language_code === 'fr' ? 'Retour aux articles' : 'Back to Articles'}
386
+ </Link>
387
+ <span className="hidden text-slate-500 dark:text-slate-400 md:inline">
388
+ {displaySummary}
389
+ </span>
390
+ </div>
391
+
392
+ {currentPostData?.feature_image_url ? (
393
+ <FeatureImageHero
394
+ imageUrl={currentPostData.feature_image_url}
395
+ alt={`Hero image for ${currentPostData.title}`}
396
+ width={currentPostData.feature_image_width}
397
+ height={currentPostData.feature_image_height}
398
+ blurDataURL={currentPostData.feature_image_blur_data_url}
399
+ frameClassName={postPresentation.featureFrameClassName}
400
+ imageClassName={postPresentation.featureImageClassName}
401
+ priority
402
+ />
403
+ ) : null}
404
+
405
+ <header className={`mx-auto max-w-4xl rounded-[1.75rem] border border-slate-200/80 bg-background/95 px-6 py-8 shadow-[0_24px_70px_-36px_rgba(15,23,42,0.4)] backdrop-blur dark:border-white/10 dark:bg-slate-950/90 ${currentPostData?.feature_image_url ? '-mt-12 md:-mt-16' : 'mt-6'}`}>
406
+ <div className="mb-4 flex flex-wrap items-center justify-center gap-3 text-sm md:justify-start">
407
+ <span className={`inline-flex items-center rounded-full border px-3 py-1 font-semibold ${postPresentation.badgeClassName}`}>
408
+ {displayLabel}
409
+ </span>
410
+ {publishedLabel ? (
411
+ <span className="text-slate-500 dark:text-slate-400">
412
+ {publishedLabel}
413
+ </span>
414
+ ) : null}
415
+ <span className="text-slate-500 dark:text-slate-400">
416
+ {estimatedReadTime} {currentPostData.language_code === 'fr' ? 'min de lecture' : 'min read'}
417
+ </span>
418
+ </div>
419
+ <h1 className="text-balance text-center text-4xl font-black tracking-tight text-slate-950 dark:text-slate-50 md:text-5xl lg:text-6xl">
420
+ {currentPostData.title}
421
+ </h1>
422
+ {displaySubtitle ? (
423
+ <p className="mt-5 text-center text-lg leading-8 text-slate-600 dark:text-slate-300 md:text-xl">
424
+ {displaySubtitle}
425
+ </p>
426
+ ) : null}
427
+ </header>
428
+ </div>
429
+
430
+ <div ref={articleBodyRef} className="post-article__body mx-auto mt-10 w-full px-4 md:mt-14">
431
+ {children}
432
+ </div>
433
+ </article>
434
+ );
435
+ }