@jhits/plugin-blog 0.0.18 → 0.0.20

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 (291) hide show
  1. package/dist/api/categories.d.ts.map +1 -1
  2. package/dist/api/categories.js +42 -38
  3. package/dist/api/handler.d.ts +1 -26
  4. package/dist/api/handler.d.ts.map +1 -1
  5. package/dist/api/handler.js +81 -500
  6. package/dist/api/router.d.ts +0 -5
  7. package/dist/api/router.d.ts.map +1 -1
  8. package/dist/api/router.js +8 -35
  9. package/dist/api/service.d.ts +80 -0
  10. package/dist/api/service.d.ts.map +1 -0
  11. package/dist/api/service.js +219 -0
  12. package/dist/hooks/useAutoSave.d.ts +10 -0
  13. package/dist/hooks/useAutoSave.d.ts.map +1 -0
  14. package/dist/hooks/useAutoSave.js +57 -0
  15. package/dist/hooks/useCategories.d.ts +1 -1
  16. package/dist/hooks/useCategories.d.ts.map +1 -1
  17. package/dist/hooks/useCategories.js +15 -46
  18. package/dist/index.d.ts +24 -31
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +44 -201
  21. package/dist/init.d.ts +20 -7
  22. package/dist/init.d.ts.map +1 -1
  23. package/dist/init.js +8 -7
  24. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  25. package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
  26. package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
  27. package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
  28. package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
  29. package/dist/lib/layouts/index.d.ts +3 -3
  30. package/dist/lib/layouts/index.js +4 -4
  31. package/dist/lib/mappers/apiMapper.d.ts +10 -0
  32. package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
  33. package/dist/lib/mappers/apiMapper.js +47 -32
  34. package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
  35. package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
  36. package/dist/lib/rich-text/RichTextEditor.js +12 -9
  37. package/dist/lib/utils/config-resolver.d.ts +28 -0
  38. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  39. package/dist/lib/utils/config-resolver.js +46 -0
  40. package/dist/lib/utils/tree.d.ts +29 -0
  41. package/dist/lib/utils/tree.d.ts.map +1 -0
  42. package/dist/lib/utils/tree.js +129 -0
  43. package/dist/state/EditorContext.d.ts +3 -25
  44. package/dist/state/EditorContext.d.ts.map +1 -1
  45. package/dist/state/EditorContext.js +124 -174
  46. package/dist/state/reducer.d.ts +1 -5
  47. package/dist/state/reducer.d.ts.map +1 -1
  48. package/dist/state/reducer.js +128 -521
  49. package/dist/state/types.d.ts +12 -1
  50. package/dist/state/types.d.ts.map +1 -1
  51. package/dist/types/block.d.ts +9 -0
  52. package/dist/types/block.d.ts.map +1 -1
  53. package/dist/types/post.d.ts +17 -1
  54. package/dist/types/post.d.ts.map +1 -1
  55. package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
  56. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
  58. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
  59. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  60. package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
  61. package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
  62. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  63. package/dist/views/CanvasEditor/EditorBody.js +34 -482
  64. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  65. package/dist/views/CanvasEditor/EditorHeader.js +27 -63
  66. package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
  67. package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
  68. package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
  69. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
  70. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  71. package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
  72. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
  73. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
  74. package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
  75. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  76. package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
  77. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
  78. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
  79. package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
  80. package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
  81. package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
  82. package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
  83. package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
  84. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
  85. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
  86. package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
  87. package/dist/views/CanvasEditor/components/index.d.ts +2 -0
  88. package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
  89. package/dist/views/CanvasEditor/components/index.js +1 -0
  90. package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
  91. package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
  92. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
  93. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
  94. package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
  95. package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
  96. package/dist/views/PostManager/EmptyState.d.ts +1 -1
  97. package/dist/views/PostManager/EmptyState.js +4 -4
  98. package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
  99. package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
  100. package/dist/views/PostManager/FilterDropdown.js +28 -0
  101. package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
  102. package/dist/views/PostManager/LanguageFlags.js +4 -1
  103. package/dist/views/PostManager/PostCards.d.ts.map +1 -1
  104. package/dist/views/PostManager/PostCards.js +23 -40
  105. package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
  106. package/dist/views/PostManager/PostFilters.js +34 -3
  107. package/dist/views/PostManager/PostManagerView.d.ts +1 -2
  108. package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
  109. package/dist/views/PostManager/PostManagerView.js +30 -96
  110. package/dist/views/PostManager/PostStats.d.ts.map +1 -1
  111. package/dist/views/PostManager/PostStats.js +10 -10
  112. package/dist/views/PostManager/PostTable.d.ts.map +1 -1
  113. package/dist/views/PostManager/PostTable.js +23 -40
  114. package/dist/views/Settings/SettingsView.d.ts +1 -1
  115. package/dist/views/Settings/SettingsView.d.ts.map +1 -1
  116. package/dist/views/Settings/SettingsView.js +12 -39
  117. package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
  118. package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
  119. package/package.json +42 -6
  120. package/src/api/categories.ts +48 -52
  121. package/src/api/handler.ts +87 -604
  122. package/src/api/router.ts +15 -65
  123. package/src/api/service.ts +241 -0
  124. package/src/hooks/useAutoSave.ts +64 -0
  125. package/src/hooks/useCategories.ts +19 -47
  126. package/src/index.tsx +79 -293
  127. package/src/init.tsx +24 -11
  128. package/src/lib/blocks/BlockRenderer.tsx +1 -0
  129. package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
  130. package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
  131. package/src/lib/layouts/index.ts +4 -4
  132. package/src/lib/mappers/apiMapper.ts +63 -32
  133. package/src/lib/rich-text/RichTextEditor.tsx +16 -9
  134. package/src/lib/utils/config-resolver.ts +64 -0
  135. package/src/lib/utils/tree.ts +150 -0
  136. package/src/state/EditorContext.tsx +153 -232
  137. package/src/state/reducer.ts +141 -606
  138. package/src/state/types.ts +14 -1
  139. package/src/types/block.ts +10 -0
  140. package/src/types/post.ts +19 -1
  141. package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
  142. package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
  143. package/src/views/CanvasEditor/EditorBody.tsx +98 -610
  144. package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
  145. package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
  146. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
  147. package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
  148. package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
  149. package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
  150. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
  151. package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
  152. package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
  153. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
  154. package/src/views/CanvasEditor/components/index.ts +2 -1
  155. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
  156. package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
  157. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
  158. package/src/views/PostManager/EmptyState.tsx +9 -10
  159. package/src/views/PostManager/FilterDropdown.tsx +95 -0
  160. package/src/views/PostManager/LanguageFlags.tsx +6 -2
  161. package/src/views/PostManager/PostCards.tsx +127 -133
  162. package/src/views/PostManager/PostFilters.tsx +73 -68
  163. package/src/views/PostManager/PostManagerView.tsx +132 -179
  164. package/src/views/PostManager/PostStats.tsx +21 -20
  165. package/src/views/PostManager/PostTable.tsx +137 -165
  166. package/src/views/Settings/SettingsView.tsx +64 -180
  167. package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
  168. package/src/hooks/index.d.ts +0 -8
  169. package/src/hooks/index.d.ts.map +0 -1
  170. package/src/hooks/useBlog.d.ts +0 -31
  171. package/src/hooks/useBlog.d.ts.map +0 -1
  172. package/src/hooks/useBlogs.d.ts +0 -39
  173. package/src/hooks/useBlogs.d.ts.map +0 -1
  174. package/src/hooks/useCategories.d.ts +0 -9
  175. package/src/hooks/useCategories.d.ts.map +0 -1
  176. package/src/lib/blocks/BlockRenderer.d.ts +0 -54
  177. package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
  178. package/src/lib/config-storage.d.ts +0 -30
  179. package/src/lib/config-storage.d.ts.map +0 -1
  180. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
  181. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
  182. package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
  183. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
  184. package/src/lib/layouts/index.d.ts +0 -23
  185. package/src/lib/layouts/index.d.ts.map +0 -1
  186. package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
  187. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
  188. package/src/lib/mappers/apiMapper.d.ts +0 -66
  189. package/src/lib/mappers/apiMapper.d.ts.map +0 -1
  190. package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
  191. package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
  192. package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
  193. package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
  194. package/src/lib/rich-text/index.d.ts +0 -9
  195. package/src/lib/rich-text/index.d.ts.map +0 -1
  196. package/src/lib/utils/blockHelpers.d.ts +0 -23
  197. package/src/lib/utils/blockHelpers.d.ts.map +0 -1
  198. package/src/lib/utils/configValidation.d.ts +0 -23
  199. package/src/lib/utils/configValidation.d.ts.map +0 -1
  200. package/src/registry/BlockRegistry.d.ts +0 -62
  201. package/src/registry/BlockRegistry.d.ts.map +0 -1
  202. package/src/registry/index.d.ts +0 -6
  203. package/src/registry/index.d.ts.map +0 -1
  204. package/src/state/EditorContext.d.ts +0 -45
  205. package/src/state/EditorContext.d.ts.map +0 -1
  206. package/src/state/index.d.ts +0 -7
  207. package/src/state/index.d.ts.map +0 -1
  208. package/src/state/reducer.d.ts +0 -11
  209. package/src/state/reducer.d.ts.map +0 -1
  210. package/src/state/types.d.ts +0 -162
  211. package/src/state/types.d.ts.map +0 -1
  212. package/src/types/block.d.ts +0 -221
  213. package/src/types/block.d.ts.map +0 -1
  214. package/src/types/index.d.ts +0 -8
  215. package/src/types/index.d.ts.map +0 -1
  216. package/src/types/post.d.ts +0 -136
  217. package/src/types/post.d.ts.map +0 -1
  218. package/src/utils/client.d.ts +0 -48
  219. package/src/utils/client.d.ts.map +0 -1
  220. package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
  221. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
  222. package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
  223. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
  224. package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
  225. package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
  226. package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
  227. package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
  228. package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
  229. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
  230. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
  231. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
  232. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
  233. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
  234. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
  235. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
  236. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
  237. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
  238. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
  239. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
  240. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
  241. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
  242. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
  243. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
  244. package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
  245. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
  246. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
  247. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
  248. package/src/views/CanvasEditor/components/index.d.ts +0 -21
  249. package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
  250. package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
  251. package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
  252. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
  253. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
  254. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
  255. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
  256. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
  257. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
  258. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
  259. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
  260. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
  261. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
  262. package/src/views/CanvasEditor/index.d.ts +0 -16
  263. package/src/views/CanvasEditor/index.d.ts.map +0 -1
  264. package/src/views/PostManager/EmptyState.d.ts +0 -10
  265. package/src/views/PostManager/EmptyState.d.ts.map +0 -1
  266. package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
  267. package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
  268. package/src/views/PostManager/PostCards.d.ts +0 -15
  269. package/src/views/PostManager/PostCards.d.ts.map +0 -1
  270. package/src/views/PostManager/PostFilters.d.ts +0 -16
  271. package/src/views/PostManager/PostFilters.d.ts.map +0 -1
  272. package/src/views/PostManager/PostManagerView.d.ts +0 -11
  273. package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
  274. package/src/views/PostManager/PostStats.d.ts +0 -11
  275. package/src/views/PostManager/PostStats.d.ts.map +0 -1
  276. package/src/views/PostManager/PostTable.d.ts +0 -15
  277. package/src/views/PostManager/PostTable.d.ts.map +0 -1
  278. package/src/views/PostManager/index.d.ts +0 -12
  279. package/src/views/PostManager/index.d.ts.map +0 -1
  280. package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
  281. package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
  282. package/src/views/Preview/index.d.ts +0 -6
  283. package/src/views/Preview/index.d.ts.map +0 -1
  284. package/src/views/Settings/SettingsView.d.ts +0 -10
  285. package/src/views/Settings/SettingsView.d.ts.map +0 -1
  286. package/src/views/Settings/index.d.ts +0 -6
  287. package/src/views/Settings/index.d.ts.map +0 -1
  288. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
  289. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
  290. package/src/views/SlugSEO/index.d.ts +0 -6
  291. package/src/views/SlugSEO/index.d.ts.map +0 -1
