@jhits/plugin-blog 0.0.9 → 0.0.11

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 (276) hide show
  1. package/package.json +58 -59
  2. package/src/api/categories.ts +0 -43
  3. package/src/api/check-title.ts +0 -60
  4. package/src/api/config-handler.ts +0 -76
  5. package/src/api/handler.ts +0 -418
  6. package/src/api/index.ts +0 -33
  7. package/src/api/route.ts +0 -116
  8. package/src/api/router.ts +0 -128
  9. package/src/api-server.ts +0 -11
  10. package/src/config.ts +0 -161
  11. package/src/hooks/index.d.ts +0 -8
  12. package/src/hooks/index.d.ts.map +0 -1
  13. package/src/hooks/index.js +0 -7
  14. package/src/hooks/index.ts +0 -9
  15. package/src/hooks/useBlog.d.ts +0 -31
  16. package/src/hooks/useBlog.d.ts.map +0 -1
  17. package/src/hooks/useBlog.js +0 -57
  18. package/src/hooks/useBlog.ts +0 -85
  19. package/src/hooks/useBlogs.d.ts +0 -39
  20. package/src/hooks/useBlogs.d.ts.map +0 -1
  21. package/src/hooks/useBlogs.js +0 -82
  22. package/src/hooks/useBlogs.ts +0 -123
  23. package/src/hooks/useCategories.d.ts +0 -9
  24. package/src/hooks/useCategories.d.ts.map +0 -1
  25. package/src/hooks/useCategories.js +0 -70
  26. package/src/hooks/useCategories.ts +0 -76
  27. package/src/index.d.ts +0 -55
  28. package/src/index.d.ts.map +0 -1
  29. package/src/index.js +0 -228
  30. package/src/index.server.ts +0 -14
  31. package/src/index.tsx +0 -335
  32. package/src/init.d.ts +0 -40
  33. package/src/init.d.ts.map +0 -1
  34. package/src/init.js +0 -41
  35. package/src/init.tsx +0 -63
  36. package/src/lib/blocks/BlockRenderer.d.ts +0 -54
  37. package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
  38. package/src/lib/blocks/BlockRenderer.js +0 -54
  39. package/src/lib/blocks/BlockRenderer.tsx +0 -141
  40. package/src/lib/blocks/index.ts +0 -6
  41. package/src/lib/config-storage.d.ts +0 -30
  42. package/src/lib/config-storage.d.ts.map +0 -1
  43. package/src/lib/config-storage.js +0 -31
  44. package/src/lib/config-storage.ts +0 -65
  45. package/src/lib/index.ts +0 -9
  46. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
  47. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
  48. package/src/lib/layouts/blocks/ColumnsBlock.js +0 -182
  49. package/src/lib/layouts/blocks/ColumnsBlock.tsx +0 -298
  50. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +0 -81
  51. package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
  52. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
  53. package/src/lib/layouts/blocks/SectionBlock.js +0 -44
  54. package/src/lib/layouts/blocks/SectionBlock.tsx +0 -104
  55. package/src/lib/layouts/blocks/index.ts +0 -8
  56. package/src/lib/layouts/index.d.ts +0 -23
  57. package/src/lib/layouts/index.d.ts.map +0 -1
  58. package/src/lib/layouts/index.js +0 -45
  59. package/src/lib/layouts/index.ts +0 -52
  60. package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
  61. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
  62. package/src/lib/layouts/registerLayoutBlocks.js +0 -60
  63. package/src/lib/layouts/registerLayoutBlocks.ts +0 -64
  64. package/src/lib/mappers/apiMapper.d.ts +0 -66
  65. package/src/lib/mappers/apiMapper.d.ts.map +0 -1
  66. package/src/lib/mappers/apiMapper.js +0 -191
  67. package/src/lib/mappers/apiMapper.ts +0 -254
  68. package/src/lib/migration/index.ts +0 -6
  69. package/src/lib/migration/mapper.ts +0 -140
  70. package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
  71. package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
  72. package/src/lib/rich-text/RichTextEditor.js +0 -564
  73. package/src/lib/rich-text/RichTextEditor.tsx +0 -826
  74. package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
  75. package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
  76. package/src/lib/rich-text/RichTextPreview.js +0 -144
  77. package/src/lib/rich-text/RichTextPreview.tsx +0 -210
  78. package/src/lib/rich-text/index.d.ts +0 -9
  79. package/src/lib/rich-text/index.d.ts.map +0 -1
  80. package/src/lib/rich-text/index.js +0 -6
  81. package/src/lib/rich-text/index.ts +0 -10
  82. package/src/lib/utils/blockHelpers.d.ts +0 -23
  83. package/src/lib/utils/blockHelpers.d.ts.map +0 -1
  84. package/src/lib/utils/blockHelpers.js +0 -65
  85. package/src/lib/utils/blockHelpers.ts +0 -72
  86. package/src/lib/utils/configValidation.d.ts +0 -23
  87. package/src/lib/utils/configValidation.d.ts.map +0 -1
  88. package/src/lib/utils/configValidation.js +0 -113
  89. package/src/lib/utils/configValidation.ts +0 -137
  90. package/src/lib/utils/index.ts +0 -8
  91. package/src/lib/utils/slugify.ts +0 -79
  92. package/src/registry/BlockRegistry.d.ts +0 -62
  93. package/src/registry/BlockRegistry.d.ts.map +0 -1
  94. package/src/registry/BlockRegistry.js +0 -112
  95. package/src/registry/BlockRegistry.ts +0 -139
  96. package/src/registry/index.d.ts +0 -6
  97. package/src/registry/index.d.ts.map +0 -1
  98. package/src/registry/index.js +0 -4
  99. package/src/registry/index.ts +0 -11
  100. package/src/state/EditorContext.d.ts +0 -45
  101. package/src/state/EditorContext.d.ts.map +0 -1
  102. package/src/state/EditorContext.js +0 -215
  103. package/src/state/EditorContext.tsx +0 -283
  104. package/src/state/index.d.ts +0 -7
  105. package/src/state/index.d.ts.map +0 -1
  106. package/src/state/index.js +0 -6
  107. package/src/state/index.ts +0 -8
  108. package/src/state/reducer.d.ts +0 -11
  109. package/src/state/reducer.d.ts.map +0 -1
  110. package/src/state/reducer.js +0 -443
  111. package/src/state/reducer.ts +0 -694
  112. package/src/state/types.d.ts +0 -162
  113. package/src/state/types.d.ts.map +0 -1
  114. package/src/state/types.js +0 -27
  115. package/src/state/types.ts +0 -160
  116. package/src/types/block.d.ts +0 -221
  117. package/src/types/block.d.ts.map +0 -1
  118. package/src/types/block.js +0 -6
  119. package/src/types/block.ts +0 -269
  120. package/src/types/index.d.ts +0 -8
  121. package/src/types/index.d.ts.map +0 -1
  122. package/src/types/index.js +0 -5
  123. package/src/types/index.ts +0 -17
  124. package/src/types/post.d.ts +0 -136
  125. package/src/types/post.d.ts.map +0 -1
  126. package/src/types/post.js +0 -5
  127. package/src/types/post.ts +0 -169
  128. package/src/utils/client.d.ts +0 -48
  129. package/src/utils/client.d.ts.map +0 -1
  130. package/src/utils/client.js +0 -77
  131. package/src/utils/client.ts +0 -122
  132. package/src/utils/index.ts +0 -7
  133. package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
  134. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
  135. package/src/views/CanvasEditor/BlockWrapper.js +0 -276
  136. package/src/views/CanvasEditor/BlockWrapper.tsx +0 -522
  137. package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
  138. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
  139. package/src/views/CanvasEditor/CanvasEditorView.js +0 -209
  140. package/src/views/CanvasEditor/CanvasEditorView.tsx +0 -337
  141. package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
  142. package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
  143. package/src/views/CanvasEditor/EditorBody.js +0 -505
  144. package/src/views/CanvasEditor/EditorBody.tsx +0 -665
  145. package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
  146. package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
  147. package/src/views/CanvasEditor/EditorHeader.js +0 -101
  148. package/src/views/CanvasEditor/EditorHeader.tsx +0 -268
  149. package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
  150. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
  151. package/src/views/CanvasEditor/LayoutContainer.js +0 -222
  152. package/src/views/CanvasEditor/LayoutContainer.tsx +0 -322
  153. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
  154. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
  155. package/src/views/CanvasEditor/SaveConfirmationModal.js +0 -78
  156. package/src/views/CanvasEditor/SaveConfirmationModal.tsx +0 -233
  157. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
  158. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
  159. package/src/views/CanvasEditor/components/CustomBlockItem.js +0 -44
  160. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +0 -92
  161. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
  162. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
  163. package/src/views/CanvasEditor/components/EditorCanvas.js +0 -32
  164. package/src/views/CanvasEditor/components/EditorCanvas.tsx +0 -160
  165. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
  166. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
  167. package/src/views/CanvasEditor/components/EditorLibrary.js +0 -25
  168. package/src/views/CanvasEditor/components/EditorLibrary.tsx +0 -122
  169. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
  170. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
  171. package/src/views/CanvasEditor/components/EditorSidebar.js +0 -20
  172. package/src/views/CanvasEditor/components/EditorSidebar.tsx +0 -181
  173. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
  174. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
  175. package/src/views/CanvasEditor/components/ErrorBanner.js +0 -8
  176. package/src/views/CanvasEditor/components/ErrorBanner.tsx +0 -31
  177. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
  178. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
  179. package/src/views/CanvasEditor/components/FeaturedMediaSection.js +0 -182
  180. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +0 -341
  181. package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
  182. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
  183. package/src/views/CanvasEditor/components/LibraryItem.js +0 -43
  184. package/src/views/CanvasEditor/components/LibraryItem.tsx +0 -80
  185. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
  186. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
  187. package/src/views/CanvasEditor/components/PrivacySettingsSection.js +0 -63
  188. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +0 -212
  189. package/src/views/CanvasEditor/components/index.d.ts +0 -21
  190. package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
  191. package/src/views/CanvasEditor/components/index.js +0 -12
  192. package/src/views/CanvasEditor/components/index.ts +0 -28
  193. package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
  194. package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
  195. package/src/views/CanvasEditor/hooks/index.js +0 -9
  196. package/src/views/CanvasEditor/hooks/index.ts +0 -10
  197. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
  198. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
  199. package/src/views/CanvasEditor/hooks/useHeroBlock.js +0 -79
  200. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +0 -103
  201. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
  202. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
  203. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.js +0 -114
  204. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +0 -142
  205. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
  206. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
  207. package/src/views/CanvasEditor/hooks/usePostLoader.js +0 -32
  208. package/src/views/CanvasEditor/hooks/usePostLoader.ts +0 -39
  209. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
  210. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
  211. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.js +0 -47
  212. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +0 -55
  213. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
  214. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
  215. package/src/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -285
  216. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +0 -339
  217. package/src/views/CanvasEditor/index.d.ts +0 -16
  218. package/src/views/CanvasEditor/index.d.ts.map +0 -1
  219. package/src/views/CanvasEditor/index.js +0 -9
  220. package/src/views/CanvasEditor/index.ts +0 -16
  221. package/src/views/PostManager/EmptyState.d.ts +0 -10
  222. package/src/views/PostManager/EmptyState.d.ts.map +0 -1
  223. package/src/views/PostManager/EmptyState.js +0 -12
  224. package/src/views/PostManager/EmptyState.tsx +0 -42
  225. package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
  226. package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
  227. package/src/views/PostManager/PostActionsMenu.js +0 -58
  228. package/src/views/PostManager/PostActionsMenu.tsx +0 -112
  229. package/src/views/PostManager/PostCards.d.ts +0 -15
  230. package/src/views/PostManager/PostCards.d.ts.map +0 -1
  231. package/src/views/PostManager/PostCards.js +0 -79
  232. package/src/views/PostManager/PostCards.tsx +0 -197
  233. package/src/views/PostManager/PostFilters.d.ts +0 -16
  234. package/src/views/PostManager/PostFilters.d.ts.map +0 -1
  235. package/src/views/PostManager/PostFilters.js +0 -10
  236. package/src/views/PostManager/PostFilters.tsx +0 -95
  237. package/src/views/PostManager/PostManagerView.d.ts +0 -11
  238. package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
  239. package/src/views/PostManager/PostManagerView.js +0 -174
  240. package/src/views/PostManager/PostManagerView.tsx +0 -289
  241. package/src/views/PostManager/PostStats.d.ts +0 -11
  242. package/src/views/PostManager/PostStats.d.ts.map +0 -1
  243. package/src/views/PostManager/PostStats.js +0 -46
  244. package/src/views/PostManager/PostStats.tsx +0 -81
  245. package/src/views/PostManager/PostTable.d.ts +0 -15
  246. package/src/views/PostManager/PostTable.d.ts.map +0 -1
  247. package/src/views/PostManager/PostTable.js +0 -79
  248. package/src/views/PostManager/PostTable.tsx +0 -230
  249. package/src/views/PostManager/index.d.ts +0 -12
  250. package/src/views/PostManager/index.d.ts.map +0 -1
  251. package/src/views/PostManager/index.js +0 -11
  252. package/src/views/PostManager/index.ts +0 -15
  253. package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
  254. package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
  255. package/src/views/Preview/PreviewBridgeView.js +0 -11
  256. package/src/views/Preview/PreviewBridgeView.tsx +0 -64
  257. package/src/views/Preview/index.d.ts +0 -6
  258. package/src/views/Preview/index.d.ts.map +0 -1
  259. package/src/views/Preview/index.js +0 -4
  260. package/src/views/Preview/index.ts +0 -7
  261. package/src/views/Settings/SettingsView.d.ts +0 -10
  262. package/src/views/Settings/SettingsView.d.ts.map +0 -1
  263. package/src/views/Settings/SettingsView.js +0 -111
  264. package/src/views/Settings/SettingsView.tsx +0 -298
  265. package/src/views/Settings/index.d.ts +0 -6
  266. package/src/views/Settings/index.d.ts.map +0 -1
  267. package/src/views/Settings/index.js +0 -4
  268. package/src/views/Settings/index.ts +0 -7
  269. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
  270. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
  271. package/src/views/SlugSEO/SlugSEOManagerView.js +0 -11
  272. package/src/views/SlugSEO/SlugSEOManagerView.tsx +0 -94
  273. package/src/views/SlugSEO/index.d.ts +0 -6
  274. package/src/views/SlugSEO/index.d.ts.map +0 -1
  275. package/src/views/SlugSEO/index.js +0 -4
  276. package/src/views/SlugSEO/index.ts +0 -7
