@carlonicora/nextjs-jsonapi 5.2.0 → 5.4.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-EGY4ZAAM.js → BlockNoteEditor-4YCAIEPT.js} +23 -22
  2. package/dist/BlockNoteEditor-4YCAIEPT.js.map +1 -0
  3. package/dist/{BlockNoteEditor-2QJVK2HH.mjs → BlockNoteEditor-YQL2SGFR.mjs} +8 -7
  4. package/dist/BlockNoteEditor-YQL2SGFR.mjs.map +1 -0
  5. package/dist/{BlockNoteViewer-6BQXJ4TB.mjs → BlockNoteViewer-AEJKJ27Q.mjs} +2 -2
  6. package/dist/{BlockNoteViewer-I2MMFH3S.js → BlockNoteViewer-YL3J7J2Y.js} +3 -3
  7. package/dist/{BlockNoteViewer-I2MMFH3S.js.map → BlockNoteViewer-YL3J7J2Y.js.map} +1 -1
  8. package/dist/BlogCategoryFields-DO48vOTE.d.mts +14 -0
  9. package/dist/BlogCategoryFields-DO48vOTE.d.ts +14 -0
  10. package/dist/BlogPostInterface-BB_4wcDR.d.mts +124 -0
  11. package/dist/BlogPostInterface-bWAT1rfR.d.ts +124 -0
  12. package/dist/billing/index.js +441 -441
  13. package/dist/billing/index.mjs +3 -3
  14. package/dist/blog-public.types-BbrzsC1c.d.mts +30 -0
  15. package/dist/blog-public.types-BbrzsC1c.d.ts +30 -0
  16. package/dist/{chunk-3VEQ5JZJ.js → chunk-3NLAXVIN.js} +860 -849
  17. package/dist/{chunk-3VEQ5JZJ.js.map → chunk-3NLAXVIN.js.map} +1 -1
  18. package/dist/{chunk-YMUPTXOT.mjs → chunk-3YW35YHQ.mjs} +1 -101
  19. package/dist/chunk-3YW35YHQ.mjs.map +1 -0
  20. package/dist/{chunk-EXICQC4A.js → chunk-5PPG6PL7.js} +3 -3
  21. package/dist/{chunk-EXICQC4A.js.map → chunk-5PPG6PL7.js.map} +1 -1
  22. package/dist/{chunk-NPKFMRH5.mjs → chunk-6DC2WVVB.mjs} +2 -2
  23. package/dist/{chunk-2Z3ZCYXX.mjs → chunk-EUKTBOI2.mjs} +19 -8
  24. package/dist/chunk-EUKTBOI2.mjs.map +1 -0
  25. package/dist/{chunk-THKYH65N.js → chunk-KSYQEKJX.js} +11 -111
  26. package/dist/chunk-KSYQEKJX.js.map +1 -0
  27. package/dist/{chunk-S5MQDWWC.js → chunk-NFBOSPNJ.js} +1451 -176
  28. package/dist/chunk-NFBOSPNJ.js.map +1 -0
  29. package/dist/{chunk-JEZQTZQ4.mjs → chunk-QMYQRPQC.mjs} +2 -2
  30. package/dist/{chunk-HNOWSFM5.mjs → chunk-WC63PJSC.mjs} +1278 -3
  31. package/dist/chunk-WC63PJSC.mjs.map +1 -0
  32. package/dist/chunk-XCHSX3VC.mjs +107 -0
  33. package/dist/chunk-XCHSX3VC.mjs.map +1 -0
  34. package/dist/{chunk-75MLT74B.js → chunk-YNCQSWQW.js} +8 -8
  35. package/dist/{chunk-75MLT74B.js.map → chunk-YNCQSWQW.js.map} +1 -1
  36. package/dist/chunk-YW4ZCEWB.js +107 -0
  37. package/dist/chunk-YW4ZCEWB.js.map +1 -0
  38. package/dist/client/index.d.mts +6 -39
  39. package/dist/client/index.d.ts +6 -39
  40. package/dist/client/index.js +4 -4
  41. package/dist/client/index.mjs +3 -3
  42. package/dist/components/index.d.mts +15 -4
  43. package/dist/components/index.d.ts +15 -4
  44. package/dist/components/index.js +4 -4
  45. package/dist/components/index.mjs +3 -3
  46. package/dist/contexts/index.js +4 -4
  47. package/dist/contexts/index.mjs +3 -3
  48. package/dist/core/index.d.mts +400 -3
  49. package/dist/core/index.d.ts +400 -3
  50. package/dist/core/index.js +64 -2
  51. package/dist/core/index.js.map +1 -1
  52. package/dist/core/index.mjs +63 -1
  53. package/dist/email-automation-enrollment.fields-BZvF14qx.d.mts +82 -0
  54. package/dist/email-automation-enrollment.fields-Cp3VMzFq.d.ts +82 -0
  55. package/dist/email-automations/index.css +30 -0
  56. package/dist/email-automations/index.css.map +1 -0
  57. package/dist/email-automations/index.d.mts +303 -0
  58. package/dist/email-automations/index.d.ts +303 -0
  59. package/dist/email-automations/index.js +1705 -0
  60. package/dist/email-automations/index.js.map +1 -0
  61. package/dist/email-automations/index.mjs +1705 -0
  62. package/dist/email-automations/index.mjs.map +1 -0
  63. package/dist/features/analytics/index.js +122 -122
  64. package/dist/features/analytics/index.mjs +4 -4
  65. package/dist/features/analytics/server.js +6 -5
  66. package/dist/features/analytics/server.js.map +1 -1
  67. package/dist/features/analytics/server.mjs +4 -3
  68. package/dist/features/analytics/server.mjs.map +1 -1
  69. package/dist/features/blog/index.css +30 -0
  70. package/dist/features/blog/index.css.map +1 -0
  71. package/dist/features/blog/index.d.mts +266 -0
  72. package/dist/features/blog/index.d.ts +266 -0
  73. package/dist/features/blog/index.js +1799 -0
  74. package/dist/features/blog/index.js.map +1 -0
  75. package/dist/features/blog/index.mjs +1799 -0
  76. package/dist/features/blog/index.mjs.map +1 -0
  77. package/dist/features/blog/server.d.mts +33 -0
  78. package/dist/features/blog/server.d.ts +33 -0
  79. package/dist/features/blog/server.js +139 -0
  80. package/dist/features/blog/server.js.map +1 -0
  81. package/dist/features/blog/server.mjs +139 -0
  82. package/dist/features/blog/server.mjs.map +1 -0
  83. package/dist/features/help/index.js +37 -37
  84. package/dist/features/help/index.mjs +3 -3
  85. package/dist/features/tokenusage/index.js +125 -125
  86. package/dist/features/tokenusage/index.mjs +4 -4
  87. package/dist/index.d.mts +5 -2
  88. package/dist/index.d.ts +5 -2
  89. package/dist/index.js +65 -3
  90. package/dist/index.js.map +1 -1
  91. package/dist/index.mjs +64 -2
  92. package/dist/{s3.service-CY-jgOAM.d.ts → s3.service-Bz_K-tjz.d.ts} +11 -0
  93. package/dist/{s3.service-Sri0QP7Z.d.mts → s3.service-D5zNCEW3.d.mts} +11 -0
  94. package/dist/server/index.d.mts +1 -1
  95. package/dist/server/index.d.ts +1 -1
  96. package/dist/server/index.js +8 -6
  97. package/dist/server/index.js.map +1 -1
  98. package/dist/server/index.mjs +12 -10
  99. package/dist/types-70acd-8C.d.mts +39 -0
  100. package/dist/types-l8SFleXi.d.ts +39 -0
  101. package/messages/en.json +210 -0
  102. package/messages/it.json +210 -0
  103. package/package.json +21 -1
  104. package/src/components/editors/BlockNoteEditor.tsx +13 -3
  105. package/src/components/forms/FormBlockNote.tsx +4 -0
  106. package/src/components/forms/FormFieldWrapper.tsx +1 -1
  107. package/src/components/forms/FormTextarea.tsx +2 -1
  108. package/src/components/navigations/ContentTitle.tsx +14 -3
  109. package/src/core/index.ts +12 -0
  110. package/src/core/registry/ModuleRegistry.ts +11 -0
  111. package/src/email-automations/index.ts +5 -0
  112. package/src/features/blog/BlogModules.ts +49 -0
  113. package/src/features/blog/components/admin/BlogAdminContainer.tsx +32 -0
  114. package/src/features/blog/components/admin/BlogCategoriesList.tsx +56 -0
  115. package/src/features/blog/components/admin/BlogCategoryEditor.tsx +230 -0
  116. package/src/features/blog/components/admin/BlogCoverImageField.tsx +123 -0
  117. package/src/features/blog/components/admin/BlogLayoutSetting.tsx +86 -0
  118. package/src/features/blog/components/admin/BlogPostComparison.tsx +54 -0
  119. package/src/features/blog/components/admin/BlogPostContainer.tsx +102 -0
  120. package/src/features/blog/components/admin/BlogPostDeleter.tsx +35 -0
  121. package/src/features/blog/components/admin/BlogPostEditor.tsx +207 -0
  122. package/src/features/blog/components/admin/BlogPostPublishButton.tsx +50 -0
  123. package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +110 -0
  124. package/src/features/blog/components/admin/BlogPostTranslationPanel.tsx +136 -0
  125. package/src/features/blog/components/admin/BlogPostsList.tsx +58 -0
  126. package/src/features/blog/components/admin/BlogTranslationStatus.tsx +104 -0
  127. package/src/features/blog/components/admin/__tests__/BlogPostTranslationPanel.spec.tsx +100 -0
  128. package/src/features/blog/components/admin/__tests__/BlogPostsList.spec.tsx +30 -0
  129. package/src/features/blog/components/admin/index.ts +14 -0
  130. package/src/features/blog/components/public/BlogCategoryChips.tsx +59 -0
  131. package/src/features/blog/components/public/BlogList.tsx +21 -0
  132. package/src/features/blog/components/public/BlogListColumn.tsx +53 -0
  133. package/src/features/blog/components/public/BlogListFeatured.tsx +57 -0
  134. package/src/features/blog/components/public/BlogListParts.tsx +74 -0
  135. package/src/features/blog/components/public/BlogListRail.tsx +62 -0
  136. package/src/features/blog/components/public/BlogPostCard.tsx +132 -0
  137. package/src/features/blog/components/public/BlogPostMeta.tsx +49 -0
  138. package/src/features/blog/components/public/BlogPostView.tsx +74 -0
  139. package/src/features/blog/components/public/BlogSearchBox.tsx +52 -0
  140. package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +173 -0
  141. package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +128 -0
  142. package/src/features/blog/components/public/blogPublicHelpers.ts +30 -0
  143. package/src/features/blog/components/public/index.ts +3 -0
  144. package/src/features/blog/contexts/BlogPostContext.tsx +66 -0
  145. package/src/features/blog/contexts/index.ts +1 -0
  146. package/src/features/blog/data/BlogCategory.ts +93 -0
  147. package/src/features/blog/data/BlogCategoryFields.ts +4 -0
  148. package/src/features/blog/data/BlogCategoryInterface.ts +24 -0
  149. package/src/features/blog/data/BlogCategoryService.ts +54 -0
  150. package/src/features/blog/data/BlogCategoryTranslation.ts +90 -0
  151. package/src/features/blog/data/BlogCategoryTranslationInterface.ts +22 -0
  152. package/src/features/blog/data/BlogCategoryTranslationService.ts +30 -0
  153. package/src/features/blog/data/BlogPost.spec.ts +123 -0
  154. package/src/features/blog/data/BlogPost.ts +159 -0
  155. package/src/features/blog/data/BlogPostFields.ts +7 -0
  156. package/src/features/blog/data/BlogPostInterface.ts +39 -0
  157. package/src/features/blog/data/BlogPostService.ts +82 -0
  158. package/src/features/blog/data/BlogPostTranslation.spec.ts +73 -0
  159. package/src/features/blog/data/BlogPostTranslation.ts +110 -0
  160. package/src/features/blog/data/BlogPostTranslationInterface.ts +27 -0
  161. package/src/features/blog/data/BlogPostTranslationService.ts +30 -0
  162. package/src/features/blog/data/BlogSettings.ts +37 -0
  163. package/src/features/blog/data/BlogSettingsInterface.ts +11 -0
  164. package/src/features/blog/data/BlogSettingsService.ts +24 -0
  165. package/src/features/blog/data/index.ts +17 -0
  166. package/src/features/blog/hooks/index.ts +3 -0
  167. package/src/features/blog/hooks/useBlogCategoryNames.ts +34 -0
  168. package/src/features/blog/hooks/useBlogCategoryTableStructure.tsx +84 -0
  169. package/src/features/blog/hooks/useBlogPostTableStructure.tsx +126 -0
  170. package/src/features/blog/index.ts +15 -0
  171. package/src/features/blog/server/fetchers.ts +120 -0
  172. package/src/features/blog/server/toPublic.spec.ts +91 -0
  173. package/src/features/blog/server/toPublic.ts +36 -0
  174. package/src/features/blog/server-entry.ts +17 -0
  175. package/src/features/blog/types/blog-public.types.ts +34 -0
  176. package/src/features/blog/types/blog.constants.ts +40 -0
  177. package/src/features/email-automation/EmailAutomationEnrollmentModule.ts +11 -0
  178. package/src/features/email-automation/EmailAutomationModule.ts +30 -0
  179. package/src/features/email-automation/EmailAutomationStepModule.ts +11 -0
  180. package/src/features/email-automation/EmailAutomationTriggerModule.ts +11 -0
  181. package/src/features/email-automation/components/__tests__/EmailAutomationEditor.spec.tsx +104 -0
  182. package/src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx +49 -0
  183. package/src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx +474 -0
  184. package/src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx +89 -0
  185. package/src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx +32 -0
  186. package/src/features/email-automation/components/canvas/__tests__/buildCanvasGraph.spec.ts +67 -0
  187. package/src/features/email-automation/components/canvas/buildCanvasGraph.ts +141 -0
  188. package/src/features/email-automation/components/canvas/edges/DelayEdge.tsx +68 -0
  189. package/src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx +43 -0
  190. package/src/features/email-automation/components/canvas/nodes/TriggerNode.tsx +23 -0
  191. package/src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx +53 -0
  192. package/src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx +11 -0
  193. package/src/features/email-automation/components/canvas/shared/__tests__/autoLayout.spec.ts +42 -0
  194. package/src/features/email-automation/components/canvas/shared/__tests__/longPress.spec.ts +51 -0
  195. package/src/features/email-automation/components/canvas/shared/autoLayout.ts +55 -0
  196. package/src/features/email-automation/components/canvas/shared/index.ts +4 -0
  197. package/src/features/email-automation/components/canvas/shared/longPress.ts +57 -0
  198. package/src/features/email-automation/components/containers/EmailAutomationContainer.tsx +80 -0
  199. package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +27 -0
  200. package/src/features/email-automation/components/forms/EmailAutomationEditor.tsx +153 -0
  201. package/src/features/email-automation/components/forms/EmailStepEditor.tsx +151 -0
  202. package/src/features/email-automation/components/index.ts +15 -0
  203. package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +90 -0
  204. package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +76 -0
  205. package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +99 -0
  206. package/src/features/email-automation/components/unsubscribe/__tests__/EmailUnsubscribe.spec.tsx +55 -0
  207. package/src/features/email-automation/contexts/EmailAutomationContext.tsx +175 -0
  208. package/src/features/email-automation/data/EmailAutomation.ts +87 -0
  209. package/src/features/email-automation/data/EmailAutomationEnrollment.ts +105 -0
  210. package/src/features/email-automation/data/EmailAutomationEnrollmentInterface.ts +18 -0
  211. package/src/features/email-automation/data/EmailAutomationEnrollmentService.ts +31 -0
  212. package/src/features/email-automation/data/EmailAutomationInterface.ts +22 -0
  213. package/src/features/email-automation/data/EmailAutomationService.ts +106 -0
  214. package/src/features/email-automation/data/EmailAutomationStep.ts +104 -0
  215. package/src/features/email-automation/data/EmailAutomationStepInterface.ts +23 -0
  216. package/src/features/email-automation/data/EmailAutomationStepService.ts +80 -0
  217. package/src/features/email-automation/data/EmailAutomationTrigger.ts +28 -0
  218. package/src/features/email-automation/data/EmailAutomationTriggerInterface.ts +6 -0
  219. package/src/features/email-automation/data/__tests__/email-automation-models.spec.ts +103 -0
  220. package/src/features/email-automation/data/email-automation-enrollment.fields.ts +8 -0
  221. package/src/features/email-automation/data/email-automation.fields.ts +9 -0
  222. package/src/features/email-automation/data/index.ts +13 -0
  223. package/src/features/email-automation/hooks/index.ts +2 -0
  224. package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +126 -0
  225. package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +119 -0
  226. package/src/features/email-automation/index.ts +5 -0
  227. package/src/features/s3/data/s3.service.ts +17 -0
  228. package/dist/BlockNoteEditor-2QJVK2HH.mjs.map +0 -1
  229. package/dist/BlockNoteEditor-EGY4ZAAM.js.map +0 -1
  230. package/dist/chunk-2Z3ZCYXX.mjs.map +0 -1
  231. package/dist/chunk-HNOWSFM5.mjs.map +0 -1
  232. package/dist/chunk-S5MQDWWC.js.map +0 -1
  233. package/dist/chunk-THKYH65N.js.map +0 -1
  234. package/dist/chunk-YMUPTXOT.mjs.map +0 -1
  235. /package/dist/{BlockNoteViewer-6BQXJ4TB.mjs.map → BlockNoteViewer-AEJKJ27Q.mjs.map} +0 -0
  236. /package/dist/{chunk-NPKFMRH5.mjs.map → chunk-6DC2WVVB.mjs.map} +0 -0
  237. /package/dist/{chunk-JEZQTZQ4.mjs.map → chunk-QMYQRPQC.mjs.map} +0 -0