@@ -5,9 +5,9 @@
5
5
 
6
6
  'use client';
7
7
 
8
- import React, { useState, useEffect } from 'react';
9
- import { Calendar, User, UserCheck } from 'lucide-react';
10
- import { Image } from '@jhits/plugin-images';
8
+ import React from 'react';
9
+ import { FileText, Tag, Activity } from 'lucide-react';
10
+ import { Image as PluginImage } from '@jhits/plugin-images';
11
11
  import { PostListItem, PostStatus } from '../../types/post';
12
12
  import { PostActionsMenu } from './PostActionsMenu';
13
13
  import { LanguageFlags } from './LanguageFlags';
@@ -25,17 +25,17 @@ export interface PostCardsProps {
25
25
  function getStatusBadgeColor(status: PostStatus | 'not-translated') {
26
26
  switch (status) {
27
27
  case 'published':
28
- return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
28
+ return 'bg-emerald-500 text-white border-emerald-400 shadow-lg shadow-emerald-500/20';
29
29
  case 'draft':
30
- return 'bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20';
30
+ return 'bg-amber-500 text-white border-amber-400 shadow-lg shadow-amber-500/20';
31
31
  case 'scheduled':
32
- return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
32
+ return 'bg-blue-500 text-white border-blue-400 shadow-lg shadow-blue-500/20';
33
33
  case 'archived':
34
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
34
+ return 'bg-neutral-500 text-white border-neutral-400';
35
35
  case 'not-translated':
36
- return 'bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/20 italic';
36
+ return 'bg-red-500/10 text-red-500 border-red-500/20 italic';
37
37
  default:
38
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
38
+ return 'bg-neutral-500/10 text-neutral-500 border-neutral-500/20';
39
39
  }
40
40
  }
41
41
 
@@ -58,150 +58,144 @@ export function PostCards({
58
58
  }: PostCardsProps) {
59
59
  const { data: session, status: sessionStatus } = useSession();
60
60
  const currentUserId = (session?.user as any)?.id;
61
- const [userMap, setUserMap] = useState<Record<string, string>>({});
62
61
 
63
62
  // Helper function to check if user is the owner
64
63
  const isPostOwner = (post: PostListItem): boolean => {
65
- if (sessionStatus === 'loading') return false; // Don't show actions while loading
64
+ if (sessionStatus === 'loading') return false;
66
65
  if (!currentUserId || !post.authorId) return false;
67
- // Convert both to strings for comparison to handle ObjectId vs string
68
66
  return String(currentUserId) === String(post.authorId);
69
67
  };
70
68
 
71
- // Fetch users to map IDs to names
72
- useEffect(() => {
73
- const fetchUsers = async () => {
74
- try {
75
- const response = await fetch('/api/users');
76
- const users = await response.json();
77
- if (Array.isArray(users)) {
78
- const map: Record<string, string> = {};
79
- users.forEach((user: { _id: string; name?: string; email?: string }) => {
80
- const id = user._id?.toString();
81
- if (id) {
82
- map[id] = user.name || user.email || 'Unknown';
83
- }
84
- });
85
- setUserMap(map);
86
- }
87
- } catch (error) {
88
- console.error('Failed to fetch users:', error);
89
- }
90
- };
91
- fetchUsers();
92
- }, []);
93
-
94
- const getAuthorName = (authorId?: string) => {
95
- if (!authorId) return 'Unknown';
96
- return userMap[authorId] || authorId;
69
+ const getAuthorData = (post: PostListItem) => {
70
+ if (post.author) {
71
+ return {
72
+ name: post.author.name || 'Unknown Author',
73
+ image: post.author.image
74
+ };
75
+ }
76
+ return { name: 'Unknown Author' };
97
77
  };
98
78
 
99
79
  return (
100
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
101
- {posts.map((post) => (
102
- <div
103
- key={post.id}
104
- className="flex flex-col bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group h-full"
105
- >
106
- {/* Featured Image */}
107
- <div className="relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden flex-shrink-0">
108
- {post.featuredImage ? (
109
- <Image
110
- id={post.featuredImage}
111
- alt={post.title}
112
- fill
113
- editable={false}
114
- className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
115
- />
116
- ) : (
117
- <div className="w-full h-full flex items-center justify-center">
118
- <span className="text-sm text-neutral-400">No Image</span>
119
- </div>
120
- )}
121
- {/* Actions Menu - Top Left - Only show for own posts */}
122
- {isPostOwner(post) && (
123
- <div className="absolute top-4 left-4 z-10">
124
- <div className="bg-white/90 dark:bg-neutral-900/90 backdrop-blur-sm rounded-full p-1 shadow-lg border border-neutral-200 dark:border-neutral-700">
125
- <PostActionsMenu
126
- onEdit={() => onEdit(post.id)}
127
- onPreview={() => onPreview(post.id)}
128
- onDuplicate={() => onDuplicate(post.id)}
129
- onDelete={() => onDelete(post.id)}
80
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
81
+ {posts.map((post) => {
82
+ const author = getAuthorData(post);
83
+ const owner = isPostOwner(post);
84
+ const initials = author.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
85
+
86
+ return (
87
+ <div
88
+ key={post.id}
89
+ className="group relative flex flex-col h-full animate-in fade-in slide-in-from-bottom-2 duration-500"
90
+ >
91
+ {/* Card Background with glass effect */}
92
+ <div className="absolute inset-0 bg-dashboard-card/40 backdrop-blur-md rounded-[2.5rem] border border-dashboard-border/40 transition-all duration-500 group-hover:border-primary/30 group-hover:bg-primary/5 group-hover:shadow-xl group-hover:shadow-primary/5" />
93
+
94
+ <div className="relative p-3.5 flex flex-col h-full space-y-5">
95
+ {/* 1. Featured Image Area */}
96
+ <div className="aspect-[16/9.5] relative bg-dashboard-bg/50 rounded-[2rem] overflow-hidden border border-dashboard-border/40 group-hover:border-primary/20 transition-all shadow-sm">
97
+ {post.featuredImage ? (
98
+ <PluginImage
99
+ id={post.featuredImage}
100
+ alt={post.title}
101
+ fill
102
+ editable={false}
103
+ className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
130
104
  />
105
+ ) : (
106
+ <div className="w-full h-full flex flex-col items-center justify-center bg-dashboard-bg/50 opacity-20">
107
+ <FileText size={40} />
108
+ </div>
109
+ )}
110
+
111
+ {/* Status Badge Overlay */}
112
+ <div className="absolute top-3.5 right-3.5">
113
+ <span
114
+ className={`inline-flex items-center px-3 py-1 rounded-full text-[9px] font-bold uppercase tracking-wider border backdrop-blur-md shadow-sm ${getStatusBadgeColor(post.status)}`}
115
+ >
116
+ {post.status === 'not-translated' ? 'Edition Missing' : post.status}
117
+ </span>
131
118
  </div>
119
+
120
+ {/* Quick Actions Overlay */}
121
+ {owner && (
122
+ <div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-all duration-500 flex items-center justify-center opacity-0 group-hover:opacity-100 z-20">
123
+ <div className="bg-white/10 backdrop-blur-md p-1.5 rounded-2xl border border-white/20 shadow-2xl flex items-center gap-1 scale-90 group-hover:scale-100 transition-transform duration-500">
124
+ <PostActionsMenu
125
+ onEdit={() => onEdit(post.id)}
126
+ onPreview={() => onPreview(post.id)}
127
+ onDuplicate={() => onDuplicate(post.id)}
128
+ onDelete={() => onDelete(post.id)}
129
+ />
130
+ </div>
131
+ </div>
132
+ )}
132
133
  </div>
133
- )}
134
- {/* Status Badge Overlay */}
135
- <div className="absolute top-4 right-4">
136
- <span
137
- className={`inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-wider border backdrop-blur-sm ${getStatusBadgeColor(post.status)}`}
138
- >
139
- {post.status}
140
- </span>
141
- </div>
142
- </div>
143
134
 
144
- {/* Card Content */}
145
- <div className="p-6 flex flex-col flex-1">
146
- {/* Title & Slug */}
147
- <div className="mb-4 min-h-[72px]">
148
- <button
149
- onClick={() => onEdit(post.id)}
150
- className="text-left w-full hover:cursor-pointer"
151
- >
152
- <h3 className="font-bold text-lg text-neutral-950 dark:text-white mb-2 line-clamp-2 group-hover:text-primary transition-colors hover:underline">
153
- {post.title}
154
- </h3>
155
- </button>
156
- <p className="text-xs text-neutral-500 dark:text-neutral-400 font-mono line-clamp-1">
157
- /{post.slug}
158
- </p>
159
- </div>
135
+ {/* 2. Content Area */}
136
+ <div className="px-3.5 pb-2.5 flex-1 flex flex-col">
137
+ <div className="flex items-center gap-3 mb-3.5">
138
+ <LanguageFlags post={post} />
139
+ <div className="h-4 w-px bg-dashboard-border/30" />
140
+ <span className="text-[10px] font-bold text-primary uppercase tracking-widest flex items-center gap-1.5 opacity-80">
141
+ <Tag size={12} />
142
+ {post.category || 'Article'}
143
+ </span>
144
+ </div>
160
145
 
161
- {/* Excerpt */}
162
- <div className="flex-1 mb-4 min-h-[40px]">
163
- {post.excerpt && (
164
- <p className="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-2">
165
- {post.excerpt}
146
+ <button
147
+ onClick={() => onEdit(post.id)}
148
+ className="text-left w-full group/title mb-2.5"
149
+ >
150
+ <h3 className="text-xl font-bold text-dashboard-text tracking-tight leading-tight line-clamp-2 group-hover/title:text-primary transition-colors">
151
+ {post.title}
152
+ </h3>
153
+ </button>
154
+
155
+ <p className="text-sm text-dashboard-text-secondary font-medium line-clamp-2 opacity-60 mb-6">
156
+ {post.excerpt || 'No summary available for this publication.'}
166
157
  </p>
167
- )}
168
- </div>
169
-
170
- {/* Languages */}
171
- <div className="mb-6">
172
- <LanguageFlags post={post} />
173
- </div>
174
-
175
- {/* Meta Information */}
176
- <div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700 mt-auto">
177
- {/* Author */}
178
- <div className="flex items-center gap-2">
179
- {isPostOwner(post) ? (
180
- <UserCheck size={14} className="text-primary" />
181
- ) : (
182
- <User size={14} className="text-neutral-400" />
183
- )}
184
- <span className={`text-xs ${isPostOwner(post) ? 'text-primary font-semibold' : 'text-neutral-600 dark:text-neutral-400'}`}>
185
- {getAuthorName(post.authorId)}
186
- {isPostOwner(post) && (
187
- <span className="ml-1">(You)</span>
188
- )}
189
- </span>
190
- </div>
191
158
 
192
- {/* Last Modified */}
193
- <div className="flex items-center gap-2">
194
- <Calendar size={14} className="text-neutral-400" />
195
- <span className="text-xs text-neutral-600 dark:text-neutral-400">
196
- {formatDate(post.updatedAt, locale)}
197
- </span>
159
+ {/* Footer Meta */}
160
+ <div className="pt-4 border-t border-dashboard-border/30 mt-auto space-y-4">
161
+ <div className="flex items-center justify-between gap-4">
162
+ <div className="flex items-center gap-3 min-w-0">
163
+ <div className={`size-9 rounded-xl flex items-center justify-center text-xs font-bold border transition-all overflow-hidden shrink-0 ${
164
+ owner ? 'bg-primary text-white border-primary/20 shadow-lg shadow-primary/20' : 'bg-dashboard-bg/50 text-dashboard-text-secondary border-dashboard-border/60'
165
+ }`}>
166
+ {author.image ? (
167
+ <img src={author.image} alt={author.name} className="size-full object-cover" crossOrigin="anonymous" />
168
+ ) : initials}
169
+ </div>
170
+ <div className="flex flex-col min-w-0">
171
+ <span className="text-[10px] font-bold text-dashboard-text uppercase tracking-tight truncate">
172
+ {author.name} {owner && <span className="text-primary ml-0.5">(YOU)</span>}
173
+ </span>
174
+ <span className="text-[9px] font-semibold text-dashboard-text-secondary uppercase tracking-widest opacity-60">
175
+ {formatDate(post.updatedAt, locale)}
176
+ </span>
177
+ </div>
178
+ </div>
179
+
180
+ {post.status === 'published' && (
181
+ <div className="flex items-center gap-1.5 px-2.5 py-0.5 bg-emerald-500/10 text-emerald-500 rounded-full border border-emerald-500/20">
182
+ <Activity size={10} className="animate-pulse" />
183
+ <span className="text-[8px] font-bold uppercase tracking-widest">Live</span>
184
+ </div>
185
+ )}
186
+ </div>
187
+
188
+ <div className="bg-dashboard-bg/40 px-3 py-1.5 rounded-xl border border-dashboard-border/30">
189
+ <p className="text-[9px] font-mono text-dashboard-text-secondary/50 uppercase tracking-tight truncate">
190
+ Path: /{post.slug}
191
+ </p>
192
+ </div>
193
+ </div>
198
194
  </div>
199
195
  </div>
200
-
201
196
  </div>
202
- </div>
203
- ))}
197
+ );
198
+ })}
204
199
  </div>
205
200
  );
206
201
  }
207
-
@@ -8,6 +8,7 @@
8
8
  import React from 'react';
9
9
  import { Search, Filter, Tag, Globe } from 'lucide-react';
10
10
  import { PostStatus } from '../../types/post';
11
+ import { FilterDropdown } from './FilterDropdown';
11
12
 
12
13
  export interface PostFiltersProps {
13
14
  search: string;
@@ -36,97 +37,101 @@ export function PostFilters({
36
37
  }: PostFiltersProps) {
37
38
  const langNames: Record<string, string> = {
38
39
  nl: 'Nederlands (NL)',
39
- en: 'English (EN)',
40
- sv: 'Svenska (SV)',
40
+ en: 'English (GB)',
41
+ sv: 'Svenska (SE)',
41
42
  de: 'Deutsch (DE)',
42
43
  fr: 'Français (FR)',
43
44
  es: 'Español (ES)',
44
45
  it: 'Italiano (IT)',
45
46
  pt: 'Português (PT)',
46
47
  };
48
+
49
+ const getFlagUrl = (lang: string) => {
50
+ const mapping: Record<string, string> = {
51
+ en: 'gb',
52
+ nl: 'nl',
53
+ sv: 'se',
54
+ de: 'de',
55
+ fr: 'fr',
56
+ es: 'es',
57
+ it: 'it',
58
+ pt: 'pt'
59
+ };
60
+ const countryCode = mapping[lang] || lang;
61
+ return `https://flagcdn.com/w40/${countryCode.toLowerCase()}.png`;
62
+ };
63
+
64
+ const statusOptions = [
65
+ { label: 'All Statuses', value: 'all' },
66
+ { label: 'Published', value: 'published' },
67
+ { label: 'Draft', value: 'draft' },
68
+ { label: 'Scheduled', value: 'scheduled' },
69
+ { label: 'Archived', value: 'archived' },
70
+ ];
71
+
72
+ const categoryOptions = [
73
+ { label: 'All Categories', value: 'all' },
74
+ ...categories.map(cat => ({ label: cat, value: cat }))
75
+ ];
76
+
77
+ const languageOptions = availableLanguages.map(lang => ({
78
+ label: langNames[lang] || lang.toUpperCase(),
79
+ value: lang,
80
+ icon: <img src={getFlagUrl(lang)} alt="" className="w-4 h-2.5 rounded-sm object-cover shadow-sm border border-white/10" />
81
+ }));
82
+
47
83
  return (
48
- <div className="flex flex-col sm:flex-row gap-4 mb-6">
84
+ <div className="flex-1 flex flex-col md:flex-row items-center gap-4">
49
85
  {/* Search Input */}
50
- <div className="relative flex-1">
51
- <label htmlFor="blog-post-search" className="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" style={{ clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }}>
52
- Search posts by title or content
53
- </label>
54
- <Search className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4" />
86
+ <div className="relative flex-1 group w-full">
87
+ <Search className="absolute left-4 top-1/2 -translate-y-1/2 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors duration-300" size={18} />
55
88
  <input
56
89
  id="blog-post-search"
57
90
  name="blog-post-search"
58
91
  type="text"
59
92
  value={search}
60
93
  onChange={(e) => onSearchChange(e.target.value)}
61
- placeholder="Search posts by title or content..."
62
- className="w-full pl-11 pr-4 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all"
94
+ placeholder="Search articles by title..."
95
+ className="w-full pl-12 pr-6 py-3.5 bg-dashboard-card/40 border border-dashboard-border/40 rounded-2xl text-sm font-semibold text-dashboard-text placeholder:text-dashboard-text-secondary/30 outline-none focus:border-primary/30 transition-all"
63
96
  />
64
97
  </div>
65
98
 
66
- {/* Status Filter */}
67
- <div className="relative">
68
- <label htmlFor="blog-post-status-filter" className="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" style={{ clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }}>
69
- Filter by status
70
- </label>
71
- <Filter className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
72
- <select
73
- id="blog-post-status-filter"
74
- name="blog-post-status-filter"
99
+ {/* Selects Container */}
100
+ <div className="flex flex-wrap items-center gap-1.5 p-1.5 bg-dashboard-card/50 rounded-xl border border-dashboard-border/40 w-full md:w-auto">
101
+ {/* Status Filter */}
102
+ <FilterDropdown
103
+ label="Status"
104
+ icon={<Filter size={14} className="text-primary/60" />}
75
105
  value={statusFilter}
76
- onChange={(e) => onStatusFilterChange(e.target.value as PostStatus | 'all')}
77
- className="pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[160px]"
78
- >
79
- <option value="all">All Statuses</option>
80
- <option value="published">Published</option>
81
- <option value="draft">Draft</option>
82
- <option value="scheduled">Scheduled</option>
83
- <option value="archived">Archived</option>
84
- </select>
85
- </div>
106
+ options={statusOptions}
107
+ onChange={(val) => onStatusFilterChange(val as any)}
108
+ minWidth="140px"
109
+ />
110
+
111
+ <div className="h-6 w-px bg-dashboard-border/30 mx-1 hidden md:block" />
86
112
 
87
- {/* Category Filter */}
88
- <div className="relative">
89
- <label htmlFor="blog-post-category-filter" className="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" style={{ clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }}>
90
- Filter by category
91
- </label>
92
- <Tag className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
93
- <select
94
- id="blog-post-category-filter"
95
- name="blog-post-category-filter"
113
+ {/* Category Filter */}
114
+ <FilterDropdown
115
+ label="Category"
116
+ icon={<Tag size={14} className="text-primary/60" />}
96
117
  value={categoryFilter}
97
- onChange={(e) => onCategoryFilterChange(e.target.value)}
98
- className="pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[160px]"
99
- >
100
- <option value="all">All Categories</option>
101
- {categories.map((category) => (
102
- <option key={category} value={category}>
103
- {category}
104
- </option>
105
- ))}
106
- </select>
107
- </div>
118
+ options={categoryOptions}
119
+ onChange={onCategoryFilterChange}
120
+ minWidth="150px"
121
+ />
108
122
 
109
- {/* Language Selector */}
110
- <div className="relative">
111
- <label htmlFor="blog-post-language-filter" className="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" style={{ clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }}>
112
- Primary Language
113
- </label>
114
- <Globe className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
115
- <select
116
- id="blog-post-language-filter"
117
- name="blog-post-language-filter"
123
+ <div className="h-6 w-px bg-dashboard-border/30 mx-1 hidden md:block" />
124
+
125
+ {/* Language Selector */}
126
+ <FilterDropdown
127
+ label="Language"
128
+ icon={<Globe size={14} className="text-primary/60" />}
118
129
  value={language}
119
- onChange={(e) => onLanguageChange(e.target.value)}
120
- className="pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[140px]"
121
- >
122
- {availableLanguages.map(lang => (
123
- <option key={lang} value={lang}>
124
- {langNames[lang] || lang.toUpperCase()}
125
- </option>
126
- ))}
127
- </select>
130
+ options={languageOptions}
131
+ onChange={onLanguageChange}
132
+ minWidth="160px"
133
+ />
128
134
  </div>
129
135
  </div>
130
136
  );
131
137
  }
132
-