@actuate-media/cms-admin 0.17.0 → 0.19.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 (117) hide show
  1. package/dist/__tests__/lib/page-editor-service-api.test.d.ts +2 -0
  2. package/dist/__tests__/lib/page-editor-service-api.test.d.ts.map +1 -0
  3. package/dist/__tests__/lib/page-editor-service-api.test.js +155 -0
  4. package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -0
  5. package/dist/__tests__/lib/pages-service-fetch.test.d.ts +2 -0
  6. package/dist/__tests__/lib/pages-service-fetch.test.d.ts.map +1 -0
  7. package/dist/__tests__/lib/pages-service-fetch.test.js +125 -0
  8. package/dist/__tests__/lib/pages-service-fetch.test.js.map +1 -0
  9. package/dist/__tests__/lib/post-editor-service.test.js +75 -23
  10. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
  11. package/dist/__tests__/lib/posts-service.test.js +132 -142
  12. package/dist/__tests__/lib/posts-service.test.js.map +1 -1
  13. package/dist/__tests__/views/page-section-editor.test.d.ts +2 -0
  14. package/dist/__tests__/views/page-section-editor.test.d.ts.map +1 -0
  15. package/dist/__tests__/views/page-section-editor.test.js +56 -0
  16. package/dist/__tests__/views/page-section-editor.test.js.map +1 -0
  17. package/dist/__tests__/views/pages-list-view.test.d.ts +2 -0
  18. package/dist/__tests__/views/pages-list-view.test.d.ts.map +1 -0
  19. package/dist/__tests__/views/pages-list-view.test.js +102 -0
  20. package/dist/__tests__/views/pages-list-view.test.js.map +1 -0
  21. package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
  22. package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
  24. package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
  25. package/dist/actuate-admin.css +1 -1
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +0 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/page-editor-service.d.ts +1 -0
  31. package/dist/lib/page-editor-service.d.ts.map +1 -1
  32. package/dist/lib/page-editor-service.js +55 -0
  33. package/dist/lib/page-editor-service.js.map +1 -1
  34. package/dist/lib/pages-service.d.ts +26 -0
  35. package/dist/lib/pages-service.d.ts.map +1 -1
  36. package/dist/lib/pages-service.js +65 -38
  37. package/dist/lib/pages-service.js.map +1 -1
  38. package/dist/lib/post-editor-service.d.ts +6 -4
  39. package/dist/lib/post-editor-service.d.ts.map +1 -1
  40. package/dist/lib/post-editor-service.js +32 -37
  41. package/dist/lib/post-editor-service.js.map +1 -1
  42. package/dist/lib/posts-service.d.ts.map +1 -1
  43. package/dist/lib/posts-service.js +91 -160
  44. package/dist/lib/posts-service.js.map +1 -1
  45. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  46. package/dist/views/Pages/PagesListView.js +71 -46
  47. package/dist/views/Pages/PagesListView.js.map +1 -1
  48. package/dist/views/Posts/NewPostPicker.d.ts +5 -4
  49. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
  50. package/dist/views/Posts/NewPostPicker.js +5 -4
  51. package/dist/views/Posts/NewPostPicker.js.map +1 -1
  52. package/dist/views/Posts/PostTypesView.d.ts +5 -4
  53. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  54. package/dist/views/Posts/PostTypesView.js.map +1 -1
  55. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  56. package/dist/views/Posts/PostsListView.js +8 -0
  57. package/dist/views/Posts/PostsListView.js.map +1 -1
  58. package/dist/views/page-editor/PagePreview.d.ts.map +1 -1
  59. package/dist/views/page-editor/PagePreview.js +10 -1
  60. package/dist/views/page-editor/PagePreview.js.map +1 -1
  61. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  62. package/dist/views/page-editor/PageSectionEditor.js +22 -2
  63. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  64. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
  65. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
  66. package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
  67. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
  68. package/dist/views/post-editor/PostEditorCanvas.js +7 -1
  69. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  70. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  71. package/dist/views/post-editor/PostFieldsModal.js +10 -4
  72. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  73. package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
  74. package/dist/views/post-editor/PostHeader.js +4 -3
  75. package/dist/views/post-editor/PostHeader.js.map +1 -1
  76. package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
  77. package/dist/views/post-editor/PostPreview.js +11 -1
  78. package/dist/views/post-editor/PostPreview.js.map +1 -1
  79. package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
  80. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  81. package/dist/views/post-editor/PostSectionEditor.js +4 -1
  82. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/__tests__/lib/page-editor-service-api.test.ts +180 -0
  85. package/src/__tests__/lib/pages-service-fetch.test.ts +160 -0
  86. package/src/__tests__/lib/post-editor-service.test.ts +78 -23
  87. package/src/__tests__/lib/posts-service.test.ts +134 -163
  88. package/src/__tests__/views/page-section-editor.test.tsx +65 -0
  89. package/src/__tests__/views/pages-list-view.test.tsx +119 -0
  90. package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
  91. package/src/index.ts +0 -2
  92. package/src/lib/page-editor-service.ts +61 -0
  93. package/src/lib/pages-service.ts +88 -35
  94. package/src/lib/post-editor-service.ts +41 -40
  95. package/src/lib/posts-service.ts +109 -180
  96. package/src/views/Pages/PagesListView.tsx +113 -82
  97. package/src/views/Posts/NewPostPicker.tsx +5 -4
  98. package/src/views/Posts/PostTypesView.tsx +5 -4
  99. package/src/views/Posts/PostsListView.tsx +9 -0
  100. package/src/views/page-editor/PagePreview.tsx +10 -1
  101. package/src/views/page-editor/PageSectionEditor.tsx +25 -1
  102. package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
  103. package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
  104. package/src/views/post-editor/PostFieldsModal.tsx +36 -21
  105. package/src/views/post-editor/PostHeader.tsx +4 -4
  106. package/src/views/post-editor/PostPreview.tsx +12 -1
  107. package/src/views/post-editor/PostSectionEditor.tsx +22 -5
  108. package/dist/views/PostEditor.d.ts +0 -6
  109. package/dist/views/PostEditor.d.ts.map +0 -1
  110. package/dist/views/PostEditor.js +0 -79
  111. package/dist/views/PostEditor.js.map +0 -1
  112. package/dist/views/Posts.d.ts +0 -5
  113. package/dist/views/Posts.d.ts.map +0 -1
  114. package/dist/views/Posts.js +0 -73
  115. package/dist/views/Posts.js.map +0 -1
  116. package/src/views/PostEditor.tsx +0 -257
  117. package/src/views/Posts.tsx +0 -375