@@ -0,0 +1,1799 @@
1
+ "use client";
2
+ import {
3
+ AdministrationProvider,
4
+ Alert,
5
+ AlertDescription,
6
+ Badge,
7
+ BlockNoteEditorContainer,
8
+ Button,
9
+ Collapsible,
10
+ CollapsibleContent,
11
+ CollapsibleTrigger,
12
+ CommonDeleter,
13
+ ContentListTable,
14
+ ContentTitle,
15
+ DetailField,
16
+ EditorSheet,
17
+ EmptyState,
18
+ EntityMultiSelector,
19
+ FileInput,
20
+ FileUploader,
21
+ FormBlockNote,
22
+ FormFieldWrapper,
23
+ FormInput,
24
+ FormSelect,
25
+ FormTextarea,
26
+ InputGroup,
27
+ InputGroupAddon,
28
+ InputGroupInput,
29
+ Link,
30
+ MicroLabel,
31
+ RadioGroup,
32
+ RadioGroupItem,
33
+ RoundPageContainer,
34
+ SectionHeader,
35
+ SharedProvider,
36
+ buttonVariants,
37
+ errorToast,
38
+ registerTableGenerator,
39
+ useDataListRetriever,
40
+ usePageUrlGenerator
41
+ } from "../../chunk-EUKTBOI2.mjs";
42
+ import "../../chunk-QMYQRPQC.mjs";
43
+ import "../../chunk-PW42LRSL.mjs";
44
+ import {
45
+ BLOG_ACTION,
46
+ BLOG_CATEGORY_UPLOAD_PREFIX,
47
+ BLOG_LIST_LAYOUTS,
48
+ BLOG_MODULE_ID,
49
+ BLOG_POST_STATUSES,
50
+ BLOG_POST_UPLOAD_PREFIX,
51
+ BLOG_PUBLIC_PREFIX,
52
+ BLOG_SETTINGS_ID,
53
+ BLOG_TRANSLATION_SOURCES,
54
+ BLOG_UPLOAD_ROOT,
55
+ BlogCategoryService,
56
+ BlogCategoryTranslationService,
57
+ BlogPostService,
58
+ BlogPostTranslationService,
59
+ BlogSettingsService,
60
+ Modules,
61
+ S3Service,
62
+ cn,
63
+ getI18nSupportedLocales,
64
+ isRemoteImageSrc,
65
+ showToast,
66
+ useI18nLocale,
67
+ useI18nRouter
68
+ } from "../../chunk-WC63PJSC.mjs";
69
+ import "../../chunk-AUXK7QSA.mjs";
70
+ import "../../chunk-PI56A4DG.mjs";
71
+ import "../../chunk-V5MI7RNB.mjs";
72
+ import "../../chunk-VOXD3ZLY.mjs";
73
+ import {
74
+ __name
75
+ } from "../../chunk-PAWJFY3S.mjs";
76
+
77
+ // src/features/blog/components/admin/BlogAdminContainer.tsx
78
+ import { useTranslations as useTranslations9 } from "next-intl";
79
+
80
+ // src/features/blog/components/admin/BlogCategoriesList.tsx
81
+ import { TagIcon } from "lucide-react";
82
+ import { useTranslations as useTranslations4 } from "next-intl";
83
+
84
+ // src/features/blog/hooks/useBlogCategoryTableStructure.tsx
85
+ import { useTranslations as useTranslations3 } from "next-intl";
86
+ import { useMemo as useMemo4 } from "react";
87
+
88
+ // src/features/blog/components/admin/BlogCategoryEditor.tsx
89
+ import { zodResolver } from "@hookform/resolvers/zod";
90
+ import { ChevronDownIcon } from "lucide-react";
91
+ import { useTranslations as useTranslations2 } from "next-intl";
92
+ import { useCallback as useCallback2, useMemo as useMemo3, useState as useState3 } from "react";
93
+ import { useForm } from "react-hook-form";
94
+ import { v4 } from "uuid";
95
+ import { z } from "zod";
96
+
97
+ // src/features/blog/components/admin/BlogCoverImageField.tsx
98
+ import { UploadIcon } from "lucide-react";
99
+ import Image from "next/image";
100
+ import { useEffect, useMemo, useState } from "react";
101
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
102
+ var dropzone = {
103
+ multiple: false,
104
+ maxSize: 100 * 1024 * 1024,
105
+ preventDropOnDocument: false,
106
+ accept: {
107
+ "application/images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"]
108
+ }
109
+ };
110
+ async function uploadBlogImage(params) {
111
+ if (!params.selection || !params.key) return;
112
+ await S3Service.upload({
113
+ key: params.key,
114
+ contentType: params.selection.contentType,
115
+ file: params.selection.file,
116
+ isPublic: true
117
+ });
118
+ }
119
+ __name(uploadBlogImage, "uploadBlogImage");
120
+ function BlogCoverImageField({
121
+ form,
122
+ id,
123
+ label,
124
+ keyPrefix,
125
+ entityId,
126
+ currentUrl,
127
+ alt,
128
+ selection,
129
+ onSelectionChange
130
+ }) {
131
+ const [files, setFiles] = useState(null);
132
+ useEffect(() => {
133
+ if (selection === null) setFiles(null);
134
+ }, [selection]);
135
+ const previewUrl = useMemo(() => selection ? URL.createObjectURL(selection.file) : void 0, [selection]);
136
+ useEffect(() => {
137
+ return () => {
138
+ if (previewUrl) URL.revokeObjectURL(previewUrl);
139
+ };
140
+ }, [previewUrl]);
141
+ const src = previewUrl ?? currentUrl;
142
+ return /* @__PURE__ */ jsx(FormFieldWrapper, { form, name: id, label, children: (field) => /* @__PURE__ */ jsx(
143
+ FileUploader,
144
+ {
145
+ value: files,
146
+ onValueChange: (value) => {
147
+ setFiles(value);
148
+ const file = value?.[0];
149
+ if (!file) return;
150
+ const extension = file.type.split("/").pop() ?? "";
151
+ field.onChange(`${keyPrefix}/${entityId}/${entityId}.${Date.now()}.${extension}`);
152
+ onSelectionChange({ file, contentType: file.type });
153
+ },
154
+ dropzoneOptions: dropzone,
155
+ className: "w-full",
156
+ children: /* @__PURE__ */ jsx(FileInput, { className: "text-muted-foreground/50 rounded-lg outline-1 outline-dashed", children: /* @__PURE__ */ jsx("div", { className: "flex w-full flex-col items-center justify-center p-4", children: src ? /* @__PURE__ */ jsx(
157
+ Image,
158
+ {
159
+ src,
160
+ alt: alt ?? label,
161
+ width: 320,
162
+ height: 180,
163
+ className: "h-auto max-h-48 w-auto rounded-md object-cover",
164
+ unoptimized: isRemoteImageSrc(previewUrl ? void 0 : currentUrl)
165
+ }
166
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
167
+ /* @__PURE__ */ jsx(UploadIcon, { className: "my-4 h-8 w-8" }),
168
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: label })
169
+ ] }) }) })
170
+ }
171
+ ) });
172
+ }
173
+ __name(BlogCoverImageField, "BlogCoverImageField");
174
+
175
+ // src/features/blog/components/admin/BlogTranslationStatus.tsx
176
+ import { LoaderCircleIcon, RotateCcwIcon } from "lucide-react";
177
+ import { useTranslations } from "next-intl";
178
+ import { useCallback, useMemo as useMemo2, useState as useState2 } from "react";
179
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
180
+ var BLOG_STATUS_BADGE = {
181
+ draft: "softGray",
182
+ published: "softGreen",
183
+ unpublished: "softAmber"
184
+ };
185
+ function blogCategoryAdminName(category) {
186
+ return category.original?.name ?? category.name ?? "";
187
+ }
188
+ __name(blogCategoryAdminName, "blogCategoryAdminName");
189
+ function useBlogLanguageName() {
190
+ const locale = useI18nLocale();
191
+ const names = useMemo2(() => {
192
+ try {
193
+ return new Intl.DisplayNames([locale], { type: "language" });
194
+ } catch {
195
+ return void 0;
196
+ }
197
+ }, [locale]);
198
+ return useCallback(
199
+ (language) => {
200
+ const name = names?.of(language);
201
+ if (!name) return language;
202
+ return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);
203
+ },
204
+ [names, locale]
205
+ );
206
+ }
207
+ __name(useBlogLanguageName, "useBlogLanguageName");
208
+ function useBlogDateFormatter() {
209
+ const locale = useI18nLocale();
210
+ return useCallback((date) => date ? date.toLocaleDateString(locale, { dateStyle: "medium" }) : "", [locale]);
211
+ }
212
+ __name(useBlogDateFormatter, "useBlogDateFormatter");
213
+ function BlogTranslationStatus({ aiStatus, onRetranslate }) {
214
+ const t = useTranslations();
215
+ const [isRetrying, setIsRetrying] = useState2(false);
216
+ if (aiStatus === "completed") return null;
217
+ if (aiStatus === "pending" || aiStatus === "in_progress") {
218
+ return /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-x-2", children: /* @__PURE__ */ jsxs2(Badge, { variant: "softBlue", children: [
219
+ /* @__PURE__ */ jsx2(LoaderCircleIcon, { className: "animate-spin" }),
220
+ aiStatus === "pending" ? t("blog.translation.pending") : t("blog.translation.translating")
221
+ ] }) });
222
+ }
223
+ const retranslate = /* @__PURE__ */ __name(async () => {
224
+ if (!onRetranslate) return;
225
+ setIsRetrying(true);
226
+ try {
227
+ await onRetranslate();
228
+ } catch (error) {
229
+ errorToast({ title: t("common.errors.error"), error });
230
+ } finally {
231
+ setIsRetrying(false);
232
+ }
233
+ }, "retranslate");
234
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-x-2", children: [
235
+ /* @__PURE__ */ jsx2(Badge, { variant: "softRed", children: t("blog.translation.failed") }),
236
+ onRetranslate && /* @__PURE__ */ jsxs2(Button, { type: "button", size: "sm", variant: "outline", onClick: () => void retranslate(), disabled: isRetrying, children: [
237
+ /* @__PURE__ */ jsx2(RotateCcwIcon, {}),
238
+ t("blog.actions.translateAgain")
239
+ ] })
240
+ ] });
241
+ }
242
+ __name(BlogTranslationStatus, "BlogTranslationStatus");
243
+
244
+ // src/features/blog/components/admin/BlogCategoryEditor.tsx
245
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
246
+ var buildTranslationSchema = /* @__PURE__ */ __name((t) => z.object({
247
+ id: z.string(),
248
+ name: z.string().min(1, { message: t("blog.errors.name") }),
249
+ slug: z.string().optional(),
250
+ description: z.string().optional()
251
+ }), "buildTranslationSchema");
252
+ var buildFormSchema = /* @__PURE__ */ __name((t) => z.object({
253
+ id: z.string().uuid(),
254
+ originalLanguage: z.string().min(1, { message: t("blog.errors.language") }),
255
+ name: z.string().min(1, { message: t("blog.errors.name") }),
256
+ slug: z.string().optional(),
257
+ description: z.string().optional(),
258
+ image: z.string().optional(),
259
+ translations: z.array(buildTranslationSchema(t))
260
+ }), "buildFormSchema");
261
+ var sameTranslation = /* @__PURE__ */ __name((a, b) => !!b && a.name === b.name && (a.slug ?? "") === (b.slug ?? "") && (a.description ?? "") === (b.description ?? ""), "sameTranslation");
262
+ function BlogCategoryEditor({ category, trigger, onSuccess }) {
263
+ const t = useTranslations2();
264
+ const formSchema = useMemo3(() => buildFormSchema(t), [t]);
265
+ const locale = useI18nLocale();
266
+ const languageName = useBlogLanguageName();
267
+ const [image, setImage] = useState3(null);
268
+ const languages = useMemo3(() => getI18nSupportedLocales(), []);
269
+ const original = category?.original;
270
+ const translations = useMemo3(
271
+ () => (category?.translations ?? []).filter((translation) => !translation.isOriginal),
272
+ [category]
273
+ );
274
+ const getDefaultValues = useCallback2(
275
+ () => ({
276
+ id: category?.id || v4(),
277
+ originalLanguage: category?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),
278
+ name: original?.name ?? category?.name ?? "",
279
+ slug: original?.slug ?? "",
280
+ description: original?.description ?? "",
281
+ // The stored value is a signed URL, not a key: only a newly picked file writes a key.
282
+ image: "",
283
+ translations: translations.map((translation) => ({
284
+ id: translation.id,
285
+ name: translation.name ?? "",
286
+ slug: translation.slug ?? "",
287
+ description: translation.description ?? ""
288
+ }))
289
+ }),
290
+ [category, original, translations, languages, locale]
291
+ );
292
+ const form = useForm({
293
+ resolver: zodResolver(formSchema),
294
+ defaultValues: getDefaultValues()
295
+ });
296
+ return /* @__PURE__ */ jsx3(
297
+ EditorSheet,
298
+ {
299
+ form,
300
+ entityType: t("blog.entities.category"),
301
+ entityName: original?.name ?? category?.name,
302
+ title: category ? t("blog.admin.editCategory") : t("blog.admin.newCategory"),
303
+ isEdit: !!category,
304
+ module: Modules.BlogCategory,
305
+ size: "md",
306
+ onSubmit: async (values) => {
307
+ if (values.image && image) await uploadBlogImage({ key: values.image, selection: image });
308
+ const imageKey = values.image && image ? values.image : void 0;
309
+ if (!category) {
310
+ return BlogCategoryService.create({
311
+ id: values.id,
312
+ originalLanguage: values.originalLanguage,
313
+ name: values.name,
314
+ description: values.description,
315
+ image: imageKey
316
+ });
317
+ }
318
+ const defaults = getDefaultValues();
319
+ const originalChanged = !!imageKey || !sameTranslation(
320
+ { id: values.id, name: values.name, slug: values.slug, description: values.description },
321
+ { id: defaults.id, name: defaults.name, slug: defaults.slug, description: defaults.description }
322
+ );
323
+ if (originalChanged) {
324
+ await BlogCategoryService.update({
325
+ id: values.id,
326
+ originalLanguage: category?.originalLanguage,
327
+ name: values.name,
328
+ slug: values.slug || void 0,
329
+ description: values.description,
330
+ image: imageKey
331
+ });
332
+ }
333
+ for (const translation of values.translations) {
334
+ const before = defaults.translations.find((candidate) => candidate.id === translation.id);
335
+ if (sameTranslation(translation, before)) continue;
336
+ await BlogCategoryTranslationService.update({
337
+ id: translation.id,
338
+ name: translation.name,
339
+ slug: translation.slug || void 0,
340
+ description: translation.description
341
+ });
342
+ }
343
+ return { id: values.id };
344
+ },
345
+ onReset: () => {
346
+ setImage(null);
347
+ return getDefaultValues();
348
+ },
349
+ onSuccess: onSuccess ?? (() => {
350
+ }),
351
+ trigger,
352
+ children: /* @__PURE__ */ jsxs3("div", { className: "flex w-full flex-col gap-y-4", children: [
353
+ !category && /* @__PURE__ */ jsx3(
354
+ FormSelect,
355
+ {
356
+ form,
357
+ id: "originalLanguage",
358
+ name: t("blog.fields.originalLanguage"),
359
+ values: languages.map((language) => ({ id: language, text: languageName(language) })),
360
+ isRequired: true
361
+ }
362
+ ),
363
+ /* @__PURE__ */ jsx3(FormInput, { form, id: "name", name: t("blog.fields.name"), isRequired: true }),
364
+ category && /* @__PURE__ */ jsx3(FormInput, { form, id: "slug", name: t("blog.fields.slug"), description: t("blog.fields.slugHelp") }),
365
+ /* @__PURE__ */ jsx3(FormTextarea, { form, id: "description", name: t("blog.fields.description") }),
366
+ /* @__PURE__ */ jsx3(
367
+ BlogCoverImageField,
368
+ {
369
+ form,
370
+ id: "image",
371
+ label: t("blog.fields.image"),
372
+ keyPrefix: BLOG_CATEGORY_UPLOAD_PREFIX,
373
+ entityId: form.getValues("id"),
374
+ currentUrl: category?.image,
375
+ selection: image,
376
+ onSelectionChange: setImage
377
+ }
378
+ ),
379
+ translations.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-y-2", children: [
380
+ /* @__PURE__ */ jsx3(SectionHeader, { className: "mt-2", children: t("blog.fields.languages") }),
381
+ translations.map((translation, index) => /* @__PURE__ */ jsxs3(Collapsible, { className: "rounded-md border", children: [
382
+ /* @__PURE__ */ jsxs3(CollapsibleTrigger, { className: "flex w-full items-center justify-between gap-x-2 px-3 py-2 text-start", children: [
383
+ /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-x-2 text-sm font-medium", children: [
384
+ languageName(translation.language),
385
+ /* @__PURE__ */ jsx3(BlogTranslationStatus, { aiStatus: translation.aiStatus })
386
+ ] }),
387
+ /* @__PURE__ */ jsx3(ChevronDownIcon, { className: "text-muted-foreground size-4" })
388
+ ] }),
389
+ /* @__PURE__ */ jsxs3(CollapsibleContent, { className: "flex flex-col gap-y-4 border-t p-3", children: [
390
+ translation.aiStatus === "failed" && /* @__PURE__ */ jsx3(
391
+ BlogTranslationStatus,
392
+ {
393
+ aiStatus: translation.aiStatus,
394
+ onRetranslate: async () => {
395
+ await BlogCategoryTranslationService.retranslate({ id: translation.id });
396
+ await onSuccess?.();
397
+ }
398
+ }
399
+ ),
400
+ /* @__PURE__ */ jsx3(FormInput, { form, id: `translations.${index}.name`, name: t("blog.fields.name"), isRequired: true }),
401
+ /* @__PURE__ */ jsx3(
402
+ FormInput,
403
+ {
404
+ form,
405
+ id: `translations.${index}.slug`,
406
+ name: t("blog.fields.slug"),
407
+ description: t("blog.fields.slugHelp")
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx3(
411
+ FormTextarea,
412
+ {
413
+ form,
414
+ id: `translations.${index}.description`,
415
+ name: t("blog.fields.description")
416
+ }
417
+ )
418
+ ] })
419
+ ] }, translation.id))
420
+ ] })
421
+ ] })
422
+ }
423
+ );
424
+ }
425
+ __name(BlogCategoryEditor, "BlogCategoryEditor");
426
+
427
+ // src/features/blog/hooks/useBlogCategoryTableStructure.tsx
428
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
429
+ var useBlogCategoryTableStructure = /* @__PURE__ */ __name((params) => {
430
+ const t = useTranslations3();
431
+ const refresh = params.dataRetriever?.refresh;
432
+ const tableData = useMemo4(() => {
433
+ return params.data.map((category) => {
434
+ const entry = { jsonApiData: category };
435
+ params.fields.forEach((field) => {
436
+ entry[field] = category[field];
437
+ });
438
+ return entry;
439
+ });
440
+ }, [params.data, params.fields]);
441
+ const fieldColumnMap = {
442
+ ["name" /* name */]: () => ({
443
+ id: "name" /* name */,
444
+ accessorKey: "name" /* name */,
445
+ header: t("blog.fields.name"),
446
+ cell: /* @__PURE__ */ __name(({ row }) => {
447
+ const category = row.original.jsonApiData;
448
+ return /* @__PURE__ */ jsx4(
449
+ BlogCategoryEditor,
450
+ {
451
+ category,
452
+ onSuccess: refresh,
453
+ trigger: /* @__PURE__ */ jsx4("button", { type: "button", className: "text-primary text-start font-medium", children: blogCategoryAdminName(category) || category.id })
454
+ }
455
+ );
456
+ }, "cell"),
457
+ enableSorting: false,
458
+ enableHiding: false
459
+ }),
460
+ ["languages" /* languages */]: () => ({
461
+ id: "languages" /* languages */,
462
+ accessorKey: "languages" /* languages */,
463
+ header: t("blog.fields.languages"),
464
+ cell: /* @__PURE__ */ __name(({ row }) => {
465
+ const category = row.original.jsonApiData;
466
+ const total = category.translations.length;
467
+ if (total === 0) return /* @__PURE__ */ jsx4("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
468
+ const ready = category.translations.filter((translation) => translation.aiStatus === "completed").length;
469
+ return /* @__PURE__ */ jsxs4("span", { className: "block text-xs tabular-nums text-end", children: [
470
+ ready,
471
+ "/",
472
+ total
473
+ ] });
474
+ }, "cell"),
475
+ enableSorting: false,
476
+ enableHiding: false
477
+ })
478
+ };
479
+ const columns = useMemo4(() => {
480
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== void 0);
481
+ }, [params.fields, fieldColumnMap, t, refresh]);
482
+ return useMemo4(() => ({ data: tableData, columns }), [tableData, columns]);
483
+ }, "useBlogCategoryTableStructure");
484
+ registerTableGenerator("blog-categories", useBlogCategoryTableStructure);
485
+
486
+ // src/features/blog/components/admin/BlogCategoriesList.tsx
487
+ import { jsx as jsx5 } from "react/jsx-runtime";
488
+ function BlogCategoriesList({ fullWidth } = {}) {
489
+ const t = useTranslations4();
490
+ const data = useDataListRetriever({
491
+ retriever: /* @__PURE__ */ __name((params) => BlogCategoryService.findMany(params), "retriever"),
492
+ retrieverParams: {},
493
+ module: Modules.BlogCategory
494
+ });
495
+ const functions = [
496
+ /* @__PURE__ */ jsx5(
497
+ BlogCategoryEditor,
498
+ {
499
+ onSuccess: data.refresh,
500
+ trigger: /* @__PURE__ */ jsx5(Button, { render: /* @__PURE__ */ jsx5("div", {}), nativeButton: false, size: "sm", variant: "outline", children: t("blog.admin.newCategory") })
501
+ },
502
+ "create-blog-category"
503
+ )
504
+ ];
505
+ return /* @__PURE__ */ jsx5(
506
+ ContentListTable,
507
+ {
508
+ data,
509
+ fields: ["name" /* name */, "languages" /* languages */],
510
+ tableGeneratorType: Modules.BlogCategory,
511
+ functions,
512
+ fullWidth,
513
+ title: t("blog.entities.categories"),
514
+ emptyState: /* @__PURE__ */ jsx5(EmptyState, { icon: TagIcon, title: t("blog.admin.emptyCategories") })
515
+ }
516
+ );
517
+ }
518
+ __name(BlogCategoriesList, "BlogCategoriesList");
519
+
520
+ // src/features/blog/components/admin/BlogLayoutSetting.tsx
521
+ import { useTranslations as useTranslations5 } from "next-intl";
522
+ import { useEffect as useEffect2, useState as useState4 } from "react";
523
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
524
+ function BlogLayoutSetting() {
525
+ const t = useTranslations5();
526
+ const [layout, setLayout] = useState4(void 0);
527
+ const [saving, setSaving] = useState4(false);
528
+ useEffect2(() => {
529
+ let active = true;
530
+ BlogSettingsService.get().then((settings) => {
531
+ if (active) setLayout(settings?.listLayout ?? "featured");
532
+ }).catch(() => {
533
+ if (active) setLayout("featured");
534
+ });
535
+ return () => {
536
+ active = false;
537
+ };
538
+ }, []);
539
+ const options = {
540
+ featured: { label: t("blog.layout.featured"), description: t("blog.layout.featuredDescription") },
541
+ column: { label: t("blog.layout.column"), description: t("blog.layout.columnDescription") },
542
+ rail: { label: t("blog.layout.rail"), description: t("blog.layout.railDescription") }
543
+ };
544
+ const save = /* @__PURE__ */ __name(async (next) => {
545
+ const previous = layout;
546
+ setLayout(next);
547
+ setSaving(true);
548
+ try {
549
+ await BlogSettingsService.save({ id: BLOG_SETTINGS_ID, listLayout: next });
550
+ showToast(t("blog.layout.saved"));
551
+ } catch (error) {
552
+ setLayout(previous);
553
+ errorToast({ title: t("common.errors.error"), error });
554
+ } finally {
555
+ setSaving(false);
556
+ }
557
+ }, "save");
558
+ return /* @__PURE__ */ jsxs5("div", { className: "flex w-full max-w-3xl flex-col gap-y-4 p-4", children: [
559
+ /* @__PURE__ */ jsx6(SectionHeader, { children: t("blog.layout.title") }),
560
+ layout && /* @__PURE__ */ jsx6(
561
+ RadioGroup,
562
+ {
563
+ value: layout,
564
+ onValueChange: (value) => {
565
+ if (!value || value === layout) return;
566
+ void save(value);
567
+ },
568
+ disabled: saving,
569
+ className: "grid grid-cols-1 gap-3 md:grid-cols-3",
570
+ children: BLOG_LIST_LAYOUTS.map((option) => /* @__PURE__ */ jsxs5(
571
+ "label",
572
+ {
573
+ htmlFor: `blog-layout-${option}`,
574
+ className: "has-data-checked:border-primary flex cursor-pointer items-start gap-x-3 rounded-lg border p-4",
575
+ children: [
576
+ /* @__PURE__ */ jsx6(RadioGroupItem, { value: option, id: `blog-layout-${option}`, className: "mt-0.5" }),
577
+ /* @__PURE__ */ jsxs5("span", { className: "flex flex-col gap-y-1", children: [
578
+ /* @__PURE__ */ jsx6("span", { className: "text-sm font-medium", children: options[option].label }),
579
+ /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground text-xs/relaxed", children: options[option].description })
580
+ ] })
581
+ ]
582
+ },
583
+ option
584
+ ))
585
+ }
586
+ )
587
+ ] });
588
+ }
589
+ __name(BlogLayoutSetting, "BlogLayoutSetting");
590
+
591
+ // src/features/blog/components/admin/BlogPostsList.tsx
592
+ import { NewspaperIcon } from "lucide-react";
593
+ import { useTranslations as useTranslations8 } from "next-intl";
594
+
595
+ // src/features/blog/hooks/useBlogPostTableStructure.tsx
596
+ import { useTranslations as useTranslations6 } from "next-intl";
597
+ import { useMemo as useMemo5 } from "react";
598
+
599
+ // src/features/blog/hooks/useBlogCategoryNames.ts
600
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState5 } from "react";
601
+ function useBlogCategoryNames() {
602
+ const [names, setNames] = useState5({});
603
+ useEffect3(() => {
604
+ let cancelled = false;
605
+ BlogCategoryService.findMany({ fetchAll: true }).then((categories) => {
606
+ if (cancelled) return;
607
+ setNames(Object.fromEntries(categories.map((category) => [category.id, blogCategoryAdminName(category)])));
608
+ }).catch(() => {
609
+ });
610
+ return () => {
611
+ cancelled = true;
612
+ };
613
+ }, []);
614
+ return useCallback3((categoryId) => names[categoryId] ?? "", [names]);
615
+ }
616
+ __name(useBlogCategoryNames, "useBlogCategoryNames");
617
+
618
+ // src/features/blog/hooks/useBlogPostTableStructure.tsx
619
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
620
+ var useBlogPostTableStructure = /* @__PURE__ */ __name((params) => {
621
+ const t = useTranslations6();
622
+ const locale = useI18nLocale();
623
+ const generateUrl = usePageUrlGenerator();
624
+ const categoryName = useBlogCategoryNames();
625
+ const tableData = useMemo5(() => {
626
+ return params.data.map((post) => {
627
+ const entry = { jsonApiData: post };
628
+ params.fields.forEach((field) => {
629
+ entry[field] = post[field];
630
+ });
631
+ return entry;
632
+ });
633
+ }, [params.data, params.fields]);
634
+ const fieldColumnMap = {
635
+ ["title" /* title */]: () => ({
636
+ id: "title" /* title */,
637
+ accessorKey: "title" /* title */,
638
+ header: t("blog.fields.title"),
639
+ cell: /* @__PURE__ */ __name(({ row }) => {
640
+ const post = row.original.jsonApiData;
641
+ return /* @__PURE__ */ jsx7(Link, { href: generateUrl({ page: Modules.BlogPost, id: post.id }), children: post.original?.title ?? post.title ?? post.id });
642
+ }, "cell"),
643
+ enableSorting: false,
644
+ enableHiding: false
645
+ }),
646
+ ["status" /* status */]: () => ({
647
+ id: "status" /* status */,
648
+ accessorKey: "status" /* status */,
649
+ header: t("blog.fields.status"),
650
+ cell: /* @__PURE__ */ __name(({ row }) => {
651
+ const post = row.original.jsonApiData;
652
+ const label = {
653
+ draft: t("blog.status.draft"),
654
+ published: t("blog.status.published"),
655
+ unpublished: t("blog.status.unpublished")
656
+ }[post.status];
657
+ return /* @__PURE__ */ jsx7(Badge, { variant: BLOG_STATUS_BADGE[post.status] ?? "softGray", children: label ?? post.status });
658
+ }, "cell"),
659
+ enableSorting: false,
660
+ enableHiding: false
661
+ }),
662
+ ["categories" /* categories */]: () => ({
663
+ id: "categories" /* categories */,
664
+ accessorKey: "categories" /* categories */,
665
+ header: t("blog.fields.categories"),
666
+ cell: /* @__PURE__ */ __name(({ row }) => {
667
+ const post = row.original.jsonApiData;
668
+ const names = post.categories.map((category) => categoryName(category.id)).filter(Boolean);
669
+ if (names.length === 0) return /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
670
+ return /* @__PURE__ */ jsx7("span", { className: "text-xs", children: names.join(", ") });
671
+ }, "cell"),
672
+ enableSorting: false,
673
+ enableHiding: false
674
+ }),
675
+ ["publishedAt" /* publishedAt */]: () => ({
676
+ id: "publishedAt" /* publishedAt */,
677
+ accessorKey: "publishedAt" /* publishedAt */,
678
+ header: t("blog.fields.publishedAt"),
679
+ cell: /* @__PURE__ */ __name(({ row }) => {
680
+ const post = row.original.jsonApiData;
681
+ if (!post.publishedAt) return /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
682
+ return /* @__PURE__ */ jsx7("span", { className: "text-xs tabular-nums", children: post.publishedAt.toLocaleDateString(locale, { dateStyle: "medium" }) });
683
+ }, "cell"),
684
+ enableSorting: false,
685
+ enableHiding: false
686
+ }),
687
+ ["languages" /* languages */]: () => ({
688
+ id: "languages" /* languages */,
689
+ accessorKey: "languages" /* languages */,
690
+ header: t("blog.fields.languages"),
691
+ cell: /* @__PURE__ */ __name(({ row }) => {
692
+ const post = row.original.jsonApiData;
693
+ const total = post.translations.length;
694
+ if (total === 0) return /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
695
+ const ready = post.translations.filter((translation) => translation.aiStatus === "completed").length;
696
+ return /* @__PURE__ */ jsxs6("span", { className: "block text-xs tabular-nums text-end", children: [
697
+ ready,
698
+ "/",
699
+ total
700
+ ] });
701
+ }, "cell"),
702
+ enableSorting: false,
703
+ enableHiding: false
704
+ })
705
+ };
706
+ const columns = useMemo5(() => {
707
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== void 0);
708
+ }, [params.fields, fieldColumnMap, t, generateUrl, locale, categoryName]);
709
+ return useMemo5(() => ({ data: tableData, columns }), [tableData, columns]);
710
+ }, "useBlogPostTableStructure");
711
+ registerTableGenerator("blog-posts", useBlogPostTableStructure);
712
+
713
+ // src/features/blog/components/admin/BlogPostEditor.tsx
714
+ import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
715
+ import { useTranslations as useTranslations7 } from "next-intl";
716
+ import { useCallback as useCallback4, useMemo as useMemo6, useState as useState6 } from "react";
717
+ import { useForm as useForm2 } from "react-hook-form";
718
+ import { v4 as v42 } from "uuid";
719
+ import { z as z2 } from "zod";
720
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
721
+ var isBodyEmpty = /* @__PURE__ */ __name((body) => !body || Array.isArray(body) && body.length === 0, "isBodyEmpty");
722
+ function BlogPostEditor({ post, trigger, onSuccess }) {
723
+ const t = useTranslations7();
724
+ const categoryName = useBlogCategoryNames();
725
+ const router = useI18nRouter();
726
+ const locale = useI18nLocale();
727
+ const languageName = useBlogLanguageName();
728
+ const original = post?.original;
729
+ const [cover, setCover] = useState6(null);
730
+ const [isBodyEmptyState, setIsBodyEmptyState] = useState6(isBodyEmpty(original?.body));
731
+ const languages = useMemo6(() => getI18nSupportedLocales(), []);
732
+ const formSchema = useMemo6(
733
+ () => z2.object({
734
+ id: z2.string().uuid(),
735
+ originalLanguage: z2.string().min(1, { message: t("blog.errors.language") }),
736
+ title: z2.string().min(1, { message: t("blog.errors.title") }),
737
+ slug: z2.string().optional(),
738
+ summary: z2.string().optional(),
739
+ body: z2.any(),
740
+ coverImage: z2.string().optional(),
741
+ coverImageAlt: z2.string().optional(),
742
+ categories: z2.array(z2.object({ id: z2.string(), name: z2.string() }))
743
+ }),
744
+ []
745
+ );
746
+ const getDefaultValues = useCallback4(
747
+ () => ({
748
+ id: post?.id || v42(),
749
+ originalLanguage: post?.originalLanguage ?? (languages.includes(locale) ? locale : languages[0]),
750
+ title: original?.title ?? "",
751
+ slug: original?.slug ?? "",
752
+ summary: original?.summary ?? "",
753
+ body: original?.body || [],
754
+ // The stored value is a signed URL, not a key: only a newly picked file
755
+ // writes a key here, and only then is the field sent.
756
+ coverImage: "",
757
+ coverImageAlt: original?.coverImageAlt ?? "",
758
+ categories: (post?.categories ?? []).map((category) => ({
759
+ id: category.id,
760
+ name: blogCategoryAdminName(category)
761
+ }))
762
+ }),
763
+ [post, original, languages, locale]
764
+ );
765
+ const form = useForm2({
766
+ resolver: zodResolver2(formSchema),
767
+ defaultValues: getDefaultValues()
768
+ });
769
+ const { dirtyFields } = form.formState;
770
+ const isFormDirty = useCallback4(() => {
771
+ const dirty = { ...dirtyFields };
772
+ if (dirty.body && isBodyEmptyState) delete dirty.body;
773
+ return Object.keys(dirty).length > 0;
774
+ }, [dirtyFields, isBodyEmptyState]);
775
+ return /* @__PURE__ */ jsx8(
776
+ EditorSheet,
777
+ {
778
+ form,
779
+ entityType: t("blog.entities.post"),
780
+ entityName: original?.title,
781
+ title: post ? t("blog.admin.editPost") : t("blog.admin.newPost"),
782
+ isEdit: !!post,
783
+ module: Modules.BlogPost,
784
+ size: "lg",
785
+ isFormDirty,
786
+ onSubmit: async (values) => {
787
+ if (values.coverImage && cover) await uploadBlogImage({ key: values.coverImage, selection: cover });
788
+ const payload = {
789
+ id: values.id,
790
+ title: values.title,
791
+ // An empty summary lets the worker write one; on edit the summary is
792
+ // sent only when changed here, so an untouched AI summary is regenerated.
793
+ summary: post ? form.getFieldState("summary").isDirty ? values.summary : void 0 : values.summary || void 0,
794
+ body: values.body,
795
+ coverImage: values.coverImage && cover ? values.coverImage : void 0,
796
+ coverImageAlt: values.coverImageAlt,
797
+ categoryIds: values.categories.map((category) => category.id)
798
+ };
799
+ if (post) {
800
+ return BlogPostService.update({
801
+ ...payload,
802
+ originalLanguage: post.originalLanguage,
803
+ slug: values.slug || void 0
804
+ });
805
+ }
806
+ return BlogPostService.create({ ...payload, originalLanguage: values.originalLanguage });
807
+ },
808
+ onReset: () => {
809
+ setCover(null);
810
+ setIsBodyEmptyState(isBodyEmpty(original?.body));
811
+ return getDefaultValues();
812
+ },
813
+ onSuccess,
814
+ onNavigate: (url) => router.push(url),
815
+ trigger,
816
+ children: /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-y-4", children: [
817
+ !post && /* @__PURE__ */ jsx8(
818
+ FormSelect,
819
+ {
820
+ form,
821
+ id: "originalLanguage",
822
+ name: t("blog.fields.originalLanguage"),
823
+ values: languages.map((language) => ({ id: language, text: languageName(language) })),
824
+ isRequired: true
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsx8(FormInput, { form, id: "title", name: t("blog.fields.title"), isRequired: true }),
828
+ post && /* @__PURE__ */ jsx8(FormInput, { form, id: "slug", name: t("blog.fields.slug"), description: t("blog.fields.slugHelp") }),
829
+ /* @__PURE__ */ jsx8(
830
+ FormTextarea,
831
+ {
832
+ form,
833
+ id: "summary",
834
+ name: t("blog.fields.summary"),
835
+ description: t("blog.fields.summaryHelp")
836
+ }
837
+ ),
838
+ /* @__PURE__ */ jsx8(
839
+ BlogCoverImageField,
840
+ {
841
+ form,
842
+ id: "coverImage",
843
+ label: t("blog.fields.coverImage"),
844
+ keyPrefix: BLOG_POST_UPLOAD_PREFIX,
845
+ entityId: form.getValues("id"),
846
+ currentUrl: post?.coverImage,
847
+ alt: original?.coverImageAlt,
848
+ selection: cover,
849
+ onSelectionChange: setCover
850
+ }
851
+ ),
852
+ /* @__PURE__ */ jsx8(FormInput, { form, id: "coverImageAlt", name: t("blog.fields.coverImageAlt") }),
853
+ /* @__PURE__ */ jsx8(
854
+ EntityMultiSelector,
855
+ {
856
+ id: "categories",
857
+ form,
858
+ label: t("blog.fields.categories"),
859
+ retriever: (params) => BlogCategoryService.findMany(params),
860
+ module: Modules.BlogCategory,
861
+ getLabel: blogCategoryAdminName,
862
+ getFormValueLabel: (value) => value.name || categoryName(value.id)
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsx8(
866
+ FormBlockNote,
867
+ {
868
+ uploadKeyPrefix: BLOG_POST_UPLOAD_PREFIX,
869
+ form,
870
+ id: "body",
871
+ name: t("blog.fields.body"),
872
+ type: "blog-post",
873
+ isRequired: true,
874
+ onEmptyChange: setIsBodyEmptyState
875
+ }
876
+ )
877
+ ] })
878
+ }
879
+ );
880
+ }
881
+ __name(BlogPostEditor, "BlogPostEditor");
882
+
883
+ // src/features/blog/components/admin/BlogPostsList.tsx
884
+ import { jsx as jsx9 } from "react/jsx-runtime";
885
+ function BlogPostsList({ fullWidth } = {}) {
886
+ const t = useTranslations8();
887
+ const data = useDataListRetriever({
888
+ retriever: /* @__PURE__ */ __name((params) => BlogPostService.findMany(params), "retriever"),
889
+ retrieverParams: {},
890
+ module: Modules.BlogPost
891
+ });
892
+ const functions = [
893
+ /* @__PURE__ */ jsx9(
894
+ BlogPostEditor,
895
+ {
896
+ trigger: /* @__PURE__ */ jsx9(Button, { render: /* @__PURE__ */ jsx9("div", {}), nativeButton: false, size: "sm", variant: "outline", children: t("blog.admin.newPost") })
897
+ },
898
+ "create-blog-post"
899
+ )
900
+ ];
901
+ return /* @__PURE__ */ jsx9(
902
+ ContentListTable,
903
+ {
904
+ data,
905
+ fields: [
906
+ "title" /* title */,
907
+ "status" /* status */,
908
+ "categories" /* categories */,
909
+ "publishedAt" /* publishedAt */,
910
+ "languages" /* languages */
911
+ ],
912
+ tableGeneratorType: Modules.BlogPost,
913
+ functions,
914
+ fullWidth,
915
+ title: t("blog.entities.posts"),
916
+ emptyState: /* @__PURE__ */ jsx9(EmptyState, { icon: NewspaperIcon, title: t("blog.admin.empty") })
917
+ }
918
+ );
919
+ }
920
+ __name(BlogPostsList, "BlogPostsList");
921
+
922
+ // src/features/blog/components/admin/BlogAdminContainer.tsx
923
+ import { jsx as jsx10 } from "react/jsx-runtime";
924
+ function BlogAdminContainer() {
925
+ const t = useTranslations9();
926
+ return /* @__PURE__ */ jsx10(AdministrationProvider, { children: /* @__PURE__ */ jsx10(
927
+ RoundPageContainer,
928
+ {
929
+ module: Modules.BlogPost,
930
+ fullWidth: true,
931
+ tabs: [
932
+ { sectionKey: "posts", label: t("blog.tabs.posts"), content: /* @__PURE__ */ jsx10(BlogPostsList, { fullWidth: true }) },
933
+ { sectionKey: "categories", label: t("blog.tabs.categories"), content: /* @__PURE__ */ jsx10(BlogCategoriesList, { fullWidth: true }) },
934
+ { sectionKey: "layout", label: t("blog.tabs.layout"), content: /* @__PURE__ */ jsx10(BlogLayoutSetting, {}) }
935
+ ]
936
+ }
937
+ ) });
938
+ }
939
+ __name(BlogAdminContainer, "BlogAdminContainer");
940
+
941
+ // src/features/blog/components/admin/BlogPostContainer.tsx
942
+ import { useTranslations as useTranslations15 } from "next-intl";
943
+ import { useMemo as useMemo9 } from "react";
944
+
945
+ // src/features/blog/contexts/BlogPostContext.tsx
946
+ import { createContext, useCallback as useCallback5, useContext, useEffect as useEffect4, useMemo as useMemo7, useState as useState7 } from "react";
947
+ import { jsx as jsx11 } from "react/jsx-runtime";
948
+ var TRANSLATION_POLL_MS = 5e3;
949
+ var BlogPostContext = createContext(void 0);
950
+ var BlogPostProvider = /* @__PURE__ */ __name(({ postId, children }) => {
951
+ const [post, setPost] = useState7(void 0);
952
+ const reload = useCallback5(async () => {
953
+ const loaded = await BlogPostService.findOne({ id: postId });
954
+ setPost(loaded);
955
+ }, [postId]);
956
+ useEffect4(() => {
957
+ void reload();
958
+ }, [reload]);
959
+ const isTranslating = useMemo7(
960
+ () => (post?.translations ?? []).some(
961
+ (translation) => translation.aiStatus === "pending" || translation.aiStatus === "in_progress"
962
+ ),
963
+ [post]
964
+ );
965
+ useEffect4(() => {
966
+ if (!isTranslating) return;
967
+ const handle = setInterval(() => {
968
+ reload().catch(() => {
969
+ });
970
+ }, TRANSLATION_POLL_MS);
971
+ return () => clearInterval(handle);
972
+ }, [isTranslating, reload]);
973
+ const value = useMemo7(() => ({ post, reload }), [post, reload]);
974
+ return /* @__PURE__ */ jsx11(BlogPostContext.Provider, { value, children });
975
+ }, "BlogPostProvider");
976
+ var useBlogPost = /* @__PURE__ */ __name(() => {
977
+ const context = useContext(BlogPostContext);
978
+ if (context === void 0) {
979
+ throw new Error("useBlogPost must be used within a BlogPostProvider");
980
+ }
981
+ return context;
982
+ }, "useBlogPost");
983
+
984
+ // src/features/blog/components/admin/BlogPostDeleter.tsx
985
+ import { useTranslations as useTranslations10 } from "next-intl";
986
+ import { jsx as jsx12 } from "react/jsx-runtime";
987
+ function BlogPostDeleter() {
988
+ const t = useTranslations10();
989
+ const generateUrl = usePageUrlGenerator();
990
+ const { post } = useBlogPost();
991
+ if (!post) return null;
992
+ const type = t("blog.entities.post");
993
+ return /* @__PURE__ */ jsx12(
994
+ CommonDeleter,
995
+ {
996
+ title: t("common.delete.title", { type }),
997
+ subtitle: t("common.delete.subtitle", { type }),
998
+ description: /* @__PURE__ */ jsx12("div", { className: "text-destructive p-4 text-sm", children: t("common.delete.description", { type }) }),
999
+ showLabel: true,
1000
+ deleteFunction: () => BlogPostService.delete({ id: post.id }),
1001
+ redirectTo: generateUrl({ page: Modules.BlogPost })
1002
+ }
1003
+ );
1004
+ }
1005
+ __name(BlogPostDeleter, "BlogPostDeleter");
1006
+
1007
+ // src/features/blog/components/admin/BlogPostPublishButton.tsx
1008
+ import { EyeIcon, EyeOffIcon } from "lucide-react";
1009
+ import { useTranslations as useTranslations11 } from "next-intl";
1010
+ import { useState as useState8 } from "react";
1011
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1012
+ function BlogPostPublishButton() {
1013
+ const t = useTranslations11();
1014
+ const { post, reload } = useBlogPost();
1015
+ const [saving, setSaving] = useState8(false);
1016
+ if (!post) return null;
1017
+ const isPublished = post.status === "published";
1018
+ const toggle = /* @__PURE__ */ __name(async () => {
1019
+ setSaving(true);
1020
+ try {
1021
+ if (isPublished) await BlogPostService.unpublish({ id: post.id });
1022
+ else await BlogPostService.publish({ id: post.id });
1023
+ await reload();
1024
+ } catch (error) {
1025
+ errorToast({ title: t("common.errors.error"), error });
1026
+ } finally {
1027
+ setSaving(false);
1028
+ }
1029
+ }, "toggle");
1030
+ return /* @__PURE__ */ jsxs8(
1031
+ Button,
1032
+ {
1033
+ type: "button",
1034
+ size: "sm",
1035
+ variant: isPublished ? "outline" : "default",
1036
+ onClick: () => void toggle(),
1037
+ disabled: saving,
1038
+ children: [
1039
+ isPublished ? /* @__PURE__ */ jsx13(EyeOffIcon, {}) : /* @__PURE__ */ jsx13(EyeIcon, {}),
1040
+ isPublished ? t("blog.actions.unpublish") : t("blog.actions.publish")
1041
+ ]
1042
+ }
1043
+ );
1044
+ }
1045
+ __name(BlogPostPublishButton, "BlogPostPublishButton");
1046
+
1047
+ // src/features/blog/components/admin/BlogPostTranslationPanel.tsx
1048
+ import { PencilIcon } from "lucide-react";
1049
+ import { useTranslations as useTranslations14 } from "next-intl";
1050
+ import Image2 from "next/image";
1051
+ import { useState as useState10 } from "react";
1052
+
1053
+ // src/features/blog/components/admin/BlogPostComparison.tsx
1054
+ import { useTranslations as useTranslations12 } from "next-intl";
1055
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1056
+ function BlogPostTranslationFields({ translation }) {
1057
+ const t = useTranslations12();
1058
+ return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-y-4", children: [
1059
+ /* @__PURE__ */ jsx14(DetailField, { label: t("blog.fields.title"), value: translation.title }),
1060
+ /* @__PURE__ */ jsx14(DetailField, { label: t("blog.fields.slug"), value: translation.slug }),
1061
+ /* @__PURE__ */ jsx14(DetailField, { label: t("blog.fields.summary"), value: translation.summary }),
1062
+ /* @__PURE__ */ jsx14(DetailField, { label: t("blog.fields.coverImageAlt"), value: translation.coverImageAlt }),
1063
+ /* @__PURE__ */ jsx14(DetailField, { label: t("blog.fields.body"), children: /* @__PURE__ */ jsx14(BlockNoteEditorContainer, { id: translation.id, type: "blog-post", initialContent: translation.body }) })
1064
+ ] });
1065
+ }
1066
+ __name(BlogPostTranslationFields, "BlogPostTranslationFields");
1067
+ function BlogPostComparison({ original, translation }) {
1068
+ const t = useTranslations12();
1069
+ const languageName = useBlogLanguageName();
1070
+ return /* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
1071
+ /* @__PURE__ */ jsxs9("div", { className: "bg-muted/40 flex flex-col gap-y-4 rounded-lg border p-4", children: [
1072
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-x-2", children: [
1073
+ /* @__PURE__ */ jsx14(MicroLabel, { as: "h3", children: languageName(original.language) }),
1074
+ /* @__PURE__ */ jsx14(Badge, { variant: "softGray", children: t("blog.translation.original") })
1075
+ ] }),
1076
+ /* @__PURE__ */ jsx14(BlogPostTranslationFields, { translation: original })
1077
+ ] }),
1078
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-y-4 rounded-lg border p-4", children: [
1079
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-x-2", children: [
1080
+ /* @__PURE__ */ jsx14(MicroLabel, { as: "h3", children: languageName(translation.language) }),
1081
+ translation.source === "manual" && /* @__PURE__ */ jsx14(Badge, { variant: "softAmber", children: t("blog.translation.editedByHand") })
1082
+ ] }),
1083
+ /* @__PURE__ */ jsx14(BlogPostTranslationFields, { translation })
1084
+ ] })
1085
+ ] });
1086
+ }
1087
+ __name(BlogPostComparison, "BlogPostComparison");
1088
+
1089
+ // src/features/blog/components/admin/BlogPostTranslationEditor.tsx
1090
+ import { zodResolver as zodResolver3 } from "@hookform/resolvers/zod";
1091
+ import { useTranslations as useTranslations13 } from "next-intl";
1092
+ import { useCallback as useCallback6, useMemo as useMemo8, useState as useState9 } from "react";
1093
+ import { useForm as useForm3 } from "react-hook-form";
1094
+ import { z as z3 } from "zod";
1095
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1096
+ var isBodyEmpty2 = /* @__PURE__ */ __name((body) => !body || Array.isArray(body) && body.length === 0, "isBodyEmpty");
1097
+ var buildFormSchema2 = /* @__PURE__ */ __name((t) => z3.object({
1098
+ id: z3.string().uuid(),
1099
+ title: z3.string().min(1, { message: t("blog.errors.title") }),
1100
+ slug: z3.string().optional(),
1101
+ summary: z3.string().optional(),
1102
+ body: z3.any(),
1103
+ coverImageAlt: z3.string().optional()
1104
+ }), "buildFormSchema");
1105
+ function BlogPostTranslationEditor({ translation, trigger, onSuccess }) {
1106
+ const t = useTranslations13();
1107
+ const formSchema = useMemo8(() => buildFormSchema2(t), [t]);
1108
+ const languageName = useBlogLanguageName();
1109
+ const [isBodyEmptyState, setIsBodyEmptyState] = useState9(isBodyEmpty2(translation.body));
1110
+ const getDefaultValues = useCallback6(
1111
+ () => ({
1112
+ id: translation.id,
1113
+ title: translation.title ?? "",
1114
+ slug: translation.slug ?? "",
1115
+ summary: translation.summary ?? "",
1116
+ body: translation.body || [],
1117
+ coverImageAlt: translation.coverImageAlt ?? ""
1118
+ }),
1119
+ [translation]
1120
+ );
1121
+ const form = useForm3({
1122
+ resolver: zodResolver3(formSchema),
1123
+ defaultValues: getDefaultValues()
1124
+ });
1125
+ const { dirtyFields } = form.formState;
1126
+ const isFormDirty = useCallback6(() => {
1127
+ const dirty = { ...dirtyFields };
1128
+ if (dirty.body && isBodyEmptyState) delete dirty.body;
1129
+ return Object.keys(dirty).length > 0;
1130
+ }, [dirtyFields, isBodyEmptyState]);
1131
+ return /* @__PURE__ */ jsx15(
1132
+ EditorSheet,
1133
+ {
1134
+ form,
1135
+ entityType: t("blog.entities.post"),
1136
+ entityName: translation.title,
1137
+ title: `${t("blog.admin.editPost")} \xB7 ${languageName(translation.language)}`,
1138
+ isEdit: true,
1139
+ module: Modules.BlogPostTranslation,
1140
+ size: "lg",
1141
+ isFormDirty,
1142
+ onSubmit: async (values) => BlogPostTranslationService.update({
1143
+ id: values.id,
1144
+ title: values.title,
1145
+ slug: values.slug || void 0,
1146
+ summary: values.summary,
1147
+ body: values.body,
1148
+ coverImageAlt: values.coverImageAlt
1149
+ }),
1150
+ onReset: () => {
1151
+ setIsBodyEmptyState(isBodyEmpty2(translation.body));
1152
+ return getDefaultValues();
1153
+ },
1154
+ onSuccess: onSuccess ?? (() => {
1155
+ }),
1156
+ trigger,
1157
+ children: /* @__PURE__ */ jsxs10("div", { className: "flex w-full flex-col gap-y-4", children: [
1158
+ /* @__PURE__ */ jsx15(FormInput, { form, id: "title", name: t("blog.fields.title"), isRequired: true }),
1159
+ /* @__PURE__ */ jsx15(FormInput, { form, id: "slug", name: t("blog.fields.slug"), description: t("blog.fields.slugHelp") }),
1160
+ /* @__PURE__ */ jsx15(FormTextarea, { form, id: "summary", name: t("blog.fields.summary") }),
1161
+ /* @__PURE__ */ jsx15(FormInput, { form, id: "coverImageAlt", name: t("blog.fields.coverImageAlt") }),
1162
+ /* @__PURE__ */ jsx15(
1163
+ FormBlockNote,
1164
+ {
1165
+ uploadKeyPrefix: BLOG_POST_UPLOAD_PREFIX,
1166
+ form,
1167
+ id: "body",
1168
+ name: t("blog.fields.body"),
1169
+ type: "blog-post",
1170
+ isRequired: true,
1171
+ onEmptyChange: setIsBodyEmptyState
1172
+ }
1173
+ )
1174
+ ] })
1175
+ }
1176
+ );
1177
+ }
1178
+ __name(BlogPostTranslationEditor, "BlogPostTranslationEditor");
1179
+
1180
+ // src/features/blog/components/admin/BlogPostTranslationPanel.tsx
1181
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1182
+ function BlogPostTranslationPanel({ post, translation, onChanged }) {
1183
+ const t = useTranslations14();
1184
+ const categoryName = useBlogCategoryNames();
1185
+ const languageName = useBlogLanguageName();
1186
+ const formatDate = useBlogDateFormatter();
1187
+ const [isComparing, setIsComparing] = useState10(false);
1188
+ const original = post.original;
1189
+ const reload = onChanged ?? (async () => {
1190
+ });
1191
+ const editTrigger = /* @__PURE__ */ jsxs11(Button, { render: /* @__PURE__ */ jsx16("div", {}), nativeButton: false, size: "sm", variant: "outline", children: [
1192
+ /* @__PURE__ */ jsx16(PencilIcon, {}),
1193
+ t("blog.actions.edit")
1194
+ ] });
1195
+ if (translation.isOriginal) {
1196
+ const statusLabel = {
1197
+ draft: t("blog.status.draft"),
1198
+ published: t("blog.status.published"),
1199
+ unpublished: t("blog.status.unpublished")
1200
+ }[post.status];
1201
+ return /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-y-6 p-4", children: [
1202
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-x-4", children: [
1203
+ /* @__PURE__ */ jsx16(Badge, { variant: "softGray", children: t("blog.translation.original") }),
1204
+ /* @__PURE__ */ jsx16(BlogPostEditor, { post, trigger: editTrigger, onSuccess: reload })
1205
+ ] }),
1206
+ post.coverImage && /* @__PURE__ */ jsx16(
1207
+ Image2,
1208
+ {
1209
+ src: post.coverImage,
1210
+ alt: translation.coverImageAlt ?? translation.title,
1211
+ width: 640,
1212
+ height: 360,
1213
+ className: "h-auto max-h-72 w-auto rounded-lg object-cover",
1214
+ unoptimized: isRemoteImageSrc(post.coverImage)
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsxs11("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
1218
+ /* @__PURE__ */ jsx16(DetailField, { label: t("blog.fields.status"), value: statusLabel }),
1219
+ /* @__PURE__ */ jsx16(DetailField, { label: t("blog.fields.publishedAt"), value: formatDate(post.publishedAt) || "\u2014" }),
1220
+ /* @__PURE__ */ jsx16(
1221
+ DetailField,
1222
+ {
1223
+ label: t("blog.fields.categories"),
1224
+ value: post.categories.map((category) => categoryName(category.id)).filter(Boolean).join(", ") || "\u2014"
1225
+ }
1226
+ )
1227
+ ] }),
1228
+ /* @__PURE__ */ jsx16(BlogPostTranslationFields, { translation })
1229
+ ] });
1230
+ }
1231
+ const isWorking = translation.aiStatus === "pending" || translation.aiStatus === "in_progress";
1232
+ return /* @__PURE__ */ jsxs11("div", { className: "flex w-full flex-col gap-y-6 p-4", children: [
1233
+ /* @__PURE__ */ jsx16(Alert, { children: /* @__PURE__ */ jsxs11(AlertDescription, { className: "flex flex-col gap-y-2", children: [
1234
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2", children: [
1235
+ translation.source === "manual" && /* @__PURE__ */ jsx16(Badge, { variant: "softAmber", children: t("blog.translation.editedByHand") }),
1236
+ original && translation.translatedAt && /* @__PURE__ */ jsx16("span", { children: t("blog.translation.translatedFrom", {
1237
+ language: languageName(original.language),
1238
+ date: formatDate(translation.translatedAt)
1239
+ }) })
1240
+ ] }),
1241
+ /* @__PURE__ */ jsx16("span", { children: t("blog.translation.overwriteWarning") }),
1242
+ original && /* @__PURE__ */ jsx16("div", { children: /* @__PURE__ */ jsx16(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setIsComparing((value) => !value), children: isComparing ? t("blog.actions.hideComparison") : t("blog.actions.compareWithOriginal") }) })
1243
+ ] }) }),
1244
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between gap-x-4", children: [
1245
+ /* @__PURE__ */ jsx16(
1246
+ BlogTranslationStatus,
1247
+ {
1248
+ aiStatus: translation.aiStatus,
1249
+ onRetranslate: async () => {
1250
+ await BlogPostTranslationService.retranslate({ id: translation.id });
1251
+ await reload();
1252
+ }
1253
+ }
1254
+ ),
1255
+ !isWorking && /* @__PURE__ */ jsx16(BlogPostTranslationEditor, { translation, trigger: editTrigger, onSuccess: reload })
1256
+ ] }),
1257
+ isComparing && original ? /* @__PURE__ */ jsx16(BlogPostComparison, { original, translation }) : /* @__PURE__ */ jsx16(BlogPostTranslationFields, { translation })
1258
+ ] });
1259
+ }
1260
+ __name(BlogPostTranslationPanel, "BlogPostTranslationPanel");
1261
+
1262
+ // src/features/blog/components/admin/BlogPostContainer.tsx
1263
+ import { jsx as jsx17 } from "react/jsx-runtime";
1264
+ function BlogPostContainer({ postId }) {
1265
+ return /* @__PURE__ */ jsx17(BlogPostProvider, { postId, children: /* @__PURE__ */ jsx17(BlogPostShell, {}) });
1266
+ }
1267
+ __name(BlogPostContainer, "BlogPostContainer");
1268
+ function BlogPostShell() {
1269
+ const t = useTranslations15();
1270
+ const generateUrl = usePageUrlGenerator();
1271
+ const languageName = useBlogLanguageName();
1272
+ const { post, reload } = useBlogPost();
1273
+ const translations = useMemo9(() => {
1274
+ const all = post?.translations ?? [];
1275
+ return [
1276
+ ...all.filter((translation) => translation.isOriginal),
1277
+ ...all.filter((translation) => !translation.isOriginal)
1278
+ ];
1279
+ }, [post]);
1280
+ if (!post) return null;
1281
+ const title = post.original?.title ?? post.title ?? "";
1282
+ const breadcrumbs = [
1283
+ { name: t("blog.admin.title"), href: generateUrl({ page: Modules.BlogPost }) },
1284
+ { name: title }
1285
+ ];
1286
+ const statusLabel = {
1287
+ draft: t("blog.status.draft"),
1288
+ published: t("blog.status.published"),
1289
+ unpublished: t("blog.status.unpublished")
1290
+ }[post.status];
1291
+ const tabLabel = /* @__PURE__ */ __name((translation) => {
1292
+ const parts = [languageName(translation.language)];
1293
+ if (translation.isOriginal) parts.push(t("blog.translation.original"));
1294
+ if (translation.aiStatus === "pending" || translation.aiStatus === "in_progress")
1295
+ parts.push(t("blog.translation.translating"));
1296
+ if (translation.aiStatus === "failed") parts.push(t("blog.translation.failed"));
1297
+ return parts.join(" \xB7 ");
1298
+ }, "tabLabel");
1299
+ return /* @__PURE__ */ jsx17(
1300
+ SharedProvider,
1301
+ {
1302
+ value: {
1303
+ breadcrumbs,
1304
+ title: {
1305
+ type: t("blog.admin.title"),
1306
+ element: title,
1307
+ functions: [
1308
+ /* @__PURE__ */ jsx17(Badge, { variant: BLOG_STATUS_BADGE[post.status], children: statusLabel }, "blogPostStatus"),
1309
+ /* @__PURE__ */ jsx17(BlogPostPublishButton, {}, "blogPostPublish"),
1310
+ /* @__PURE__ */ jsx17(BlogPostDeleter, {}, "blogPostDeleter")
1311
+ ]
1312
+ }
1313
+ },
1314
+ children: /* @__PURE__ */ jsx17(
1315
+ RoundPageContainer,
1316
+ {
1317
+ module: Modules.BlogPost,
1318
+ id: post.id,
1319
+ fullWidth: true,
1320
+ forceHeader: true,
1321
+ tabs: translations.map((translation) => ({
1322
+ sectionKey: translation.language,
1323
+ label: tabLabel(translation),
1324
+ content: /* @__PURE__ */ jsx17(BlogPostTranslationPanel, { post, translation, onChanged: reload })
1325
+ }))
1326
+ }
1327
+ )
1328
+ }
1329
+ );
1330
+ }
1331
+ __name(BlogPostShell, "BlogPostShell");
1332
+
1333
+ // src/features/blog/components/public/BlogCategoryChips.tsx
1334
+ import { useTranslations as useTranslations16 } from "next-intl";
1335
+
1336
+ // src/features/blog/components/public/blogPublicHelpers.ts
1337
+ function blogListHref(basePath, params) {
1338
+ const search = new URLSearchParams();
1339
+ if (params.category) search.set("category", params.category);
1340
+ if (params.q) search.set("q", params.q);
1341
+ if (params.page && params.page > 1) search.set("page", String(params.page));
1342
+ const query = search.toString();
1343
+ return query ? `${basePath}?${query}` : basePath;
1344
+ }
1345
+ __name(blogListHref, "blogListHref");
1346
+ function blogPostHref(basePath, slug) {
1347
+ return `${basePath}/${encodeURIComponent(slug)}`;
1348
+ }
1349
+ __name(blogPostHref, "blogPostHref");
1350
+ function resolvePostCategory(post, categories) {
1351
+ for (const id of post.categoryIds) {
1352
+ const category = categories.find((c) => c.id === id);
1353
+ if (category) return category;
1354
+ }
1355
+ return void 0;
1356
+ }
1357
+ __name(resolvePostCategory, "resolvePostCategory");
1358
+
1359
+ // src/features/blog/components/public/BlogCategoryChips.tsx
1360
+ import { jsx as jsx18 } from "react/jsx-runtime";
1361
+ function BlogCategoryChips(props) {
1362
+ const { categories, basePath, activeCategorySlug, query, variant = "chips", className } = props;
1363
+ const t = useTranslations16();
1364
+ const items = [
1365
+ {
1366
+ key: "all",
1367
+ label: t("blog.public.all"),
1368
+ href: blogListHref(basePath, { q: query }),
1369
+ active: !activeCategorySlug
1370
+ },
1371
+ ...categories.map((category) => ({
1372
+ key: category.id,
1373
+ label: category.name,
1374
+ href: blogListHref(basePath, { category: category.slug, q: query }),
1375
+ active: category.slug === activeCategorySlug
1376
+ }))
1377
+ ];
1378
+ const inactiveVariant = variant === "chips" ? "outline" : "ghost";
1379
+ return /* @__PURE__ */ jsx18("nav", { "aria-label": t("blog.entities.categories"), className, children: /* @__PURE__ */ jsx18("ul", { className: variant === "chips" ? "flex flex-wrap items-center gap-2" : "flex flex-col items-start gap-2", children: items.map((item) => /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsx18(
1380
+ Badge,
1381
+ {
1382
+ variant: item.active ? "default" : inactiveVariant,
1383
+ render: /* @__PURE__ */ jsx18(Link, { href: item.href, "aria-current": item.active ? "page" : void 0 }),
1384
+ children: item.label
1385
+ }
1386
+ ) }, item.key)) }) });
1387
+ }
1388
+ __name(BlogCategoryChips, "BlogCategoryChips");
1389
+
1390
+ // src/features/blog/components/public/BlogListParts.tsx
1391
+ import { useTranslations as useTranslations17 } from "next-intl";
1392
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1393
+ function BlogListHeader(props) {
1394
+ const { align = "start", showSubtitle = true } = props;
1395
+ const t = useTranslations17();
1396
+ return /* @__PURE__ */ jsxs12("header", { className: align === "center" ? "text-center" : void 0, children: [
1397
+ /* @__PURE__ */ jsx19(
1398
+ ContentTitle,
1399
+ {
1400
+ type: t("blog.public.eyebrow"),
1401
+ element: t("blog.public.title"),
1402
+ headingLevel: 1,
1403
+ className: cn("mb-0", align === "center" && "[&_div]:justify-center")
1404
+ }
1405
+ ),
1406
+ showSubtitle ? /* @__PURE__ */ jsx19("p", { className: "text-muted-foreground mt-3 text-sm leading-relaxed", children: t("blog.public.subtitle") }) : null
1407
+ ] });
1408
+ }
1409
+ __name(BlogListHeader, "BlogListHeader");
1410
+ function BlogListEmpty(props) {
1411
+ const t = useTranslations17();
1412
+ return /* @__PURE__ */ jsx19("p", { className: cn("text-muted-foreground py-12 text-center text-sm", props.className), children: props.query ? t("blog.public.noResults") : t("blog.public.empty") });
1413
+ }
1414
+ __name(BlogListEmpty, "BlogListEmpty");
1415
+ function BlogLoadMore(props) {
1416
+ const { basePath, page, activeCategorySlug, query, className } = props;
1417
+ const t = useTranslations17();
1418
+ return /* @__PURE__ */ jsx19("div", { className: cn("flex justify-center", className), children: /* @__PURE__ */ jsx19(
1419
+ Link,
1420
+ {
1421
+ href: blogListHref(basePath, { category: activeCategorySlug, q: query, page: page + 1 }),
1422
+ scroll: false,
1423
+ className: buttonVariants({ variant: "outline" }),
1424
+ children: t("blog.public.loadMore")
1425
+ }
1426
+ ) });
1427
+ }
1428
+ __name(BlogLoadMore, "BlogLoadMore");
1429
+
1430
+ // src/features/blog/components/public/BlogPostCard.tsx
1431
+ import { useFormatter as useFormatter2 } from "next-intl";
1432
+ import Image3 from "next/image";
1433
+
1434
+ // src/features/blog/components/public/BlogPostMeta.tsx
1435
+ import { useFormatter, useTranslations as useTranslations18 } from "next-intl";
1436
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1437
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
1438
+ function BlogPostMeta(props) {
1439
+ const { post, showAuthor, showDate, showUpdated, className } = props;
1440
+ const t = useTranslations18();
1441
+ const format = useFormatter();
1442
+ const parts = [];
1443
+ if (showAuthor && post.authorName) parts.push(t("blog.public.by", { author: post.authorName }));
1444
+ if (showDate && post.publishedAt) {
1445
+ parts.push(format.dateTime(new Date(post.publishedAt), { dateStyle: "medium" }));
1446
+ }
1447
+ if (showUpdated && post.publishedAt && post.updatedAt && new Date(post.updatedAt).getTime() - new Date(post.publishedAt).getTime() > ONE_DAY_MS) {
1448
+ parts.push(t("blog.public.updated", { date: format.dateTime(new Date(post.updatedAt), { dateStyle: "medium" }) }));
1449
+ }
1450
+ parts.push(t("blog.public.readingTime", { minutes: post.readingMinutes }));
1451
+ return /* @__PURE__ */ jsx20("p", { className: cn("text-muted-foreground text-xs", className), children: parts.map((part, index) => /* @__PURE__ */ jsxs13("span", { children: [
1452
+ index > 0 ? " \xB7 " : null,
1453
+ part
1454
+ ] }, index)) });
1455
+ }
1456
+ __name(BlogPostMeta, "BlogPostMeta");
1457
+
1458
+ // src/features/blog/components/public/BlogPostCard.tsx
1459
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
1460
+ function BlogPostCover(props) {
1461
+ const { src, alt, sizes, priority, className } = props;
1462
+ return /* @__PURE__ */ jsx21("div", { className: cn("border-border bg-primary/10 relative overflow-hidden rounded-lg border", className), children: src ? /* @__PURE__ */ jsx21(
1463
+ Image3,
1464
+ {
1465
+ src,
1466
+ alt: alt ?? "",
1467
+ fill: true,
1468
+ sizes,
1469
+ priority,
1470
+ className: "object-cover",
1471
+ unoptimized: isRemoteImageSrc(src)
1472
+ }
1473
+ ) : null });
1474
+ }
1475
+ __name(BlogPostCover, "BlogPostCover");
1476
+ function BlogPostCard(props) {
1477
+ const { post, categories, basePath, variant = "card", priority, headingLevel = "h2" } = props;
1478
+ const format = useFormatter2();
1479
+ const category = resolvePostCategory(post, categories);
1480
+ const isProminent = variant === "hero" || variant === "large";
1481
+ const eyebrow = variant === "row" && post.publishedAt ? [category?.name, format.dateTime(new Date(post.publishedAt), { day: "numeric", month: "short" })].filter(Boolean).join(" \xB7 ") : category?.name;
1482
+ const text = /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 flex-col", children: [
1483
+ eyebrow ? /* @__PURE__ */ jsx21(MicroLabel, { as: "span", children: eyebrow }) : null,
1484
+ /* @__PURE__ */ jsx21(SectionHeader, { level: headingLevel === "h3" ? 3 : 2, className: "mt-1.5", children: /* @__PURE__ */ jsx21(Link, { href: blogPostHref(basePath, post.slug), className: "after:absolute after:inset-0", children: post.title }) }),
1485
+ post.summary ? /* @__PURE__ */ jsx21("p", { className: "text-muted-foreground mt-2 text-sm leading-relaxed", children: post.summary }) : null,
1486
+ /* @__PURE__ */ jsx21(BlogPostMeta, { post, showAuthor: isProminent, showDate: isProminent, className: "mt-3" })
1487
+ ] });
1488
+ if (variant === "hero") {
1489
+ return /* @__PURE__ */ jsxs14("article", { className: "group relative grid items-center gap-6 md:grid-cols-[1.4fr_1fr] md:gap-10", children: [
1490
+ /* @__PURE__ */ jsx21(
1491
+ BlogPostCover,
1492
+ {
1493
+ src: post.coverImage,
1494
+ alt: post.coverImageAlt,
1495
+ sizes: "(min-width: 1024px) 560px, (min-width: 768px) 58vw, 100vw",
1496
+ priority,
1497
+ className: "aspect-[16/10]"
1498
+ }
1499
+ ),
1500
+ text
1501
+ ] });
1502
+ }
1503
+ if (variant === "row") {
1504
+ return /* @__PURE__ */ jsxs14("article", { className: "group relative grid items-center gap-4 sm:grid-cols-[10rem_1fr] sm:gap-6", children: [
1505
+ /* @__PURE__ */ jsx21(
1506
+ BlogPostCover,
1507
+ {
1508
+ src: post.coverImage,
1509
+ alt: post.coverImageAlt,
1510
+ sizes: "(min-width: 640px) 160px, 100vw",
1511
+ priority,
1512
+ className: "aspect-[16/10]"
1513
+ }
1514
+ ),
1515
+ text
1516
+ ] });
1517
+ }
1518
+ return /* @__PURE__ */ jsxs14("article", { className: "group relative flex flex-col gap-4", children: [
1519
+ /* @__PURE__ */ jsx21(
1520
+ BlogPostCover,
1521
+ {
1522
+ src: post.coverImage,
1523
+ alt: post.coverImageAlt,
1524
+ sizes: variant === "large" ? "(min-width: 1024px) 760px, 100vw" : "(min-width: 1024px) 320px, (min-width: 640px) 50vw, 100vw",
1525
+ priority,
1526
+ className: variant === "large" ? "aspect-[16/8]" : "aspect-[16/10]"
1527
+ }
1528
+ ),
1529
+ text
1530
+ ] });
1531
+ }
1532
+ __name(BlogPostCard, "BlogPostCard");
1533
+
1534
+ // src/features/blog/components/public/BlogSearchBox.tsx
1535
+ import { SearchIcon } from "lucide-react";
1536
+ import { useTranslations as useTranslations19 } from "next-intl";
1537
+ import { useEffect as useEffect5, useState as useState11 } from "react";
1538
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1539
+ function BlogSearchBox(props) {
1540
+ const { basePath, query, activeCategorySlug, className } = props;
1541
+ const t = useTranslations19();
1542
+ const router = useI18nRouter();
1543
+ const [value, setValue] = useState11(query ?? "");
1544
+ useEffect5(() => setValue(query ?? ""), [query]);
1545
+ const onSubmit = /* @__PURE__ */ __name((event) => {
1546
+ event.preventDefault();
1547
+ const q = value.trim();
1548
+ router.push(blogListHref(basePath, { category: activeCategorySlug, q: q || void 0 }));
1549
+ }, "onSubmit");
1550
+ return /* @__PURE__ */ jsx22("form", { role: "search", onSubmit, className, children: /* @__PURE__ */ jsxs15(InputGroup, { children: [
1551
+ /* @__PURE__ */ jsx22(InputGroupAddon, { children: /* @__PURE__ */ jsx22(SearchIcon, {}) }),
1552
+ /* @__PURE__ */ jsx22(
1553
+ InputGroupInput,
1554
+ {
1555
+ type: "search",
1556
+ name: "q",
1557
+ value,
1558
+ onChange: (event) => setValue(event.target.value),
1559
+ placeholder: t("blog.public.searchPlaceholder"),
1560
+ "aria-label": t("blog.public.search")
1561
+ }
1562
+ )
1563
+ ] }) });
1564
+ }
1565
+ __name(BlogSearchBox, "BlogSearchBox");
1566
+
1567
+ // src/features/blog/components/public/BlogListColumn.tsx
1568
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1569
+ function BlogListColumn(props) {
1570
+ const { list, categories, activeCategorySlug, query, page, basePath } = props;
1571
+ return /* @__PURE__ */ jsx23("section", { className: "px-4 pt-16 pb-20 md:pt-20", children: /* @__PURE__ */ jsxs16("div", { className: "mx-auto max-w-2xl", children: [
1572
+ /* @__PURE__ */ jsx23(BlogListHeader, { align: "center" }),
1573
+ /* @__PURE__ */ jsx23(
1574
+ BlogCategoryChips,
1575
+ {
1576
+ categories,
1577
+ basePath,
1578
+ activeCategorySlug,
1579
+ query,
1580
+ className: "mt-8 [&>ul]:justify-center"
1581
+ }
1582
+ ),
1583
+ /* @__PURE__ */ jsx23(BlogSearchBox, { basePath, query, activeCategorySlug, className: "mt-4" }),
1584
+ list.posts.length === 0 ? /* @__PURE__ */ jsx23(BlogListEmpty, { query }) : /* @__PURE__ */ jsx23("div", { className: "divide-border mt-10 flex flex-col divide-y", children: list.posts.map((post, index) => /* @__PURE__ */ jsx23("div", { className: "py-6 first:pt-0", children: /* @__PURE__ */ jsx23(
1585
+ BlogPostCard,
1586
+ {
1587
+ post,
1588
+ categories,
1589
+ basePath,
1590
+ variant: "row",
1591
+ priority: index === 0
1592
+ }
1593
+ ) }, post.id)) }),
1594
+ list.hasMore ? /* @__PURE__ */ jsx23(
1595
+ BlogLoadMore,
1596
+ {
1597
+ basePath,
1598
+ page,
1599
+ activeCategorySlug,
1600
+ query,
1601
+ className: "mt-10"
1602
+ }
1603
+ ) : null
1604
+ ] }) });
1605
+ }
1606
+ __name(BlogListColumn, "BlogListColumn");
1607
+
1608
+ // src/features/blog/components/public/BlogListFeatured.tsx
1609
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1610
+ function BlogListFeatured(props) {
1611
+ const { list, categories, activeCategorySlug, query, page, basePath } = props;
1612
+ const [hero, ...rest] = list.posts;
1613
+ return /* @__PURE__ */ jsx24("section", { className: "px-4 pt-16 pb-20 md:pt-20", children: /* @__PURE__ */ jsxs17("div", { className: "mx-auto max-w-5xl", children: [
1614
+ /* @__PURE__ */ jsx24(BlogListHeader, {}),
1615
+ hero ? /* @__PURE__ */ jsx24("div", { className: "mt-10", children: /* @__PURE__ */ jsx24(BlogPostCard, { post: hero, categories, basePath, variant: "hero", priority: true }) }) : null,
1616
+ /* @__PURE__ */ jsxs17("div", { className: "mt-12 flex flex-col gap-3 md:flex-row md:items-center", children: [
1617
+ /* @__PURE__ */ jsx24(
1618
+ BlogCategoryChips,
1619
+ {
1620
+ categories,
1621
+ basePath,
1622
+ activeCategorySlug,
1623
+ query,
1624
+ className: "flex-1"
1625
+ }
1626
+ ),
1627
+ /* @__PURE__ */ jsx24(
1628
+ BlogSearchBox,
1629
+ {
1630
+ basePath,
1631
+ query,
1632
+ activeCategorySlug,
1633
+ className: "md:w-64"
1634
+ }
1635
+ )
1636
+ ] }),
1637
+ list.posts.length === 0 ? /* @__PURE__ */ jsx24(BlogListEmpty, { query }) : null,
1638
+ rest.length > 0 ? /* @__PURE__ */ jsx24("div", { className: "mt-8 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3", children: rest.map((post) => /* @__PURE__ */ jsx24(BlogPostCard, { post, categories, basePath }, post.id)) }) : null,
1639
+ list.hasMore ? /* @__PURE__ */ jsx24(
1640
+ BlogLoadMore,
1641
+ {
1642
+ basePath,
1643
+ page,
1644
+ activeCategorySlug,
1645
+ query,
1646
+ className: "mt-12"
1647
+ }
1648
+ ) : null
1649
+ ] }) });
1650
+ }
1651
+ __name(BlogListFeatured, "BlogListFeatured");
1652
+
1653
+ // src/features/blog/components/public/BlogListRail.tsx
1654
+ import { useTranslations as useTranslations20 } from "next-intl";
1655
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1656
+ function BlogListRail(props) {
1657
+ const { list, categories, activeCategorySlug, query, page, basePath } = props;
1658
+ const t = useTranslations20();
1659
+ const [first, ...rest] = list.posts;
1660
+ return /* @__PURE__ */ jsx25("section", { className: "px-4 pt-16 pb-20 md:pt-20", children: /* @__PURE__ */ jsxs18("div", { className: "mx-auto grid max-w-5xl gap-10 md:grid-cols-[13rem_1fr] md:gap-12", children: [
1661
+ /* @__PURE__ */ jsxs18("aside", { className: "flex flex-col gap-6", children: [
1662
+ /* @__PURE__ */ jsx25(BlogListHeader, { showSubtitle: false }),
1663
+ /* @__PURE__ */ jsx25(BlogSearchBox, { basePath, query, activeCategorySlug }),
1664
+ /* @__PURE__ */ jsxs18("div", { children: [
1665
+ /* @__PURE__ */ jsx25(MicroLabel, { as: "span", className: "block", children: t("blog.entities.categories") }),
1666
+ /* @__PURE__ */ jsx25(
1667
+ BlogCategoryChips,
1668
+ {
1669
+ categories,
1670
+ basePath,
1671
+ activeCategorySlug,
1672
+ query,
1673
+ variant: "list",
1674
+ className: "mt-3"
1675
+ }
1676
+ )
1677
+ ] })
1678
+ ] }),
1679
+ /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1680
+ first ? /* @__PURE__ */ jsx25(BlogPostCard, { post: first, categories, basePath, variant: "large", priority: true }) : /* @__PURE__ */ jsx25(BlogListEmpty, { query }),
1681
+ rest.length > 0 ? /* @__PURE__ */ jsx25("div", { className: "border-border mt-10 grid gap-x-6 gap-y-10 border-t pt-10 sm:grid-cols-2", children: rest.map((post) => /* @__PURE__ */ jsx25(BlogPostCard, { post, categories, basePath }, post.id)) }) : null,
1682
+ list.hasMore ? /* @__PURE__ */ jsx25(
1683
+ BlogLoadMore,
1684
+ {
1685
+ basePath,
1686
+ page,
1687
+ activeCategorySlug,
1688
+ query,
1689
+ className: "mt-12"
1690
+ }
1691
+ ) : null
1692
+ ] })
1693
+ ] }) });
1694
+ }
1695
+ __name(BlogListRail, "BlogListRail");
1696
+
1697
+ // src/features/blog/components/public/BlogList.tsx
1698
+ import { jsx as jsx26 } from "react/jsx-runtime";
1699
+ function BlogList(props) {
1700
+ switch (props.layout) {
1701
+ case "column":
1702
+ return /* @__PURE__ */ jsx26(BlogListColumn, { ...props });
1703
+ case "rail":
1704
+ return /* @__PURE__ */ jsx26(BlogListRail, { ...props });
1705
+ case "featured":
1706
+ default:
1707
+ return /* @__PURE__ */ jsx26(BlogListFeatured, { ...props });
1708
+ }
1709
+ }
1710
+ __name(BlogList, "BlogList");
1711
+
1712
+ // src/features/blog/components/public/BlogPostView.tsx
1713
+ import { ArrowLeftIcon } from "lucide-react";
1714
+ import { useTranslations as useTranslations21 } from "next-intl";
1715
+ import { jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
1716
+ function BlogPostView(props) {
1717
+ const { post, categories, related, basePath } = props;
1718
+ const t = useTranslations21();
1719
+ const category = resolvePostCategory(post, categories);
1720
+ const more = related.slice(0, 3);
1721
+ return /* @__PURE__ */ jsxs19("article", { className: "px-4 pt-12 pb-20 md:pt-16", children: [
1722
+ /* @__PURE__ */ jsxs19("div", { className: "mx-auto max-w-3xl", children: [
1723
+ /* @__PURE__ */ jsxs19(Link, { href: basePath, className: "inline-flex items-center gap-1.5", children: [
1724
+ /* @__PURE__ */ jsx27(ArrowLeftIcon, { className: "size-4 rtl:rotate-180" }),
1725
+ t("blog.public.back")
1726
+ ] }),
1727
+ /* @__PURE__ */ jsxs19("header", { className: "mt-8", children: [
1728
+ /* @__PURE__ */ jsx27(ContentTitle, { type: category?.name, element: post.title, headingLevel: 1, className: "mb-0" }),
1729
+ post.summary ? /* @__PURE__ */ jsx27("p", { className: "text-muted-foreground mt-3 text-sm leading-relaxed", children: post.summary }) : null,
1730
+ /* @__PURE__ */ jsx27(BlogPostMeta, { post, showAuthor: true, showDate: true, showUpdated: true, className: "mt-4" })
1731
+ ] }),
1732
+ post.coverImage ? /* @__PURE__ */ jsx27(
1733
+ BlogPostCover,
1734
+ {
1735
+ src: post.coverImage,
1736
+ alt: post.coverImageAlt,
1737
+ sizes: "(min-width: 768px) 768px, 100vw",
1738
+ priority: true,
1739
+ className: "mt-8 aspect-[16/9]"
1740
+ }
1741
+ ) : null,
1742
+ /* @__PURE__ */ jsx27("div", { className: "mt-8", children: /* @__PURE__ */ jsx27(BlockNoteEditorContainer, { id: post.id, type: "blog-post", initialContent: post.body }) })
1743
+ ] }),
1744
+ more.length > 0 ? /* @__PURE__ */ jsx27("section", { className: "mx-auto mt-16 max-w-5xl", children: /* @__PURE__ */ jsxs19("div", { className: "border-border border-t pt-10", children: [
1745
+ /* @__PURE__ */ jsx27(SectionHeader, { level: 2, children: t("blog.public.moreIn", { category: category?.name ?? "" }) }),
1746
+ /* @__PURE__ */ jsx27("div", { className: "mt-6 grid gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3", children: more.map((relatedPost) => /* @__PURE__ */ jsx27(
1747
+ BlogPostCard,
1748
+ {
1749
+ post: relatedPost,
1750
+ categories,
1751
+ basePath,
1752
+ headingLevel: "h3"
1753
+ },
1754
+ relatedPost.id
1755
+ )) })
1756
+ ] }) }) : null
1757
+ ] });
1758
+ }
1759
+ __name(BlogPostView, "BlogPostView");
1760
+ export {
1761
+ BLOG_ACTION,
1762
+ BLOG_CATEGORY_UPLOAD_PREFIX,
1763
+ BLOG_LIST_LAYOUTS,
1764
+ BLOG_MODULE_ID,
1765
+ BLOG_POST_STATUSES,
1766
+ BLOG_POST_UPLOAD_PREFIX,
1767
+ BLOG_PUBLIC_PREFIX,
1768
+ BLOG_SETTINGS_ID,
1769
+ BLOG_STATUS_BADGE,
1770
+ BLOG_TRANSLATION_SOURCES,
1771
+ BLOG_UPLOAD_ROOT,
1772
+ BlogAdminContainer,
1773
+ BlogCategoriesList,
1774
+ BlogCategoryEditor,
1775
+ BlogCoverImageField,
1776
+ BlogLayoutSetting,
1777
+ BlogList,
1778
+ BlogPostComparison,
1779
+ BlogPostContainer,
1780
+ BlogPostDeleter,
1781
+ BlogPostEditor,
1782
+ BlogPostProvider,
1783
+ BlogPostPublishButton,
1784
+ BlogPostTranslationEditor,
1785
+ BlogPostTranslationFields,
1786
+ BlogPostTranslationPanel,
1787
+ BlogPostView,
1788
+ BlogPostsList,
1789
+ BlogTranslationStatus,
1790
+ blogCategoryAdminName,
1791
+ uploadBlogImage,
1792
+ useBlogCategoryNames,
1793
+ useBlogCategoryTableStructure,
1794
+ useBlogDateFormatter,
1795
+ useBlogLanguageName,
1796
+ useBlogPost,
1797
+ useBlogPostTableStructure
1798
+ };
1799
+ //# sourceMappingURL=index.mjs.map