@carlonicora/nextjs-jsonapi 5.4.0 → 5.6.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 (237) hide show
  1. package/dist/{BlockNoteEditor-4YCAIEPT.js → BlockNoteEditor-AE3ZTEEH.js} +20 -20
  2. package/dist/{BlockNoteEditor-4YCAIEPT.js.map → BlockNoteEditor-AE3ZTEEH.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-YQL2SGFR.mjs → BlockNoteEditor-NEYG5L6W.mjs} +5 -5
  4. package/dist/{BlockNoteViewer-AEJKJ27Q.mjs → BlockNoteViewer-3OUT5UFY.mjs} +3 -3
  5. package/dist/{BlockNoteViewer-YL3J7J2Y.js → BlockNoteViewer-4CFKQM7U.js} +4 -4
  6. package/dist/{BlockNoteViewer-YL3J7J2Y.js.map → BlockNoteViewer-4CFKQM7U.js.map} +1 -1
  7. package/dist/billing/index.d.mts +186 -25
  8. package/dist/billing/index.d.ts +186 -25
  9. package/dist/billing/index.js +1149 -588
  10. package/dist/billing/index.js.map +1 -1
  11. package/dist/billing/index.mjs +786 -225
  12. package/dist/billing/index.mjs.map +1 -1
  13. package/dist/{chunk-5PPG6PL7.js → chunk-3I5AAJXV.js} +3 -3
  14. package/dist/{chunk-5PPG6PL7.js.map → chunk-3I5AAJXV.js.map} +1 -1
  15. package/dist/{chunk-YW4ZCEWB.js → chunk-53BAULDP.js} +4 -4
  16. package/dist/{chunk-YW4ZCEWB.js.map → chunk-53BAULDP.js.map} +1 -1
  17. package/dist/{chunk-XCHSX3VC.mjs → chunk-BTPUYUQ3.mjs} +2 -2
  18. package/dist/{chunk-WC63PJSC.mjs → chunk-EYA2F6AN.mjs} +756 -24
  19. package/dist/chunk-EYA2F6AN.mjs.map +1 -0
  20. package/dist/{chunk-NFBOSPNJ.js → chunk-HKZAESS6.js} +954 -222
  21. package/dist/chunk-HKZAESS6.js.map +1 -0
  22. package/dist/{chunk-6DC2WVVB.mjs → chunk-KQHBONL4.mjs} +2 -2
  23. package/dist/{chunk-3NLAXVIN.js → chunk-LQ4DK4JF.js} +1988 -1089
  24. package/dist/chunk-LQ4DK4JF.js.map +1 -0
  25. package/dist/{chunk-YNCQSWQW.js → chunk-RL3PL3KV.js} +8 -8
  26. package/dist/{chunk-YNCQSWQW.js.map → chunk-RL3PL3KV.js.map} +1 -1
  27. package/dist/{chunk-EUKTBOI2.mjs → chunk-SFYPXCK4.mjs} +3382 -2483
  28. package/dist/chunk-SFYPXCK4.mjs.map +1 -0
  29. package/dist/{chunk-QMYQRPQC.mjs → chunk-Y7RR57EW.mjs} +2 -2
  30. package/dist/client/index.js +5 -5
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/index.mjs +4 -4
  33. package/dist/components/editors/server.d.mts +15 -0
  34. package/dist/components/editors/server.d.ts +15 -0
  35. package/dist/components/editors/server.js +38 -0
  36. package/dist/components/editors/server.js.map +1 -0
  37. package/dist/components/editors/server.mjs +38 -0
  38. package/dist/components/editors/server.mjs.map +1 -0
  39. package/dist/components/index.d.mts +33 -10
  40. package/dist/components/index.d.ts +33 -10
  41. package/dist/components/index.js +7 -5
  42. package/dist/components/index.js.map +1 -1
  43. package/dist/components/index.mjs +8 -6
  44. package/dist/contexts/index.d.mts +9 -1
  45. package/dist/contexts/index.d.ts +9 -1
  46. package/dist/contexts/index.js +5 -5
  47. package/dist/contexts/index.mjs +4 -4
  48. package/dist/core/index.d.mts +242 -3
  49. package/dist/core/index.d.ts +242 -3
  50. package/dist/core/index.js +36 -4
  51. package/dist/core/index.js.map +1 -1
  52. package/dist/core/index.mjs +35 -3
  53. package/dist/email-automations/index.d.mts +3 -0
  54. package/dist/email-automations/index.d.ts +3 -0
  55. package/dist/email-automations/index.js +99 -95
  56. package/dist/email-automations/index.js.map +1 -1
  57. package/dist/email-automations/index.mjs +30 -26
  58. package/dist/email-automations/index.mjs.map +1 -1
  59. package/dist/features/analytics/index.d.mts +11 -4
  60. package/dist/features/analytics/index.d.ts +11 -4
  61. package/dist/features/analytics/index.js +127 -135
  62. package/dist/features/analytics/index.js.map +1 -1
  63. package/dist/features/analytics/index.mjs +16 -24
  64. package/dist/features/analytics/index.mjs.map +1 -1
  65. package/dist/features/analytics/server.d.mts +5 -2
  66. package/dist/features/analytics/server.d.ts +5 -2
  67. package/dist/features/analytics/server.js +7 -7
  68. package/dist/features/analytics/server.js.map +1 -1
  69. package/dist/features/analytics/server.mjs +5 -5
  70. package/dist/features/analytics/server.mjs.map +1 -1
  71. package/dist/features/blog/index.d.mts +12 -2
  72. package/dist/features/blog/index.d.ts +12 -2
  73. package/dist/features/blog/index.js +206 -195
  74. package/dist/features/blog/index.js.map +1 -1
  75. package/dist/features/blog/index.mjs +71 -60
  76. package/dist/features/blog/index.mjs.map +1 -1
  77. package/dist/features/blog/server.d.mts +4 -1
  78. package/dist/features/blog/server.d.ts +4 -1
  79. package/dist/features/blog/server.js +49 -21
  80. package/dist/features/blog/server.js.map +1 -1
  81. package/dist/features/blog/server.mjs +36 -8
  82. package/dist/features/blog/server.mjs.map +1 -1
  83. package/dist/features/help/index.js +38 -38
  84. package/dist/features/help/index.mjs +4 -4
  85. package/dist/features/tokenusage/index.d.mts +1 -3
  86. package/dist/features/tokenusage/index.d.ts +1 -3
  87. package/dist/features/tokenusage/index.js +127 -135
  88. package/dist/features/tokenusage/index.js.map +1 -1
  89. package/dist/features/tokenusage/index.mjs +10 -18
  90. package/dist/features/tokenusage/index.mjs.map +1 -1
  91. package/dist/{stripe-promotion-code.interface-C3qqh3mi.d.ts → format-options-BtX3o0Yu.d.ts} +104 -1
  92. package/dist/{stripe-promotion-code.interface-ClZ7DxS9.d.mts → format-options-D6c7FNDi.d.mts} +104 -1
  93. package/dist/index.d.mts +2 -2
  94. package/dist/index.d.ts +2 -2
  95. package/dist/index.js +37 -5
  96. package/dist/index.js.map +1 -1
  97. package/dist/index.mjs +36 -4
  98. package/dist/server/index.js +4 -4
  99. package/dist/server/index.mjs +3 -3
  100. package/messages/en.json +125 -3
  101. package/messages/it.json +125 -3
  102. package/package.json +9 -1
  103. package/scripts/validate-messages.mjs +1 -0
  104. package/src/billing/index.ts +1 -0
  105. package/src/client/index.ts +4 -0
  106. package/src/components/editors/server/BlockNoteServerContent.tsx +28 -0
  107. package/src/components/editors/server/__tests__/renderBlockNoteHTML.spec.ts +44 -0
  108. package/src/components/editors/server/renderBlockNoteHTML.ts +26 -0
  109. package/src/components/editors/server-entry.ts +7 -0
  110. package/src/components/index.ts +1 -0
  111. package/src/components/viewers/BlockNoteStaticViewer.tsx +44 -0
  112. package/src/core/index.ts +1 -0
  113. package/src/core/registry/ModuleRegistry.ts +3 -0
  114. package/src/features/administration/components/AdminIndexContainer.tsx +61 -33
  115. package/src/features/administration/components/__tests__/AdminIndexContainer.spec.tsx +61 -1
  116. package/src/features/administration/components/index.ts +2 -1
  117. package/src/features/administration/contexts/AdministrationContext.tsx +11 -3
  118. package/src/features/administration/contexts/__tests__/AdministrationContext.spec.tsx +40 -0
  119. package/src/features/ai-connection/ai-connection.module.ts +2 -0
  120. package/src/features/ai-connection/contexts/AiConnectionsContext.tsx +3 -16
  121. package/src/features/ai-connection/contexts/__tests__/AiConnectionsContext.spec.tsx +33 -0
  122. package/src/features/analytics/__tests__/server-entry.spec.tsx +2 -1
  123. package/src/features/analytics/analytics.modules.ts +22 -5
  124. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +4 -3
  125. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +6 -3
  126. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +4 -3
  127. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +11 -0
  128. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +12 -1
  129. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +6 -3
  130. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +8 -17
  131. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +17 -0
  132. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +3 -2
  133. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +4 -3
  134. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +22 -5
  135. package/src/features/billing/components/utils/__tests__/discount.spec.ts +59 -0
  136. package/src/features/billing/components/utils/__tests__/url.spec.ts +32 -0
  137. package/src/features/billing/components/utils/discount.ts +38 -0
  138. package/src/features/billing/components/utils/index.ts +2 -0
  139. package/src/features/billing/components/utils/url.ts +12 -0
  140. package/src/features/billing/contexts/CouponContext.tsx +211 -0
  141. package/src/features/billing/contexts/index.ts +1 -0
  142. package/src/features/billing/i18n-keys.ts +79 -0
  143. package/src/features/billing/stripe-coupon/components/containers/CouponContainer.tsx +38 -0
  144. package/src/features/billing/stripe-coupon/components/containers/index.ts +1 -0
  145. package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +69 -0
  146. package/src/features/billing/stripe-coupon/components/details/index.ts +1 -0
  147. package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +79 -0
  148. package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +192 -0
  149. package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +333 -0
  150. package/src/features/billing/stripe-coupon/components/forms/CouponRefresher.tsx +45 -0
  151. package/src/features/billing/stripe-coupon/components/forms/index.ts +6 -0
  152. package/src/features/billing/stripe-coupon/components/index.ts +5 -0
  153. package/src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx +61 -0
  154. package/src/features/billing/stripe-coupon/components/lists/CouponsList.tsx +67 -0
  155. package/src/features/billing/stripe-coupon/components/lists/index.ts +2 -0
  156. package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +80 -0
  157. package/src/features/billing/stripe-coupon/components/widgets/index.ts +1 -0
  158. package/src/features/billing/stripe-coupon/data/__tests__/stripe-coupon-models.spec.ts +114 -0
  159. package/src/features/billing/stripe-coupon/data/index.ts +10 -0
  160. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.fields.ts +8 -0
  161. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.interface.ts +44 -0
  162. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.service.ts +92 -0
  163. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.ts +132 -0
  164. package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.interface.ts +17 -0
  165. package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.ts +70 -0
  166. package/src/features/billing/stripe-coupon/data/stripe-coupon.fields.ts +7 -0
  167. package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +51 -0
  168. package/src/features/billing/stripe-coupon/data/stripe-coupon.service.ts +93 -0
  169. package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +165 -0
  170. package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +154 -0
  171. package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +113 -0
  172. package/src/features/billing/stripe-coupon/index.ts +5 -0
  173. package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +14 -0
  174. package/src/features/billing/stripe-coupon/stripe-coupon-offer.module.ts +10 -0
  175. package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +18 -0
  176. package/src/features/billing/stripe-coupon/utils/__tests__/coupon-places.spec.ts +19 -0
  177. package/src/features/billing/stripe-coupon/utils/__tests__/to-promotion-code-result.spec.ts +41 -0
  178. package/src/features/billing/stripe-coupon/utils/coupon-places.ts +23 -0
  179. package/src/features/billing/stripe-coupon/utils/format-coupon-discount.ts +36 -0
  180. package/src/features/billing/stripe-coupon/utils/index.ts +3 -0
  181. package/src/features/billing/stripe-coupon/utils/to-promotion-code-result.ts +23 -0
  182. package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +24 -8
  183. package/src/features/billing/stripe-price/stripe-price.module.ts +2 -0
  184. package/src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx +21 -6
  185. package/src/features/billing/stripe-product/stripe-product.module.ts +2 -0
  186. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +60 -19
  187. package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.interface.ts +2 -0
  188. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +24 -5
  189. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +21 -2
  190. package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +1 -1
  191. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +1 -1
  192. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +60 -49
  193. package/src/features/billing/stripe-subscription/components/wizards/__tests__/review-card.spec.ts +34 -0
  194. package/src/features/billing/stripe-subscription/components/wizards/review-card.ts +24 -0
  195. package/src/features/billing/stripe-subscription/data/__tests__/stripe-subscription.discount.spec.ts +67 -0
  196. package/src/features/billing/stripe-subscription/data/stripe-subscription.interface.ts +14 -0
  197. package/src/features/billing/stripe-subscription/data/stripe-subscription.service.ts +39 -0
  198. package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +21 -0
  199. package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-payment.spec.ts +48 -0
  200. package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-promotion.spec.ts +132 -0
  201. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +138 -12
  202. package/src/features/billing/stripe-subscription/hooks/wizard-payment.ts +30 -0
  203. package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +54 -0
  204. package/src/features/blog/components/admin/BlogAdminContainer.tsx +13 -6
  205. package/src/features/blog/components/admin/BlogCategoriesList.tsx +1 -1
  206. package/src/features/blog/components/admin/BlogCoverImageField.tsx +1 -1
  207. package/src/features/blog/components/admin/BlogPostContainer.tsx +0 -1
  208. package/src/features/blog/components/admin/BlogPostEditor.tsx +42 -33
  209. package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +12 -4
  210. package/src/features/blog/components/admin/BlogPostsList.tsx +1 -1
  211. package/src/features/blog/components/public/BlogPostCard.tsx +2 -9
  212. package/src/features/blog/components/public/BlogPostMeta.tsx +2 -2
  213. package/src/features/blog/components/public/BlogPostView.tsx +9 -3
  214. package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +2 -0
  215. package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +17 -11
  216. package/src/features/blog/server/fetchers.ts +57 -8
  217. package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +12 -3
  218. package/src/features/rbac/contexts/RbacContext.tsx +4 -7
  219. package/src/features/rbac/contexts/__tests__/RbacContext.spec.tsx +42 -0
  220. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +4 -3
  221. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +3 -18
  222. package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +17 -0
  223. package/src/features/tokenusage/tokenusage-admin.module.ts +4 -3
  224. package/src/features/tokenusage/tokenusage.modules.ts +12 -2
  225. package/src/features/user/contexts/CurrentUserContext.tsx +25 -0
  226. package/src/features/user/contexts/UserContext.tsx +18 -3
  227. package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +121 -2
  228. package/src/features/user/contexts/__tests__/UserContext.spec.tsx +95 -0
  229. package/dist/chunk-3NLAXVIN.js.map +0 -1
  230. package/dist/chunk-EUKTBOI2.mjs.map +0 -1
  231. package/dist/chunk-NFBOSPNJ.js.map +0 -1
  232. package/dist/chunk-WC63PJSC.mjs.map +0 -1
  233. /package/dist/{BlockNoteEditor-YQL2SGFR.mjs.map → BlockNoteEditor-NEYG5L6W.mjs.map} +0 -0
  234. /package/dist/{BlockNoteViewer-AEJKJ27Q.mjs.map → BlockNoteViewer-3OUT5UFY.mjs.map} +0 -0
  235. /package/dist/{chunk-XCHSX3VC.mjs.map → chunk-BTPUYUQ3.mjs.map} +0 -0
  236. /package/dist/{chunk-6DC2WVVB.mjs.map → chunk-KQHBONL4.mjs.map} +0 -0
  237. /package/dist/{chunk-QMYQRPQC.mjs.map → chunk-Y7RR57EW.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/blog/components/admin/BlogAdminContainer.tsx","../../../src/features/blog/components/admin/BlogCategoriesList.tsx","../../../src/features/blog/hooks/useBlogCategoryTableStructure.tsx","../../../src/features/blog/components/admin/BlogCategoryEditor.tsx","../../../src/features/blog/components/admin/BlogCoverImageField.tsx","../../../src/features/blog/components/admin/BlogTranslationStatus.tsx","../../../src/features/blog/components/admin/BlogLayoutSetting.tsx","../../../src/features/blog/components/admin/BlogPostsList.tsx","../../../src/features/blog/hooks/useBlogPostTableStructure.tsx","../../../src/features/blog/hooks/useBlogCategoryNames.ts","../../../src/features/blog/components/admin/BlogPostEditor.tsx","../../../src/features/blog/components/admin/BlogPostContainer.tsx","../../../src/features/blog/contexts/BlogPostContext.tsx","../../../src/features/blog/components/admin/BlogPostDeleter.tsx","../../../src/features/blog/components/admin/BlogPostPublishButton.tsx","../../../src/features/blog/components/admin/BlogPostTranslationPanel.tsx","../../../src/features/blog/components/admin/BlogPostComparison.tsx","../../../src/features/blog/components/admin/BlogPostTranslationEditor.tsx","../../../src/features/blog/components/public/BlogCategoryChips.tsx","../../../src/features/blog/components/public/blogPublicHelpers.ts","../../../src/features/blog/components/public/BlogListParts.tsx","../../../src/features/blog/components/public/BlogPostCard.tsx","../../../src/features/blog/components/public/BlogPostMeta.tsx","../../../src/features/blog/components/public/BlogSearchBox.tsx","../../../src/features/blog/components/public/BlogListColumn.tsx","../../../src/features/blog/components/public/BlogListFeatured.tsx","../../../src/features/blog/components/public/BlogListRail.tsx","../../../src/features/blog/components/public/BlogList.tsx","../../../src/features/blog/components/public/BlogPostView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { RoundPageContainer } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { AdministrationProvider } from \"../../../administration/contexts/AdministrationContext\";\nimport { BlogCategoriesList } from \"./BlogCategoriesList\";\nimport { BlogLayoutSetting } from \"./BlogLayoutSetting\";\nimport { BlogPostsList } from \"./BlogPostsList\";\n\n/**\n * Blog admin page: posts, categories and the public list layout, one tab each.\n * Client component because `module={Modules.BlogPost}` carries an icon component.\n * Mirrors features/email-automation/components/containers/EmailAutomationsListContainer.tsx.\n */\nexport function BlogAdminContainer() {\n const t = useTranslations();\n\n return (\n <AdministrationProvider>\n <RoundPageContainer\n module={Modules.BlogPost}\n fullWidth\n tabs={[\n { sectionKey: \"posts\", label: t(\"blog.tabs.posts\"), content: <BlogPostsList fullWidth /> },\n { sectionKey: \"categories\", label: t(\"blog.tabs.categories\"), content: <BlogCategoriesList fullWidth /> },\n { sectionKey: \"layout\", label: t(\"blog.tabs.layout\"), content: <BlogLayoutSetting /> },\n ]}\n />\n </AdministrationProvider>\n );\n}\n","\"use client\";\n\nimport { TagIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode } from \"react\";\nimport { ContentListTable } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { DataListRetriever, useDataListRetriever } from \"../../../../hooks\";\nimport { Button, EmptyState } from \"../../../../shadcnui\";\nimport { BlogCategoryFields, BlogCategoryInterface, BlogCategoryService } from \"../../data\";\nimport \"../../hooks/useBlogCategoryTableStructure\";\nimport { BlogCategoryEditor } from \"./BlogCategoryEditor\";\n\ntype BlogCategoriesListProps = {\n /** Pass when the list is the page's own content inside a `RoundPageContainer fullWidth`. */\n fullWidth?: boolean;\n};\n\n/**\n * Admin categories table. Categories have no page of their own: creating and\n * editing both stay on this tab and refresh the list.\n * Mirrors features/email-automation/components/lists/EmailAutomationsList.tsx.\n */\nexport function BlogCategoriesList({ fullWidth }: BlogCategoriesListProps = {}) {\n const t = useTranslations();\n\n const data: DataListRetriever<BlogCategoryInterface> = useDataListRetriever({\n retriever: (params) => BlogCategoryService.findMany(params),\n retrieverParams: {},\n module: Modules.BlogCategory,\n });\n\n const functions: ReactNode[] = [\n <BlogCategoryEditor\n key=\"create-blog-category\"\n onSuccess={data.refresh}\n trigger={\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n {t(\"blog.admin.newCategory\")}\n </Button>\n }\n />,\n ];\n\n return (\n <ContentListTable\n data={data}\n fields={[BlogCategoryFields.name, BlogCategoryFields.languages]}\n tableGeneratorType={Modules.BlogCategory}\n functions={functions}\n fullWidth={fullWidth}\n title={t(\"blog.entities.categories\")}\n emptyState={<EmptyState icon={TagIcon} title={t(\"blog.admin.emptyCategories\")} />}\n />\n );\n}\n","\"use client\";\n\nimport { ColumnDef } from \"@tanstack/react-table\";\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { registerTableGenerator, TableContent, UseTableStructureHook } from \"../../../hooks\";\nimport { BlogCategoryEditor } from \"../components/admin/BlogCategoryEditor\";\nimport { blogCategoryAdminName } from \"../components/admin/BlogTranslationStatus\";\nimport { BlogCategoryFields, BlogCategoryInterface } from \"../data\";\n\n/**\n * Admin categories table: the name (original language) opens BlogCategoryEditor\n * for that row; \"languages ready\" counts completed translations. Categories have\n * no page of their own. Mirrors hooks/useEmailAutomationTableStructure.tsx in\n * features/email-automation.\n */\nexport const useBlogCategoryTableStructure: UseTableStructureHook<BlogCategoryInterface, BlogCategoryFields> = (\n params,\n) => {\n const t = useTranslations();\n const refresh = params.dataRetriever?.refresh;\n\n const tableData = useMemo(() => {\n return params.data.map((category: BlogCategoryInterface) => {\n const entry: TableContent<BlogCategoryInterface> = { jsonApiData: category };\n params.fields.forEach((field) => {\n entry[field] = (category as any)[field as keyof BlogCategoryInterface];\n });\n return entry;\n });\n }, [params.data, params.fields]);\n\n const fieldColumnMap: Partial<Record<BlogCategoryFields, () => any>> = {\n [BlogCategoryFields.name]: () => ({\n id: BlogCategoryFields.name,\n accessorKey: BlogCategoryFields.name,\n header: t(\"blog.fields.name\"),\n cell: ({ row }: { row: TableContent<BlogCategoryInterface> }) => {\n const category: BlogCategoryInterface = row.original.jsonApiData;\n return (\n <BlogCategoryEditor\n category={category}\n onSuccess={refresh}\n trigger={\n <button type=\"button\" className=\"text-primary text-start font-medium\">\n {blogCategoryAdminName(category) || category.id}\n </button>\n }\n />\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogCategoryFields.languages]: () => ({\n id: BlogCategoryFields.languages,\n accessorKey: BlogCategoryFields.languages,\n header: t(\"blog.fields.languages\"),\n cell: ({ row }: { row: TableContent<BlogCategoryInterface> }) => {\n const category: BlogCategoryInterface = row.original.jsonApiData;\n const total = category.translations.length;\n if (total === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n const ready = category.translations.filter((translation) => translation.aiStatus === \"completed\").length;\n return (\n <span className=\"block text-xs tabular-nums text-end\">\n {ready}/{total}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n };\n\n const columns = useMemo(() => {\n return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<\n TableContent<BlogCategoryInterface>\n >[];\n }, [params.fields, fieldColumnMap, t, refresh]);\n\n return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);\n};\n\nregisterTableGenerator(\"blog-categories\", useBlogCategoryTableStructure);\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { v4 } from \"uuid\";\nimport { z } from \"zod\";\nimport { EditorSheet, FormInput, FormSelect, FormTextarea, SectionHeader } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { getI18nSupportedLocales, useI18nLocale } from \"../../../../i18n\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../../../../shadcnui\";\nimport {\n BlogCategoryInterface,\n BlogCategoryService,\n BlogCategoryTranslationInterface,\n BlogCategoryTranslationService,\n} from \"../../data\";\nimport { BlogCoverImageField, BlogImageSelection, uploadBlogImage } from \"./BlogCoverImageField\";\nimport { BlogTranslationStatus, useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_CATEGORY_UPLOAD_PREFIX } from \"../../types/blog.constants\";\n\ntype BlogCategoryEditorProps = {\n /** Edit mode when present. */\n category?: BlogCategoryInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst buildTranslationSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string(),\n name: z.string().min(1, { message: t(\"blog.errors.name\") }),\n slug: z.string().optional(),\n description: z.string().optional(),\n });\n\nconst buildFormSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string().uuid(),\n originalLanguage: z.string().min(1, { message: t(\"blog.errors.language\") }),\n name: z.string().min(1, { message: t(\"blog.errors.name\") }),\n slug: z.string().optional(),\n description: z.string().optional(),\n image: z.string().optional(),\n translations: z.array(buildTranslationSchema(t)),\n });\n\ntype TranslationValues = z.infer<ReturnType<typeof buildTranslationSchema>>;\n\nconst sameTranslation = (a: TranslationValues, b?: TranslationValues) =>\n !!b && a.name === b.name && (a.slug ?? \"\") === (b.slug ?? \"\") && (a.description ?? \"\") === (b.description ?? \"\");\n\n/**\n * Creates a category, or edits its original-language text and image plus, in\n * edit mode, each AI translation by hand (one collapsible section per language).\n * Submit updates the category when the original changed (which re-translates\n * every other language) and each translation that changed (marked `manual`).\n * Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogCategoryEditor({ category, trigger, onSuccess }: BlogCategoryEditorProps) {\n const t = useTranslations();\n const formSchema = useMemo(() => buildFormSchema(t), [t]);\n const locale = useI18nLocale();\n const languageName = useBlogLanguageName();\n const [image, setImage] = useState<BlogImageSelection>(null);\n\n const languages = useMemo(() => getI18nSupportedLocales(), []);\n const original = category?.original;\n const translations: BlogCategoryTranslationInterface[] = useMemo(\n () => (category?.translations ?? []).filter((translation) => !translation.isOriginal),\n [category],\n );\n\n const getDefaultValues = useCallback(\n () => ({\n id: category?.id || v4(),\n originalLanguage: category?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),\n name: original?.name ?? category?.name ?? \"\",\n slug: original?.slug ?? \"\",\n description: original?.description ?? \"\",\n // The stored value is a signed URL, not a key: only a newly picked file writes a key.\n image: \"\",\n translations: translations.map((translation) => ({\n id: translation.id,\n name: translation.name ?? \"\",\n slug: translation.slug ?? \"\",\n description: translation.description ?? \"\",\n })),\n }),\n [category, original, translations, languages, locale],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.category\")}\n entityName={original?.name ?? category?.name}\n title={category ? t(\"blog.admin.editCategory\") : t(\"blog.admin.newCategory\")}\n isEdit={!!category}\n module={Modules.BlogCategory}\n size=\"md\"\n onSubmit={async (values) => {\n if (values.image && image) await uploadBlogImage({ key: values.image, selection: image });\n const imageKey = values.image && image ? values.image : undefined;\n\n if (!category) {\n return BlogCategoryService.create({\n id: values.id,\n originalLanguage: values.originalLanguage,\n name: values.name,\n description: values.description,\n image: imageKey,\n });\n }\n\n const defaults = getDefaultValues();\n const originalChanged =\n !!imageKey ||\n !sameTranslation(\n { id: values.id, name: values.name, slug: values.slug, description: values.description },\n { id: defaults.id, name: defaults.name, slug: defaults.slug, description: defaults.description },\n );\n\n if (originalChanged) {\n await BlogCategoryService.update({\n id: values.id,\n originalLanguage: category?.originalLanguage,\n name: values.name,\n slug: values.slug || undefined,\n description: values.description,\n image: imageKey,\n });\n }\n\n for (const translation of values.translations) {\n const before = defaults.translations.find((candidate) => candidate.id === translation.id);\n if (sameTranslation(translation, before)) continue;\n await BlogCategoryTranslationService.update({\n id: translation.id,\n name: translation.name,\n slug: translation.slug || undefined,\n description: translation.description,\n });\n }\n\n return { id: values.id };\n }}\n onReset={() => {\n setImage(null);\n return getDefaultValues();\n }}\n onSuccess={onSuccess ?? (() => {})}\n trigger={trigger}\n >\n <div className=\"flex w-full flex-col gap-y-4\">\n {!category && (\n <FormSelect\n form={form}\n id=\"originalLanguage\"\n name={t(\"blog.fields.originalLanguage\")}\n values={languages.map((language) => ({ id: language, text: languageName(language) }))}\n isRequired\n />\n )}\n <FormInput form={form} id=\"name\" name={t(\"blog.fields.name\")} isRequired />\n {category && (\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n )}\n <FormTextarea form={form} id=\"description\" name={t(\"blog.fields.description\")} />\n <BlogCoverImageField\n form={form}\n id=\"image\"\n label={t(\"blog.fields.image\")}\n keyPrefix={BLOG_CATEGORY_UPLOAD_PREFIX}\n entityId={form.getValues(\"id\")}\n currentUrl={category?.image}\n selection={image}\n onSelectionChange={setImage}\n />\n\n {translations.length > 0 && (\n <div className=\"flex flex-col gap-y-2\">\n <SectionHeader className=\"mt-2\">{t(\"blog.fields.languages\")}</SectionHeader>\n {translations.map((translation, index) => (\n <Collapsible key={translation.id} className=\"rounded-md border\">\n <CollapsibleTrigger className=\"flex w-full items-center justify-between gap-x-2 px-3 py-2 text-start\">\n <span className=\"flex items-center gap-x-2 text-sm font-medium\">\n {languageName(translation.language)}\n <BlogTranslationStatus aiStatus={translation.aiStatus} />\n </span>\n <ChevronDownIcon className=\"text-muted-foreground size-4\" />\n </CollapsibleTrigger>\n <CollapsibleContent className=\"flex flex-col gap-y-4 border-t p-3\">\n {translation.aiStatus === \"failed\" && (\n <BlogTranslationStatus\n aiStatus={translation.aiStatus}\n onRetranslate={async () => {\n await BlogCategoryTranslationService.retranslate({ id: translation.id });\n await onSuccess?.();\n }}\n />\n )}\n <FormInput form={form} id={`translations.${index}.name`} name={t(\"blog.fields.name\")} isRequired />\n <FormInput\n form={form}\n id={`translations.${index}.slug`}\n name={t(\"blog.fields.slug\")}\n description={t(\"blog.fields.slugHelp\")}\n />\n <FormTextarea\n form={form}\n id={`translations.${index}.description`}\n name={t(\"blog.fields.description\")}\n />\n </CollapsibleContent>\n </Collapsible>\n ))}\n </div>\n )}\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { UploadIcon } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { DropzoneOptions } from \"react-dropzone\";\nimport { FileInput, FileUploader, FormFieldWrapper } from \"../../../../components\";\nimport { isRemoteImageSrc } from \"../../../../utils\";\nimport { S3Service } from \"../../../s3/data/s3.service\";\n\n/** A picked image waiting to be uploaded: the S3 key is already in the form field. */\nexport type BlogImageSelection = { file: File; contentType: string } | null;\n\nconst dropzone = {\n multiple: false,\n maxSize: 100 * 1024 * 1024,\n preventDropOnDocument: false,\n accept: {\n \"application/images\": [\".jpg\", \".jpeg\", \".png\", \".gif\", \".webp\", \".svg\"],\n },\n} satisfies DropzoneOptions;\n\n/** Uploads a picked image to its presigned S3 key. */\nexport async function uploadBlogImage(params: { key: string; selection: BlogImageSelection }): Promise<void> {\n if (!params.selection || !params.key) return;\n\n await S3Service.upload({\n key: params.key,\n contentType: params.selection.contentType,\n file: params.selection.file,\n isPublic: true,\n });\n}\n\ntype BlogCoverImageFieldProps = {\n form: any;\n /** The form field that receives the new S3 key (\"coverImage\" or \"image\"). */\n id: string;\n label: string;\n /** BLOG_POST_UPLOAD_PREFIX or BLOG_CATEGORY_UPLOAD_PREFIX. */\n keyPrefix: string;\n /** The id of the post or category the image belongs to. */\n entityId: string;\n /** The signed URL of the image already stored, for the preview. */\n currentUrl?: string;\n alt?: string;\n selection: BlogImageSelection;\n onSelectionChange: (selection: BlogImageSelection) => void;\n};\n\n/**\n * Dropzone for a post cover or a category image. Picking a file writes its S3\n * key (`<keyPrefix>/<id>/<id>.<timestamp>.<ext>`, under the platform blog root,\n * see BLOG_UPLOAD_ROOT) into the form field and hands\n * the file to the editor, which uploads it with `uploadBlogImage` on submit.\n */\nexport function BlogCoverImageField({\n form,\n id,\n label,\n keyPrefix,\n entityId,\n currentUrl,\n alt,\n selection,\n onSelectionChange,\n}: BlogCoverImageFieldProps) {\n const [files, setFiles] = useState<File[] | null>(null);\n\n useEffect(() => {\n if (selection === null) setFiles(null);\n }, [selection]);\n\n const previewUrl = useMemo(() => (selection ? URL.createObjectURL(selection.file) : undefined), [selection]);\n\n useEffect(() => {\n return () => {\n if (previewUrl) URL.revokeObjectURL(previewUrl);\n };\n }, [previewUrl]);\n\n const src = previewUrl ?? currentUrl;\n\n return (\n <FormFieldWrapper form={form} name={id} label={label}>\n {(field) => (\n <FileUploader\n value={files}\n onValueChange={(value: File[] | null) => {\n setFiles(value);\n const file = value?.[0];\n if (!file) return;\n const extension = file.type.split(\"/\").pop() ?? \"\";\n field.onChange(`${keyPrefix}/${entityId}/${entityId}.${Date.now()}.${extension}`);\n onSelectionChange({ file, contentType: file.type });\n }}\n dropzoneOptions={dropzone}\n className=\"w-full\"\n >\n <FileInput className=\"text-muted-foreground/50 rounded-lg outline-1 outline-dashed\">\n <div className=\"flex w-full flex-col items-center justify-center p-4\">\n {src ? (\n <Image\n src={src}\n alt={alt ?? label}\n width={320}\n height={180}\n className=\"h-auto max-h-48 w-auto rounded-md object-cover\"\n unoptimized={isRemoteImageSrc(previewUrl ? undefined : currentUrl)}\n />\n ) : (\n <>\n <UploadIcon className=\"my-4 h-8 w-8\" />\n <span className=\"text-muted-foreground text-xs\">{label}</span>\n </>\n )}\n </div>\n </FileInput>\n </FileUploader>\n )}\n </FormFieldWrapper>\n );\n}\n","\"use client\";\n\nimport { LoaderCircleIcon, RotateCcwIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { errorToast } from \"../../../../components\";\nimport { useI18nLocale } from \"../../../../i18n\";\nimport { Badge, Button } from \"../../../../shadcnui\";\nimport { BlogCategoryInterface } from \"../../data\";\nimport { BlogAiStatus } from \"../../types/blog.constants\";\n\n/** Status pill colours for a post, shared by the post header and the admin posts table. */\nexport const BLOG_STATUS_BADGE = {\n draft: \"softGray\",\n published: \"softGreen\",\n unpublished: \"softAmber\",\n} as const;\n\n/** The category label in the admin: the category's own original-language name. */\nexport function blogCategoryAdminName(category: BlogCategoryInterface): string {\n return category.original?.name ?? category.name ?? \"\";\n}\n\n/**\n * Language names come from `Intl.DisplayNames` in the viewer's locale: there is\n * no per-language message key. An unknown code falls back to the code itself.\n */\nexport function useBlogLanguageName(): (language: string) => string {\n const locale = useI18nLocale();\n const names = useMemo(() => {\n try {\n return new Intl.DisplayNames([locale], { type: \"language\" });\n } catch {\n return undefined;\n }\n }, [locale]);\n\n return useCallback(\n (language: string) => {\n const name = names?.of(language);\n if (!name) return language;\n return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);\n },\n [names, locale],\n );\n}\n\n/** Formats a date for the admin pages in the viewer's locale. */\nexport function useBlogDateFormatter(): (date?: Date) => string {\n const locale = useI18nLocale();\n return useCallback((date?: Date) => (date ? date.toLocaleDateString(locale, { dateStyle: \"medium\" }) : \"\"), [locale]);\n}\n\ntype BlogTranslationStatusProps = {\n aiStatus: BlogAiStatus;\n /** Re-queues the translation of this one language. Shown only when the job failed. */\n onRetranslate?: () => Promise<void>;\n};\n\n/**\n * The translation state of one language. Nothing is rendered once the\n * translation is `completed`; `failed` adds the \"Translate again\" action.\n */\nexport function BlogTranslationStatus({ aiStatus, onRetranslate }: BlogTranslationStatusProps) {\n const t = useTranslations();\n const [isRetrying, setIsRetrying] = useState<boolean>(false);\n\n if (aiStatus === \"completed\") return null;\n\n if (aiStatus === \"pending\" || aiStatus === \"in_progress\") {\n return (\n <div className=\"flex items-center gap-x-2\">\n <Badge variant=\"softBlue\">\n <LoaderCircleIcon className=\"animate-spin\" />\n {aiStatus === \"pending\" ? t(\"blog.translation.pending\") : t(\"blog.translation.translating\")}\n </Badge>\n </div>\n );\n }\n\n const retranslate = async () => {\n if (!onRetranslate) return;\n setIsRetrying(true);\n try {\n await onRetranslate();\n } catch (error) {\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setIsRetrying(false);\n }\n };\n\n return (\n <div className=\"flex items-center gap-x-2\">\n <Badge variant=\"softRed\">{t(\"blog.translation.failed\")}</Badge>\n {onRetranslate && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => void retranslate()} disabled={isRetrying}>\n <RotateCcwIcon />\n {t(\"blog.actions.translateAgain\")}\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { useEffect, useState } from \"react\";\nimport { errorToast, SectionHeader } from \"../../../../components\";\nimport { RadioGroup, RadioGroupItem } from \"../../../../shadcnui\";\nimport { showToast } from \"../../../../utils/toast\";\nimport { BlogSettingsService } from \"../../data\";\nimport { BLOG_LIST_LAYOUTS, BLOG_SETTINGS_ID, BlogListLayout } from \"../../types/blog.constants\";\n\n/**\n * Picks the public list layout (featured / column / rail). The choice is saved\n * at once and the public blog uses it within the cache window.\n */\nexport function BlogLayoutSetting() {\n const t = useTranslations();\n const [layout, setLayout] = useState<BlogListLayout | undefined>(undefined);\n const [saving, setSaving] = useState<boolean>(false);\n\n useEffect(() => {\n let active = true;\n BlogSettingsService.get()\n .then((settings) => {\n if (active) setLayout(settings?.listLayout ?? \"featured\");\n })\n .catch(() => {\n if (active) setLayout(\"featured\");\n });\n return () => {\n active = false;\n };\n }, []);\n\n const options: Record<BlogListLayout, { label: string; description: string }> = {\n featured: { label: t(\"blog.layout.featured\"), description: t(\"blog.layout.featuredDescription\") },\n column: { label: t(\"blog.layout.column\"), description: t(\"blog.layout.columnDescription\") },\n rail: { label: t(\"blog.layout.rail\"), description: t(\"blog.layout.railDescription\") },\n };\n\n const save = async (next: BlogListLayout) => {\n const previous = layout;\n setLayout(next);\n setSaving(true);\n try {\n await BlogSettingsService.save({ id: BLOG_SETTINGS_ID, listLayout: next });\n showToast(t(\"blog.layout.saved\"));\n } catch (error) {\n setLayout(previous);\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"flex w-full max-w-3xl flex-col gap-y-4 p-4\">\n <SectionHeader>{t(\"blog.layout.title\")}</SectionHeader>\n {layout && (\n <RadioGroup\n value={layout}\n onValueChange={(value) => {\n // Base UI can emit a null value; ignore it and an unchanged choice.\n if (!value || value === layout) return;\n void save(value as BlogListLayout);\n }}\n disabled={saving}\n className=\"grid grid-cols-1 gap-3 md:grid-cols-3\"\n >\n {BLOG_LIST_LAYOUTS.map((option) => (\n <label\n key={option}\n htmlFor={`blog-layout-${option}`}\n className=\"has-data-checked:border-primary flex cursor-pointer items-start gap-x-3 rounded-lg border p-4\"\n >\n <RadioGroupItem value={option} id={`blog-layout-${option}`} className=\"mt-0.5\" />\n <span className=\"flex flex-col gap-y-1\">\n <span className=\"text-sm font-medium\">{options[option].label}</span>\n <span className=\"text-muted-foreground text-xs/relaxed\">{options[option].description}</span>\n </span>\n </label>\n ))}\n </RadioGroup>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { NewspaperIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode } from \"react\";\nimport { ContentListTable } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { DataListRetriever, useDataListRetriever } from \"../../../../hooks\";\nimport { Button, EmptyState } from \"../../../../shadcnui\";\nimport { BlogPostFields, BlogPostInterface, BlogPostService } from \"../../data\";\nimport \"../../hooks/useBlogPostTableStructure\";\nimport { BlogPostEditor } from \"./BlogPostEditor\";\n\ntype BlogPostsListProps = {\n /** Pass when the list is the page's own content inside a `RoundPageContainer fullWidth`. */\n fullWidth?: boolean;\n};\n\n/** Admin posts table. Mirrors features/email-automation/components/lists/EmailAutomationsList.tsx. */\nexport function BlogPostsList({ fullWidth }: BlogPostsListProps = {}) {\n const t = useTranslations();\n\n const data: DataListRetriever<BlogPostInterface> = useDataListRetriever({\n retriever: (params) => BlogPostService.findMany(params),\n retrieverParams: {},\n module: Modules.BlogPost,\n });\n\n // Creating navigates to the new post's page (EditorSheet's default without onSuccess).\n const functions: ReactNode[] = [\n <BlogPostEditor\n key=\"create-blog-post\"\n trigger={\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n {t(\"blog.admin.newPost\")}\n </Button>\n }\n />,\n ];\n\n return (\n <ContentListTable\n data={data}\n fields={[\n BlogPostFields.title,\n BlogPostFields.status,\n BlogPostFields.categories,\n BlogPostFields.publishedAt,\n BlogPostFields.languages,\n ]}\n tableGeneratorType={Modules.BlogPost}\n functions={functions}\n fullWidth={fullWidth}\n title={t(\"blog.entities.posts\")}\n emptyState={<EmptyState icon={NewspaperIcon} title={t(\"blog.admin.empty\")} />}\n />\n );\n}\n","\"use client\";\n\nimport { ColumnDef } from \"@tanstack/react-table\";\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { Modules } from \"../../../core\";\nimport { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from \"../../../hooks\";\nimport { useI18nLocale } from \"../../../i18n\";\nimport { Badge, Link } from \"../../../shadcnui\";\nimport { BLOG_STATUS_BADGE } from \"../components/admin/BlogTranslationStatus\";\nimport { useBlogCategoryNames } from \"./useBlogCategoryNames\";\nimport { BlogPostFields, BlogPostInterface } from \"../data\";\n\n/**\n * Admin posts table: title (original language, links to the post page),\n * status, categories, published date and \"languages ready\" (completed\n * translations / all translations). Mirrors hooks/useEmailAutomationTableStructure.tsx\n * in features/email-automation.\n */\nexport const useBlogPostTableStructure: UseTableStructureHook<BlogPostInterface, BlogPostFields> = (params) => {\n const t = useTranslations();\n const locale = useI18nLocale();\n const generateUrl = usePageUrlGenerator();\n const categoryName = useBlogCategoryNames();\n\n const tableData = useMemo(() => {\n return params.data.map((post: BlogPostInterface) => {\n const entry: TableContent<BlogPostInterface> = { jsonApiData: post };\n params.fields.forEach((field) => {\n entry[field] = (post as any)[field as keyof BlogPostInterface];\n });\n return entry;\n });\n }, [params.data, params.fields]);\n\n const fieldColumnMap: Partial<Record<BlogPostFields, () => any>> = {\n [BlogPostFields.title]: () => ({\n id: BlogPostFields.title,\n accessorKey: BlogPostFields.title,\n header: t(\"blog.fields.title\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n return (\n <Link href={generateUrl({ page: Modules.BlogPost, id: post.id })}>\n {post.original?.title ?? post.title ?? post.id}\n </Link>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.status]: () => ({\n id: BlogPostFields.status,\n accessorKey: BlogPostFields.status,\n header: t(\"blog.fields.status\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const label = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n return <Badge variant={BLOG_STATUS_BADGE[post.status] ?? \"softGray\"}>{label ?? post.status}</Badge>;\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.categories]: () => ({\n id: BlogPostFields.categories,\n accessorKey: BlogPostFields.categories,\n header: t(\"blog.fields.categories\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const names = post.categories.map((category) => categoryName(category.id)).filter(Boolean);\n if (names.length === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n return <span className=\"text-xs\">{names.join(\", \")}</span>;\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.publishedAt]: () => ({\n id: BlogPostFields.publishedAt,\n accessorKey: BlogPostFields.publishedAt,\n header: t(\"blog.fields.publishedAt\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n if (!post.publishedAt) return <span className=\"text-muted-foreground text-xs\">—</span>;\n return (\n <span className=\"text-xs tabular-nums\">\n {post.publishedAt.toLocaleDateString(locale, { dateStyle: \"medium\" })}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.languages]: () => ({\n id: BlogPostFields.languages,\n accessorKey: BlogPostFields.languages,\n header: t(\"blog.fields.languages\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const total = post.translations.length;\n if (total === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n const ready = post.translations.filter((translation) => translation.aiStatus === \"completed\").length;\n return (\n <span className=\"block text-xs tabular-nums text-end\">\n {ready}/{total}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n };\n\n const columns = useMemo(() => {\n return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<\n TableContent<BlogPostInterface>\n >[];\n }, [params.fields, fieldColumnMap, t, generateUrl, locale, categoryName]);\n\n return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);\n};\n\nregisterTableGenerator(\"blog-posts\", useBlogPostTableStructure);\n","\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { BlogCategoryService } from \"../data\";\nimport { blogCategoryAdminName } from \"../components/admin/BlogTranslationStatus\";\n\n/**\n * Category names for the admin views, in each category's original language.\n *\n * A post response includes its categories without their translations (the\n * framework includes one level), so the name is not on the included record.\n * The category list does include the translations: it is read once and the\n * returned lookup maps a category id to its name (\"\" while loading).\n */\nexport function useBlogCategoryNames(): (categoryId: string) => string {\n const [names, setNames] = useState<Record<string, string>>({});\n\n useEffect(() => {\n let cancelled = false;\n BlogCategoryService.findMany({ fetchAll: true })\n .then((categories) => {\n if (cancelled) return;\n setNames(Object.fromEntries(categories.map((category) => [category.id, blogCategoryAdminName(category)])));\n })\n .catch(() => {\n // The names are decoration on an admin page: a failed read leaves the ids unnamed.\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n return useCallback((categoryId: string) => names[categoryId] ?? \"\", [names]);\n}\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { v4 } from \"uuid\";\nimport { z } from \"zod\";\nimport {\n EditorSheet,\n EntityMultiSelector,\n FormBlockNote,\n FormInput,\n FormSelect,\n FormTextarea,\n} from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { getI18nSupportedLocales, useI18nLocale, useI18nRouter } from \"../../../../i18n\";\nimport {\n BlogCategoryInterface,\n BlogCategoryService,\n BlogPostInput,\n BlogPostInterface,\n BlogPostService,\n} from \"../../data\";\nimport { BlogCoverImageField, BlogImageSelection, uploadBlogImage } from \"./BlogCoverImageField\";\nimport { blogCategoryAdminName, useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_POST_UPLOAD_PREFIX } from \"../../types/blog.constants\";\nimport { useBlogCategoryNames } from \"../../hooks/useBlogCategoryNames\";\n\ntype BlogPostEditorProps = {\n /** Edit mode when present: the post's original translation and shared fields. */\n post?: BlogPostInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst isBodyEmpty = (body: any) => !body || (Array.isArray(body) && body.length === 0);\n\n/**\n * Creates a post, or edits its original-language text plus the fields every\n * language shares (cover image, categories). Saving the original makes the\n * worker translate it again. Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogPostEditor({ post, trigger, onSuccess }: BlogPostEditorProps) {\n const t = useTranslations();\n const categoryName = useBlogCategoryNames();\n const router = useI18nRouter();\n const locale = useI18nLocale();\n const languageName = useBlogLanguageName();\n const original = post?.original;\n const [cover, setCover] = useState<BlogImageSelection>(null);\n const [isBodyEmptyState, setIsBodyEmptyState] = useState<boolean>(isBodyEmpty(original?.body));\n\n const languages = useMemo(() => getI18nSupportedLocales(), []);\n\n const formSchema = useMemo(\n () =>\n z.object({\n id: z.string().uuid(),\n originalLanguage: z.string().min(1, { message: t(\"blog.errors.language\") }),\n title: z.string().min(1, { message: t(\"blog.errors.title\") }),\n slug: z.string().optional(),\n summary: z.string().optional(),\n body: z.any(),\n coverImage: z.string().optional(),\n coverImageAlt: z.string().optional(),\n categories: z.array(z.object({ id: z.string(), name: z.string() })),\n }),\n [],\n );\n\n const getDefaultValues = useCallback(\n () => ({\n id: post?.id || v4(),\n originalLanguage: post?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),\n title: original?.title ?? \"\",\n slug: original?.slug ?? \"\",\n summary: original?.summary ?? \"\",\n body: original?.body || [],\n // The stored value is a signed URL, not a key: only a newly picked file\n // writes a key here, and only then is the field sent.\n coverImage: \"\",\n coverImageAlt: original?.coverImageAlt ?? \"\",\n categories: (post?.categories ?? []).map((category) => ({\n id: category.id,\n name: blogCategoryAdminName(category),\n })),\n }),\n [post, original, languages, locale],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n const { dirtyFields } = form.formState;\n const isFormDirty = useCallback(() => {\n const dirty: Record<string, unknown> = { ...dirtyFields };\n if (dirty.body && isBodyEmptyState) delete dirty.body;\n return Object.keys(dirty).length > 0;\n }, [dirtyFields, isBodyEmptyState]);\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.post\")}\n entityName={original?.title}\n title={post ? t(\"blog.admin.editPost\") : t(\"blog.admin.newPost\")}\n isEdit={!!post}\n module={Modules.BlogPost}\n size=\"lg\"\n isFormDirty={isFormDirty}\n onSubmit={async (values) => {\n if (values.coverImage && cover) await uploadBlogImage({ key: values.coverImage, selection: cover });\n\n const payload: BlogPostInput = {\n id: values.id,\n title: values.title,\n // An empty summary lets the worker write one; on edit the summary is\n // sent only when changed here, so an untouched AI summary is regenerated.\n summary: post\n ? form.getFieldState(\"summary\").isDirty\n ? values.summary\n : undefined\n : values.summary || undefined,\n body: values.body,\n coverImage: values.coverImage && cover ? values.coverImage : undefined,\n coverImageAlt: values.coverImageAlt,\n categoryIds: values.categories.map((category) => category.id),\n };\n\n if (post) {\n return BlogPostService.update({\n ...payload,\n originalLanguage: post.originalLanguage,\n slug: values.slug || undefined,\n });\n }\n return BlogPostService.create({ ...payload, originalLanguage: values.originalLanguage });\n }}\n onReset={() => {\n setCover(null);\n setIsBodyEmptyState(isBodyEmpty(original?.body));\n return getDefaultValues();\n }}\n onSuccess={onSuccess}\n onNavigate={(url) => router.push(url)}\n trigger={trigger}\n >\n <div className=\"flex w-full flex-col gap-y-4\">\n {!post && (\n <FormSelect\n form={form}\n id=\"originalLanguage\"\n name={t(\"blog.fields.originalLanguage\")}\n values={languages.map((language) => ({ id: language, text: languageName(language) }))}\n isRequired\n />\n )}\n <FormInput form={form} id=\"title\" name={t(\"blog.fields.title\")} isRequired />\n {post && (\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n )}\n <FormTextarea\n form={form}\n id=\"summary\"\n name={t(\"blog.fields.summary\")}\n description={t(\"blog.fields.summaryHelp\")}\n />\n <BlogCoverImageField\n form={form}\n id=\"coverImage\"\n label={t(\"blog.fields.coverImage\")}\n keyPrefix={BLOG_POST_UPLOAD_PREFIX}\n entityId={form.getValues(\"id\")}\n currentUrl={post?.coverImage}\n alt={original?.coverImageAlt}\n selection={cover}\n onSelectionChange={setCover}\n />\n <FormInput form={form} id=\"coverImageAlt\" name={t(\"blog.fields.coverImageAlt\")} />\n <EntityMultiSelector<BlogCategoryInterface>\n id=\"categories\"\n form={form}\n label={t(\"blog.fields.categories\")}\n retriever={(params) => BlogCategoryService.findMany(params)}\n module={Modules.BlogCategory}\n getLabel={blogCategoryAdminName}\n // A post's included categories carry no translations, so the stored chip\n // names come from the category list.\n getFormValueLabel={(value: { id: string; name?: string }) => value.name || categoryName(value.id)}\n />\n <FormBlockNote\n uploadKeyPrefix={BLOG_POST_UPLOAD_PREFIX}\n form={form}\n id=\"body\"\n name={t(\"blog.fields.body\")}\n type=\"blog-post\"\n isRequired\n onEmptyChange={setIsBodyEmptyState}\n />\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { RoundPageContainer } from \"../../../../components\";\nimport { SharedProvider } from \"../../../../contexts/SharedContext\";\nimport { Modules } from \"../../../../core\";\nimport { usePageUrlGenerator } from \"../../../../hooks\";\nimport { BreadcrumbItemData } from \"../../../../interfaces\";\nimport { Badge } from \"../../../../shadcnui\";\nimport { BlogPostProvider, useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostDeleter } from \"./BlogPostDeleter\";\nimport { BlogPostPublishButton } from \"./BlogPostPublishButton\";\nimport { BlogPostTranslationPanel } from \"./BlogPostTranslationPanel\";\nimport { BLOG_STATUS_BADGE, useBlogLanguageName } from \"./BlogTranslationStatus\";\n\nexport type BlogPostContainerProps = {\n postId: string;\n};\n\n/**\n * Admin post page: one tab per language, the original first and marked\n * \"Original\" (approved mockup A). Mirrors\n * features/email-automation/components/containers/EmailAutomationContainer.tsx.\n */\nexport function BlogPostContainer({ postId }: BlogPostContainerProps) {\n return (\n <BlogPostProvider postId={postId}>\n <BlogPostShell />\n </BlogPostProvider>\n );\n}\n\nfunction BlogPostShell() {\n const t = useTranslations();\n const generateUrl = usePageUrlGenerator();\n const languageName = useBlogLanguageName();\n const { post, reload } = useBlogPost();\n\n const translations = useMemo(() => {\n const all = post?.translations ?? [];\n return [\n ...all.filter((translation) => translation.isOriginal),\n ...all.filter((translation) => !translation.isOriginal),\n ];\n }, [post]);\n\n if (!post) return null;\n\n const title = post.original?.title ?? post.title ?? \"\";\n\n const breadcrumbs: BreadcrumbItemData[] = [\n { name: t(\"blog.admin.title\"), href: generateUrl({ page: Modules.BlogPost }) },\n { name: title },\n ];\n\n const statusLabel = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n\n const tabLabel = (translation: (typeof translations)[number]) => {\n const parts = [languageName(translation.language)];\n if (translation.isOriginal) parts.push(t(\"blog.translation.original\"));\n if (translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\")\n parts.push(t(\"blog.translation.translating\"));\n if (translation.aiStatus === \"failed\") parts.push(t(\"blog.translation.failed\"));\n return parts.join(\" · \");\n };\n\n return (\n <SharedProvider\n value={{\n breadcrumbs,\n title: {\n type: t(\"blog.admin.title\"),\n element: title,\n functions: [\n <Badge key=\"blogPostStatus\" variant={BLOG_STATUS_BADGE[post.status]}>\n {statusLabel}\n </Badge>,\n <BlogPostPublishButton key=\"blogPostPublish\" />,\n <BlogPostDeleter key=\"blogPostDeleter\" />,\n ],\n },\n }}\n >\n <RoundPageContainer\n module={Modules.BlogPost}\n id={post.id}\n fullWidth\n forceHeader\n tabs={translations.map((translation) => ({\n sectionKey: translation.language,\n label: tabLabel(translation),\n content: <BlogPostTranslationPanel post={post} translation={translation} onChanged={reload} />,\n }))}\n />\n </SharedProvider>\n );\n}\n","\"use client\";\n\nimport { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport { BlogPostInterface, BlogPostService } from \"../data\";\n\n/** How often the post page re-reads the post while a translation job runs. */\nconst TRANSLATION_POLL_MS = 5000;\n\nexport interface BlogPostContextType {\n post: BlogPostInterface | undefined;\n reload: () => Promise<void>;\n}\n\nconst BlogPostContext = createContext<BlogPostContextType | undefined>(undefined);\n\nexport type BlogPostProviderProps = {\n postId: string;\n children: ReactNode;\n};\n\n/**\n * Loads one post (with its translations, categories and author) for the admin\n * post page. While any translation is `pending` or `in_progress` it re-reads\n * the post every 5 s, so the language tabs move from \"Translating…\" to the\n * translated text without a manual refresh; it stops once none are running.\n */\nexport const BlogPostProvider = ({ postId, children }: BlogPostProviderProps) => {\n const [post, setPost] = useState<BlogPostInterface | undefined>(undefined);\n\n const reload = useCallback(async () => {\n const loaded = await BlogPostService.findOne({ id: postId });\n setPost(loaded);\n }, [postId]);\n\n useEffect(() => {\n void reload();\n }, [reload]);\n\n const isTranslating = useMemo(\n () =>\n (post?.translations ?? []).some(\n (translation) => translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\",\n ),\n [post],\n );\n\n useEffect(() => {\n if (!isTranslating) return;\n const handle = setInterval(() => {\n reload().catch(() => {});\n }, TRANSLATION_POLL_MS);\n return () => clearInterval(handle);\n }, [isTranslating, reload]);\n\n const value = useMemo<BlogPostContextType>(() => ({ post, reload }), [post, reload]);\n\n return <BlogPostContext.Provider value={value}>{children}</BlogPostContext.Provider>;\n};\n\nexport const useBlogPost = (): BlogPostContextType => {\n const context = useContext(BlogPostContext);\n if (context === undefined) {\n throw new Error(\"useBlogPost must be used within a BlogPostProvider\");\n }\n return context;\n};\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { CommonDeleter } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { usePageUrlGenerator } from \"../../../../hooks\";\nimport { useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostService } from \"../../data\";\n\n/**\n * Deletes the post and returns to the blog admin page.\n * Mirrors features/how-to/components/forms/HowToDeleter.tsx. The dialog texts\n * are passed explicitly: CommonDeleter's default reads `entities.<type>`, a\n * namespace only the app may fill, so the blog's own entity name is used.\n */\nexport function BlogPostDeleter() {\n const t = useTranslations();\n const generateUrl = usePageUrlGenerator();\n const { post } = useBlogPost();\n\n if (!post) return null;\n\n const type = t(\"blog.entities.post\");\n\n return (\n <CommonDeleter\n title={t(\"common.delete.title\", { type })}\n subtitle={t(\"common.delete.subtitle\", { type })}\n description={<div className=\"text-destructive p-4 text-sm\">{t(\"common.delete.description\", { type })}</div>}\n showLabel\n deleteFunction={() => BlogPostService.delete({ id: post.id })}\n redirectTo={generateUrl({ page: Modules.BlogPost })}\n />\n );\n}\n","\"use client\";\n\nimport { EyeIcon, EyeOffIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { useState } from \"react\";\nimport { errorToast } from \"../../../../components\";\nimport { Button } from \"../../../../shadcnui\";\nimport { useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostService } from \"../../data\";\n\n/**\n * Publish / Unpublish in the post page header. Publishing an unpublished post\n * republishes it (the first publish date is kept server-side).\n * Mirrors features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx.\n */\nexport function BlogPostPublishButton() {\n const t = useTranslations();\n const { post, reload } = useBlogPost();\n const [saving, setSaving] = useState<boolean>(false);\n\n if (!post) return null;\n\n const isPublished = post.status === \"published\";\n\n const toggle = async () => {\n setSaving(true);\n try {\n if (isPublished) await BlogPostService.unpublish({ id: post.id });\n else await BlogPostService.publish({ id: post.id });\n await reload();\n } catch (error) {\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <Button\n type=\"button\"\n size=\"sm\"\n variant={isPublished ? \"outline\" : \"default\"}\n onClick={() => void toggle()}\n disabled={saving}\n >\n {isPublished ? <EyeOffIcon /> : <EyeIcon />}\n {isPublished ? t(\"blog.actions.unpublish\") : t(\"blog.actions.publish\")}\n </Button>\n );\n}\n","\"use client\";\n\nimport { PencilIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { DetailField } from \"../../../../components\";\nimport { Alert, AlertDescription, Badge, Button } from \"../../../../shadcnui\";\nimport { isRemoteImageSrc } from \"../../../../utils\";\nimport { BlogPostInterface, BlogPostTranslationInterface, BlogPostTranslationService } from \"../../data\";\nimport { BlogPostComparison, BlogPostTranslationFields } from \"./BlogPostComparison\";\nimport { BlogPostEditor } from \"./BlogPostEditor\";\nimport { BlogPostTranslationEditor } from \"./BlogPostTranslationEditor\";\nimport { useBlogCategoryNames } from \"../../hooks/useBlogCategoryNames\";\nimport { BlogTranslationStatus, useBlogDateFormatter, useBlogLanguageName } from \"./BlogTranslationStatus\";\n\ntype BlogPostTranslationPanelProps = {\n post: BlogPostInterface;\n translation: BlogPostTranslationInterface;\n /** Re-reads the post after an edit or a re-translation request. */\n onChanged?: () => Promise<void>;\n};\n\n/**\n * One language tab of the admin post page (approved mockup A).\n * - Original tab: cover image, categories, status, the text fields and an Edit\n * that opens BlogPostEditor (which re-translates every other language).\n * - Translation tab: where the text came from, whether it was edited by hand,\n * the overwrite warning, \"Compare with original\", the job status, the text\n * fields and an Edit that opens BlogPostTranslationEditor.\n */\nexport function BlogPostTranslationPanel({ post, translation, onChanged }: BlogPostTranslationPanelProps) {\n const t = useTranslations();\n const categoryName = useBlogCategoryNames();\n const languageName = useBlogLanguageName();\n const formatDate = useBlogDateFormatter();\n const [isComparing, setIsComparing] = useState<boolean>(false);\n\n const original = post.original;\n const reload = onChanged ?? (async () => {});\n\n const editTrigger = (\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n <PencilIcon />\n {t(\"blog.actions.edit\")}\n </Button>\n );\n\n if (translation.isOriginal) {\n const statusLabel = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n\n return (\n <div className=\"flex w-full flex-col gap-y-6 p-4\">\n <div className=\"flex items-center justify-between gap-x-4\">\n <Badge variant=\"softGray\">{t(\"blog.translation.original\")}</Badge>\n <BlogPostEditor post={post} trigger={editTrigger} onSuccess={reload} />\n </div>\n {post.coverImage && (\n <Image\n src={post.coverImage}\n alt={translation.coverImageAlt ?? translation.title}\n width={640}\n height={360}\n className=\"h-auto max-h-72 w-auto rounded-lg object-cover\"\n unoptimized={isRemoteImageSrc(post.coverImage)}\n />\n )}\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <DetailField label={t(\"blog.fields.status\")} value={statusLabel} />\n <DetailField label={t(\"blog.fields.publishedAt\")} value={formatDate(post.publishedAt) || \"—\"} />\n <DetailField\n label={t(\"blog.fields.categories\")}\n value={\n post.categories\n .map((category) => categoryName(category.id))\n .filter(Boolean)\n .join(\", \") || \"—\"\n }\n />\n </div>\n <BlogPostTranslationFields translation={translation} />\n </div>\n );\n }\n\n const isWorking = translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\";\n\n return (\n <div className=\"flex w-full flex-col gap-y-6 p-4\">\n <Alert>\n <AlertDescription className=\"flex flex-col gap-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {translation.source === \"manual\" && <Badge variant=\"softAmber\">{t(\"blog.translation.editedByHand\")}</Badge>}\n {original && translation.translatedAt && (\n <span>\n {t(\"blog.translation.translatedFrom\", {\n language: languageName(original.language),\n date: formatDate(translation.translatedAt),\n })}\n </span>\n )}\n </div>\n <span>{t(\"blog.translation.overwriteWarning\")}</span>\n {original && (\n <div>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={() => setIsComparing((value) => !value)}>\n {isComparing ? t(\"blog.actions.hideComparison\") : t(\"blog.actions.compareWithOriginal\")}\n </Button>\n </div>\n )}\n </AlertDescription>\n </Alert>\n\n <div className=\"flex items-center justify-between gap-x-4\">\n <BlogTranslationStatus\n aiStatus={translation.aiStatus}\n onRetranslate={async () => {\n await BlogPostTranslationService.retranslate({ id: translation.id });\n await reload();\n }}\n />\n {!isWorking && <BlogPostTranslationEditor translation={translation} trigger={editTrigger} onSuccess={reload} />}\n </div>\n\n {isComparing && original ? (\n <BlogPostComparison original={original} translation={translation} />\n ) : (\n <BlogPostTranslationFields translation={translation} />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { BlockNoteEditorContainer, DetailField, MicroLabel } from \"../../../../components\";\nimport { Badge } from \"../../../../shadcnui\";\nimport { BlogPostTranslationInterface } from \"../../data\";\nimport { useBlogLanguageName } from \"./BlogTranslationStatus\";\n\ntype BlogPostComparisonProps = {\n original: BlogPostTranslationInterface;\n translation: BlogPostTranslationInterface;\n};\n\n/** One language's text fields, read-only. Shared by both sides of the comparison. */\nexport function BlogPostTranslationFields({ translation }: { translation: BlogPostTranslationInterface }) {\n const t = useTranslations();\n\n return (\n <div className=\"flex flex-col gap-y-4\">\n <DetailField label={t(\"blog.fields.title\")} value={translation.title} />\n <DetailField label={t(\"blog.fields.slug\")} value={translation.slug} />\n <DetailField label={t(\"blog.fields.summary\")} value={translation.summary} />\n <DetailField label={t(\"blog.fields.coverImageAlt\")} value={translation.coverImageAlt} />\n <DetailField label={t(\"blog.fields.body\")}>\n <BlockNoteEditorContainer id={translation.id} type=\"blog-post\" initialContent={translation.body} />\n </DetailField>\n </div>\n );\n}\n\n/** Original on the inline start, translation on the inline end, same fields (mockup A → B's split view). */\nexport function BlogPostComparison({ original, translation }: BlogPostComparisonProps) {\n const t = useTranslations();\n const languageName = useBlogLanguageName();\n\n return (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"bg-muted/40 flex flex-col gap-y-4 rounded-lg border p-4\">\n <div className=\"flex items-center gap-x-2\">\n <MicroLabel as=\"h3\">{languageName(original.language)}</MicroLabel>\n <Badge variant=\"softGray\">{t(\"blog.translation.original\")}</Badge>\n </div>\n <BlogPostTranslationFields translation={original} />\n </div>\n <div className=\"flex flex-col gap-y-4 rounded-lg border p-4\">\n <div className=\"flex items-center gap-x-2\">\n <MicroLabel as=\"h3\">{languageName(translation.language)}</MicroLabel>\n {translation.source === \"manual\" && <Badge variant=\"softAmber\">{t(\"blog.translation.editedByHand\")}</Badge>}\n </div>\n <BlogPostTranslationFields translation={translation} />\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { EditorSheet, FormBlockNote, FormInput, FormTextarea } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { BlogPostTranslationInterface, BlogPostTranslationService } from \"../../data\";\nimport { useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_POST_UPLOAD_PREFIX } from \"../../types/blog.constants\";\n\ntype BlogPostTranslationEditorProps = {\n translation: BlogPostTranslationInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst isBodyEmpty = (body: any) => !body || (Array.isArray(body) && body.length === 0);\n\nconst buildFormSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string().uuid(),\n title: z.string().min(1, { message: t(\"blog.errors.title\") }),\n slug: z.string().optional(),\n summary: z.string().optional(),\n body: z.any(),\n coverImageAlt: z.string().optional(),\n });\n\n/**\n * Edits one non-original translation by hand. Saving marks it `manual` and\n * re-runs nothing. Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogPostTranslationEditor({ translation, trigger, onSuccess }: BlogPostTranslationEditorProps) {\n const t = useTranslations();\n const formSchema = useMemo(() => buildFormSchema(t), [t]);\n const languageName = useBlogLanguageName();\n const [isBodyEmptyState, setIsBodyEmptyState] = useState<boolean>(isBodyEmpty(translation.body));\n\n const getDefaultValues = useCallback(\n () => ({\n id: translation.id,\n title: translation.title ?? \"\",\n slug: translation.slug ?? \"\",\n summary: translation.summary ?? \"\",\n body: translation.body || [],\n coverImageAlt: translation.coverImageAlt ?? \"\",\n }),\n [translation],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n const { dirtyFields } = form.formState;\n const isFormDirty = useCallback(() => {\n const dirty: Record<string, unknown> = { ...dirtyFields };\n if (dirty.body && isBodyEmptyState) delete dirty.body;\n return Object.keys(dirty).length > 0;\n }, [dirtyFields, isBodyEmptyState]);\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.post\")}\n entityName={translation.title}\n title={`${t(\"blog.admin.editPost\")} · ${languageName(translation.language)}`}\n isEdit\n module={Modules.BlogPostTranslation}\n size=\"lg\"\n isFormDirty={isFormDirty}\n onSubmit={async (values) =>\n BlogPostTranslationService.update({\n id: values.id,\n title: values.title,\n slug: values.slug || undefined,\n summary: values.summary,\n body: values.body,\n coverImageAlt: values.coverImageAlt,\n })\n }\n onReset={() => {\n setIsBodyEmptyState(isBodyEmpty(translation.body));\n return getDefaultValues();\n }}\n onSuccess={onSuccess ?? (() => {})}\n trigger={trigger}\n >\n <div className=\"flex w-full flex-col gap-y-4\">\n <FormInput form={form} id=\"title\" name={t(\"blog.fields.title\")} isRequired />\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n <FormTextarea form={form} id=\"summary\" name={t(\"blog.fields.summary\")} />\n <FormInput form={form} id=\"coverImageAlt\" name={t(\"blog.fields.coverImageAlt\")} />\n <FormBlockNote\n uploadKeyPrefix={BLOG_POST_UPLOAD_PREFIX}\n form={form}\n id=\"body\"\n name={t(\"blog.fields.body\")}\n type=\"blog-post\"\n isRequired\n onEmptyChange={setIsBodyEmptyState}\n />\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { Badge, Link } from \"../../../../shadcnui\";\nimport type { PublicBlogCategory } from \"../../types/blog-public.types\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\n/**\n * Category filter: \"All\" plus one link per category. Every link keeps the\n * search query and drops the page, so a new filter starts from page one.\n * `chips` is the horizontal pill row; `list` is the vertical rail list.\n */\nexport function BlogCategoryChips(props: {\n categories: PublicBlogCategory[];\n basePath: string;\n activeCategorySlug?: string;\n query?: string;\n variant?: \"chips\" | \"list\";\n className?: string;\n}) {\n const { categories, basePath, activeCategorySlug, query, variant = \"chips\", className } = props;\n const t = useTranslations();\n\n const items = [\n {\n key: \"all\",\n label: t(\"blog.public.all\"),\n href: blogListHref(basePath, { q: query }),\n active: !activeCategorySlug,\n },\n ...categories.map((category) => ({\n key: category.id,\n label: category.name,\n href: blogListHref(basePath, { category: category.slug, q: query }),\n active: category.slug === activeCategorySlug,\n })),\n ];\n\n // Pills are Badges rendered as links: the active one is the filled default\n // variant, the rest are outline (chips row) or ghost (rail list).\n const inactiveVariant = variant === \"chips\" ? \"outline\" : \"ghost\";\n\n return (\n <nav aria-label={t(\"blog.entities.categories\")} className={className}>\n <ul className={variant === \"chips\" ? \"flex flex-wrap items-center gap-2\" : \"flex flex-col items-start gap-2\"}>\n {items.map((item) => (\n <li key={item.key}>\n <Badge\n variant={item.active ? \"default\" : inactiveVariant}\n render={<Link href={item.href} aria-current={item.active ? \"page\" : undefined} />}\n >\n {item.label}\n </Badge>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n","import type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\n\n/**\n * Locale-free href to the public list, keeping the active filters.\n * Parameter order is fixed (category, q, page) so links are stable.\n */\nexport function blogListHref(basePath: string, params: { category?: string; q?: string; page?: number }): string {\n const search = new URLSearchParams();\n if (params.category) search.set(\"category\", params.category);\n if (params.q) search.set(\"q\", params.q);\n if (params.page && params.page > 1) search.set(\"page\", String(params.page));\n const query = search.toString();\n return query ? `${basePath}?${query}` : basePath;\n}\n\nexport function blogPostHref(basePath: string, slug: string): string {\n return `${basePath}/${encodeURIComponent(slug)}`;\n}\n\n/** The first of the post's categories that the visitor's category list knows. */\nexport function resolvePostCategory(\n post: PublicBlogPost,\n categories: PublicBlogCategory[],\n): PublicBlogCategory | undefined {\n for (const id of post.categoryIds) {\n const category = categories.find((c) => c.id === id);\n if (category) return category;\n }\n return undefined;\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { ContentTitle } from \"../../../../components/navigations/ContentTitle\";\nimport { buttonVariants, Link } from \"../../../../shadcnui\";\nimport { cn } from \"../../../../utils/cn\";\nimport type { PublicBlogCategory, PublicBlogList } from \"../../types/blog-public.types\";\nimport type { BlogListLayout } from \"../../types/blog.constants\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\nexport type BlogListProps = {\n layout: BlogListLayout;\n list: PublicBlogList;\n categories: PublicBlogCategory[];\n activeCategorySlug?: string;\n query?: string;\n page: number;\n basePath: string;\n};\n\n/** Eyebrow, page title and (optionally) the subtitle. */\nexport function BlogListHeader(props: { align?: \"start\" | \"center\"; showSubtitle?: boolean }) {\n const { align = \"start\", showSubtitle = true } = props;\n const t = useTranslations();\n return (\n <header className={align === \"center\" ? \"text-center\" : undefined}>\n <ContentTitle\n type={t(\"blog.public.eyebrow\")}\n element={t(\"blog.public.title\")}\n headingLevel={1}\n className={cn(\"mb-0\", align === \"center\" && \"[&_div]:justify-center\")}\n />\n {showSubtitle ? (\n <p className=\"text-muted-foreground mt-3 text-sm leading-relaxed\">{t(\"blog.public.subtitle\")}</p>\n ) : null}\n </header>\n );\n}\n\n/** Shown in place of the posts: \"no results\" when searching, \"no posts yet\" otherwise. */\nexport function BlogListEmpty(props: { query?: string; className?: string }) {\n const t = useTranslations();\n return (\n <p className={cn(\"text-muted-foreground py-12 text-center text-sm\", props.className)}>\n {props.query ? t(\"blog.public.noResults\") : t(\"blog.public.empty\")}\n </p>\n );\n}\n\n/** \"Load more\": a plain link to the next page, keeping category and query. */\nexport function BlogLoadMore(props: {\n basePath: string;\n page: number;\n activeCategorySlug?: string;\n query?: string;\n className?: string;\n}) {\n const { basePath, page, activeCategorySlug, query, className } = props;\n const t = useTranslations();\n return (\n <div className={cn(\"flex justify-center\", className)}>\n {/* A styled Link, not <Button render={<Link />} nativeButton={false}>: Base UI then\n stamps role=\"button\" on the anchor, so a control that navigates would announce\n as a button (same reasoning as HelpHeader). */}\n <Link\n href={blogListHref(basePath, { category: activeCategorySlug, q: query, page: page + 1 })}\n scroll={false}\n className={buttonVariants({ variant: \"outline\" })}\n >\n {t(\"blog.public.loadMore\")}\n </Link>\n </div>\n );\n}\n","\"use client\";\n\nimport { useFormatter } from \"next-intl\";\nimport Image from \"next/image\";\nimport { MicroLabel, SectionHeader } from \"../../../../components/typography\";\nimport { Link } from \"../../../../shadcnui\";\nimport { cn } from \"../../../../utils/cn\";\nimport { isRemoteImageSrc } from \"../../../../utils/isRemoteImageSrc\";\nimport type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\nimport { blogPostHref, resolvePostCategory } from \"./blogPublicHelpers\";\nimport { BlogPostMeta } from \"./BlogPostMeta\";\n\n/**\n * Cover image in a fixed-ratio frame. With no image it renders a\n * token-coloured placeholder of the same footprint, so rows stay aligned.\n */\nexport function BlogPostCover(props: {\n src?: string;\n alt?: string;\n sizes: string;\n priority?: boolean;\n className?: string;\n}) {\n const { src, alt, sizes, priority, className } = props;\n return (\n <div className={cn(\"border-border bg-primary/10 relative overflow-hidden rounded-lg border\", className)}>\n {src ? (\n <Image\n src={src}\n alt={alt ?? \"\"}\n fill\n sizes={sizes}\n priority={priority}\n className=\"object-cover\"\n unoptimized={isRemoteImageSrc(src)}\n />\n ) : null}\n </div>\n );\n}\n\ntype BlogPostCardVariant = \"hero\" | \"large\" | \"card\" | \"row\";\n\n/**\n * One post in a public list.\n * - hero: image and text side by side, author and date (featured layout)\n * - large: image above text, author and date (rail layout)\n * - card: small grid card (featured grid, rail columns, related posts)\n * - row: image at the start, text at the end (column layout)\n * The title link is stretched over the whole card, so the card is one target\n * whose accessible name is the title alone.\n */\nexport function BlogPostCard(props: {\n post: PublicBlogPost;\n categories: PublicBlogCategory[];\n basePath: string;\n variant?: BlogPostCardVariant;\n priority?: boolean;\n headingLevel?: \"h2\" | \"h3\";\n}) {\n const { post, categories, basePath, variant = \"card\", priority, headingLevel = \"h2\" } = props;\n const format = useFormatter();\n const category = resolvePostCategory(post, categories);\n const isProminent = variant === \"hero\" || variant === \"large\";\n\n const eyebrow =\n variant === \"row\" && post.publishedAt\n ? [category?.name, format.dateTime(new Date(post.publishedAt), { day: \"numeric\", month: \"short\" })]\n .filter(Boolean)\n .join(\" · \")\n : category?.name;\n\n const text = (\n <div className=\"flex min-w-0 flex-col\">\n {eyebrow ? <MicroLabel as=\"span\">{eyebrow}</MicroLabel> : null}\n <SectionHeader level={headingLevel === \"h3\" ? 3 : 2} className=\"mt-1.5\">\n <Link href={blogPostHref(basePath, post.slug)} className=\"after:absolute after:inset-0\">\n {post.title}\n </Link>\n </SectionHeader>\n {post.summary ? <p className=\"text-muted-foreground mt-2 text-sm leading-relaxed\">{post.summary}</p> : null}\n <BlogPostMeta post={post} showAuthor={isProminent} showDate={isProminent} className=\"mt-3\" />\n </div>\n );\n\n if (variant === \"hero\") {\n return (\n <article className=\"group relative grid items-center gap-6 md:grid-cols-[1.4fr_1fr] md:gap-10\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 1024px) 560px, (min-width: 768px) 58vw, 100vw\"\n priority={priority}\n className=\"aspect-[16/10]\"\n />\n {text}\n </article>\n );\n }\n\n if (variant === \"row\") {\n return (\n <article className=\"group relative grid items-center gap-4 sm:grid-cols-[10rem_1fr] sm:gap-6\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 640px) 160px, 100vw\"\n priority={priority}\n className=\"aspect-[16/10]\"\n />\n {text}\n </article>\n );\n }\n\n return (\n <article className=\"group relative flex flex-col gap-4\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes={\n variant === \"large\"\n ? \"(min-width: 1024px) 760px, 100vw\"\n : \"(min-width: 1024px) 320px, (min-width: 640px) 50vw, 100vw\"\n }\n priority={priority}\n className={variant === \"large\" ? \"aspect-[16/8]\" : \"aspect-[16/10]\"}\n />\n {text}\n </article>\n );\n}\n","\"use client\";\n\nimport { useFormatter, useTranslations } from \"next-intl\";\nimport { cn } from \"../../../../utils/cn\";\nimport type { PublicBlogPost } from \"../../types/blog-public.types\";\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * \"author · date · updated · reading time\". Each part is opt-in so cards show\n * only the reading time while the hero and the post page show more.\n */\nexport function BlogPostMeta(props: {\n post: PublicBlogPost;\n showAuthor?: boolean;\n showDate?: boolean;\n showUpdated?: boolean;\n className?: string;\n}) {\n const { post, showAuthor, showDate, showUpdated, className } = props;\n const t = useTranslations();\n const format = useFormatter();\n\n const parts: string[] = [];\n if (showAuthor && post.authorName) parts.push(t(\"blog.public.by\", { author: post.authorName }));\n if (showDate && post.publishedAt) {\n parts.push(format.dateTime(new Date(post.publishedAt), { dateStyle: \"medium\" }));\n }\n if (\n showUpdated &&\n post.publishedAt &&\n post.updatedAt &&\n new Date(post.updatedAt).getTime() - new Date(post.publishedAt).getTime() > ONE_DAY_MS\n ) {\n parts.push(t(\"blog.public.updated\", { date: format.dateTime(new Date(post.updatedAt), { dateStyle: \"medium\" }) }));\n }\n parts.push(t(\"blog.public.readingTime\", { minutes: post.readingMinutes }));\n\n return (\n <p className={cn(\"text-muted-foreground text-xs\", className)}>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? \" · \" : null}\n {part}\n </span>\n ))}\n </p>\n );\n}\n","\"use client\";\n\nimport { SearchIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { FormEvent, useEffect, useState } from \"react\";\nimport { useI18nRouter } from \"../../../../i18n\";\nimport { InputGroup, InputGroupAddon, InputGroupInput } from \"../../../../shadcnui\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\n/**\n * Search form for the public list. The current query and category come in as\n * props (the page already parsed the search params), so this component never\n * reads `useSearchParams` and needs no Suspense boundary of its own.\n * Submitting keeps the category and resets to page one.\n */\nexport function BlogSearchBox(props: {\n basePath: string;\n query?: string;\n activeCategorySlug?: string;\n className?: string;\n}) {\n const { basePath, query, activeCategorySlug, className } = props;\n const t = useTranslations();\n const router = useI18nRouter();\n const [value, setValue] = useState(query ?? \"\");\n\n useEffect(() => setValue(query ?? \"\"), [query]);\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const q = value.trim();\n router.push(blogListHref(basePath, { category: activeCategorySlug, q: q || undefined }));\n };\n\n return (\n <form role=\"search\" onSubmit={onSubmit} className={className}>\n <InputGroup>\n <InputGroupAddon>\n <SearchIcon />\n </InputGroupAddon>\n <InputGroupInput\n type=\"search\"\n name=\"q\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n placeholder={t(\"blog.public.searchPlaceholder\")}\n aria-label={t(\"blog.public.search\")}\n />\n </InputGroup>\n </form>\n );\n}\n","\"use client\";\n\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout B: one narrow reading column, every post a row (image at the start, text at the end). */\nexport function BlogListColumn(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto max-w-2xl\">\n <BlogListHeader align=\"center\" />\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-8 [&>ul]:justify-center\"\n />\n <BlogSearchBox basePath={basePath} query={query} activeCategorySlug={activeCategorySlug} className=\"mt-4\" />\n {list.posts.length === 0 ? (\n <BlogListEmpty query={query} />\n ) : (\n <div className=\"divide-border mt-10 flex flex-col divide-y\">\n {list.posts.map((post, index) => (\n <div key={post.id} className=\"py-6 first:pt-0\">\n <BlogPostCard\n post={post}\n categories={categories}\n basePath={basePath}\n variant=\"row\"\n priority={index === 0}\n />\n </div>\n ))}\n </div>\n )}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-10\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout A: newest post as a hero, then filters, then a three-column grid. */\nexport function BlogListFeatured(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n const [hero, ...rest] = list.posts;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto max-w-5xl\">\n <BlogListHeader />\n {hero ? (\n <div className=\"mt-10\">\n <BlogPostCard post={hero} categories={categories} basePath={basePath} variant=\"hero\" priority />\n </div>\n ) : null}\n <div className=\"mt-12 flex flex-col gap-3 md:flex-row md:items-center\">\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"flex-1\"\n />\n <BlogSearchBox\n basePath={basePath}\n query={query}\n activeCategorySlug={activeCategorySlug}\n className=\"md:w-64\"\n />\n </div>\n {list.posts.length === 0 ? <BlogListEmpty query={query} /> : null}\n {rest.length > 0 ? (\n <div className=\"mt-8 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3\">\n {rest.map((post) => (\n <BlogPostCard key={post.id} post={post} categories={categories} basePath={basePath} />\n ))}\n </div>\n ) : null}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-12\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { MicroLabel } from \"../../../../components/typography\";\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout C: search and categories in a side rail; newest post large, the rest in two columns. */\nexport function BlogListRail(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n const t = useTranslations();\n const [first, ...rest] = list.posts;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto grid max-w-5xl gap-10 md:grid-cols-[13rem_1fr] md:gap-12\">\n <aside className=\"flex flex-col gap-6\">\n <BlogListHeader showSubtitle={false} />\n <BlogSearchBox basePath={basePath} query={query} activeCategorySlug={activeCategorySlug} />\n <div>\n <MicroLabel as=\"span\" className=\"block\">\n {t(\"blog.entities.categories\")}\n </MicroLabel>\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n variant=\"list\"\n className=\"mt-3\"\n />\n </div>\n </aside>\n <div className=\"min-w-0\">\n {first ? (\n <BlogPostCard post={first} categories={categories} basePath={basePath} variant=\"large\" priority />\n ) : (\n <BlogListEmpty query={query} />\n )}\n {rest.length > 0 ? (\n <div className=\"border-border mt-10 grid gap-x-6 gap-y-10 border-t pt-10 sm:grid-cols-2\">\n {rest.map((post) => (\n <BlogPostCard key={post.id} post={post} categories={categories} basePath={basePath} />\n ))}\n </div>\n ) : null}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-12\"\n />\n ) : null}\n </div>\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { BlogListColumn } from \"./BlogListColumn\";\nimport { BlogListFeatured } from \"./BlogListFeatured\";\nimport type { BlogListProps } from \"./BlogListParts\";\nimport { BlogListRail } from \"./BlogListRail\";\n\n/** Public blog list in the layout an administrator picked (BlogSettings.listLayout). */\nexport function BlogList(props: BlogListProps) {\n switch (props.layout) {\n case \"column\":\n return <BlogListColumn {...props} />;\n case \"rail\":\n return <BlogListRail {...props} />;\n case \"featured\":\n default:\n return <BlogListFeatured {...props} />;\n }\n}\n\nexport type { BlogListProps };\n","\"use client\";\n\nimport { ArrowLeftIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { BlockNoteEditorContainer } from \"../../../../components/editors/BlockNoteEditorContainer\";\nimport { ContentTitle } from \"../../../../components/navigations/ContentTitle\";\nimport { SectionHeader } from \"../../../../components/typography\";\nimport { Link } from \"../../../../shadcnui\";\nimport type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\nimport { resolvePostCategory } from \"./blogPublicHelpers\";\nimport { BlogPostCard, BlogPostCover } from \"./BlogPostCard\";\nimport { BlogPostMeta } from \"./BlogPostMeta\";\n\n/**\n * Public post page: back link, category, title, summary, meta, cover, body,\n * then up to three posts from the same category.\n */\nexport function BlogPostView(props: {\n post: PublicBlogPost;\n categories: PublicBlogCategory[];\n related: PublicBlogPost[];\n basePath: string;\n}) {\n const { post, categories, related, basePath } = props;\n const t = useTranslations();\n const category = resolvePostCategory(post, categories);\n const more = related.slice(0, 3);\n\n return (\n <article className=\"px-4 pt-12 pb-20 md:pt-16\">\n <div className=\"mx-auto max-w-3xl\">\n <Link href={basePath} className=\"inline-flex items-center gap-1.5\">\n <ArrowLeftIcon className=\"size-4 rtl:rotate-180\" />\n {t(\"blog.public.back\")}\n </Link>\n <header className=\"mt-8\">\n <ContentTitle type={category?.name} element={post.title} headingLevel={1} className=\"mb-0\" />\n {post.summary ? <p className=\"text-muted-foreground mt-3 text-sm leading-relaxed\">{post.summary}</p> : null}\n <BlogPostMeta post={post} showAuthor showDate showUpdated className=\"mt-4\" />\n </header>\n {post.coverImage ? (\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 768px) 768px, 100vw\"\n priority\n className=\"mt-8 aspect-[16/9]\"\n />\n ) : null}\n <div className=\"mt-8\">\n <BlockNoteEditorContainer id={post.id} type=\"blog-post\" initialContent={post.body} />\n </div>\n </div>\n {more.length > 0 ? (\n <section className=\"mx-auto mt-16 max-w-5xl\">\n <div className=\"border-border border-t pt-10\">\n <SectionHeader level={2}>{t(\"blog.public.moreIn\", { category: category?.name ?? \"\" })}</SectionHeader>\n <div className=\"mt-6 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3\">\n {more.map((relatedPost) => (\n <BlogPostCard\n key={relatedPost.id}\n post={relatedPost}\n categories={categories}\n basePath={basePath}\n headingLevel=\"h3\"\n />\n ))}\n </div>\n </div>\n </section>\n ) : null}\n </article>\n );\n}\n"],"mappings":"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","names":["useTranslations","useTranslations","useTranslations","useMemo","useTranslations","useCallback","useMemo","useState","useMemo","useState","jsx","jsxs","useMemo","useState","jsx","jsxs","useTranslations","useMemo","useState","useCallback","jsx","jsxs","useTranslations","useMemo","jsx","useTranslations","useTranslations","useEffect","useState","jsx","jsxs","useTranslations","useState","useEffect","useTranslations","useTranslations","useMemo","useCallback","useEffect","useState","useState","useEffect","useCallback","jsx","jsxs","useTranslations","useMemo","zodResolver","useTranslations","useCallback","useMemo","useState","useForm","v4","z","jsx","jsxs","useTranslations","useState","useMemo","z","useCallback","v4","useForm","zodResolver","jsx","useTranslations","jsx","useTranslations","useTranslations","useMemo","useCallback","useEffect","useMemo","useState","jsx","useState","useCallback","useEffect","useMemo","useTranslations","jsx","useTranslations","useTranslations","useState","jsx","jsxs","useTranslations","useState","useTranslations","Image","useState","useTranslations","jsx","jsxs","useTranslations","zodResolver","useTranslations","useCallback","useMemo","useState","useForm","z","jsx","jsxs","isBodyEmpty","buildFormSchema","z","useTranslations","useMemo","useState","useCallback","useForm","zodResolver","jsx","jsxs","useTranslations","useState","Image","jsx","useTranslations","useMemo","useTranslations","jsx","useTranslations","useTranslations","jsx","jsxs","useTranslations","useFormatter","Image","useTranslations","jsx","jsxs","useTranslations","jsx","jsxs","Image","useFormatter","useTranslations","useEffect","useState","jsx","jsxs","useTranslations","useState","useEffect","jsx","jsxs","jsx","jsxs","useTranslations","jsx","jsxs","useTranslations","jsx","useTranslations","jsx","jsxs","useTranslations"]}