@@ -1,3 +1,4 @@
1
+ import { computeReadingTime } from '@actuate-media/cms-core/page-sections'
1
2
  import type { PostHeaderConfig } from '../page-editor/section-types.js'
2
3
  import type { PostRenderContext } from '../page-editor/sections/index.js'
3
4
 
@@ -37,7 +38,7 @@ export function PostHeader({ config, context }: PostHeaderProps) {
37
38
  )}
38
39
  {config.showAuthor && <span>{context.author?.name || 'Author name'}</span>}
39
40
  {date && <span>{date}</span>}
40
- {config.showReadingTime && <span>5 min read</span>}
41
+ {config.showReadingTime && <span>{computeReadingTime(context.body)} min read</span>}
41
42
  </div>
42
43
  )
43
44
 
@@ -45,7 +46,7 @@ export function PostHeader({ config, context }: PostHeaderProps) {
45
46
  return (
46
47
  <header className="relative">
47
48
  <img src={context.featuredImageUrl} alt="" className="h-[360px] w-full object-cover" />
48
- <div className="absolute inset-0 bg-gradient-to-t from-black/70 to-black/10" aria-hidden />
49
+ <div className="absolute inset-0 bg-linear-to-t from-black/70 to-black/10" aria-hidden />
49
50
  <div className="absolute inset-x-0 bottom-0 px-6 pb-8">
50
51
  <div className={`mx-auto flex max-w-3xl flex-col ${align}`}>
51
52
  <h1 className="text-3xl font-semibold tracking-tight text-white sm:text-4xl">
@@ -66,7 +67,6 @@ export function PostHeader({ config, context }: PostHeaderProps) {
66
67
  <div
67
68
  className={`flex max-w-3xl flex-col ${align} ${config.layout === 'centered' ? 'mx-auto' : ''}`}
68
69
  >
69
- {config.showCategory && context.category && config.layout !== 'centered' && null}
70
70
  <h1 className="text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl">{title}</h1>
71
71
  {config.showExcerpt && context.excerpt && (
72
72
  <p className="mt-3 max-w-2xl text-lg text-zinc-600">{context.excerpt}</p>
@@ -78,7 +78,7 @@ export function PostHeader({ config, context }: PostHeaderProps) {
78
78
  <img
79
79
  src={context.featuredImageUrl}
80
80
  alt=""
81
- className="aspect-[16/9] w-full rounded-xl object-cover"
81
+ className="aspect-video w-full rounded-xl object-cover"
82
82
  />
83
83
  </div>
84
84
  )}
@@ -78,6 +78,17 @@ export function PostPreview({ postType, documentId, session, onNavigate }: PostP
78
78
  }
79
79
  const visible = post.sections.filter((s) => s.visible)
80
80
 
81
+ // `window.close()` only works for tabs JS opened (the "View preview" action
82
+ // opens this view via window.open). When the user reached it through in-app
83
+ // navigation there is no opener, so close() is a silent no-op — fall back to
84
+ // the editor after a tick so the Close button always does something.
85
+ const handleClose = () => {
86
+ window.close()
87
+ setTimeout(() => {
88
+ onNavigate(`/posts/${postType}/${documentId}/edit`)
89
+ }, 120)
90
+ }
91
+
81
92
  return (
82
93
  <div className="flex h-full flex-col">
83
94
  {/* Slim preview bar. This view opens in its own browser tab, so
@@ -85,7 +96,7 @@ export function PostPreview({ postType, documentId, session, onNavigate }: PostP
85
96
  <div className="bg-card border-border flex h-12 shrink-0 items-center justify-between border-b px-4">
86
97
  <button
87
98
  type="button"
88
- onClick={() => window.close()}
99
+ onClick={handleClose}
89
100
  className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm"
90
101
  aria-label="Close preview tab"
91
102
  >
@@ -43,12 +43,23 @@ import { ValidationSummary } from '../page-editor/ValidationSummary.js'
43
43
  import { PostEditorCanvas } from './PostEditorCanvas.js'
44
44
  import { PostFieldsModal } from './PostFieldsModal.js'
45
45
 
46
+ /** The slice of the resolved CMS config this editor reads. */
47
+ interface EditorConfig {
48
+ collections?: Record<string, { labels?: { singular?: string }; urlPrefix?: string } | undefined>
49
+ seo?: { siteUrl?: string }
50
+ }
51
+
52
+ /** The slice of the admin session this editor reads. */
53
+ interface EditorSession {
54
+ user?: { role?: string; name?: string; email?: string }
55
+ }
56
+
46
57
  interface PostSectionEditorProps {
47
58
  postType: string
48
59
  /** undefined when creating a new post (`/posts/:type/new`) */
49
60
  documentId?: string
50
- config?: any
51
- session?: any
61
+ config?: EditorConfig
62
+ session?: EditorSession
52
63
  onNavigate: (path: string) => void
53
64
  /**
54
65
  * Open a preview route in a new browser tab. `prepare` runs before the tab
@@ -58,9 +69,15 @@ interface PostSectionEditorProps {
58
69
  onOpenPreview?: (path: string, prepare?: () => Promise<boolean | void> | boolean | void) => void
59
70
  }
60
71
 
61
- function deriveCan(session: any): { canEdit: boolean; canPublish: boolean } {
62
- const role: string | undefined = session?.user?.role
63
- if (!role) return { canEdit: true, canPublish: true }
72
+ function deriveCan(session: EditorSession | undefined): {
73
+ canEdit: boolean
74
+ canPublish: boolean
75
+ } {
76
+ const role = session?.user?.role
77
+ // Default closed: when we can't identify a role, hide the edit/publish
78
+ // affordances rather than exposing them. The server independently enforces
79
+ // access on every write, so this is a UX guard — but it should fail safe.
80
+ if (!role) return { canEdit: false, canPublish: false }
64
81
  const editors = ['ADMIN', 'EDITOR', 'AUTHOR']
65
82
  const publishers = ['ADMIN', 'EDITOR']
66
83
  return { canEdit: editors.includes(role), canPublish: publishers.includes(role) }
@@ -1,6 +0,0 @@
1
- export interface PostEditorProps {
2
- id?: string;
3
- onNavigate?: (path: string) => void;
4
- }
5
- export declare function PostEditor({ id, onNavigate }: PostEditorProps): import("react/jsx-runtime").JSX.Element;
6
- //# sourceMappingURL=PostEditor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostEditor.d.ts","sourceRoot":"","sources":["../../src/views/PostEditor.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,eAAe,2CAiP7D"}
@@ -1,79 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { ArrowLeft, Eye, Loader2, AlertTriangle } from 'lucide-react';
5
- import { toast } from 'sonner';
6
- import { TipTapEditor } from '../components/TipTapEditor.js';
7
- import { SEOPanel } from '../components/SEOPanel.js';
8
- import { useApiData } from '../lib/useApiData.js';
9
- import { cmsApi } from '../lib/api.js';
10
- export function PostEditor({ id, onNavigate }) {
11
- const isNew = !id;
12
- const { data, loading, error } = useApiData(isNew ? '' : `/collections/posts/${id}`);
13
- const [title, setTitle] = useState('');
14
- const [content, setContent] = useState('');
15
- const [status, setStatus] = useState('draft');
16
- const [slug, setSlug] = useState('');
17
- const [saving, setSaving] = useState(false);
18
- const [initialized, setInitialized] = useState(isNew);
19
- const [seoData, setSeoData] = useState({});
20
- useEffect(() => {
21
- if (data && !initialized) {
22
- setTitle(data.title ?? '');
23
- setContent(data.content ?? data.data?.content ?? '');
24
- setStatus(data.status === 'PUBLISHED' ? 'published' : 'draft');
25
- setSlug(data.slug ?? '');
26
- setInitialized(true);
27
- }
28
- }, [data, initialized]);
29
- const savePost = async () => {
30
- setSaving(true);
31
- const body = JSON.stringify({
32
- title,
33
- content,
34
- slug,
35
- status: status === 'published' ? 'PUBLISHED' : 'DRAFT',
36
- });
37
- const res = isNew
38
- ? await cmsApi('/collections/posts', { method: 'POST', body })
39
- : await cmsApi(`/collections/posts/${id}`, { method: 'PUT', body });
40
- setSaving(false);
41
- if (res.error) {
42
- toast.error(res.error);
43
- }
44
- else {
45
- toast.success('Post saved successfully!');
46
- if (isNew && res.data?.id) {
47
- onNavigate?.(`/posts/${res.data.id}`);
48
- }
49
- }
50
- };
51
- const publishPost = async () => {
52
- setSaving(true);
53
- const body = JSON.stringify({
54
- title,
55
- content,
56
- slug,
57
- status: 'PUBLISHED',
58
- });
59
- const endpoint = isNew ? '/collections/posts' : `/collections/posts/${id}`;
60
- const method = isNew ? 'POST' : 'PUT';
61
- const res = await cmsApi(endpoint, { method, body });
62
- setSaving(false);
63
- if (res.error) {
64
- toast.error(res.error);
65
- }
66
- else {
67
- setStatus('published');
68
- toast.success('Post published!');
69
- if (isNew && res.data?.id) {
70
- onNavigate?.(`/posts/${res.data.id}`);
71
- }
72
- }
73
- };
74
- if (!isNew && loading) {
75
- return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-blue-600" }) }));
76
- }
77
- return (_jsxs("div", { className: "flex h-full flex-col bg-white", children: [error && (_jsxs("div", { className: "mx-4 mt-3 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3", children: [_jsx(AlertTriangle, { className: "h-5 w-5 shrink-0 text-red-600" }), _jsx("span", { className: "flex-1 text-sm text-red-800", children: error })] })), _jsx("div", { className: "border-b border-gray-200 px-3 py-3 sm:px-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("button", { onClick: () => onNavigate?.('/posts'), className: "inline-flex items-center gap-2 text-sm text-gray-600 transition-colors hover:text-gray-900", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Back to Posts" }), _jsx("span", { className: "sm:hidden", children: "Back" })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("button", { className: "flex items-center gap-2 rounded-lg border border-gray-300 px-3 py-1.5 text-sm transition-colors hover:bg-gray-50", children: [_jsx(Eye, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "Preview" })] }) })] }) }), _jsxs("div", { className: "flex flex-1 flex-col overflow-hidden lg:flex-row", children: [_jsx("div", { className: "flex-1 overflow-y-auto", children: _jsxs("div", { className: "mx-auto max-w-4xl p-3 pr-6 sm:p-4 sm:pr-8", children: [_jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Post title", className: "mb-4 w-full border-none px-0 text-2xl font-bold placeholder:text-gray-300 focus:ring-0 focus:outline-none sm:text-3xl" }), _jsxs("div", { className: "mb-4", children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-gray-600", children: "URL Slug" }), _jsx("input", { type: "text", value: slug, onChange: (e) => setSlug(e.target.value), placeholder: "url-slug", className: "w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" })] }), _jsx("div", { className: "mb-4", children: _jsx(TipTapEditor, { content: content, onChange: setContent }) })] }) }), _jsx("div", { className: "hidden w-[30%] overflow-y-auto border-l border-gray-200 bg-gray-50 lg:block", children: _jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", children: [_jsx("h3", { className: "mb-3 text-sm font-semibold text-gray-900", children: "Publish" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-gray-700", children: "Status" }), _jsxs("select", { value: status, onChange: (e) => setStatus(e.target.value), className: "w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none", children: [_jsx("option", { value: "draft", children: "Draft" }), _jsx("option", { value: "published", children: "Published" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-gray-700", children: "Publish Date" }), _jsx("input", { type: "datetime-local", className: "w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" })] }), _jsx("button", { onClick: savePost, disabled: saving, className: "w-full rounded-lg bg-blue-600 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50", children: saving ? 'Saving...' : 'Save Post' }), status === 'draft' && (_jsx("button", { onClick: publishPost, disabled: saving, className: "w-full rounded-lg bg-green-600 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-50", children: saving ? 'Publishing...' : 'Publish' }))] })] }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", children: [_jsx("h3", { className: "mb-3 text-sm font-semibold text-gray-900", children: "Categories & Tags" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-gray-700", children: "Category" }), _jsxs("select", { className: "w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none", children: [_jsx("option", { children: "Technology" }), _jsx("option", { children: "Design" }), _jsx("option", { children: "Business" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-gray-700", children: "Tags" }), _jsx("input", { type: "text", placeholder: "Add tags...", className: "w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" })] })] })] }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", children: [_jsx("h3", { className: "mb-3 text-sm font-semibold text-gray-900", children: "Featured Image" }), _jsx("button", { className: "w-full rounded-lg border-2 border-dashed border-gray-300 py-2 text-sm text-gray-600 transition-colors hover:border-gray-400", children: "Upload Image" })] }), _jsx(SEOPanel, { title: title, slug: slug, content: content, seoData: seoData, onChange: setSeoData })] }) }), _jsx("div", { className: "border-t border-gray-200 bg-white p-3 lg:hidden", children: _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: savePost, disabled: saving, className: "flex-1 rounded-lg bg-blue-600 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50", children: saving ? 'Saving...' : 'Save Post' }), status === 'draft' && (_jsx("button", { onClick: publishPost, disabled: saving, className: "flex-1 rounded-lg bg-green-600 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-50", children: "Publish" }))] }) })] })] }));
78
- }
79
- //# sourceMappingURL=PostEditor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PostEditor.js","sourceRoot":"","sources":["../../src/views/PostEditor.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- export interface PostsProps {
2
- onNavigate?: (path: string) => void;
3
- }
4
- export declare function Posts({ onNavigate }: PostsProps): import("react/jsx-runtime").JSX.Element;
5
- //# sourceMappingURL=Posts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Posts.d.ts","sourceRoot":"","sources":["../../src/views/Posts.tsx"],"names":[],"mappings":"AAsBA,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED,wBAAgB,KAAK,CAAC,EAAE,UAAU,EAAE,EAAE,UAAU,2CA4V/C"}
@@ -1,73 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Plus, Search, Trash2, SlidersHorizontal, Pencil, ArrowUpDown, ArrowUp, ArrowDown, Loader2, AlertTriangle, } from 'lucide-react';
4
- import { useState, useMemo } from 'react';
5
- import { toast } from 'sonner';
6
- import { sortByRelevance, toggleSort } from '../lib/search.js';
7
- import { useApiData } from '../lib/useApiData.js';
8
- import { cmsApi } from '../lib/api.js';
9
- export function Posts({ onNavigate }) {
10
- const { data, loading, error, refetch } = useApiData('/collections/posts?pageSize=100');
11
- const [searchQuery, setSearchQuery] = useState('');
12
- const [filterStatus, setFilterStatus] = useState('all');
13
- const [filterCategory, setFilterCategory] = useState('all');
14
- const [selectedPosts, setSelectedPosts] = useState([]);
15
- const [sortConfig, setSortConfig] = useState(null);
16
- const posts = data?.docs ?? [];
17
- const filteredAndSorted = useMemo(() => {
18
- let results = posts.filter((post) => {
19
- const matchesSearch = (post.title ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
20
- (post.author ?? '').toLowerCase().includes(searchQuery.toLowerCase());
21
- const matchesStatus = filterStatus === 'all' || (post.status ?? '').toLowerCase() === filterStatus.toLowerCase();
22
- const matchesCategory = filterCategory === 'all' ||
23
- (post.category ?? '').toLowerCase() === filterCategory.toLowerCase();
24
- return matchesSearch && matchesStatus && matchesCategory;
25
- });
26
- if (searchQuery.trim()) {
27
- results = sortByRelevance(results, searchQuery, (p) => [
28
- p.title,
29
- p.author ?? '',
30
- p.category ?? '',
31
- ]);
32
- }
33
- else if (sortConfig) {
34
- results = [...results].sort((a, b) => {
35
- const aVal = a[sortConfig.key] ?? '';
36
- const bVal = b[sortConfig.key] ?? '';
37
- const cmp = String(aVal).localeCompare(String(bVal));
38
- return sortConfig.direction === 'asc' ? cmp : -cmp;
39
- });
40
- }
41
- return results;
42
- }, [posts, searchQuery, filterStatus, filterCategory, sortConfig]);
43
- const handleSelectAll = (checked) => {
44
- setSelectedPosts(checked ? filteredAndSorted.map((p) => p.id) : []);
45
- };
46
- const handleSelectPost = (id) => {
47
- setSelectedPosts((prev) => prev.includes(id) ? prev.filter((pid) => pid !== id) : [...prev, id]);
48
- };
49
- const handleBulkDelete = async () => {
50
- for (const id of selectedPosts) {
51
- await cmsApi(`/collections/posts/${id}`, { method: 'DELETE' });
52
- }
53
- toast.success(`${selectedPosts.length} posts deleted`);
54
- setSelectedPosts([]);
55
- refetch();
56
- };
57
- const handleDelete = async (id) => {
58
- await cmsApi(`/collections/posts/${id}`, { method: 'DELETE' });
59
- toast.success('Post deleted');
60
- setSelectedPosts((prev) => prev.filter((pid) => pid !== id));
61
- refetch();
62
- };
63
- function SortHeader({ label, sortKey }) {
64
- const active = sortConfig?.key === sortKey;
65
- return (_jsxs("button", { type: "button", onClick: () => setSortConfig(toggleSort(sortConfig, sortKey)), className: "flex items-center gap-1 text-xs font-medium text-gray-700 transition-colors hover:text-gray-900", children: [label, active ? (sortConfig.direction === 'asc' ? (_jsx(ArrowUp, { className: "h-3 w-3" })) : (_jsx(ArrowDown, { className: "h-3 w-3" }))) : (_jsx(ArrowUpDown, { className: "h-3 w-3 text-gray-400" }))] }));
66
- }
67
- if (loading) {
68
- return (_jsx("div", { className: "flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-blue-600" }) }));
69
- }
70
- return (_jsxs("div", { className: "flex h-full flex-col p-3 pr-6 sm:p-4 sm:pr-8", children: [error && (_jsxs("div", { className: "mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3", children: [_jsx(AlertTriangle, { className: "h-5 w-5 shrink-0 text-red-600" }), _jsx("span", { className: "flex-1 text-sm text-red-800", children: error }), _jsx("button", { onClick: refetch, className: "rounded-lg border border-red-300 px-3 py-1 text-sm text-red-700 transition-colors hover:bg-red-100", children: "Retry" })] })), _jsxs("div", { className: "mb-4 flex flex-col justify-between gap-3 sm:flex-row sm:items-center", children: [_jsxs("div", { children: [_jsx("h1", { className: "mb-1 text-xl font-semibold text-gray-900 sm:text-2xl", children: "Posts" }), _jsxs("p", { className: "text-sm text-gray-600", children: [filteredAndSorted.length, " total posts"] })] }), _jsxs("button", { onClick: () => onNavigate?.('/posts/new'), className: "flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-700", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "New Post" }), _jsx("span", { className: "sm:hidden", children: "New" })] })] }), _jsx("div", { className: "mb-4 rounded-lg border border-gray-200 bg-white", children: _jsxs("div", { className: "flex flex-col gap-3 p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search posts...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full rounded-lg border border-gray-300 py-2 pr-3 pl-9 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none" })] }), _jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [_jsxs("select", { value: filterStatus, onChange: (e) => setFilterStatus(e.target.value), className: "flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none", children: [_jsx("option", { value: "all", children: "All Status" }), _jsx("option", { value: "published", children: "Published" }), _jsx("option", { value: "draft", children: "Draft" })] }), _jsxs("select", { value: filterCategory, onChange: (e) => setFilterCategory(e.target.value), className: "flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none", children: [_jsx("option", { value: "all", children: "All Categories" }), _jsx("option", { value: "technology", children: "Technology" }), _jsx("option", { value: "design", children: "Design" }), _jsx("option", { value: "business", children: "Business" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-center gap-2 rounded-lg border border-gray-300 px-3 py-2 text-sm transition-colors hover:bg-gray-50", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: "More Filters" })] })] })] }) }), selectedPosts.length > 0 && (_jsx("div", { className: "mb-4 rounded-lg border border-blue-200 bg-blue-50 p-3", children: _jsxs("div", { className: "flex flex-col justify-between gap-2 sm:flex-row sm:items-center", children: [_jsxs("span", { className: "text-sm text-blue-900", children: [selectedPosts.length, " post", selectedPosts.length !== 1 ? 's' : '', " selected"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: handleBulkDelete, className: "flex-1 rounded-lg bg-red-600 px-3 py-1.5 text-sm text-white transition-colors hover:bg-red-700 sm:flex-none", children: "Delete" }), _jsx("button", { type: "button", onClick: () => setSelectedPosts([]), className: "flex-1 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm transition-colors hover:bg-gray-50 sm:flex-none", children: "Cancel" })] })] }) })), filteredAndSorted.length === 0 && !error ? (_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center rounded-lg border border-gray-200 bg-white p-8 text-center", children: [_jsx("p", { className: "mb-2 text-sm text-gray-500", children: "No posts yet" }), _jsx("button", { onClick: () => onNavigate?.('/posts/new'), className: "rounded-lg bg-blue-600 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-700", children: "Create your first post" })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden flex-1 overflow-hidden rounded-lg border border-gray-200 bg-white md:block", children: _jsx("div", { className: "h-full overflow-x-auto", children: _jsxs("table", { className: "w-full", children: [_jsx("thead", { className: "sticky top-0 border-b border-gray-200 bg-gray-50", children: _jsxs("tr", { children: [_jsx("th", { className: "w-8 px-3 py-2 text-left", children: _jsx("input", { type: "checkbox", checked: selectedPosts.length === filteredAndSorted.length &&
71
- filteredAndSorted.length > 0, onChange: (e) => handleSelectAll(e.target.checked), className: "rounded border-gray-300" }) }), _jsx("th", { className: "px-3 py-2 text-left", children: _jsx(SortHeader, { label: "Title", sortKey: "title" }) }), _jsx("th", { className: "px-3 py-2 text-left", children: _jsx(SortHeader, { label: "Author", sortKey: "author" }) }), _jsx("th", { className: "px-3 py-2 text-left", children: _jsx(SortHeader, { label: "Category", sortKey: "category" }) }), _jsx("th", { className: "px-3 py-2 text-left", children: _jsx(SortHeader, { label: "Status", sortKey: "status" }) }), _jsx("th", { className: "px-3 py-2 text-left", children: _jsx(SortHeader, { label: "Date", sortKey: "date" }) }), _jsx("th", { className: "px-3 py-2 text-left text-xs font-medium text-gray-700", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-gray-200", children: filteredAndSorted.map((post) => (_jsxs("tr", { className: "transition-colors hover:bg-gray-50", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", checked: selectedPosts.includes(post.id), onChange: () => handleSelectPost(post.id), className: "rounded border-gray-300" }) }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onNavigate?.(`/posts/${post.id}`), className: "text-left text-sm font-medium text-gray-900 hover:text-blue-600", children: post.title }) }), _jsx("td", { className: "px-3 py-2 text-sm text-gray-600", children: post.author }), _jsx("td", { className: "px-3 py-2 text-sm text-gray-600", children: post.category }), _jsx("td", { className: "px-3 py-2", children: _jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${post.status === 'Published' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`, children: post.status }) }), _jsx("td", { className: "px-3 py-2 text-sm text-gray-600", children: post.date }), _jsx("td", { className: "px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: () => onNavigate?.(`/posts/${post.id}`), className: "rounded p-1.5 transition-colors hover:bg-gray-100", title: "Edit", children: _jsx(Pencil, { className: "h-4 w-4 text-gray-600" }) }), _jsx("button", { type: "button", onClick: () => handleDelete(post.id), className: "rounded p-1.5 transition-colors hover:bg-gray-100", title: "Delete", children: _jsx(Trash2, { className: "h-4 w-4 text-red-600" }) })] }) })] }, post.id))) })] }) }) }), _jsx("div", { className: "flex-1 overflow-auto rounded-lg border border-gray-200 bg-white md:hidden", children: _jsx("div", { className: "divide-y divide-gray-200", children: filteredAndSorted.map((post) => (_jsx("div", { className: "p-3", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("input", { type: "checkbox", checked: selectedPosts.includes(post.id), onChange: () => handleSelectPost(post.id), className: "mt-1 rounded border-gray-300" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("button", { type: "button", onClick: () => onNavigate?.(`/posts/${post.id}`), className: "mb-1 block text-left text-sm font-medium text-gray-900 hover:text-blue-600", children: post.title }), _jsxs("div", { className: "mb-2 flex flex-wrap items-center gap-2 text-xs text-gray-600", children: [_jsx("span", { children: post.author }), _jsx("span", { children: "\u2022" }), _jsx("span", { children: post.date }), _jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${post.status === 'Published' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`, children: post.status })] })] }), _jsx("button", { type: "button", onClick: () => handleDelete(post.id), className: "rounded p-1.5 transition-colors hover:bg-gray-100", children: _jsx(Trash2, { className: "h-4 w-4 text-red-600" }) })] }) }, post.id))) }) })] }))] }));
72
- }
73
- //# sourceMappingURL=Posts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Posts.js","sourceRoot":"","sources":["../../src/views/Posts.tsx"],"names":[],"mappings":"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"}
@@ -1,257 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useEffect } from 'react'
4
- import { ArrowLeft, Eye, Loader2, AlertTriangle } from 'lucide-react'
5
- import { toast } from 'sonner'
6
- import { TipTapEditor } from '../components/TipTapEditor.js'
7
- import { SEOPanel, type SEOData } from '../components/SEOPanel.js'
8
- import { useApiData } from '../lib/useApiData.js'
9
- import { cmsApi } from '../lib/api.js'
10
-
11
- export interface PostEditorProps {
12
- id?: string
13
- onNavigate?: (path: string) => void
14
- }
15
-
16
- export function PostEditor({ id, onNavigate }: PostEditorProps) {
17
- const isNew = !id
18
- const { data, loading, error } = useApiData<any>(isNew ? '' : `/collections/posts/${id}`)
19
-
20
- const [title, setTitle] = useState('')
21
- const [content, setContent] = useState('')
22
- const [status, setStatus] = useState<'draft' | 'published'>('draft')
23
- const [slug, setSlug] = useState('')
24
- const [saving, setSaving] = useState(false)
25
- const [initialized, setInitialized] = useState(isNew)
26
- const [seoData, setSeoData] = useState<SEOData>({})
27
-
28
- useEffect(() => {
29
- if (data && !initialized) {
30
- setTitle(data.title ?? '')
31
- setContent(data.content ?? data.data?.content ?? '')
32
- setStatus(data.status === 'PUBLISHED' ? 'published' : 'draft')
33
- setSlug(data.slug ?? '')
34
- setInitialized(true)
35
- }
36
- }, [data, initialized])
37
-
38
- const savePost = async () => {
39
- setSaving(true)
40
- const body = JSON.stringify({
41
- title,
42
- content,
43
- slug,
44
- status: status === 'published' ? 'PUBLISHED' : 'DRAFT',
45
- })
46
-
47
- const res = isNew
48
- ? await cmsApi('/collections/posts', { method: 'POST', body })
49
- : await cmsApi(`/collections/posts/${id}`, { method: 'PUT', body })
50
-
51
- setSaving(false)
52
- if (res.error) {
53
- toast.error(res.error)
54
- } else {
55
- toast.success('Post saved successfully!')
56
- if (isNew && (res.data as any)?.id) {
57
- onNavigate?.(`/posts/${(res.data as any).id}`)
58
- }
59
- }
60
- }
61
-
62
- const publishPost = async () => {
63
- setSaving(true)
64
- const body = JSON.stringify({
65
- title,
66
- content,
67
- slug,
68
- status: 'PUBLISHED',
69
- })
70
-
71
- const endpoint = isNew ? '/collections/posts' : `/collections/posts/${id}`
72
- const method = isNew ? 'POST' : 'PUT'
73
- const res = await cmsApi(endpoint, { method, body })
74
-
75
- setSaving(false)
76
- if (res.error) {
77
- toast.error(res.error)
78
- } else {
79
- setStatus('published')
80
- toast.success('Post published!')
81
- if (isNew && (res.data as any)?.id) {
82
- onNavigate?.(`/posts/${(res.data as any).id}`)
83
- }
84
- }
85
- }
86
-
87
- if (!isNew && loading) {
88
- return (
89
- <div className="flex h-full items-center justify-center">
90
- <Loader2 className="h-6 w-6 animate-spin text-blue-600" />
91
- </div>
92
- )
93
- }
94
-
95
- return (
96
- <div className="flex h-full flex-col bg-white">
97
- {error && (
98
- <div className="mx-4 mt-3 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
99
- <AlertTriangle className="h-5 w-5 shrink-0 text-red-600" />
100
- <span className="flex-1 text-sm text-red-800">{error}</span>
101
- </div>
102
- )}
103
-
104
- <div className="border-b border-gray-200 px-3 py-3 sm:px-4">
105
- <div className="flex items-center justify-between">
106
- <button
107
- onClick={() => onNavigate?.('/posts')}
108
- className="inline-flex items-center gap-2 text-sm text-gray-600 transition-colors hover:text-gray-900"
109
- >
110
- <ArrowLeft className="h-4 w-4" />
111
- <span className="hidden sm:inline">Back to Posts</span>
112
- <span className="sm:hidden">Back</span>
113
- </button>
114
- <div className="flex items-center gap-2">
115
- <button className="flex items-center gap-2 rounded-lg border border-gray-300 px-3 py-1.5 text-sm transition-colors hover:bg-gray-50">
116
- <Eye className="h-4 w-4" />
117
- <span className="hidden sm:inline">Preview</span>
118
- </button>
119
- </div>
120
- </div>
121
- </div>
122
-
123
- <div className="flex flex-1 flex-col overflow-hidden lg:flex-row">
124
- <div className="flex-1 overflow-y-auto">
125
- <div className="mx-auto max-w-4xl p-3 pr-6 sm:p-4 sm:pr-8">
126
- <input
127
- type="text"
128
- value={title}
129
- onChange={(e) => setTitle(e.target.value)}
130
- placeholder="Post title"
131
- className="mb-4 w-full border-none px-0 text-2xl font-bold placeholder:text-gray-300 focus:ring-0 focus:outline-none sm:text-3xl"
132
- />
133
-
134
- <div className="mb-4">
135
- <label className="mb-1 block text-xs font-medium text-gray-600">URL Slug</label>
136
- <input
137
- type="text"
138
- value={slug}
139
- onChange={(e) => setSlug(e.target.value)}
140
- placeholder="url-slug"
141
- className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
142
- />
143
- </div>
144
-
145
- <div className="mb-4">
146
- <TipTapEditor content={content} onChange={setContent} />
147
- </div>
148
- </div>
149
- </div>
150
-
151
- <div className="hidden w-[30%] overflow-y-auto border-l border-gray-200 bg-gray-50 lg:block">
152
- <div className="space-y-4 p-4">
153
- <div className="rounded-lg border border-gray-200 bg-white p-4">
154
- <h3 className="mb-3 text-sm font-semibold text-gray-900">Publish</h3>
155
- <div className="space-y-3">
156
- <div>
157
- <label className="mb-1 block text-xs font-medium text-gray-700">Status</label>
158
- <select
159
- value={status}
160
- onChange={(e) => setStatus(e.target.value as 'draft' | 'published')}
161
- className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
162
- >
163
- <option value="draft">Draft</option>
164
- <option value="published">Published</option>
165
- </select>
166
- </div>
167
- <div>
168
- <label className="mb-1 block text-xs font-medium text-gray-700">
169
- Publish Date
170
- </label>
171
- <input
172
- type="datetime-local"
173
- className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
174
- />
175
- </div>
176
- <button
177
- onClick={savePost}
178
- disabled={saving}
179
- className="w-full rounded-lg bg-blue-600 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
180
- >
181
- {saving ? 'Saving...' : 'Save Post'}
182
- </button>
183
- {status === 'draft' && (
184
- <button
185
- onClick={publishPost}
186
- disabled={saving}
187
- className="w-full rounded-lg bg-green-600 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-50"
188
- >
189
- {saving ? 'Publishing...' : 'Publish'}
190
- </button>
191
- )}
192
- </div>
193
- </div>
194
-
195
- <div className="rounded-lg border border-gray-200 bg-white p-4">
196
- <h3 className="mb-3 text-sm font-semibold text-gray-900">Categories & Tags</h3>
197
- <div className="space-y-3">
198
- <div>
199
- <label className="mb-1 block text-xs font-medium text-gray-700">Category</label>
200
- <select className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none">
201
- <option>Technology</option>
202
- <option>Design</option>
203
- <option>Business</option>
204
- </select>
205
- </div>
206
- <div>
207
- <label className="mb-1 block text-xs font-medium text-gray-700">Tags</label>
208
- <input
209
- type="text"
210
- placeholder="Add tags..."
211
- className="w-full rounded-lg border border-gray-300 px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
212
- />
213
- </div>
214
- </div>
215
- </div>
216
-
217
- <div className="rounded-lg border border-gray-200 bg-white p-4">
218
- <h3 className="mb-3 text-sm font-semibold text-gray-900">Featured Image</h3>
219
- <button className="w-full rounded-lg border-2 border-dashed border-gray-300 py-2 text-sm text-gray-600 transition-colors hover:border-gray-400">
220
- Upload Image
221
- </button>
222
- </div>
223
-
224
- <SEOPanel
225
- title={title}
226
- slug={slug}
227
- content={content}
228
- seoData={seoData}
229
- onChange={setSeoData}
230
- />
231
- </div>
232
- </div>
233
-
234
- <div className="border-t border-gray-200 bg-white p-3 lg:hidden">
235
- <div className="flex gap-2">
236
- <button
237
- onClick={savePost}
238
- disabled={saving}
239
- className="flex-1 rounded-lg bg-blue-600 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
240
- >
241
- {saving ? 'Saving...' : 'Save Post'}
242
- </button>
243
- {status === 'draft' && (
244
- <button
245
- onClick={publishPost}
246
- disabled={saving}
247
- className="flex-1 rounded-lg bg-green-600 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-50"
248
- >
249
- Publish
250
- </button>
251
- )}
252
- </div>
253
- </div>
254
- </div>
255
- </div>
256
- )
257
- }