@@ -1,79 +0,0 @@
1
- /**
2
- * Post Cards Component
3
- * Card-based layout for displaying posts
4
- */
5
- 'use client';
6
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- import { useState, useEffect } from 'react';
8
- import { Calendar, User, UserCheck } from 'lucide-react';
9
- import { Image } from '@jhits/plugin-images';
10
- import { PostActionsMenu } from './PostActionsMenu';
11
- import { useSession } from 'next-auth/react';
12
- function getStatusBadgeColor(status) {
13
- switch (status) {
14
- case 'published':
15
- return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
16
- case 'draft':
17
- return 'bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20';
18
- case 'scheduled':
19
- return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
20
- case 'archived':
21
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
22
- default:
23
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
24
- }
25
- }
26
- function formatDate(dateString, locale) {
27
- if (!dateString)
28
- return 'No date';
29
- return new Date(dateString).toLocaleDateString(locale, {
30
- day: 'numeric',
31
- month: 'short',
32
- year: 'numeric',
33
- });
34
- }
35
- export function PostCards({ posts, locale, onEdit, onPreview, onDuplicate, onDelete, }) {
36
- var _a;
37
- const { data: session, status: sessionStatus } = useSession();
38
- const currentUserId = (_a = session === null || session === void 0 ? void 0 : session.user) === null || _a === void 0 ? void 0 : _a.id;
39
- const [userMap, setUserMap] = useState({});
40
- // Helper function to check if user is the owner
41
- const isPostOwner = (post) => {
42
- if (sessionStatus === 'loading')
43
- return false; // Don't show actions while loading
44
- if (!currentUserId || !post.authorId)
45
- return false;
46
- // Convert both to strings for comparison to handle ObjectId vs string
47
- return String(currentUserId) === String(post.authorId);
48
- };
49
- // Fetch users to map IDs to names
50
- useEffect(() => {
51
- const fetchUsers = async () => {
52
- try {
53
- const response = await fetch('/api/users');
54
- const users = await response.json();
55
- if (Array.isArray(users)) {
56
- const map = {};
57
- users.forEach((user) => {
58
- var _a;
59
- const id = (_a = user._id) === null || _a === void 0 ? void 0 : _a.toString();
60
- if (id) {
61
- map[id] = user.name || user.email || 'Unknown';
62
- }
63
- });
64
- setUserMap(map);
65
- }
66
- }
67
- catch (error) {
68
- console.error('Failed to fetch users:', error);
69
- }
70
- };
71
- fetchUsers();
72
- }, []);
73
- const getAuthorName = (authorId) => {
74
- if (!authorId)
75
- return 'Unknown';
76
- return userMap[authorId] || authorId;
77
- };
78
- return (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: posts.map((post) => (_jsxs("div", { className: "bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group", children: [_jsxs("div", { className: "relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden", children: [post.featuredImage ? (_jsx(Image, { id: post.featuredImage, alt: post.title, fill: true, editable: false, className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center", children: _jsx("span", { className: "text-sm text-neutral-400", children: "No Image" }) })), isPostOwner(post) && (_jsx("div", { className: "absolute top-4 left-4 z-10", children: _jsx("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", children: _jsx(PostActionsMenu, { onEdit: () => onEdit(post.id), onPreview: () => onPreview(post.id), onDuplicate: () => onDuplicate(post.id), onDelete: () => onDelete(post.id) }) }) })), _jsx("div", { className: "absolute top-4 right-4", children: _jsx("span", { 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)}`, children: post.status }) })] }), _jsxs("div", { className: "p-6", children: [_jsxs("div", { className: "mb-4", children: [_jsx("button", { onClick: () => onEdit(post.id), className: "text-left w-full hover:cursor-pointer", children: _jsx("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", children: post.title }) }), _jsxs("p", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-mono", children: ["/", post.slug] })] }), post.excerpt && (_jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 mb-4 line-clamp-2", children: post.excerpt })), _jsxs("div", { className: "space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isPostOwner(post) ? (_jsx(UserCheck, { size: 14, className: "text-primary" })) : (_jsx(User, { size: 14, className: "text-neutral-400" })), _jsxs("span", { className: `text-xs ${isPostOwner(post) ? 'text-primary font-semibold' : 'text-neutral-600 dark:text-neutral-400'}`, children: [getAuthorName(post.authorId), isPostOwner(post) && (_jsx("span", { className: "ml-1", children: "(You)" }))] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 14, className: "text-neutral-400" }), _jsx("span", { className: "text-xs text-neutral-600 dark:text-neutral-400", children: formatDate(post.updatedAt, locale) })] })] })] })] }, post.id))) }));
79
- }
@@ -1,197 +0,0 @@
1
- /**
2
- * Post Cards Component
3
- * Card-based layout for displaying posts
4
- */
5
-
6
- 'use client';
7
-
8
- import React, { useState, useEffect } from 'react';
9
- import { Calendar, User, UserCheck } from 'lucide-react';
10
- import { Image } from '@jhits/plugin-images';
11
- import { PostListItem, PostStatus } from '../../types/post';
12
- import { PostActionsMenu } from './PostActionsMenu';
13
- import { useSession } from 'next-auth/react';
14
-
15
- export interface PostCardsProps {
16
- posts: PostListItem[];
17
- locale: string;
18
- onEdit: (postId: string) => void;
19
- onPreview: (postId: string) => void;
20
- onDuplicate: (postId: string) => void;
21
- onDelete: (postId: string) => void;
22
- }
23
-
24
- function getStatusBadgeColor(status: PostStatus) {
25
- switch (status) {
26
- case 'published':
27
- return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
28
- case 'draft':
29
- return 'bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20';
30
- case 'scheduled':
31
- return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
32
- case 'archived':
33
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
34
- default:
35
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
36
- }
37
- }
38
-
39
- function formatDate(dateString: string | undefined, locale: string) {
40
- if (!dateString) return 'No date';
41
- return new Date(dateString).toLocaleDateString(locale, {
42
- day: 'numeric',
43
- month: 'short',
44
- year: 'numeric',
45
- });
46
- }
47
-
48
- export function PostCards({
49
- posts,
50
- locale,
51
- onEdit,
52
- onPreview,
53
- onDuplicate,
54
- onDelete,
55
- }: PostCardsProps) {
56
- const { data: session, status: sessionStatus } = useSession();
57
- const currentUserId = (session?.user as any)?.id;
58
- const [userMap, setUserMap] = useState<Record<string, string>>({});
59
-
60
- // Helper function to check if user is the owner
61
- const isPostOwner = (post: PostListItem): boolean => {
62
- if (sessionStatus === 'loading') return false; // Don't show actions while loading
63
- if (!currentUserId || !post.authorId) return false;
64
- // Convert both to strings for comparison to handle ObjectId vs string
65
- return String(currentUserId) === String(post.authorId);
66
- };
67
-
68
- // Fetch users to map IDs to names
69
- useEffect(() => {
70
- const fetchUsers = async () => {
71
- try {
72
- const response = await fetch('/api/users');
73
- const users = await response.json();
74
- if (Array.isArray(users)) {
75
- const map: Record<string, string> = {};
76
- users.forEach((user: { _id: string; name?: string; email?: string }) => {
77
- const id = user._id?.toString();
78
- if (id) {
79
- map[id] = user.name || user.email || 'Unknown';
80
- }
81
- });
82
- setUserMap(map);
83
- }
84
- } catch (error) {
85
- console.error('Failed to fetch users:', error);
86
- }
87
- };
88
- fetchUsers();
89
- }, []);
90
-
91
- const getAuthorName = (authorId?: string) => {
92
- if (!authorId) return 'Unknown';
93
- return userMap[authorId] || authorId;
94
- };
95
-
96
- return (
97
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
98
- {posts.map((post) => (
99
- <div
100
- key={post.id}
101
- className="bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group"
102
- >
103
- {/* Featured Image */}
104
- <div className="relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden">
105
- {post.featuredImage ? (
106
- <Image
107
- id={post.featuredImage}
108
- alt={post.title}
109
- fill
110
- editable={false}
111
- className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
112
- />
113
- ) : (
114
- <div className="w-full h-full flex items-center justify-center">
115
- <span className="text-sm text-neutral-400">No Image</span>
116
- </div>
117
- )}
118
- {/* Actions Menu - Top Left - Only show for own posts */}
119
- {isPostOwner(post) && (
120
- <div className="absolute top-4 left-4 z-10">
121
- <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">
122
- <PostActionsMenu
123
- onEdit={() => onEdit(post.id)}
124
- onPreview={() => onPreview(post.id)}
125
- onDuplicate={() => onDuplicate(post.id)}
126
- onDelete={() => onDelete(post.id)}
127
- />
128
- </div>
129
- </div>
130
- )}
131
- {/* Status Badge Overlay */}
132
- <div className="absolute top-4 right-4">
133
- <span
134
- 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)}`}
135
- >
136
- {post.status}
137
- </span>
138
- </div>
139
- </div>
140
-
141
- {/* Card Content */}
142
- <div className="p-6">
143
- {/* Title & Slug */}
144
- <div className="mb-4">
145
- <button
146
- onClick={() => onEdit(post.id)}
147
- className="text-left w-full hover:cursor-pointer"
148
- >
149
- <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">
150
- {post.title}
151
- </h3>
152
- </button>
153
- <p className="text-xs text-neutral-500 dark:text-neutral-400 font-mono">
154
- /{post.slug}
155
- </p>
156
- </div>
157
-
158
- {/* Excerpt */}
159
- {post.excerpt && (
160
- <p className="text-sm text-neutral-600 dark:text-neutral-400 mb-4 line-clamp-2">
161
- {post.excerpt}
162
- </p>
163
- )}
164
-
165
- {/* Meta Information */}
166
- <div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700">
167
- {/* Author */}
168
- <div className="flex items-center gap-2">
169
- {isPostOwner(post) ? (
170
- <UserCheck size={14} className="text-primary" />
171
- ) : (
172
- <User size={14} className="text-neutral-400" />
173
- )}
174
- <span className={`text-xs ${isPostOwner(post) ? 'text-primary font-semibold' : 'text-neutral-600 dark:text-neutral-400'}`}>
175
- {getAuthorName(post.authorId)}
176
- {isPostOwner(post) && (
177
- <span className="ml-1">(You)</span>
178
- )}
179
- </span>
180
- </div>
181
-
182
- {/* Last Modified */}
183
- <div className="flex items-center gap-2">
184
- <Calendar size={14} className="text-neutral-400" />
185
- <span className="text-xs text-neutral-600 dark:text-neutral-400">
186
- {formatDate(post.updatedAt, locale)}
187
- </span>
188
- </div>
189
- </div>
190
-
191
- </div>
192
- </div>
193
- ))}
194
- </div>
195
- );
196
- }
197
-
@@ -1,16 +0,0 @@
1
- /**
2
- * Post Filters Component
3
- * Search and filter controls for posts
4
- */
5
- import { PostStatus } from '../../types/post';
6
- export interface PostFiltersProps {
7
- search: string;
8
- onSearchChange: (value: string) => void;
9
- statusFilter: PostStatus | 'all';
10
- onStatusFilterChange: (value: PostStatus | 'all') => void;
11
- categoryFilter: string;
12
- onCategoryFilterChange: (value: string) => void;
13
- categories: string[];
14
- }
15
- export declare function PostFilters({ search, onSearchChange, statusFilter, onStatusFilterChange, categoryFilter, onCategoryFilterChange, categories, }: PostFiltersProps): import("react/jsx-runtime").JSX.Element;
16
- //# sourceMappingURL=PostFilters.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostFilters.d.ts","sourceRoot":"","sources":["PostFilters.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,UAAU,GAAG,KAAK,CAAC;IACjC,oBAAoB,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,KAAK,IAAI,CAAC;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,UAAU,GACb,EAAE,gBAAgB,2CAgElB"}
@@ -1,10 +0,0 @@
1
- /**
2
- * Post Filters Component
3
- * Search and filter controls for posts
4
- */
5
- 'use client';
6
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- import { Search, Filter, Tag } from 'lucide-react';
8
- export function PostFilters({ search, onSearchChange, statusFilter, onStatusFilterChange, categoryFilter, onCategoryFilterChange, categories, }) {
9
- return (_jsxs("div", { className: "flex flex-col sm:flex-row gap-4 mb-6", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx("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%)' }, children: "Search posts by title or content" }), _jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4" }), _jsx("input", { id: "blog-post-search", name: "blog-post-search", type: "text", value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: "Search posts by title or content...", 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" })] }), _jsxs("div", { className: "relative", children: [_jsx("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%)' }, children: "Filter by status" }), _jsx(Filter, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" }), _jsxs("select", { id: "blog-post-status-filter", name: "blog-post-status-filter", value: statusFilter, onChange: (e) => onStatusFilterChange(e.target.value), 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]", children: [_jsx("option", { value: "all", children: "All Statuses" }), _jsx("option", { value: "published", children: "Published" }), _jsx("option", { value: "draft", children: "Draft" }), _jsx("option", { value: "scheduled", children: "Scheduled" }), _jsx("option", { value: "archived", children: "Archived" })] })] }), _jsxs("div", { className: "relative", children: [_jsx("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%)' }, children: "Filter by category" }), _jsx(Tag, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" }), _jsxs("select", { id: "blog-post-category-filter", name: "blog-post-category-filter", value: categoryFilter, onChange: (e) => onCategoryFilterChange(e.target.value), 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]", children: [_jsx("option", { value: "all", children: "All Categories" }), categories.map((category) => (_jsx("option", { value: category, children: category }, category)))] })] })] }));
10
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * Post Filters Component
3
- * Search and filter controls for posts
4
- */
5
-
6
- 'use client';
7
-
8
- import React from 'react';
9
- import { Search, Filter, Tag } from 'lucide-react';
10
- import { PostStatus } from '../../types/post';
11
-
12
- export interface PostFiltersProps {
13
- search: string;
14
- onSearchChange: (value: string) => void;
15
- statusFilter: PostStatus | 'all';
16
- onStatusFilterChange: (value: PostStatus | 'all') => void;
17
- categoryFilter: string;
18
- onCategoryFilterChange: (value: string) => void;
19
- categories: string[];
20
- }
21
-
22
- export function PostFilters({
23
- search,
24
- onSearchChange,
25
- statusFilter,
26
- onStatusFilterChange,
27
- categoryFilter,
28
- onCategoryFilterChange,
29
- categories,
30
- }: PostFiltersProps) {
31
- return (
32
- <div className="flex flex-col sm:flex-row gap-4 mb-6">
33
- {/* Search Input */}
34
- <div className="relative flex-1">
35
- <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%)' }}>
36
- Search posts by title or content
37
- </label>
38
- <Search className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4" />
39
- <input
40
- id="blog-post-search"
41
- name="blog-post-search"
42
- type="text"
43
- value={search}
44
- onChange={(e) => onSearchChange(e.target.value)}
45
- placeholder="Search posts by title or content..."
46
- 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"
47
- />
48
- </div>
49
-
50
- {/* Status Filter */}
51
- <div className="relative">
52
- <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%)' }}>
53
- Filter by status
54
- </label>
55
- <Filter className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
56
- <select
57
- id="blog-post-status-filter"
58
- name="blog-post-status-filter"
59
- value={statusFilter}
60
- onChange={(e) => onStatusFilterChange(e.target.value as PostStatus | 'all')}
61
- 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]"
62
- >
63
- <option value="all">All Statuses</option>
64
- <option value="published">Published</option>
65
- <option value="draft">Draft</option>
66
- <option value="scheduled">Scheduled</option>
67
- <option value="archived">Archived</option>
68
- </select>
69
- </div>
70
-
71
- {/* Category Filter */}
72
- <div className="relative">
73
- <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%)' }}>
74
- Filter by category
75
- </label>
76
- <Tag className="absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" />
77
- <select
78
- id="blog-post-category-filter"
79
- name="blog-post-category-filter"
80
- value={categoryFilter}
81
- onChange={(e) => onCategoryFilterChange(e.target.value)}
82
- 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]"
83
- >
84
- <option value="all">All Categories</option>
85
- {categories.map((category) => (
86
- <option key={category} value={category}>
87
- {category}
88
- </option>
89
- ))}
90
- </select>
91
- </div>
92
- </div>
93
- );
94
- }
95
-
@@ -1,11 +0,0 @@
1
- /**
2
- * Post Manager View
3
- * Production-ready listing page for managing blog posts
4
- * Follows dashboard earth-tone design system
5
- */
6
- export interface PostManagerViewProps {
7
- siteId: string;
8
- locale: string;
9
- }
10
- export declare function PostManagerView({ siteId, locale }: PostManagerViewProps): import("react/jsx-runtime").JSX.Element;
11
- //# sourceMappingURL=PostManagerView.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostManagerView.d.ts","sourceRoot":"","sources":["PostManagerView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,2CAqQvE"}
@@ -1,174 +0,0 @@
1
- /**
2
- * Post Manager View
3
- * Production-ready listing page for managing blog posts
4
- * Follows dashboard earth-tone design system
5
- */
6
- 'use client';
7
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
- import React, { useState, useEffect } from 'react';
9
- import { Plus, List, Grid3x3 } from 'lucide-react';
10
- import { PostStats } from './PostStats';
11
- import { PostFilters } from './PostFilters';
12
- import { PostTable } from './PostTable';
13
- import { PostCards } from './PostCards';
14
- import { EmptyState } from './EmptyState';
15
- import { apiToBlogPost } from '../../lib/mappers/apiMapper';
16
- const STORAGE_KEY_PREFIX = 'blog-view-mode';
17
- export function PostManagerView({ siteId, locale }) {
18
- const [posts, setPosts] = useState([]);
19
- const [totalPosts, setTotalPosts] = useState(0);
20
- const [loading, setLoading] = useState(true);
21
- const [search, setSearch] = useState('');
22
- const [statusFilter, setStatusFilter] = useState('all');
23
- const [categoryFilter, setCategoryFilter] = useState('all');
24
- // Load view mode preference from localStorage
25
- const getStoredViewMode = () => {
26
- if (typeof window === 'undefined')
27
- return 'list';
28
- const stored = localStorage.getItem(`${STORAGE_KEY_PREFIX}-${siteId}`);
29
- return (stored === 'list' || stored === 'cards') ? stored : 'list';
30
- };
31
- const [viewMode, setViewMode] = useState(getStoredViewMode);
32
- // Save view mode preference to localStorage when it changes
33
- useEffect(() => {
34
- if (typeof window !== 'undefined') {
35
- localStorage.setItem(`${STORAGE_KEY_PREFIX}-${siteId}`, viewMode);
36
- }
37
- }, [viewMode, siteId]);
38
- // Fetch posts from API
39
- useEffect(() => {
40
- const fetchPosts = async () => {
41
- try {
42
- setLoading(true);
43
- const response = await fetch('/api/plugin-blog?admin=true');
44
- const data = await response.json();
45
- if (data.blogs && Array.isArray(data.blogs)) {
46
- // Convert API format to PostListItem format
47
- const postListItems = data.blogs.map((doc) => {
48
- var _a;
49
- const blogPost = apiToBlogPost(doc);
50
- // Use semantic ID (id) - plugin-images handles resolution
51
- // The id is the semantic ID (e.g., "blog-featured-{slug}") which plugin-images resolves
52
- const featuredImageId = (_a = blogPost.metadata.featuredImage) === null || _a === void 0 ? void 0 : _a.id;
53
- // Extract category from metadata or hero block
54
- let category = undefined;
55
- if (blogPost.metadata.categories && blogPost.metadata.categories.length > 0) {
56
- category = blogPost.metadata.categories[0];
57
- }
58
- else {
59
- // Check hero block for category
60
- const heroBlock = blogPost.blocks.find(block => block.type === 'hero');
61
- if (heroBlock && heroBlock.data && typeof heroBlock.data === 'object') {
62
- const heroCategory = heroBlock.data.category;
63
- if (heroCategory && typeof heroCategory === 'string' && heroCategory.trim()) {
64
- category = heroCategory.trim();
65
- }
66
- }
67
- }
68
- return {
69
- id: blogPost.id,
70
- title: blogPost.title,
71
- slug: blogPost.slug,
72
- status: blogPost.publication.status,
73
- date: blogPost.publication.date,
74
- excerpt: blogPost.metadata.excerpt,
75
- featuredImage: featuredImageId,
76
- authorId: blogPost.publication.authorId,
77
- updatedAt: blogPost.updatedAt,
78
- category: category,
79
- };
80
- });
81
- setPosts(postListItems);
82
- setTotalPosts(data.total);
83
- }
84
- }
85
- catch (error) {
86
- console.error('Failed to fetch posts:', error);
87
- }
88
- finally {
89
- setLoading(false);
90
- }
91
- };
92
- fetchPosts();
93
- }, []);
94
- // Extract unique categories from posts
95
- const categories = React.useMemo(() => {
96
- const categorySet = new Set();
97
- posts.forEach(post => {
98
- if (post.category && post.category.trim()) {
99
- categorySet.add(post.category.trim());
100
- }
101
- });
102
- return Array.from(categorySet).sort();
103
- }, [posts]);
104
- // Filter posts
105
- const filteredPosts = React.useMemo(() => {
106
- return posts.filter((post) => {
107
- var _a;
108
- const matchesSearch = search === '' ||
109
- post.title.toLowerCase().includes(search.toLowerCase()) ||
110
- ((_a = post.excerpt) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(search.toLowerCase())) ||
111
- post.slug.toLowerCase().includes(search.toLowerCase());
112
- const matchesStatus = statusFilter === 'all' || post.status === statusFilter;
113
- const matchesCategory = categoryFilter === 'all' || post.category === categoryFilter;
114
- return matchesSearch && matchesStatus && matchesCategory;
115
- });
116
- }, [posts, search, statusFilter, categoryFilter]);
117
- // Action handlers
118
- const handleCreatePost = () => {
119
- // Navigate to editor route - the plugin router will handle this
120
- // The route 'new' maps to the editor view
121
- window.location.href = '/dashboard/blog/new';
122
- };
123
- const handleEdit = (postId) => {
124
- // Find the post to get its slug
125
- const post = posts.find(p => p.id === postId);
126
- if (post) {
127
- // Navigate to editor with slug (API uses slug, not ID)
128
- window.location.href = `/dashboard/blog/editor/${post.slug}`;
129
- }
130
- };
131
- const handlePreview = (postId) => {
132
- // Open preview in new tab
133
- window.open(`/dashboard/blog/preview/${postId}`, '_blank');
134
- };
135
- const handleDuplicate = (postId) => {
136
- // TODO: Implement duplicate functionality
137
- const post = posts.find((p) => p.id === postId);
138
- if (post) {
139
- const duplicated = Object.assign(Object.assign({}, post), { id: `duplicate-${Date.now()}`, title: `${post.title} (Copy)`, slug: `${post.slug}-copy-${Date.now()}`, status: 'draft', updatedAt: new Date().toISOString() });
140
- setPosts((prev) => [...prev, duplicated]);
141
- }
142
- };
143
- const handleDelete = async (postId) => {
144
- if (confirm('Are you sure you want to delete this post?')) {
145
- try {
146
- const post = posts.find(p => p.id === postId);
147
- if (post) {
148
- const response = await fetch(`/api/plugin-blog/${post.slug}`, {
149
- method: 'DELETE',
150
- });
151
- if (response.ok) {
152
- // Remove from local state
153
- setPosts((prev) => prev.filter((p) => p.id !== postId));
154
- setTotalPosts(prev => prev - 1);
155
- }
156
- else {
157
- const error = await response.json();
158
- alert(error.error || 'Failed to delete post');
159
- }
160
- }
161
- }
162
- catch (error) {
163
- console.error('Failed to delete post:', error);
164
- alert('Failed to delete post');
165
- }
166
- }
167
- };
168
- const hasActiveFilters = search !== '' || statusFilter !== 'all' || categoryFilter !== 'all';
169
- return (_jsxs("div", { className: "h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-neutral-950 dark:text-white uppercase tracking-tighter mb-2", children: "Blog Posts" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Manage your blog posts, drafts, and published content" })] }), _jsxs("button", { onClick: handleCreatePost, className: "inline-flex items-center gap-2 px-6 py-3 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-primary/20", children: [_jsx(Plus, { size: 16 }), "New Post"] })] }), _jsx(PostStats, { total: totalPosts, posts: posts }), _jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6", children: [_jsx(PostFilters, { search: search, onSearchChange: setSearch, statusFilter: statusFilter, onStatusFilterChange: setStatusFilter, categoryFilter: categoryFilter, onCategoryFilterChange: setCategoryFilter, categories: categories }), _jsxs("div", { className: "flex items-center gap-2 bg-neutral-100 dark:bg-neutral-800/50 rounded-full p-1 border border-neutral-300 dark:border-neutral-700", children: [_jsx("button", { onClick: () => setViewMode('list'), className: `p-2 rounded-full transition-all ${viewMode === 'list'
170
- ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
171
- : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'}`, title: "List View", children: _jsx(List, { size: 18 }) }), _jsx("button", { onClick: () => setViewMode('cards'), className: `p-2 rounded-full transition-all ${viewMode === 'cards'
172
- ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
173
- : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'}`, title: "Card View", children: _jsx(Grid3x3, { size: 18 }) })] })] }), loading ? (_jsx("div", { className: "flex items-center justify-center py-20", children: _jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" }) })) : filteredPosts.length === 0 ? (_jsx(EmptyState, { hasFilters: hasActiveFilters, onCreatePost: handleCreatePost })) : viewMode === 'list' ? (_jsx(PostTable, { posts: filteredPosts, locale: locale, onEdit: handleEdit, onPreview: handlePreview, onDuplicate: handleDuplicate, onDelete: handleDelete })) : (_jsx(PostCards, { posts: filteredPosts, locale: locale, onEdit: handleEdit, onPreview: handlePreview, onDuplicate: handleDuplicate, onDelete: handleDelete }))] }));
174
- }