1
+ {"version":3,"sources":["../../../src/features/blog/components/admin/BlogAdminContainer.tsx","../../../src/features/blog/components/admin/BlogCategoriesList.tsx","../../../src/features/blog/hooks/useBlogCategoryTableStructure.tsx","../../../src/features/blog/components/admin/BlogCategoryEditor.tsx","../../../src/features/blog/components/admin/BlogCoverImageField.tsx","../../../src/features/blog/components/admin/BlogTranslationStatus.tsx","../../../src/features/blog/components/admin/BlogLayoutSetting.tsx","../../../src/features/blog/components/admin/BlogPostsList.tsx","../../../src/features/blog/hooks/useBlogPostTableStructure.tsx","../../../src/features/blog/hooks/useBlogCategoryNames.ts","../../../src/features/blog/components/admin/BlogPostEditor.tsx","../../../src/features/blog/components/admin/BlogPostContainer.tsx","../../../src/features/blog/contexts/BlogPostContext.tsx","../../../src/features/blog/components/admin/BlogPostDeleter.tsx","../../../src/features/blog/components/admin/BlogPostPublishButton.tsx","../../../src/features/blog/components/admin/BlogPostTranslationPanel.tsx","../../../src/features/blog/components/admin/BlogPostComparison.tsx","../../../src/features/blog/components/admin/BlogPostTranslationEditor.tsx","../../../src/features/blog/components/public/BlogCategoryChips.tsx","../../../src/features/blog/components/public/blogPublicHelpers.ts","../../../src/features/blog/components/public/BlogListParts.tsx","../../../src/features/blog/components/public/BlogPostCard.tsx","../../../src/features/blog/components/public/BlogPostMeta.tsx","../../../src/features/blog/components/public/BlogSearchBox.tsx","../../../src/features/blog/components/public/BlogListColumn.tsx","../../../src/features/blog/components/public/BlogListFeatured.tsx","../../../src/features/blog/components/public/BlogListRail.tsx","../../../src/features/blog/components/public/BlogList.tsx","../../../src/features/blog/components/public/BlogPostView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { RoundPageContainer } from \"../../../../components\";\nimport { SharedProvider } from \"../../../../contexts/SharedContext\";\nimport { Modules } from \"../../../../core\";\nimport { BreadcrumbItemData } from \"../../../../interfaces\";\nimport { BlogCategoriesList } from \"./BlogCategoriesList\";\nimport { BlogLayoutSetting } from \"./BlogLayoutSetting\";\nimport { BlogPostsList } from \"./BlogPostsList\";\n\n/**\n * Blog admin page: posts, categories and the public list layout, one tab each.\n * Client component because `module={Modules.BlogPost}` carries an icon component.\n * Mirrors features/email-automation/components/containers/EmailAutomationsListContainer.tsx.\n *\n * Publishes its own breadcrumb, as CompanyProvider does for the companies list:\n * the list root comes first and has no href because it is the current page.\n * The lists inside the tabs omit `fullWidth` so they keep their bordered card,\n * as PricesList does inside ProductContainer.\n */\nexport function BlogAdminContainer() {\n const t = useTranslations();\n\n const breadcrumbs: BreadcrumbItemData[] = [{ name: t(\"blog.admin.title\") }];\n\n return (\n <SharedProvider value={{ breadcrumbs, title: { type: t(\"blog.admin.title\") } }}>\n <RoundPageContainer\n module={Modules.BlogPost}\n tabs={[\n { sectionKey: \"posts\", label: t(\"blog.tabs.posts\"), content: <BlogPostsList /> },\n { sectionKey: \"categories\", label: t(\"blog.tabs.categories\"), content: <BlogCategoriesList /> },\n { sectionKey: \"layout\", label: t(\"blog.tabs.layout\"), content: <BlogLayoutSetting /> },\n ]}\n />\n </SharedProvider>\n );\n}\n","\"use client\";\n\nimport { TagIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode } from \"react\";\nimport { ContentListTable } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { DataListRetriever, useDataListRetriever } from \"../../../../hooks\";\nimport { Button, EmptyState } from \"../../../../shadcnui\";\nimport { BlogCategoryFields, BlogCategoryInterface, BlogCategoryService } from \"../../data\";\nimport \"../../hooks/useBlogCategoryTableStructure\";\nimport { BlogCategoryEditor } from \"./BlogCategoryEditor\";\n\ntype BlogCategoriesListProps = {\n /** Pass only when the list is the page's whole content inside a `RoundPageContainer fullWidth`; omit it in a tab so the list keeps its bordered card. */\n fullWidth?: boolean;\n};\n\n/**\n * Admin categories table. Categories have no page of their own: creating and\n * editing both stay on this tab and refresh the list.\n * Mirrors features/email-automation/components/lists/EmailAutomationsList.tsx.\n */\nexport function BlogCategoriesList({ fullWidth }: BlogCategoriesListProps = {}) {\n const t = useTranslations();\n\n const data: DataListRetriever<BlogCategoryInterface> = useDataListRetriever({\n retriever: (params) => BlogCategoryService.findMany(params),\n retrieverParams: {},\n module: Modules.BlogCategory,\n });\n\n const functions: ReactNode[] = [\n <BlogCategoryEditor\n key=\"create-blog-category\"\n onSuccess={data.refresh}\n trigger={\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n {t(\"blog.admin.newCategory\")}\n </Button>\n }\n />,\n ];\n\n return (\n <ContentListTable\n data={data}\n fields={[BlogCategoryFields.name, BlogCategoryFields.languages]}\n tableGeneratorType={Modules.BlogCategory}\n functions={functions}\n fullWidth={fullWidth}\n title={t(\"blog.entities.categories\")}\n emptyState={<EmptyState icon={TagIcon} title={t(\"blog.admin.emptyCategories\")} />}\n />\n );\n}\n","\"use client\";\n\nimport { ColumnDef } from \"@tanstack/react-table\";\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { registerTableGenerator, TableContent, UseTableStructureHook } from \"../../../hooks\";\nimport { BlogCategoryEditor } from \"../components/admin/BlogCategoryEditor\";\nimport { blogCategoryAdminName } from \"../components/admin/BlogTranslationStatus\";\nimport { BlogCategoryFields, BlogCategoryInterface } from \"../data\";\n\n/**\n * Admin categories table: the name (original language) opens BlogCategoryEditor\n * for that row; \"languages ready\" counts completed translations. Categories have\n * no page of their own. Mirrors hooks/useEmailAutomationTableStructure.tsx in\n * features/email-automation.\n */\nexport const useBlogCategoryTableStructure: UseTableStructureHook<BlogCategoryInterface, BlogCategoryFields> = (\n params,\n) => {\n const t = useTranslations();\n const refresh = params.dataRetriever?.refresh;\n\n const tableData = useMemo(() => {\n return params.data.map((category: BlogCategoryInterface) => {\n const entry: TableContent<BlogCategoryInterface> = { jsonApiData: category };\n params.fields.forEach((field) => {\n entry[field] = (category as any)[field as keyof BlogCategoryInterface];\n });\n return entry;\n });\n }, [params.data, params.fields]);\n\n const fieldColumnMap: Partial<Record<BlogCategoryFields, () => any>> = {\n [BlogCategoryFields.name]: () => ({\n id: BlogCategoryFields.name,\n accessorKey: BlogCategoryFields.name,\n header: t(\"blog.fields.name\"),\n cell: ({ row }: { row: TableContent<BlogCategoryInterface> }) => {\n const category: BlogCategoryInterface = row.original.jsonApiData;\n return (\n <BlogCategoryEditor\n category={category}\n onSuccess={refresh}\n trigger={\n <button type=\"button\" className=\"text-primary text-start font-medium\">\n {blogCategoryAdminName(category) || category.id}\n </button>\n }\n />\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogCategoryFields.languages]: () => ({\n id: BlogCategoryFields.languages,\n accessorKey: BlogCategoryFields.languages,\n header: t(\"blog.fields.languages\"),\n cell: ({ row }: { row: TableContent<BlogCategoryInterface> }) => {\n const category: BlogCategoryInterface = row.original.jsonApiData;\n const total = category.translations.length;\n if (total === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n const ready = category.translations.filter((translation) => translation.aiStatus === \"completed\").length;\n return (\n <span className=\"block text-xs tabular-nums text-end\">\n {ready}/{total}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n };\n\n const columns = useMemo(() => {\n return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<\n TableContent<BlogCategoryInterface>\n >[];\n }, [params.fields, fieldColumnMap, t, refresh]);\n\n return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);\n};\n\nregisterTableGenerator(\"blog-categories\", useBlogCategoryTableStructure);\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { v4 } from \"uuid\";\nimport { z } from \"zod\";\nimport { EditorSheet, FormInput, FormSelect, FormTextarea, SectionHeader } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { getI18nSupportedLocales, useI18nLocale } from \"../../../../i18n\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"../../../../shadcnui\";\nimport {\n BlogCategoryInterface,\n BlogCategoryService,\n BlogCategoryTranslationInterface,\n BlogCategoryTranslationService,\n} from \"../../data\";\nimport { BlogCoverImageField, BlogImageSelection, uploadBlogImage } from \"./BlogCoverImageField\";\nimport { BlogTranslationStatus, useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_CATEGORY_UPLOAD_PREFIX } from \"../../types/blog.constants\";\n\ntype BlogCategoryEditorProps = {\n /** Edit mode when present. */\n category?: BlogCategoryInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst buildTranslationSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string(),\n name: z.string().min(1, { message: t(\"blog.errors.name\") }),\n slug: z.string().optional(),\n description: z.string().optional(),\n });\n\nconst buildFormSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string().uuid(),\n originalLanguage: z.string().min(1, { message: t(\"blog.errors.language\") }),\n name: z.string().min(1, { message: t(\"blog.errors.name\") }),\n slug: z.string().optional(),\n description: z.string().optional(),\n image: z.string().optional(),\n translations: z.array(buildTranslationSchema(t)),\n });\n\ntype TranslationValues = z.infer<ReturnType<typeof buildTranslationSchema>>;\n\nconst sameTranslation = (a: TranslationValues, b?: TranslationValues) =>\n !!b && a.name === b.name && (a.slug ?? \"\") === (b.slug ?? \"\") && (a.description ?? \"\") === (b.description ?? \"\");\n\n/**\n * Creates a category, or edits its original-language text and image plus, in\n * edit mode, each AI translation by hand (one collapsible section per language).\n * Submit updates the category when the original changed (which re-translates\n * every other language) and each translation that changed (marked `manual`).\n * Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogCategoryEditor({ category, trigger, onSuccess }: BlogCategoryEditorProps) {\n const t = useTranslations();\n const formSchema = useMemo(() => buildFormSchema(t), [t]);\n const locale = useI18nLocale();\n const languageName = useBlogLanguageName();\n const [image, setImage] = useState<BlogImageSelection>(null);\n\n const languages = useMemo(() => getI18nSupportedLocales(), []);\n const original = category?.original;\n const translations: BlogCategoryTranslationInterface[] = useMemo(\n () => (category?.translations ?? []).filter((translation) => !translation.isOriginal),\n [category],\n );\n\n const getDefaultValues = useCallback(\n () => ({\n id: category?.id || v4(),\n originalLanguage: category?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),\n name: original?.name ?? category?.name ?? \"\",\n slug: original?.slug ?? \"\",\n description: original?.description ?? \"\",\n // The stored value is a signed URL, not a key: only a newly picked file writes a key.\n image: \"\",\n translations: translations.map((translation) => ({\n id: translation.id,\n name: translation.name ?? \"\",\n slug: translation.slug ?? \"\",\n description: translation.description ?? \"\",\n })),\n }),\n [category, original, translations, languages, locale],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.category\")}\n entityName={original?.name ?? category?.name}\n title={category ? t(\"blog.admin.editCategory\") : t(\"blog.admin.newCategory\")}\n isEdit={!!category}\n module={Modules.BlogCategory}\n size=\"md\"\n onSubmit={async (values) => {\n if (values.image && image) await uploadBlogImage({ key: values.image, selection: image });\n const imageKey = values.image && image ? values.image : undefined;\n\n if (!category) {\n return BlogCategoryService.create({\n id: values.id,\n originalLanguage: values.originalLanguage,\n name: values.name,\n description: values.description,\n image: imageKey,\n });\n }\n\n const defaults = getDefaultValues();\n const originalChanged =\n !!imageKey ||\n !sameTranslation(\n { id: values.id, name: values.name, slug: values.slug, description: values.description },\n { id: defaults.id, name: defaults.name, slug: defaults.slug, description: defaults.description },\n );\n\n if (originalChanged) {\n await BlogCategoryService.update({\n id: values.id,\n originalLanguage: category?.originalLanguage,\n name: values.name,\n slug: values.slug || undefined,\n description: values.description,\n image: imageKey,\n });\n }\n\n for (const translation of values.translations) {\n const before = defaults.translations.find((candidate) => candidate.id === translation.id);\n if (sameTranslation(translation, before)) continue;\n await BlogCategoryTranslationService.update({\n id: translation.id,\n name: translation.name,\n slug: translation.slug || undefined,\n description: translation.description,\n });\n }\n\n return { id: values.id };\n }}\n onReset={() => {\n setImage(null);\n return getDefaultValues();\n }}\n onSuccess={onSuccess ?? (() => {})}\n trigger={trigger}\n >\n <div className=\"flex w-full flex-col gap-y-4\">\n {!category && (\n <FormSelect\n form={form}\n id=\"originalLanguage\"\n name={t(\"blog.fields.originalLanguage\")}\n values={languages.map((language) => ({ id: language, text: languageName(language) }))}\n isRequired\n />\n )}\n <FormInput form={form} id=\"name\" name={t(\"blog.fields.name\")} isRequired />\n {category && (\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n )}\n <FormTextarea form={form} id=\"description\" name={t(\"blog.fields.description\")} />\n <BlogCoverImageField\n form={form}\n id=\"image\"\n label={t(\"blog.fields.image\")}\n keyPrefix={BLOG_CATEGORY_UPLOAD_PREFIX}\n entityId={form.getValues(\"id\")}\n currentUrl={category?.image}\n selection={image}\n onSelectionChange={setImage}\n />\n\n {translations.length > 0 && (\n <div className=\"flex flex-col gap-y-2\">\n <SectionHeader className=\"mt-2\">{t(\"blog.fields.languages\")}</SectionHeader>\n {translations.map((translation, index) => (\n <Collapsible key={translation.id} className=\"rounded-md border\">\n <CollapsibleTrigger className=\"flex w-full items-center justify-between gap-x-2 px-3 py-2 text-start\">\n <span className=\"flex items-center gap-x-2 text-sm font-medium\">\n {languageName(translation.language)}\n <BlogTranslationStatus aiStatus={translation.aiStatus} />\n </span>\n <ChevronDownIcon className=\"text-muted-foreground size-4\" />\n </CollapsibleTrigger>\n <CollapsibleContent className=\"flex flex-col gap-y-4 border-t p-3\">\n {translation.aiStatus === \"failed\" && (\n <BlogTranslationStatus\n aiStatus={translation.aiStatus}\n onRetranslate={async () => {\n await BlogCategoryTranslationService.retranslate({ id: translation.id });\n await onSuccess?.();\n }}\n />\n )}\n <FormInput form={form} id={`translations.${index}.name`} name={t(\"blog.fields.name\")} isRequired />\n <FormInput\n form={form}\n id={`translations.${index}.slug`}\n name={t(\"blog.fields.slug\")}\n description={t(\"blog.fields.slugHelp\")}\n />\n <FormTextarea\n form={form}\n id={`translations.${index}.description`}\n name={t(\"blog.fields.description\")}\n />\n </CollapsibleContent>\n </Collapsible>\n ))}\n </div>\n )}\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { UploadIcon } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { DropzoneOptions } from \"react-dropzone\";\nimport { FileInput, FileUploader, FormFieldWrapper } from \"../../../../components\";\nimport { isRemoteImageSrc } from \"../../../../utils\";\nimport { S3Service } from \"../../../s3/data/s3.service\";\n\n/** A picked image waiting to be uploaded: the S3 key is already in the form field. */\nexport type BlogImageSelection = { file: File; contentType: string } | null;\n\nconst dropzone = {\n multiple: false,\n maxSize: 100 * 1024 * 1024,\n preventDropOnDocument: false,\n accept: {\n \"application/images\": [\".jpg\", \".jpeg\", \".png\", \".gif\", \".webp\", \".svg\"],\n },\n} satisfies DropzoneOptions;\n\n/** Uploads a picked image to its presigned S3 key. */\nexport async function uploadBlogImage(params: { key: string; selection: BlogImageSelection }): Promise<void> {\n if (!params.selection || !params.key) return;\n\n await S3Service.upload({\n key: params.key,\n contentType: params.selection.contentType,\n file: params.selection.file,\n isPublic: true,\n });\n}\n\ntype BlogCoverImageFieldProps = {\n form: any;\n /** The form field that receives the new S3 key (\"coverImage\" or \"image\"). */\n id: string;\n label: string;\n /** BLOG_POST_UPLOAD_PREFIX or BLOG_CATEGORY_UPLOAD_PREFIX. */\n keyPrefix: string;\n /** The id of the post or category the image belongs to. */\n entityId: string;\n /** The signed URL of the image already stored, for the preview. */\n currentUrl?: string;\n alt?: string;\n selection: BlogImageSelection;\n onSelectionChange: (selection: BlogImageSelection) => void;\n};\n\n/**\n * Dropzone for a post cover or a category image. Picking a file writes its S3\n * key (`<keyPrefix>/<id>/<id>.<timestamp>.<ext>`, under the platform blog root,\n * see BLOG_UPLOAD_ROOT) into the form field and hands\n * the file to the editor, which uploads it with `uploadBlogImage` on submit.\n */\nexport function BlogCoverImageField({\n form,\n id,\n label,\n keyPrefix,\n entityId,\n currentUrl,\n alt,\n selection,\n onSelectionChange,\n}: BlogCoverImageFieldProps) {\n const [files, setFiles] = useState<File[] | null>(null);\n\n useEffect(() => {\n if (selection === null) setFiles(null);\n }, [selection]);\n\n const previewUrl = useMemo(() => (selection ? URL.createObjectURL(selection.file) : undefined), [selection]);\n\n useEffect(() => {\n return () => {\n if (previewUrl) URL.revokeObjectURL(previewUrl);\n };\n }, [previewUrl]);\n\n const src = previewUrl ?? currentUrl;\n\n return (\n <FormFieldWrapper form={form} name={id} label={label}>\n {(field) => (\n <FileUploader\n value={files}\n onValueChange={(value: File[] | null) => {\n setFiles(value);\n const file = value?.[0];\n if (!file) return;\n const extension = file.type.split(\"/\").pop() ?? \"\";\n field.onChange(`${keyPrefix}/${entityId}/${entityId}.${Date.now()}.${extension}`);\n onSelectionChange({ file, contentType: file.type });\n }}\n dropzoneOptions={dropzone}\n className=\"w-full\"\n >\n <FileInput className=\"text-muted-foreground/50 rounded-lg outline-1 outline-dashed\">\n <div className=\"flex w-full flex-col items-center justify-center p-4\">\n {src ? (\n <Image\n src={src}\n alt={alt ?? label}\n width={320}\n height={180}\n className=\"h-auto max-h-48 w-auto max-w-full rounded-md object-cover\"\n unoptimized={isRemoteImageSrc(previewUrl ? undefined : currentUrl)}\n />\n ) : (\n <>\n <UploadIcon className=\"my-4 h-8 w-8\" />\n <span className=\"text-muted-foreground text-xs\">{label}</span>\n </>\n )}\n </div>\n </FileInput>\n </FileUploader>\n )}\n </FormFieldWrapper>\n );\n}\n","\"use client\";\n\nimport { LoaderCircleIcon, RotateCcwIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { errorToast } from \"../../../../components\";\nimport { useI18nLocale } from \"../../../../i18n\";\nimport { Badge, Button } from \"../../../../shadcnui\";\nimport { BlogCategoryInterface } from \"../../data\";\nimport { BlogAiStatus } from \"../../types/blog.constants\";\n\n/** Status pill colours for a post, shared by the post header and the admin posts table. */\nexport const BLOG_STATUS_BADGE = {\n draft: \"softGray\",\n published: \"softGreen\",\n unpublished: \"softAmber\",\n} as const;\n\n/** The category label in the admin: the category's own original-language name. */\nexport function blogCategoryAdminName(category: BlogCategoryInterface): string {\n return category.original?.name ?? category.name ?? \"\";\n}\n\n/**\n * Language names come from `Intl.DisplayNames` in the viewer's locale: there is\n * no per-language message key. An unknown code falls back to the code itself.\n */\nexport function useBlogLanguageName(): (language: string) => string {\n const locale = useI18nLocale();\n const names = useMemo(() => {\n try {\n return new Intl.DisplayNames([locale], { type: \"language\" });\n } catch {\n return undefined;\n }\n }, [locale]);\n\n return useCallback(\n (language: string) => {\n const name = names?.of(language);\n if (!name) return language;\n return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);\n },\n [names, locale],\n );\n}\n\n/** Formats a date for the admin pages in the viewer's locale. */\nexport function useBlogDateFormatter(): (date?: Date) => string {\n const locale = useI18nLocale();\n return useCallback((date?: Date) => (date ? date.toLocaleDateString(locale, { dateStyle: \"medium\" }) : \"\"), [locale]);\n}\n\ntype BlogTranslationStatusProps = {\n aiStatus: BlogAiStatus;\n /** Re-queues the translation of this one language. Shown only when the job failed. */\n onRetranslate?: () => Promise<void>;\n};\n\n/**\n * The translation state of one language. Nothing is rendered once the\n * translation is `completed`; `failed` adds the \"Translate again\" action.\n */\nexport function BlogTranslationStatus({ aiStatus, onRetranslate }: BlogTranslationStatusProps) {\n const t = useTranslations();\n const [isRetrying, setIsRetrying] = useState<boolean>(false);\n\n if (aiStatus === \"completed\") return null;\n\n if (aiStatus === \"pending\" || aiStatus === \"in_progress\") {\n return (\n <div className=\"flex items-center gap-x-2\">\n <Badge variant=\"softBlue\">\n <LoaderCircleIcon className=\"animate-spin\" />\n {aiStatus === \"pending\" ? t(\"blog.translation.pending\") : t(\"blog.translation.translating\")}\n </Badge>\n </div>\n );\n }\n\n const retranslate = async () => {\n if (!onRetranslate) return;\n setIsRetrying(true);\n try {\n await onRetranslate();\n } catch (error) {\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setIsRetrying(false);\n }\n };\n\n return (\n <div className=\"flex items-center gap-x-2\">\n <Badge variant=\"softRed\">{t(\"blog.translation.failed\")}</Badge>\n {onRetranslate && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => void retranslate()} disabled={isRetrying}>\n <RotateCcwIcon />\n {t(\"blog.actions.translateAgain\")}\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { useEffect, useState } from \"react\";\nimport { errorToast, SectionHeader } from \"../../../../components\";\nimport { RadioGroup, RadioGroupItem } from \"../../../../shadcnui\";\nimport { showToast } from \"../../../../utils/toast\";\nimport { BlogSettingsService } from \"../../data\";\nimport { BLOG_LIST_LAYOUTS, BLOG_SETTINGS_ID, BlogListLayout } from \"../../types/blog.constants\";\n\n/**\n * Picks the public list layout (featured / column / rail). The choice is saved\n * at once and the public blog uses it within the cache window.\n */\nexport function BlogLayoutSetting() {\n const t = useTranslations();\n const [layout, setLayout] = useState<BlogListLayout | undefined>(undefined);\n const [saving, setSaving] = useState<boolean>(false);\n\n useEffect(() => {\n let active = true;\n BlogSettingsService.get()\n .then((settings) => {\n if (active) setLayout(settings?.listLayout ?? \"featured\");\n })\n .catch(() => {\n if (active) setLayout(\"featured\");\n });\n return () => {\n active = false;\n };\n }, []);\n\n const options: Record<BlogListLayout, { label: string; description: string }> = {\n featured: { label: t(\"blog.layout.featured\"), description: t(\"blog.layout.featuredDescription\") },\n column: { label: t(\"blog.layout.column\"), description: t(\"blog.layout.columnDescription\") },\n rail: { label: t(\"blog.layout.rail\"), description: t(\"blog.layout.railDescription\") },\n };\n\n const save = async (next: BlogListLayout) => {\n const previous = layout;\n setLayout(next);\n setSaving(true);\n try {\n await BlogSettingsService.save({ id: BLOG_SETTINGS_ID, listLayout: next });\n showToast(t(\"blog.layout.saved\"));\n } catch (error) {\n setLayout(previous);\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"flex w-full max-w-3xl flex-col gap-y-4 p-4\">\n <SectionHeader>{t(\"blog.layout.title\")}</SectionHeader>\n {layout && (\n <RadioGroup\n value={layout}\n onValueChange={(value) => {\n // Base UI can emit a null value; ignore it and an unchanged choice.\n if (!value || value === layout) return;\n void save(value as BlogListLayout);\n }}\n disabled={saving}\n className=\"grid grid-cols-1 gap-3 md:grid-cols-3\"\n >\n {BLOG_LIST_LAYOUTS.map((option) => (\n <label\n key={option}\n htmlFor={`blog-layout-${option}`}\n className=\"has-data-checked:border-primary flex cursor-pointer items-start gap-x-3 rounded-lg border p-4\"\n >\n <RadioGroupItem value={option} id={`blog-layout-${option}`} className=\"mt-0.5\" />\n <span className=\"flex flex-col gap-y-1\">\n <span className=\"text-sm font-medium\">{options[option].label}</span>\n <span className=\"text-muted-foreground text-xs/relaxed\">{options[option].description}</span>\n </span>\n </label>\n ))}\n </RadioGroup>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { NewspaperIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode } from \"react\";\nimport { ContentListTable } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { DataListRetriever, useDataListRetriever } from \"../../../../hooks\";\nimport { Button, EmptyState } from \"../../../../shadcnui\";\nimport { BlogPostFields, BlogPostInterface, BlogPostService } from \"../../data\";\nimport \"../../hooks/useBlogPostTableStructure\";\nimport { BlogPostEditor } from \"./BlogPostEditor\";\n\ntype BlogPostsListProps = {\n /** Pass only when the list is the page's whole content inside a `RoundPageContainer fullWidth`; omit it in a tab so the list keeps its bordered card. */\n fullWidth?: boolean;\n};\n\n/** Admin posts table. Mirrors features/email-automation/components/lists/EmailAutomationsList.tsx. */\nexport function BlogPostsList({ fullWidth }: BlogPostsListProps = {}) {\n const t = useTranslations();\n\n const data: DataListRetriever<BlogPostInterface> = useDataListRetriever({\n retriever: (params) => BlogPostService.findMany(params),\n retrieverParams: {},\n module: Modules.BlogPost,\n });\n\n // Creating navigates to the new post's page (EditorSheet's default without onSuccess).\n const functions: ReactNode[] = [\n <BlogPostEditor\n key=\"create-blog-post\"\n trigger={\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n {t(\"blog.admin.newPost\")}\n </Button>\n }\n />,\n ];\n\n return (\n <ContentListTable\n data={data}\n fields={[\n BlogPostFields.title,\n BlogPostFields.status,\n BlogPostFields.categories,\n BlogPostFields.publishedAt,\n BlogPostFields.languages,\n ]}\n tableGeneratorType={Modules.BlogPost}\n functions={functions}\n fullWidth={fullWidth}\n title={t(\"blog.entities.posts\")}\n emptyState={<EmptyState icon={NewspaperIcon} title={t(\"blog.admin.empty\")} />}\n />\n );\n}\n","\"use client\";\n\nimport { ColumnDef } from \"@tanstack/react-table\";\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { Modules } from \"../../../core\";\nimport { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from \"../../../hooks\";\nimport { useI18nLocale } from \"../../../i18n\";\nimport { Badge, Link } from \"../../../shadcnui\";\nimport { BLOG_STATUS_BADGE } from \"../components/admin/BlogTranslationStatus\";\nimport { useBlogCategoryNames } from \"./useBlogCategoryNames\";\nimport { BlogPostFields, BlogPostInterface } from \"../data\";\n\n/**\n * Admin posts table: title (original language, links to the post page),\n * status, categories, published date and \"languages ready\" (completed\n * translations / all translations). Mirrors hooks/useEmailAutomationTableStructure.tsx\n * in features/email-automation.\n */\nexport const useBlogPostTableStructure: UseTableStructureHook<BlogPostInterface, BlogPostFields> = (params) => {\n const t = useTranslations();\n const locale = useI18nLocale();\n const generateUrl = usePageUrlGenerator();\n const categoryName = useBlogCategoryNames();\n\n const tableData = useMemo(() => {\n return params.data.map((post: BlogPostInterface) => {\n const entry: TableContent<BlogPostInterface> = { jsonApiData: post };\n params.fields.forEach((field) => {\n entry[field] = (post as any)[field as keyof BlogPostInterface];\n });\n return entry;\n });\n }, [params.data, params.fields]);\n\n const fieldColumnMap: Partial<Record<BlogPostFields, () => any>> = {\n [BlogPostFields.title]: () => ({\n id: BlogPostFields.title,\n accessorKey: BlogPostFields.title,\n header: t(\"blog.fields.title\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n return (\n <Link href={generateUrl({ page: Modules.BlogPost, id: post.id })}>\n {post.original?.title ?? post.title ?? post.id}\n </Link>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.status]: () => ({\n id: BlogPostFields.status,\n accessorKey: BlogPostFields.status,\n header: t(\"blog.fields.status\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const label = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n return <Badge variant={BLOG_STATUS_BADGE[post.status] ?? \"softGray\"}>{label ?? post.status}</Badge>;\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.categories]: () => ({\n id: BlogPostFields.categories,\n accessorKey: BlogPostFields.categories,\n header: t(\"blog.fields.categories\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const names = post.categories.map((category) => categoryName(category.id)).filter(Boolean);\n if (names.length === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n return <span className=\"text-xs\">{names.join(\", \")}</span>;\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.publishedAt]: () => ({\n id: BlogPostFields.publishedAt,\n accessorKey: BlogPostFields.publishedAt,\n header: t(\"blog.fields.publishedAt\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n if (!post.publishedAt) return <span className=\"text-muted-foreground text-xs\">—</span>;\n return (\n <span className=\"text-xs tabular-nums\">\n {post.publishedAt.toLocaleDateString(locale, { dateStyle: \"medium\" })}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n [BlogPostFields.languages]: () => ({\n id: BlogPostFields.languages,\n accessorKey: BlogPostFields.languages,\n header: t(\"blog.fields.languages\"),\n cell: ({ row }: { row: TableContent<BlogPostInterface> }) => {\n const post: BlogPostInterface = row.original.jsonApiData;\n const total = post.translations.length;\n if (total === 0) return <span className=\"text-muted-foreground text-xs\">—</span>;\n const ready = post.translations.filter((translation) => translation.aiStatus === \"completed\").length;\n return (\n <span className=\"block text-xs tabular-nums text-end\">\n {ready}/{total}\n </span>\n );\n },\n enableSorting: false,\n enableHiding: false,\n }),\n };\n\n const columns = useMemo(() => {\n return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<\n TableContent<BlogPostInterface>\n >[];\n }, [params.fields, fieldColumnMap, t, generateUrl, locale, categoryName]);\n\n return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);\n};\n\nregisterTableGenerator(\"blog-posts\", useBlogPostTableStructure);\n","\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { BlogCategoryService } from \"../data\";\nimport { blogCategoryAdminName } from \"../components/admin/BlogTranslationStatus\";\n\n/**\n * Category names for the admin views, in each category's original language.\n *\n * A post response includes its categories without their translations (the\n * framework includes one level), so the name is not on the included record.\n * The category list does include the translations: it is read once and the\n * returned lookup maps a category id to its name (\"\" while loading).\n */\nexport function useBlogCategoryNames(): (categoryId: string) => string {\n const [names, setNames] = useState<Record<string, string>>({});\n\n useEffect(() => {\n let cancelled = false;\n BlogCategoryService.findMany({ fetchAll: true })\n .then((categories) => {\n if (cancelled) return;\n setNames(Object.fromEntries(categories.map((category) => [category.id, blogCategoryAdminName(category)])));\n })\n .catch(() => {\n // The names are decoration on an admin page: a failed read leaves the ids unnamed.\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n return useCallback((categoryId: string) => names[categoryId] ?? \"\", [names]);\n}\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { v4 } from \"uuid\";\nimport { z } from \"zod\";\nimport {\n EditorSheet,\n EntityMultiSelector,\n FormBlockNote,\n FormInput,\n FormSelect,\n FormTextarea,\n} from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { getI18nSupportedLocales, useI18nLocale, useI18nRouter } from \"../../../../i18n\";\nimport {\n BlogCategoryInterface,\n BlogCategoryService,\n BlogPostInput,\n BlogPostInterface,\n BlogPostService,\n} from \"../../data\";\nimport { BlogCoverImageField, BlogImageSelection, uploadBlogImage } from \"./BlogCoverImageField\";\nimport { blogCategoryAdminName, useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_POST_UPLOAD_PREFIX } from \"../../types/blog.constants\";\nimport { useBlogCategoryNames } from \"../../hooks/useBlogCategoryNames\";\n\ntype BlogPostEditorProps = {\n /** Edit mode when present: the post's original translation and shared fields. */\n post?: BlogPostInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst isBodyEmpty = (body: any) => !body || (Array.isArray(body) && body.length === 0);\n\n/**\n * Creates a post, or edits its original-language text plus the fields every\n * language shares (cover image, categories). Saving the original makes the\n * worker translate it again. Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogPostEditor({ post, trigger, onSuccess }: BlogPostEditorProps) {\n const t = useTranslations();\n const categoryName = useBlogCategoryNames();\n const router = useI18nRouter();\n const locale = useI18nLocale();\n const languageName = useBlogLanguageName();\n const original = post?.original;\n const [cover, setCover] = useState<BlogImageSelection>(null);\n const [isBodyEmptyState, setIsBodyEmptyState] = useState<boolean>(isBodyEmpty(original?.body));\n\n const languages = useMemo(() => getI18nSupportedLocales(), []);\n\n const formSchema = useMemo(\n () =>\n z.object({\n id: z.string().uuid(),\n originalLanguage: z.string().min(1, { message: t(\"blog.errors.language\") }),\n title: z.string().min(1, { message: t(\"blog.errors.title\") }),\n slug: z.string().optional(),\n summary: z.string().optional(),\n body: z.any(),\n coverImage: z.string().optional(),\n coverImageAlt: z.string().optional(),\n categories: z.array(z.object({ id: z.string(), name: z.string() })),\n }),\n [],\n );\n\n const getDefaultValues = useCallback(\n () => ({\n id: post?.id || v4(),\n originalLanguage: post?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),\n title: original?.title ?? \"\",\n slug: original?.slug ?? \"\",\n summary: original?.summary ?? \"\",\n body: original?.body || [],\n // The stored value is a signed URL, not a key: only a newly picked file\n // writes a key here, and only then is the field sent.\n coverImage: \"\",\n coverImageAlt: original?.coverImageAlt ?? \"\",\n categories: (post?.categories ?? []).map((category) => ({\n id: category.id,\n name: blogCategoryAdminName(category),\n })),\n }),\n [post, original, languages, locale],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n const { dirtyFields } = form.formState;\n const isFormDirty = useCallback(() => {\n const dirty: Record<string, unknown> = { ...dirtyFields };\n if (dirty.body && isBodyEmptyState) delete dirty.body;\n return Object.keys(dirty).length > 0;\n }, [dirtyFields, isBodyEmptyState]);\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.post\")}\n entityName={original?.title}\n title={post ? t(\"blog.admin.editPost\") : t(\"blog.admin.newPost\")}\n isEdit={!!post}\n module={Modules.BlogPost}\n size=\"lg\"\n isFormDirty={isFormDirty}\n onSubmit={async (values) => {\n if (values.coverImage && cover) await uploadBlogImage({ key: values.coverImage, selection: cover });\n\n const payload: BlogPostInput = {\n id: values.id,\n title: values.title,\n // An empty summary lets the worker write one; on edit the summary is\n // sent only when changed here, so an untouched AI summary is regenerated.\n summary: post\n ? form.getFieldState(\"summary\").isDirty\n ? values.summary\n : undefined\n : values.summary || undefined,\n body: values.body,\n coverImage: values.coverImage && cover ? values.coverImage : undefined,\n coverImageAlt: values.coverImageAlt,\n categoryIds: values.categories.map((category) => category.id),\n };\n\n if (post) {\n return BlogPostService.update({\n ...payload,\n originalLanguage: post.originalLanguage,\n slug: values.slug || undefined,\n });\n }\n return BlogPostService.create({ ...payload, originalLanguage: values.originalLanguage });\n }}\n onReset={() => {\n setCover(null);\n setIsBodyEmptyState(isBodyEmpty(original?.body));\n return getDefaultValues();\n }}\n onSuccess={onSuccess}\n onNavigate={(url) => router.push(url)}\n trigger={trigger}\n >\n <div className=\"flex h-full min-h-0 w-full flex-col gap-y-4\">\n {!post && (\n <FormSelect\n form={form}\n id=\"originalLanguage\"\n name={t(\"blog.fields.originalLanguage\")}\n values={languages.map((language) => ({ id: language, text: languageName(language) }))}\n isRequired\n />\n )}\n <FormInput form={form} id=\"title\" name={t(\"blog.fields.title\")} isRequired />\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"flex flex-col gap-y-4\">\n {post && (\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n )}\n <EntityMultiSelector<BlogCategoryInterface>\n id=\"categories\"\n form={form}\n label={t(\"blog.fields.categories\")}\n retriever={(params) => BlogCategoryService.findMany(params)}\n module={Modules.BlogCategory}\n getLabel={blogCategoryAdminName}\n // A post's included categories carry no translations, so the stored chip\n // names come from the category list.\n getFormValueLabel={(value: { id: string; name?: string }) => value.name || categoryName(value.id)}\n />\n <FormTextarea\n form={form}\n id=\"summary\"\n name={t(\"blog.fields.summary\")}\n description={t(\"blog.fields.summaryHelp\")}\n className=\"min-h-24\"\n />\n </div>\n <div className=\"flex flex-col gap-y-4\">\n <BlogCoverImageField\n form={form}\n id=\"coverImage\"\n label={t(\"blog.fields.coverImage\")}\n keyPrefix={BLOG_POST_UPLOAD_PREFIX}\n entityId={form.getValues(\"id\")}\n currentUrl={post?.coverImage}\n alt={original?.coverImageAlt}\n selection={cover}\n onSelectionChange={setCover}\n />\n <FormInput form={form} id=\"coverImageAlt\" name={t(\"blog.fields.coverImageAlt\")} />\n </div>\n </div>\n <FormBlockNote\n uploadKeyPrefix={BLOG_POST_UPLOAD_PREFIX}\n form={form}\n id=\"body\"\n name={t(\"blog.fields.body\")}\n type=\"blog-post\"\n isRequired\n stretch\n className=\"min-h-96\"\n onEmptyChange={setIsBodyEmptyState}\n />\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { useMemo } from \"react\";\nimport { RoundPageContainer } from \"../../../../components\";\nimport { SharedProvider } from \"../../../../contexts/SharedContext\";\nimport { Modules } from \"../../../../core\";\nimport { usePageUrlGenerator } from \"../../../../hooks\";\nimport { BreadcrumbItemData } from \"../../../../interfaces\";\nimport { Badge } from \"../../../../shadcnui\";\nimport { BlogPostProvider, useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostDeleter } from \"./BlogPostDeleter\";\nimport { BlogPostPublishButton } from \"./BlogPostPublishButton\";\nimport { BlogPostTranslationPanel } from \"./BlogPostTranslationPanel\";\nimport { BLOG_STATUS_BADGE, useBlogLanguageName } from \"./BlogTranslationStatus\";\n\nexport type BlogPostContainerProps = {\n postId: string;\n};\n\n/**\n * Admin post page: one tab per language, the original first and marked\n * \"Original\" (approved mockup A). Mirrors\n * features/email-automation/components/containers/EmailAutomationContainer.tsx.\n */\nexport function BlogPostContainer({ postId }: BlogPostContainerProps) {\n return (\n <BlogPostProvider postId={postId}>\n <BlogPostShell />\n </BlogPostProvider>\n );\n}\n\nfunction BlogPostShell() {\n const t = useTranslations();\n const generateUrl = usePageUrlGenerator();\n const languageName = useBlogLanguageName();\n const { post, reload } = useBlogPost();\n\n const translations = useMemo(() => {\n const all = post?.translations ?? [];\n return [\n ...all.filter((translation) => translation.isOriginal),\n ...all.filter((translation) => !translation.isOriginal),\n ];\n }, [post]);\n\n if (!post) return null;\n\n const title = post.original?.title ?? post.title ?? \"\";\n\n const breadcrumbs: BreadcrumbItemData[] = [\n { name: t(\"blog.admin.title\"), href: generateUrl({ page: Modules.BlogPost }) },\n { name: title },\n ];\n\n const statusLabel = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n\n const tabLabel = (translation: (typeof translations)[number]) => {\n const parts = [languageName(translation.language)];\n if (translation.isOriginal) parts.push(t(\"blog.translation.original\"));\n if (translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\")\n parts.push(t(\"blog.translation.translating\"));\n if (translation.aiStatus === \"failed\") parts.push(t(\"blog.translation.failed\"));\n return parts.join(\" · \");\n };\n\n return (\n <SharedProvider\n value={{\n breadcrumbs,\n title: {\n type: t(\"blog.admin.title\"),\n element: title,\n functions: [\n <Badge key=\"blogPostStatus\" variant={BLOG_STATUS_BADGE[post.status]}>\n {statusLabel}\n </Badge>,\n <BlogPostPublishButton key=\"blogPostPublish\" />,\n <BlogPostDeleter key=\"blogPostDeleter\" />,\n ],\n },\n }}\n >\n <RoundPageContainer\n module={Modules.BlogPost}\n id={post.id}\n forceHeader\n tabs={translations.map((translation) => ({\n sectionKey: translation.language,\n label: tabLabel(translation),\n content: <BlogPostTranslationPanel post={post} translation={translation} onChanged={reload} />,\n }))}\n />\n </SharedProvider>\n );\n}\n","\"use client\";\n\nimport { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport { BlogPostInterface, BlogPostService } from \"../data\";\n\n/** How often the post page re-reads the post while a translation job runs. */\nconst TRANSLATION_POLL_MS = 5000;\n\nexport interface BlogPostContextType {\n post: BlogPostInterface | undefined;\n reload: () => Promise<void>;\n}\n\nconst BlogPostContext = createContext<BlogPostContextType | undefined>(undefined);\n\nexport type BlogPostProviderProps = {\n postId: string;\n children: ReactNode;\n};\n\n/**\n * Loads one post (with its translations, categories and author) for the admin\n * post page. While any translation is `pending` or `in_progress` it re-reads\n * the post every 5 s, so the language tabs move from \"Translating…\" to the\n * translated text without a manual refresh; it stops once none are running.\n */\nexport const BlogPostProvider = ({ postId, children }: BlogPostProviderProps) => {\n const [post, setPost] = useState<BlogPostInterface | undefined>(undefined);\n\n const reload = useCallback(async () => {\n const loaded = await BlogPostService.findOne({ id: postId });\n setPost(loaded);\n }, [postId]);\n\n useEffect(() => {\n void reload();\n }, [reload]);\n\n const isTranslating = useMemo(\n () =>\n (post?.translations ?? []).some(\n (translation) => translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\",\n ),\n [post],\n );\n\n useEffect(() => {\n if (!isTranslating) return;\n const handle = setInterval(() => {\n reload().catch(() => {});\n }, TRANSLATION_POLL_MS);\n return () => clearInterval(handle);\n }, [isTranslating, reload]);\n\n const value = useMemo<BlogPostContextType>(() => ({ post, reload }), [post, reload]);\n\n return <BlogPostContext.Provider value={value}>{children}</BlogPostContext.Provider>;\n};\n\nexport const useBlogPost = (): BlogPostContextType => {\n const context = useContext(BlogPostContext);\n if (context === undefined) {\n throw new Error(\"useBlogPost must be used within a BlogPostProvider\");\n }\n return context;\n};\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { CommonDeleter } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { usePageUrlGenerator } from \"../../../../hooks\";\nimport { useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostService } from \"../../data\";\n\n/**\n * Deletes the post and returns to the blog admin page.\n * Mirrors features/how-to/components/forms/HowToDeleter.tsx. The dialog texts\n * are passed explicitly: CommonDeleter's default reads `entities.<type>`, a\n * namespace only the app may fill, so the blog's own entity name is used.\n */\nexport function BlogPostDeleter() {\n const t = useTranslations();\n const generateUrl = usePageUrlGenerator();\n const { post } = useBlogPost();\n\n if (!post) return null;\n\n const type = t(\"blog.entities.post\");\n\n return (\n <CommonDeleter\n title={t(\"common.delete.title\", { type })}\n subtitle={t(\"common.delete.subtitle\", { type })}\n description={<div className=\"text-destructive p-4 text-sm\">{t(\"common.delete.description\", { type })}</div>}\n showLabel\n deleteFunction={() => BlogPostService.delete({ id: post.id })}\n redirectTo={generateUrl({ page: Modules.BlogPost })}\n />\n );\n}\n","\"use client\";\n\nimport { EyeIcon, EyeOffIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { useState } from \"react\";\nimport { errorToast } from \"../../../../components\";\nimport { Button } from \"../../../../shadcnui\";\nimport { useBlogPost } from \"../../contexts/BlogPostContext\";\nimport { BlogPostService } from \"../../data\";\n\n/**\n * Publish / Unpublish in the post page header. Publishing an unpublished post\n * republishes it (the first publish date is kept server-side).\n * Mirrors features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx.\n */\nexport function BlogPostPublishButton() {\n const t = useTranslations();\n const { post, reload } = useBlogPost();\n const [saving, setSaving] = useState<boolean>(false);\n\n if (!post) return null;\n\n const isPublished = post.status === \"published\";\n\n const toggle = async () => {\n setSaving(true);\n try {\n if (isPublished) await BlogPostService.unpublish({ id: post.id });\n else await BlogPostService.publish({ id: post.id });\n await reload();\n } catch (error) {\n errorToast({ title: t(\"common.errors.error\"), error });\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <Button\n type=\"button\"\n size=\"sm\"\n variant={isPublished ? \"outline\" : \"default\"}\n onClick={() => void toggle()}\n disabled={saving}\n >\n {isPublished ? <EyeOffIcon /> : <EyeIcon />}\n {isPublished ? t(\"blog.actions.unpublish\") : t(\"blog.actions.publish\")}\n </Button>\n );\n}\n","\"use client\";\n\nimport { PencilIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { DetailField } from \"../../../../components\";\nimport { Alert, AlertDescription, Badge, Button } from \"../../../../shadcnui\";\nimport { isRemoteImageSrc } from \"../../../../utils\";\nimport { BlogPostInterface, BlogPostTranslationInterface, BlogPostTranslationService } from \"../../data\";\nimport { BlogPostComparison, BlogPostTranslationFields } from \"./BlogPostComparison\";\nimport { BlogPostEditor } from \"./BlogPostEditor\";\nimport { BlogPostTranslationEditor } from \"./BlogPostTranslationEditor\";\nimport { useBlogCategoryNames } from \"../../hooks/useBlogCategoryNames\";\nimport { BlogTranslationStatus, useBlogDateFormatter, useBlogLanguageName } from \"./BlogTranslationStatus\";\n\ntype BlogPostTranslationPanelProps = {\n post: BlogPostInterface;\n translation: BlogPostTranslationInterface;\n /** Re-reads the post after an edit or a re-translation request. */\n onChanged?: () => Promise<void>;\n};\n\n/**\n * One language tab of the admin post page (approved mockup A).\n * - Original tab: cover image, categories, status, the text fields and an Edit\n * that opens BlogPostEditor (which re-translates every other language).\n * - Translation tab: where the text came from, whether it was edited by hand,\n * the overwrite warning, \"Compare with original\", the job status, the text\n * fields and an Edit that opens BlogPostTranslationEditor.\n */\nexport function BlogPostTranslationPanel({ post, translation, onChanged }: BlogPostTranslationPanelProps) {\n const t = useTranslations();\n const categoryName = useBlogCategoryNames();\n const languageName = useBlogLanguageName();\n const formatDate = useBlogDateFormatter();\n const [isComparing, setIsComparing] = useState<boolean>(false);\n\n const original = post.original;\n const reload = onChanged ?? (async () => {});\n\n const editTrigger = (\n <Button render={<div />} nativeButton={false} size=\"sm\" variant=\"outline\">\n <PencilIcon />\n {t(\"blog.actions.edit\")}\n </Button>\n );\n\n if (translation.isOriginal) {\n const statusLabel = {\n draft: t(\"blog.status.draft\"),\n published: t(\"blog.status.published\"),\n unpublished: t(\"blog.status.unpublished\"),\n }[post.status];\n\n return (\n <div className=\"flex w-full flex-col gap-y-6 p-4\">\n <div className=\"flex items-center justify-between gap-x-4\">\n <Badge variant=\"softGray\">{t(\"blog.translation.original\")}</Badge>\n <BlogPostEditor post={post} trigger={editTrigger} onSuccess={reload} />\n </div>\n {post.coverImage && (\n <Image\n src={post.coverImage}\n alt={translation.coverImageAlt ?? translation.title}\n width={640}\n height={360}\n className=\"h-auto max-h-72 w-auto rounded-lg object-cover\"\n unoptimized={isRemoteImageSrc(post.coverImage)}\n />\n )}\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <DetailField label={t(\"blog.fields.status\")} value={statusLabel} />\n <DetailField label={t(\"blog.fields.publishedAt\")} value={formatDate(post.publishedAt) || \"—\"} />\n <DetailField\n label={t(\"blog.fields.categories\")}\n value={\n post.categories\n .map((category) => categoryName(category.id))\n .filter(Boolean)\n .join(\", \") || \"—\"\n }\n />\n </div>\n <BlogPostTranslationFields translation={translation} />\n </div>\n );\n }\n\n const isWorking = translation.aiStatus === \"pending\" || translation.aiStatus === \"in_progress\";\n\n return (\n <div className=\"flex w-full flex-col gap-y-6 p-4\">\n <Alert>\n <AlertDescription className=\"flex flex-col gap-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {translation.source === \"manual\" && <Badge variant=\"softAmber\">{t(\"blog.translation.editedByHand\")}</Badge>}\n {original && translation.translatedAt && (\n <span>\n {t(\"blog.translation.translatedFrom\", {\n language: languageName(original.language),\n date: formatDate(translation.translatedAt),\n })}\n </span>\n )}\n </div>\n <span>{t(\"blog.translation.overwriteWarning\")}</span>\n {original && (\n <div>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={() => setIsComparing((value) => !value)}>\n {isComparing ? t(\"blog.actions.hideComparison\") : t(\"blog.actions.compareWithOriginal\")}\n </Button>\n </div>\n )}\n </AlertDescription>\n </Alert>\n\n <div className=\"flex items-center justify-between gap-x-4\">\n <BlogTranslationStatus\n aiStatus={translation.aiStatus}\n onRetranslate={async () => {\n await BlogPostTranslationService.retranslate({ id: translation.id });\n await reload();\n }}\n />\n {!isWorking && <BlogPostTranslationEditor translation={translation} trigger={editTrigger} onSuccess={reload} />}\n </div>\n\n {isComparing && original ? (\n <BlogPostComparison original={original} translation={translation} />\n ) : (\n <BlogPostTranslationFields translation={translation} />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { BlockNoteEditorContainer, DetailField, MicroLabel } from \"../../../../components\";\nimport { Badge } from \"../../../../shadcnui\";\nimport { BlogPostTranslationInterface } from \"../../data\";\nimport { useBlogLanguageName } from \"./BlogTranslationStatus\";\n\ntype BlogPostComparisonProps = {\n original: BlogPostTranslationInterface;\n translation: BlogPostTranslationInterface;\n};\n\n/** One language's text fields, read-only. Shared by both sides of the comparison. */\nexport function BlogPostTranslationFields({ translation }: { translation: BlogPostTranslationInterface }) {\n const t = useTranslations();\n\n return (\n <div className=\"flex flex-col gap-y-4\">\n <DetailField label={t(\"blog.fields.title\")} value={translation.title} />\n <DetailField label={t(\"blog.fields.slug\")} value={translation.slug} />\n <DetailField label={t(\"blog.fields.summary\")} value={translation.summary} />\n <DetailField label={t(\"blog.fields.coverImageAlt\")} value={translation.coverImageAlt} />\n <DetailField label={t(\"blog.fields.body\")}>\n <BlockNoteEditorContainer id={translation.id} type=\"blog-post\" initialContent={translation.body} />\n </DetailField>\n </div>\n );\n}\n\n/** Original on the inline start, translation on the inline end, same fields (mockup A → B's split view). */\nexport function BlogPostComparison({ original, translation }: BlogPostComparisonProps) {\n const t = useTranslations();\n const languageName = useBlogLanguageName();\n\n return (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"bg-muted/40 flex flex-col gap-y-4 rounded-lg border p-4\">\n <div className=\"flex items-center gap-x-2\">\n <MicroLabel as=\"h3\">{languageName(original.language)}</MicroLabel>\n <Badge variant=\"softGray\">{t(\"blog.translation.original\")}</Badge>\n </div>\n <BlogPostTranslationFields translation={original} />\n </div>\n <div className=\"flex flex-col gap-y-4 rounded-lg border p-4\">\n <div className=\"flex items-center gap-x-2\">\n <MicroLabel as=\"h3\">{languageName(translation.language)}</MicroLabel>\n {translation.source === \"manual\" && <Badge variant=\"softAmber\">{t(\"blog.translation.editedByHand\")}</Badge>}\n </div>\n <BlogPostTranslationFields translation={translation} />\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useTranslations } from \"next-intl\";\nimport { ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\nimport { EditorSheet, FormBlockNote, FormInput, FormTextarea } from \"../../../../components\";\nimport { Modules } from \"../../../../core\";\nimport { BlogPostTranslationInterface, BlogPostTranslationService } from \"../../data\";\nimport { useBlogLanguageName } from \"./BlogTranslationStatus\";\nimport { BLOG_POST_UPLOAD_PREFIX } from \"../../types/blog.constants\";\n\ntype BlogPostTranslationEditorProps = {\n translation: BlogPostTranslationInterface;\n trigger?: ReactNode;\n onSuccess?: () => void | Promise<void>;\n};\n\nconst isBodyEmpty = (body: any) => !body || (Array.isArray(body) && body.length === 0);\n\nconst buildFormSchema = (t: (key: string) => string) =>\n z.object({\n id: z.string().uuid(),\n title: z.string().min(1, { message: t(\"blog.errors.title\") }),\n slug: z.string().optional(),\n summary: z.string().optional(),\n body: z.any(),\n coverImageAlt: z.string().optional(),\n });\n\n/**\n * Edits one non-original translation by hand. Saving marks it `manual` and\n * re-runs nothing. Mirrors features/how-to/components/forms/HowToEditor.tsx.\n */\nexport function BlogPostTranslationEditor({ translation, trigger, onSuccess }: BlogPostTranslationEditorProps) {\n const t = useTranslations();\n const formSchema = useMemo(() => buildFormSchema(t), [t]);\n const languageName = useBlogLanguageName();\n const [isBodyEmptyState, setIsBodyEmptyState] = useState<boolean>(isBodyEmpty(translation.body));\n\n const getDefaultValues = useCallback(\n () => ({\n id: translation.id,\n title: translation.title ?? \"\",\n slug: translation.slug ?? \"\",\n summary: translation.summary ?? \"\",\n body: translation.body || [],\n coverImageAlt: translation.coverImageAlt ?? \"\",\n }),\n [translation],\n );\n\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n defaultValues: getDefaultValues(),\n });\n\n const { dirtyFields } = form.formState;\n const isFormDirty = useCallback(() => {\n const dirty: Record<string, unknown> = { ...dirtyFields };\n if (dirty.body && isBodyEmptyState) delete dirty.body;\n return Object.keys(dirty).length > 0;\n }, [dirtyFields, isBodyEmptyState]);\n\n return (\n <EditorSheet\n form={form}\n entityType={t(\"blog.entities.post\")}\n entityName={translation.title}\n title={`${t(\"blog.admin.editPost\")} · ${languageName(translation.language)}`}\n isEdit\n module={Modules.BlogPostTranslation}\n size=\"lg\"\n isFormDirty={isFormDirty}\n onSubmit={async (values) =>\n BlogPostTranslationService.update({\n id: values.id,\n title: values.title,\n slug: values.slug || undefined,\n summary: values.summary,\n body: values.body,\n coverImageAlt: values.coverImageAlt,\n })\n }\n onReset={() => {\n setIsBodyEmptyState(isBodyEmpty(translation.body));\n return getDefaultValues();\n }}\n onSuccess={onSuccess ?? (() => {})}\n trigger={trigger}\n >\n <div className=\"flex h-full min-h-0 w-full flex-col gap-y-4\">\n <FormInput form={form} id=\"title\" name={t(\"blog.fields.title\")} isRequired />\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"flex flex-col gap-y-4\">\n <FormInput form={form} id=\"slug\" name={t(\"blog.fields.slug\")} description={t(\"blog.fields.slugHelp\")} />\n <FormTextarea form={form} id=\"summary\" name={t(\"blog.fields.summary\")} className=\"min-h-24\" />\n </div>\n <div className=\"flex flex-col gap-y-4\">\n <FormInput form={form} id=\"coverImageAlt\" name={t(\"blog.fields.coverImageAlt\")} />\n </div>\n </div>\n <FormBlockNote\n uploadKeyPrefix={BLOG_POST_UPLOAD_PREFIX}\n form={form}\n id=\"body\"\n name={t(\"blog.fields.body\")}\n type=\"blog-post\"\n isRequired\n stretch\n className=\"min-h-96\"\n onEmptyChange={setIsBodyEmptyState}\n />\n </div>\n </EditorSheet>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { Badge, Link } from \"../../../../shadcnui\";\nimport type { PublicBlogCategory } from \"../../types/blog-public.types\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\n/**\n * Category filter: \"All\" plus one link per category. Every link keeps the\n * search query and drops the page, so a new filter starts from page one.\n * `chips` is the horizontal pill row; `list` is the vertical rail list.\n */\nexport function BlogCategoryChips(props: {\n categories: PublicBlogCategory[];\n basePath: string;\n activeCategorySlug?: string;\n query?: string;\n variant?: \"chips\" | \"list\";\n className?: string;\n}) {\n const { categories, basePath, activeCategorySlug, query, variant = \"chips\", className } = props;\n const t = useTranslations();\n\n const items = [\n {\n key: \"all\",\n label: t(\"blog.public.all\"),\n href: blogListHref(basePath, { q: query }),\n active: !activeCategorySlug,\n },\n ...categories.map((category) => ({\n key: category.id,\n label: category.name,\n href: blogListHref(basePath, { category: category.slug, q: query }),\n active: category.slug === activeCategorySlug,\n })),\n ];\n\n // Pills are Badges rendered as links: the active one is the filled default\n // variant, the rest are outline (chips row) or ghost (rail list).\n const inactiveVariant = variant === \"chips\" ? \"outline\" : \"ghost\";\n\n return (\n <nav aria-label={t(\"blog.entities.categories\")} className={className}>\n <ul className={variant === \"chips\" ? \"flex flex-wrap items-center gap-2\" : \"flex flex-col items-start gap-2\"}>\n {items.map((item) => (\n <li key={item.key}>\n <Badge\n variant={item.active ? \"default\" : inactiveVariant}\n render={<Link href={item.href} aria-current={item.active ? \"page\" : undefined} />}\n >\n {item.label}\n </Badge>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n","import type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\n\n/**\n * Locale-free href to the public list, keeping the active filters.\n * Parameter order is fixed (category, q, page) so links are stable.\n */\nexport function blogListHref(basePath: string, params: { category?: string; q?: string; page?: number }): string {\n const search = new URLSearchParams();\n if (params.category) search.set(\"category\", params.category);\n if (params.q) search.set(\"q\", params.q);\n if (params.page && params.page > 1) search.set(\"page\", String(params.page));\n const query = search.toString();\n return query ? `${basePath}?${query}` : basePath;\n}\n\nexport function blogPostHref(basePath: string, slug: string): string {\n return `${basePath}/${encodeURIComponent(slug)}`;\n}\n\n/** The first of the post's categories that the visitor's category list knows. */\nexport function resolvePostCategory(\n post: PublicBlogPost,\n categories: PublicBlogCategory[],\n): PublicBlogCategory | undefined {\n for (const id of post.categoryIds) {\n const category = categories.find((c) => c.id === id);\n if (category) return category;\n }\n return undefined;\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { ContentTitle } from \"../../../../components/navigations/ContentTitle\";\nimport { buttonVariants, Link } from \"../../../../shadcnui\";\nimport { cn } from \"../../../../utils/cn\";\nimport type { PublicBlogCategory, PublicBlogList } from \"../../types/blog-public.types\";\nimport type { BlogListLayout } from \"../../types/blog.constants\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\nexport type BlogListProps = {\n layout: BlogListLayout;\n list: PublicBlogList;\n categories: PublicBlogCategory[];\n activeCategorySlug?: string;\n query?: string;\n page: number;\n basePath: string;\n};\n\n/** Eyebrow, page title and (optionally) the subtitle. */\nexport function BlogListHeader(props: { align?: \"start\" | \"center\"; showSubtitle?: boolean }) {\n const { align = \"start\", showSubtitle = true } = props;\n const t = useTranslations();\n return (\n <header className={align === \"center\" ? \"text-center\" : undefined}>\n <ContentTitle\n type={t(\"blog.public.eyebrow\")}\n element={t(\"blog.public.title\")}\n headingLevel={1}\n className={cn(\"mb-0\", align === \"center\" && \"[&_div]:justify-center\")}\n />\n {showSubtitle ? (\n <p className=\"text-muted-foreground mt-3 text-sm leading-relaxed\">{t(\"blog.public.subtitle\")}</p>\n ) : null}\n </header>\n );\n}\n\n/** Shown in place of the posts: \"no results\" when searching, \"no posts yet\" otherwise. */\nexport function BlogListEmpty(props: { query?: string; className?: string }) {\n const t = useTranslations();\n return (\n <p className={cn(\"text-muted-foreground py-12 text-center text-sm\", props.className)}>\n {props.query ? t(\"blog.public.noResults\") : t(\"blog.public.empty\")}\n </p>\n );\n}\n\n/** \"Load more\": a plain link to the next page, keeping category and query. */\nexport function BlogLoadMore(props: {\n basePath: string;\n page: number;\n activeCategorySlug?: string;\n query?: string;\n className?: string;\n}) {\n const { basePath, page, activeCategorySlug, query, className } = props;\n const t = useTranslations();\n return (\n <div className={cn(\"flex justify-center\", className)}>\n {/* A styled Link, not <Button render={<Link />} nativeButton={false}>: Base UI then\n stamps role=\"button\" on the anchor, so a control that navigates would announce\n as a button (same reasoning as HelpHeader). */}\n <Link\n href={blogListHref(basePath, { category: activeCategorySlug, q: query, page: page + 1 })}\n scroll={false}\n className={buttonVariants({ variant: \"outline\" })}\n >\n {t(\"blog.public.loadMore\")}\n </Link>\n </div>\n );\n}\n","\"use client\";\n\nimport Image from \"next/image\";\nimport { MicroLabel, SectionHeader } from \"../../../../components/typography\";\nimport { Link } from \"../../../../shadcnui\";\nimport { cn } from \"../../../../utils/cn\";\nimport { isRemoteImageSrc } from \"../../../../utils/isRemoteImageSrc\";\nimport type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\nimport { blogPostHref, resolvePostCategory } from \"./blogPublicHelpers\";\nimport { BlogPostMeta } from \"./BlogPostMeta\";\n\n/**\n * Cover image in a fixed-ratio frame. With no image it renders a\n * token-coloured placeholder of the same footprint, so rows stay aligned.\n */\nexport function BlogPostCover(props: {\n src?: string;\n alt?: string;\n sizes: string;\n priority?: boolean;\n className?: string;\n}) {\n const { src, alt, sizes, priority, className } = props;\n return (\n <div className={cn(\"border-border bg-primary/10 relative overflow-hidden rounded-lg border\", className)}>\n {src ? (\n <Image\n src={src}\n alt={alt ?? \"\"}\n fill\n sizes={sizes}\n priority={priority}\n className=\"object-cover\"\n unoptimized={isRemoteImageSrc(src)}\n />\n ) : null}\n </div>\n );\n}\n\ntype BlogPostCardVariant = \"hero\" | \"large\" | \"card\" | \"row\";\n\n/**\n * One post in a public list.\n * - hero: image and text side by side, author and date (featured layout)\n * - large: image above text, author and date (rail layout)\n * - card: small grid card (featured grid, rail columns, related posts)\n * - row: image at the start, text at the end (column layout)\n * The title link is stretched over the whole card, so the card is one target\n * whose accessible name is the title alone.\n */\nexport function BlogPostCard(props: {\n post: PublicBlogPost;\n categories: PublicBlogCategory[];\n basePath: string;\n variant?: BlogPostCardVariant;\n priority?: boolean;\n headingLevel?: \"h2\" | \"h3\";\n}) {\n const { post, categories, basePath, variant = \"card\", priority, headingLevel = \"h2\" } = props;\n const category = resolvePostCategory(post, categories);\n const isProminent = variant === \"hero\" || variant === \"large\";\n\n const eyebrow = category?.name;\n\n const text = (\n <div className=\"flex min-w-0 flex-col\">\n {eyebrow ? <MicroLabel as=\"span\">{eyebrow}</MicroLabel> : null}\n <SectionHeader level={headingLevel === \"h3\" ? 3 : 2} className=\"mt-1.5\">\n <Link href={blogPostHref(basePath, post.slug)} className=\"after:absolute after:inset-0\">\n {post.title}\n </Link>\n </SectionHeader>\n {post.summary ? <p className=\"text-muted-foreground mt-2 text-sm leading-relaxed\">{post.summary}</p> : null}\n <BlogPostMeta post={post} showAuthor={isProminent} className=\"mt-3\" />\n </div>\n );\n\n if (variant === \"hero\") {\n return (\n <article className=\"group relative grid items-center gap-6 md:grid-cols-[1.4fr_1fr] md:gap-10\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 1024px) 560px, (min-width: 768px) 58vw, 100vw\"\n priority={priority}\n className=\"aspect-[16/10]\"\n />\n {text}\n </article>\n );\n }\n\n if (variant === \"row\") {\n return (\n <article className=\"group relative grid items-center gap-4 sm:grid-cols-[10rem_1fr] sm:gap-6\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 640px) 160px, 100vw\"\n priority={priority}\n className=\"aspect-[16/10]\"\n />\n {text}\n </article>\n );\n }\n\n return (\n <article className=\"group relative flex flex-col gap-4\">\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes={\n variant === \"large\"\n ? \"(min-width: 1024px) 760px, 100vw\"\n : \"(min-width: 1024px) 320px, (min-width: 640px) 50vw, 100vw\"\n }\n priority={priority}\n className={variant === \"large\" ? \"aspect-[16/8]\" : \"aspect-[16/10]\"}\n />\n {text}\n </article>\n );\n}\n","\"use client\";\n\nimport { useFormatter, useTranslations } from \"next-intl\";\nimport { cn } from \"../../../../utils/cn\";\nimport type { PublicBlogPost } from \"../../types/blog-public.types\";\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * \"author · date · updated · reading time\". Each part except reading time is\n * opt-in. The public blog shows no dates, so its callers pass only showAuthor.\n */\nexport function BlogPostMeta(props: {\n post: PublicBlogPost;\n showAuthor?: boolean;\n showDate?: boolean;\n showUpdated?: boolean;\n className?: string;\n}) {\n const { post, showAuthor, showDate, showUpdated, className } = props;\n const t = useTranslations();\n const format = useFormatter();\n\n const parts: string[] = [];\n if (showAuthor && post.authorName) parts.push(t(\"blog.public.by\", { author: post.authorName }));\n if (showDate && post.publishedAt) {\n parts.push(format.dateTime(new Date(post.publishedAt), { dateStyle: \"medium\" }));\n }\n if (\n showUpdated &&\n post.publishedAt &&\n post.updatedAt &&\n new Date(post.updatedAt).getTime() - new Date(post.publishedAt).getTime() > ONE_DAY_MS\n ) {\n parts.push(t(\"blog.public.updated\", { date: format.dateTime(new Date(post.updatedAt), { dateStyle: \"medium\" }) }));\n }\n parts.push(t(\"blog.public.readingTime\", { minutes: post.readingMinutes }));\n\n return (\n <p className={cn(\"text-muted-foreground text-xs\", className)}>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? \" · \" : null}\n {part}\n </span>\n ))}\n </p>\n );\n}\n","\"use client\";\n\nimport { SearchIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport { FormEvent, useEffect, useState } from \"react\";\nimport { useI18nRouter } from \"../../../../i18n\";\nimport { InputGroup, InputGroupAddon, InputGroupInput } from \"../../../../shadcnui\";\nimport { blogListHref } from \"./blogPublicHelpers\";\n\n/**\n * Search form for the public list. The current query and category come in as\n * props (the page already parsed the search params), so this component never\n * reads `useSearchParams` and needs no Suspense boundary of its own.\n * Submitting keeps the category and resets to page one.\n */\nexport function BlogSearchBox(props: {\n basePath: string;\n query?: string;\n activeCategorySlug?: string;\n className?: string;\n}) {\n const { basePath, query, activeCategorySlug, className } = props;\n const t = useTranslations();\n const router = useI18nRouter();\n const [value, setValue] = useState(query ?? \"\");\n\n useEffect(() => setValue(query ?? \"\"), [query]);\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const q = value.trim();\n router.push(blogListHref(basePath, { category: activeCategorySlug, q: q || undefined }));\n };\n\n return (\n <form role=\"search\" onSubmit={onSubmit} className={className}>\n <InputGroup>\n <InputGroupAddon>\n <SearchIcon />\n </InputGroupAddon>\n <InputGroupInput\n type=\"search\"\n name=\"q\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n placeholder={t(\"blog.public.searchPlaceholder\")}\n aria-label={t(\"blog.public.search\")}\n />\n </InputGroup>\n </form>\n );\n}\n","\"use client\";\n\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout B: one narrow reading column, every post a row (image at the start, text at the end). */\nexport function BlogListColumn(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto max-w-2xl\">\n <BlogListHeader align=\"center\" />\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-8 [&>ul]:justify-center\"\n />\n <BlogSearchBox basePath={basePath} query={query} activeCategorySlug={activeCategorySlug} className=\"mt-4\" />\n {list.posts.length === 0 ? (\n <BlogListEmpty query={query} />\n ) : (\n <div className=\"divide-border mt-10 flex flex-col divide-y\">\n {list.posts.map((post, index) => (\n <div key={post.id} className=\"py-6 first:pt-0\">\n <BlogPostCard\n post={post}\n categories={categories}\n basePath={basePath}\n variant=\"row\"\n priority={index === 0}\n />\n </div>\n ))}\n </div>\n )}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-10\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout A: newest post as a hero, then filters, then a three-column grid. */\nexport function BlogListFeatured(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n const [hero, ...rest] = list.posts;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto max-w-5xl\">\n <BlogListHeader />\n {hero ? (\n <div className=\"mt-10\">\n <BlogPostCard post={hero} categories={categories} basePath={basePath} variant=\"hero\" priority />\n </div>\n ) : null}\n <div className=\"mt-12 flex flex-col gap-3 md:flex-row md:items-center\">\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"flex-1\"\n />\n <BlogSearchBox\n basePath={basePath}\n query={query}\n activeCategorySlug={activeCategorySlug}\n className=\"md:w-64\"\n />\n </div>\n {list.posts.length === 0 ? <BlogListEmpty query={query} /> : null}\n {rest.length > 0 ? (\n <div className=\"mt-8 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3\">\n {rest.map((post) => (\n <BlogPostCard key={post.id} post={post} categories={categories} basePath={basePath} />\n ))}\n </div>\n ) : null}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-12\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\nimport { MicroLabel } from \"../../../../components/typography\";\nimport { BlogCategoryChips } from \"./BlogCategoryChips\";\nimport { BlogListEmpty, BlogListHeader, type BlogListProps, BlogLoadMore } from \"./BlogListParts\";\nimport { BlogPostCard } from \"./BlogPostCard\";\nimport { BlogSearchBox } from \"./BlogSearchBox\";\n\n/** Layout C: search and categories in a side rail; newest post large, the rest in two columns. */\nexport function BlogListRail(props: BlogListProps) {\n const { list, categories, activeCategorySlug, query, page, basePath } = props;\n const t = useTranslations();\n const [first, ...rest] = list.posts;\n\n return (\n <section className=\"px-4 pt-16 pb-20 md:pt-20\">\n <div className=\"mx-auto grid max-w-5xl gap-10 md:grid-cols-[13rem_1fr] md:gap-12\">\n <aside className=\"flex flex-col gap-6\">\n <BlogListHeader showSubtitle={false} />\n <BlogSearchBox basePath={basePath} query={query} activeCategorySlug={activeCategorySlug} />\n <div>\n <MicroLabel as=\"span\" className=\"block\">\n {t(\"blog.entities.categories\")}\n </MicroLabel>\n <BlogCategoryChips\n categories={categories}\n basePath={basePath}\n activeCategorySlug={activeCategorySlug}\n query={query}\n variant=\"list\"\n className=\"mt-3\"\n />\n </div>\n </aside>\n <div className=\"min-w-0\">\n {first ? (\n <BlogPostCard post={first} categories={categories} basePath={basePath} variant=\"large\" priority />\n ) : (\n <BlogListEmpty query={query} />\n )}\n {rest.length > 0 ? (\n <div className=\"border-border mt-10 grid gap-x-6 gap-y-10 border-t pt-10 sm:grid-cols-2\">\n {rest.map((post) => (\n <BlogPostCard key={post.id} post={post} categories={categories} basePath={basePath} />\n ))}\n </div>\n ) : null}\n {list.hasMore ? (\n <BlogLoadMore\n basePath={basePath}\n page={page}\n activeCategorySlug={activeCategorySlug}\n query={query}\n className=\"mt-12\"\n />\n ) : null}\n </div>\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { BlogListColumn } from \"./BlogListColumn\";\nimport { BlogListFeatured } from \"./BlogListFeatured\";\nimport type { BlogListProps } from \"./BlogListParts\";\nimport { BlogListRail } from \"./BlogListRail\";\n\n/** Public blog list in the layout an administrator picked (BlogSettings.listLayout). */\nexport function BlogList(props: BlogListProps) {\n switch (props.layout) {\n case \"column\":\n return <BlogListColumn {...props} />;\n case \"rail\":\n return <BlogListRail {...props} />;\n case \"featured\":\n default:\n return <BlogListFeatured {...props} />;\n }\n}\n\nexport type { BlogListProps };\n","\"use client\";\n\nimport { ArrowLeftIcon } from \"lucide-react\";\nimport { useTranslations } from \"next-intl\";\nimport type { ReactNode } from \"react\";\nimport { BlockNoteEditorContainer } from \"../../../../components/editors/BlockNoteEditorContainer\";\nimport { ContentTitle } from \"../../../../components/navigations/ContentTitle\";\nimport { SectionHeader } from \"../../../../components/typography\";\nimport { Link } from \"../../../../shadcnui\";\nimport type { PublicBlogCategory, PublicBlogPost } from \"../../types/blog-public.types\";\nimport { resolvePostCategory } from \"./blogPublicHelpers\";\nimport { BlogPostCard, BlogPostCover } from \"./BlogPostCard\";\nimport { BlogPostMeta } from \"./BlogPostMeta\";\n\n/**\n * Public post page: back link, category, title, summary, meta, cover, body,\n * then up to three posts from the same category.\n *\n * `body` replaces the client-only editor with server-rendered content (the app\n * passes BlockNoteServerContent from `@carlonicora/nextjs-jsonapi/blocknote/server`),\n * so the post text is in the HTML crawlers read.\n */\nexport function BlogPostView(props: {\n post: PublicBlogPost;\n categories: PublicBlogCategory[];\n related: PublicBlogPost[];\n basePath: string;\n body?: ReactNode;\n}) {\n const { post, categories, related, basePath, body } = props;\n const t = useTranslations();\n const category = resolvePostCategory(post, categories);\n const more = related.slice(0, 3);\n\n return (\n <article className=\"px-4 pt-12 pb-20 md:pt-16\">\n <div className=\"mx-auto max-w-3xl\">\n <Link href={basePath} className=\"inline-flex items-center gap-1.5\">\n <ArrowLeftIcon className=\"size-4 rtl:rotate-180\" />\n {t(\"blog.public.back\")}\n </Link>\n <header className=\"mt-8\">\n <ContentTitle type={category?.name} element={post.title} headingLevel={1} className=\"mb-0\" />\n {post.summary ? <p className=\"text-muted-foreground mt-3 text-sm leading-relaxed\">{post.summary}</p> : null}\n <BlogPostMeta post={post} showAuthor className=\"mt-4\" />\n </header>\n {post.coverImage ? (\n <BlogPostCover\n src={post.coverImage}\n alt={post.coverImageAlt}\n sizes=\"(min-width: 768px) 768px, 100vw\"\n priority\n className=\"mt-8 aspect-[16/9]\"\n />\n ) : null}\n <div className=\"mt-8\">\n {body ?? <BlockNoteEditorContainer id={post.id} type=\"blog-post\" initialContent={post.body} />}\n </div>\n </div>\n {more.length > 0 ? (\n <section className=\"mx-auto mt-16 max-w-5xl\">\n <div className=\"border-border border-t pt-10\">\n <SectionHeader level={2}>{t(\"blog.public.moreIn\", { category: category?.name ?? \"\" })}</SectionHeader>\n <div className=\"mt-6 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3\">\n {more.map((relatedPost) => (\n <BlogPostCard\n key={relatedPost.id}\n post={relatedPost}\n categories={categories}\n basePath={basePath}\n headingLevel=\"h3\"\n />\n ))}\n </div>\n </div>\n </section>\n ) : null}\n </article>\n );\n}\n"],"mappings":"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","names":["useTranslations","useTranslations","useTranslations","useMemo","useTranslations","useCallback","useMemo","useState","useMemo","useState","jsx","jsxs","useMemo","useState","jsx","jsxs","useTranslations","useMemo","useState","useCallback","jsx","jsxs","useTranslations","useMemo","jsx","useTranslations","useTranslations","useEffect","useState","jsx","jsxs","useTranslations","useState","useEffect","useTranslations","useTranslations","useMemo","useCallback","useEffect","useState","useState","useEffect","useCallback","jsx","jsxs","useTranslations","useMemo","zodResolver","useTranslations","useCallback","useMemo","useState","useForm","v4","z","jsx","jsxs","useTranslations","useState","useMemo","z","useCallback","v4","useForm","zodResolver","jsx","useTranslations","jsx","useTranslations","useTranslations","useMemo","useCallback","useEffect","useMemo","useState","jsx","useState","useCallback","useEffect","useMemo","useTranslations","jsx","useTranslations","useTranslations","useState","jsx","jsxs","useTranslations","useState","useTranslations","Image","useState","useTranslations","jsx","jsxs","useTranslations","zodResolver","useTranslations","useCallback","useMemo","useState","useForm","z","jsx","jsxs","isBodyEmpty","buildFormSchema","z","useTranslations","useMemo","useState","useCallback","useForm","zodResolver","jsx","jsxs","useTranslations","useState","Image","jsx","useTranslations","useMemo","useTranslations","jsx","useTranslations","useTranslations","jsx","jsxs","useTranslations","Image","useTranslations","jsx","jsxs","useTranslations","jsx","jsxs","Image","useTranslations","useEffect","useState","jsx","jsxs","useTranslations","useState","useEffect","jsx","jsxs","jsx","jsxs","useTranslations","jsx","jsxs","useTranslations","jsx","useTranslations","jsx","jsxs","useTranslations"]}