@actuate-media/cms-admin 0.18.0 → 0.20.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.
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +9 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/lib/post-editor-service.test.js +75 -23
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/posts-service.test.js +132 -142
- package/dist/__tests__/lib/posts-service.test.js.map +1 -1
- package/dist/__tests__/views/posts-list-view.render.test.d.ts +2 -0
- package/dist/__tests__/views/posts-list-view.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/posts-list-view.render.test.js +73 -0
- package/dist/__tests__/views/posts-list-view.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/seo/Drawer.d.ts +11 -0
- package/dist/components/seo/Drawer.d.ts.map +1 -0
- package/dist/components/seo/Drawer.js +13 -0
- package/dist/components/seo/Drawer.js.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts +9 -0
- package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/RedirectEditorDrawer.js +94 -0
- package/dist/components/seo/RedirectEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts +10 -0
- package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoCopilotDrawer.js +34 -0
- package/dist/components/seo/SeoCopilotDrawer.js.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts +8 -0
- package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoEditorDrawer.js +111 -0
- package/dist/components/seo/SeoEditorDrawer.js.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts +8 -0
- package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -0
- package/dist/components/seo/SeoIssueFixDrawer.js +111 -0
- package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -0
- package/dist/components/seo/primitives.d.ts +53 -0
- package/dist/components/seo/primitives.d.ts.map +1 -0
- package/dist/components/seo/primitives.js +100 -0
- package/dist/components/seo/primitives.js.map +1 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +3 -0
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/post-editor-service.d.ts +6 -4
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +32 -37
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/posts-service.d.ts.map +1 -1
- package/dist/lib/posts-service.js +91 -160
- package/dist/lib/posts-service.js.map +1 -1
- package/dist/lib/seo-service.d.ts +322 -0
- package/dist/lib/seo-service.d.ts.map +1 -0
- package/dist/lib/seo-service.js +295 -0
- package/dist/lib/seo-service.js.map +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +4 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +36 -25
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Posts/NewPostPicker.d.ts +5 -4
- package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
- package/dist/views/Posts/NewPostPicker.js +5 -4
- package/dist/views/Posts/NewPostPicker.js.map +1 -1
- package/dist/views/Posts/NewPostTypeInstructions.js +2 -2
- package/dist/views/Posts/PostTypesView.d.ts +5 -4
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
- package/dist/views/Posts/PostTypesView.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +8 -0
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Posts/post-type-presets.d.ts +5 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -1
- package/dist/views/Posts/post-type-presets.js +10 -64
- package/dist/views/Posts/post-type-presets.js.map +1 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +89 -132
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts +6 -2
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js +8 -8
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +7 -1
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
- package/dist/views/post-editor/PostFieldsModal.js +10 -4
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
- package/dist/views/post-editor/PostHeader.js +4 -3
- package/dist/views/post-editor/PostHeader.js.map +1 -1
- package/dist/views/post-editor/PostPreview.d.ts.map +1 -1
- package/dist/views/post-editor/PostPreview.js +11 -1
- package/dist/views/post-editor/PostPreview.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts +22 -2
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +4 -1
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/seo/AuditTab.d.ts +6 -0
- package/dist/views/seo/AuditTab.d.ts.map +1 -0
- package/dist/views/seo/AuditTab.js +57 -0
- package/dist/views/seo/AuditTab.js.map +1 -0
- package/dist/views/seo/ContentTab.d.ts +5 -0
- package/dist/views/seo/ContentTab.d.ts.map +1 -0
- package/dist/views/seo/ContentTab.js +27 -0
- package/dist/views/seo/ContentTab.js.map +1 -0
- package/dist/views/seo/OverviewTab.d.ts +8 -0
- package/dist/views/seo/OverviewTab.d.ts.map +1 -0
- package/dist/views/seo/OverviewTab.js +35 -0
- package/dist/views/seo/OverviewTab.js.map +1 -0
- package/dist/views/seo/RedirectsTab.d.ts +2 -0
- package/dist/views/seo/RedirectsTab.d.ts.map +1 -0
- package/dist/views/seo/RedirectsTab.js +124 -0
- package/dist/views/seo/RedirectsTab.js.map +1 -0
- package/dist/views/seo/TechnicalTab.d.ts +2 -0
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -0
- package/dist/views/seo/TechnicalTab.js +102 -0
- package/dist/views/seo/TechnicalTab.js.map +1 -0
- package/dist/views/seo/useSeoResource.d.ts +8 -0
- package/dist/views/seo/useSeoResource.d.ts.map +1 -0
- package/dist/views/seo/useSeoResource.js +42 -0
- package/dist/views/seo/useSeoResource.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +9 -5
- package/src/__tests__/lib/post-editor-service.test.ts +78 -23
- package/src/__tests__/lib/posts-service.test.ts +134 -163
- package/src/__tests__/views/posts-list-view.render.test.tsx +84 -0
- package/src/components/seo/Drawer.tsx +61 -0
- package/src/components/seo/RedirectEditorDrawer.tsx +235 -0
- package/src/components/seo/SeoCopilotDrawer.tsx +76 -0
- package/src/components/seo/SeoEditorDrawer.tsx +347 -0
- package/src/components/seo/SeoIssueFixDrawer.tsx +244 -0
- package/src/components/seo/primitives.tsx +237 -0
- package/src/index.ts +0 -2
- package/src/layout/Sidebar.tsx +3 -0
- package/src/lib/post-editor-service.ts +41 -40
- package/src/lib/posts-service.ts +109 -180
- package/src/lib/seo-service.ts +623 -0
- package/src/router/index.ts +4 -1
- package/src/views/MediaBrowser.tsx +89 -67
- package/src/views/Posts/NewPostPicker.tsx +5 -4
- package/src/views/Posts/NewPostTypeInstructions.tsx +2 -2
- package/src/views/Posts/PostTypesView.tsx +5 -4
- package/src/views/Posts/PostsListView.tsx +9 -0
- package/src/views/Posts/post-type-presets.ts +15 -64
- package/src/views/SEO.tsx +186 -863
- package/src/views/page-editor/sections/ArticleBodySection.tsx +12 -16
- package/src/views/post-editor/PostEditorCanvas.tsx +7 -1
- package/src/views/post-editor/PostFieldsModal.tsx +36 -21
- package/src/views/post-editor/PostHeader.tsx +4 -4
- package/src/views/post-editor/PostPreview.tsx +12 -1
- package/src/views/post-editor/PostSectionEditor.tsx +22 -5
- package/src/views/seo/AuditTab.tsx +229 -0
- package/src/views/seo/ContentTab.tsx +164 -0
- package/src/views/seo/OverviewTab.tsx +257 -0
- package/src/views/seo/RedirectsTab.tsx +392 -0
- package/src/views/seo/TechnicalTab.tsx +303 -0
- package/src/views/seo/useSeoResource.ts +52 -0
- package/dist/views/PostEditor.d.ts +0 -6
- package/dist/views/PostEditor.d.ts.map +0 -1
- package/dist/views/PostEditor.js +0 -79
- package/dist/views/PostEditor.js.map +0 -1
- package/dist/views/Posts.d.ts +0 -5
- package/dist/views/Posts.d.ts.map +0 -1
- package/dist/views/Posts.js +0 -73
- package/dist/views/Posts.js.map +0 -1
- package/src/views/PostEditor.tsx +0 -257
- package/src/views/Posts.tsx +0 -375
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { RefreshCw, Map, FileCode2, AlertTriangle, CheckCircle2, ExternalLink } from 'lucide-react'
|
|
5
|
+
import { toast } from 'sonner'
|
|
6
|
+
import {
|
|
7
|
+
fetchSitemapStatus,
|
|
8
|
+
regenerateSitemap,
|
|
9
|
+
fetchCrawlSettings,
|
|
10
|
+
updateCrawlSettings,
|
|
11
|
+
fetchRobotsTxt,
|
|
12
|
+
updateRobotsTxt,
|
|
13
|
+
validateRobotsTxt,
|
|
14
|
+
type CrawlSettings,
|
|
15
|
+
} from '../../lib/seo-service.js'
|
|
16
|
+
import {
|
|
17
|
+
SectionCard,
|
|
18
|
+
SeoLoading,
|
|
19
|
+
SeoErrorState,
|
|
20
|
+
btnPrimary,
|
|
21
|
+
btnSecondary,
|
|
22
|
+
} from '../../components/seo/primitives.js'
|
|
23
|
+
import { useSeoResource } from './useSeoResource.js'
|
|
24
|
+
|
|
25
|
+
const CRAWL_TOGGLES: { key: keyof CrawlSettings; label: string; help: string }[] = [
|
|
26
|
+
{
|
|
27
|
+
key: 'indexEntireSite',
|
|
28
|
+
label: 'Index entire site',
|
|
29
|
+
help: 'Allow search engines to index all public pages.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'includeImagesInSitemap',
|
|
33
|
+
label: 'Include images in sitemap',
|
|
34
|
+
help: 'Add image entries to the XML sitemap.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'includePostsInSitemap',
|
|
38
|
+
label: 'Include posts in sitemap',
|
|
39
|
+
help: 'Add published posts to the sitemap.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'noindexPaginatedArchives',
|
|
43
|
+
label: 'No-index paginated archives',
|
|
44
|
+
help: 'Avoid duplicate-content penalties on page 2+.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: 'noindexTagPages',
|
|
48
|
+
label: 'No-index tag pages',
|
|
49
|
+
help: 'Keep thin tag archives out of the index.',
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
function Toggle({
|
|
54
|
+
checked,
|
|
55
|
+
onChange,
|
|
56
|
+
label,
|
|
57
|
+
help,
|
|
58
|
+
}: {
|
|
59
|
+
checked: boolean
|
|
60
|
+
onChange: (v: boolean) => void
|
|
61
|
+
label: string
|
|
62
|
+
help: string
|
|
63
|
+
}) {
|
|
64
|
+
return (
|
|
65
|
+
<label className="flex items-center justify-between gap-3 py-2.5">
|
|
66
|
+
<span className="min-w-0">
|
|
67
|
+
<span className="text-foreground block text-sm font-medium">{label}</span>
|
|
68
|
+
<span className="text-muted-foreground block text-sm">{help}</span>
|
|
69
|
+
</span>
|
|
70
|
+
<input
|
|
71
|
+
type="checkbox"
|
|
72
|
+
role="switch"
|
|
73
|
+
aria-checked={checked}
|
|
74
|
+
checked={checked}
|
|
75
|
+
onChange={(e) => onChange(e.target.checked)}
|
|
76
|
+
className="accent-primary h-4 w-4 shrink-0"
|
|
77
|
+
/>
|
|
78
|
+
</label>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function SitemapCard() {
|
|
83
|
+
const { data, loading, error, refetch } = useSeoResource(fetchSitemapStatus, [])
|
|
84
|
+
const [busy, setBusy] = useState(false)
|
|
85
|
+
|
|
86
|
+
async function handleRegenerate() {
|
|
87
|
+
setBusy(true)
|
|
88
|
+
const res = await regenerateSitemap()
|
|
89
|
+
setBusy(false)
|
|
90
|
+
if (res.error) {
|
|
91
|
+
toast.error(res.error)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
toast.success('Sitemap regenerated.')
|
|
95
|
+
refetch()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<SectionCard
|
|
100
|
+
title="Sitemap"
|
|
101
|
+
action={
|
|
102
|
+
<button className={btnSecondary} onClick={handleRegenerate} disabled={busy || loading}>
|
|
103
|
+
<RefreshCw className={`h-4 w-4 ${busy ? 'animate-spin' : ''}`} aria-hidden />
|
|
104
|
+
Regenerate
|
|
105
|
+
</button>
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
{loading ? (
|
|
109
|
+
<SeoLoading label="Loading sitemap status…" />
|
|
110
|
+
) : error ? (
|
|
111
|
+
<SeoErrorState message={error} onRetry={refetch} />
|
|
112
|
+
) : !data ? (
|
|
113
|
+
<p className="text-muted-foreground text-sm">Sitemap status unavailable.</p>
|
|
114
|
+
) : (
|
|
115
|
+
<div className="space-y-3">
|
|
116
|
+
<a
|
|
117
|
+
href={data.sitemapUrl}
|
|
118
|
+
target="_blank"
|
|
119
|
+
rel="noopener noreferrer"
|
|
120
|
+
className="text-primary inline-flex items-center gap-1 text-sm hover:underline"
|
|
121
|
+
>
|
|
122
|
+
<Map className="h-4 w-4" aria-hidden />
|
|
123
|
+
{data.sitemapUrl}
|
|
124
|
+
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
|
|
125
|
+
</a>
|
|
126
|
+
<dl className="grid grid-cols-2 gap-3 text-sm">
|
|
127
|
+
<div>
|
|
128
|
+
<dt className="text-muted-foreground">Pages included</dt>
|
|
129
|
+
<dd className="text-foreground font-medium tabular-nums">{data.pagesIncluded}</dd>
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
<dt className="text-muted-foreground">Pages excluded</dt>
|
|
133
|
+
<dd className="text-foreground font-medium tabular-nums">{data.pagesExcluded}</dd>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="col-span-2">
|
|
136
|
+
<dt className="text-muted-foreground">Last generated</dt>
|
|
137
|
+
<dd className="text-foreground">
|
|
138
|
+
{data.lastGeneratedAt ? new Date(data.lastGeneratedAt).toLocaleString() : 'Never'}
|
|
139
|
+
</dd>
|
|
140
|
+
</div>
|
|
141
|
+
</dl>
|
|
142
|
+
{data.warnings.length > 0 && (
|
|
143
|
+
<ul className="space-y-1">
|
|
144
|
+
{data.warnings.map((w, i) => (
|
|
145
|
+
<li key={i} className="text-warning flex items-center gap-1.5 text-sm">
|
|
146
|
+
<AlertTriangle className="h-3.5 w-3.5" aria-hidden /> {w}
|
|
147
|
+
</li>
|
|
148
|
+
))}
|
|
149
|
+
</ul>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</SectionCard>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function CrawlCard() {
|
|
158
|
+
const { data, loading, error, refetch } = useSeoResource(fetchCrawlSettings, [])
|
|
159
|
+
const [settings, setSettings] = useState<CrawlSettings | null>(null)
|
|
160
|
+
const [saving, setSaving] = useState(false)
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (data) setSettings(data)
|
|
164
|
+
}, [data])
|
|
165
|
+
|
|
166
|
+
async function save(next: CrawlSettings, changed: keyof CrawlSettings) {
|
|
167
|
+
setSettings(next)
|
|
168
|
+
setSaving(true)
|
|
169
|
+
const res = await updateCrawlSettings({ [changed]: next[changed] })
|
|
170
|
+
setSaving(false)
|
|
171
|
+
if (res.error) {
|
|
172
|
+
toast.error(res.error)
|
|
173
|
+
refetch()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<SectionCard title="Crawl Settings">
|
|
179
|
+
{loading ? (
|
|
180
|
+
<SeoLoading label="Loading crawl settings…" />
|
|
181
|
+
) : error ? (
|
|
182
|
+
<SeoErrorState message={error} onRetry={refetch} />
|
|
183
|
+
) : !settings ? (
|
|
184
|
+
<p className="text-muted-foreground text-sm">Crawl settings unavailable.</p>
|
|
185
|
+
) : (
|
|
186
|
+
<div className="divide-border divide-y" aria-busy={saving}>
|
|
187
|
+
{CRAWL_TOGGLES.map((t) => (
|
|
188
|
+
<Toggle
|
|
189
|
+
key={t.key}
|
|
190
|
+
label={t.label}
|
|
191
|
+
help={t.help}
|
|
192
|
+
checked={settings[t.key]}
|
|
193
|
+
onChange={(v) => save({ ...settings, [t.key]: v }, t.key)}
|
|
194
|
+
/>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</SectionCard>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function RobotsCard() {
|
|
203
|
+
const { data, loading, error, refetch } = useSeoResource(fetchRobotsTxt, [])
|
|
204
|
+
const [body, setBody] = useState('')
|
|
205
|
+
const [saving, setSaving] = useState(false)
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (data) setBody(data.body)
|
|
209
|
+
}, [data])
|
|
210
|
+
|
|
211
|
+
const validation = validateRobotsTxt(body)
|
|
212
|
+
|
|
213
|
+
async function handleSave() {
|
|
214
|
+
setSaving(true)
|
|
215
|
+
const res = await updateRobotsTxt(body)
|
|
216
|
+
setSaving(false)
|
|
217
|
+
if (res.error) {
|
|
218
|
+
toast.error(res.error)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
toast.success('robots.txt saved.')
|
|
222
|
+
refetch()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<SectionCard
|
|
227
|
+
title="robots.txt"
|
|
228
|
+
description="Controls which crawlers can access your site."
|
|
229
|
+
action={
|
|
230
|
+
<button
|
|
231
|
+
className={btnPrimary}
|
|
232
|
+
onClick={handleSave}
|
|
233
|
+
disabled={saving || loading || !validation.valid}
|
|
234
|
+
>
|
|
235
|
+
{saving ? 'Saving…' : 'Save'}
|
|
236
|
+
</button>
|
|
237
|
+
}
|
|
238
|
+
>
|
|
239
|
+
{loading ? (
|
|
240
|
+
<SeoLoading label="Loading robots.txt…" />
|
|
241
|
+
) : error ? (
|
|
242
|
+
<SeoErrorState message={error} onRetry={refetch} />
|
|
243
|
+
) : (
|
|
244
|
+
<div className="space-y-3">
|
|
245
|
+
{validation.blocksEntireSite && (
|
|
246
|
+
<div
|
|
247
|
+
className="border-destructive/30 bg-destructive/10 flex items-start gap-2 rounded-md border p-3"
|
|
248
|
+
role="alert"
|
|
249
|
+
>
|
|
250
|
+
<AlertTriangle className="text-destructive mt-0.5 h-4 w-4 shrink-0" aria-hidden />
|
|
251
|
+
<p className="text-destructive text-sm">
|
|
252
|
+
This configuration blocks your entire site from all search engines. Pages will be
|
|
253
|
+
removed from search results.
|
|
254
|
+
</p>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
<label className="sr-only" htmlFor="robots-editor">
|
|
258
|
+
robots.txt contents
|
|
259
|
+
</label>
|
|
260
|
+
<textarea
|
|
261
|
+
id="robots-editor"
|
|
262
|
+
value={body}
|
|
263
|
+
onChange={(e) => setBody(e.target.value)}
|
|
264
|
+
spellCheck={false}
|
|
265
|
+
className="border-border bg-input-background text-foreground focus-visible:ring-ring min-h-48 w-full rounded-md border p-3 font-mono text-sm focus-visible:ring-2 focus-visible:outline-none"
|
|
266
|
+
/>
|
|
267
|
+
{validation.errors.length > 0 ? (
|
|
268
|
+
<ul className="space-y-1">
|
|
269
|
+
{validation.errors.map((e, i) => (
|
|
270
|
+
<li key={i} className="text-destructive flex items-center gap-1.5 text-sm">
|
|
271
|
+
<AlertTriangle className="h-3.5 w-3.5" aria-hidden /> {e}
|
|
272
|
+
</li>
|
|
273
|
+
))}
|
|
274
|
+
</ul>
|
|
275
|
+
) : (
|
|
276
|
+
<p className="text-success flex items-center gap-1.5 text-sm">
|
|
277
|
+
<CheckCircle2 className="h-3.5 w-3.5" aria-hidden /> Valid robots.txt
|
|
278
|
+
</p>
|
|
279
|
+
)}
|
|
280
|
+
{validation.warnings
|
|
281
|
+
.filter((w) => !validation.blocksEntireSite || !w.includes('blocks the entire site'))
|
|
282
|
+
.map((w, i) => (
|
|
283
|
+
<p key={i} className="text-warning flex items-center gap-1.5 text-sm">
|
|
284
|
+
<AlertTriangle className="h-3.5 w-3.5" aria-hidden /> {w}
|
|
285
|
+
</p>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</SectionCard>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function TechnicalTab() {
|
|
294
|
+
return (
|
|
295
|
+
<div className="space-y-4">
|
|
296
|
+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
297
|
+
<SitemapCard />
|
|
298
|
+
<CrawlCard />
|
|
299
|
+
</div>
|
|
300
|
+
<RobotsCard />
|
|
301
|
+
</div>
|
|
302
|
+
)
|
|
303
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Small async-resource hook for the SEO tabs: wraps a service fetcher with
|
|
5
|
+
* loading / error / data / refetch and abort-on-unmount safety. Keeps each
|
|
6
|
+
* tab declarative without re-implementing fetch plumbing.
|
|
7
|
+
*/
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
9
|
+
|
|
10
|
+
export interface SeoResource<T> {
|
|
11
|
+
data: T | null
|
|
12
|
+
loading: boolean
|
|
13
|
+
error: string | null
|
|
14
|
+
refetch: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useSeoResource<T>(
|
|
18
|
+
fetcher: () => Promise<T>,
|
|
19
|
+
deps: ReadonlyArray<unknown> = [],
|
|
20
|
+
): SeoResource<T> {
|
|
21
|
+
const [data, setData] = useState<T | null>(null)
|
|
22
|
+
const [loading, setLoading] = useState(true)
|
|
23
|
+
const [error, setError] = useState<string | null>(null)
|
|
24
|
+
const aliveRef = useRef(true)
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
const stableFetcher = useCallback(fetcher, deps)
|
|
27
|
+
|
|
28
|
+
const load = useCallback(async () => {
|
|
29
|
+
setLoading(true)
|
|
30
|
+
setError(null)
|
|
31
|
+
try {
|
|
32
|
+
const result = await stableFetcher()
|
|
33
|
+
if (aliveRef.current) setData(result)
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (aliveRef.current) {
|
|
36
|
+
setError(err instanceof Error ? err.message : 'Failed to load data')
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
if (aliveRef.current) setLoading(false)
|
|
40
|
+
}
|
|
41
|
+
}, [stableFetcher])
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
aliveRef.current = true
|
|
45
|
+
load()
|
|
46
|
+
return () => {
|
|
47
|
+
aliveRef.current = false
|
|
48
|
+
}
|
|
49
|
+
}, [load])
|
|
50
|
+
|
|
51
|
+
return { data, loading, error, refetch: load }
|
|
52
|
+
}
|
|
@@ -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"}
|
package/dist/views/PostEditor.js
DELETED
|
@@ -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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"}
|
package/dist/views/Posts.d.ts
DELETED
|
@@ -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"}
|
package/dist/views/Posts.js
DELETED
|
@@ -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
|
package/dist/views/Posts.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Posts.js","sourceRoot":"","sources":["../../src/views/Posts.tsx"],"names":[],"mappings":"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"}
|