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