@actuate-media/cms-admin 0.7.0 → 0.7.2

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 (46) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +8 -4
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/actuate-admin.css +1 -1
  5. package/dist/lib/search.d.ts +2 -2
  6. package/dist/lib/search.d.ts.map +1 -1
  7. package/dist/lib/search.js +5 -2
  8. package/dist/lib/search.js.map +1 -1
  9. package/dist/views/FormSubmissions.js +11 -11
  10. package/dist/views/FormSubmissions.js.map +1 -1
  11. package/dist/views/Forms.js +1 -1
  12. package/dist/views/Forms.js.map +1 -1
  13. package/dist/views/MediaBrowser.d.ts.map +1 -1
  14. package/dist/views/MediaBrowser.js +28 -8
  15. package/dist/views/MediaBrowser.js.map +1 -1
  16. package/dist/views/Pages.d.ts.map +1 -1
  17. package/dist/views/Pages.js +7 -4
  18. package/dist/views/Pages.js.map +1 -1
  19. package/dist/views/Posts.js +1 -1
  20. package/dist/views/Posts.js.map +1 -1
  21. package/dist/views/Redirects.js +2 -2
  22. package/dist/views/Redirects.js.map +1 -1
  23. package/dist/views/SEO.js +3 -3
  24. package/dist/views/SEO.js.map +1 -1
  25. package/dist/views/ScriptTags.d.ts.map +1 -1
  26. package/dist/views/ScriptTags.js +8 -6
  27. package/dist/views/ScriptTags.js.map +1 -1
  28. package/dist/views/Users.js +3 -3
  29. package/dist/views/Users.js.map +1 -1
  30. package/dist/views/page-builder/PageTemplates.d.ts +5 -0
  31. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -0
  32. package/dist/views/page-builder/PageTemplates.js +13 -0
  33. package/dist/views/page-builder/PageTemplates.js.map +1 -0
  34. package/package.json +2 -2
  35. package/src/AdminRoot.tsx +10 -5
  36. package/src/lib/search.ts +8 -4
  37. package/src/views/FormSubmissions.tsx +12 -12
  38. package/src/views/Forms.tsx +1 -1
  39. package/src/views/MediaBrowser.tsx +46 -15
  40. package/src/views/Pages.tsx +8 -4
  41. package/src/views/Posts.tsx +1 -1
  42. package/src/views/Redirects.tsx +2 -2
  43. package/src/views/SEO.tsx +3 -3
  44. package/src/views/ScriptTags.tsx +8 -6
  45. package/src/views/Users.tsx +3 -3
  46. package/src/views/page-builder/PageTemplates.tsx +105 -0
package/src/views/SEO.tsx CHANGED
@@ -44,7 +44,7 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
44
44
  // --- Pages tab data ---
45
45
  const filtered = seoPages
46
46
  .filter((page: any) => {
47
- const matchesSearch = page.url.toLowerCase().includes(searchQuery.toLowerCase()) || page.title.toLowerCase().includes(searchQuery.toLowerCase());
47
+ const matchesSearch = (page.url ?? '').toLowerCase().includes(searchQuery.toLowerCase()) || (page.title ?? '').toLowerCase().includes(searchQuery.toLowerCase());
48
48
  const matchesScore = filterScore === 'all' || (filterScore === 'good' && page.score >= 80) || (filterScore === 'warning' && page.score >= 50 && page.score < 80) || (filterScore === 'critical' && page.score < 50);
49
49
  return matchesSearch && matchesScore;
50
50
  })
@@ -62,8 +62,8 @@ export function SEO({ onNavigate, initialTab = 'pages' }: SEOProps) {
62
62
 
63
63
  // --- Redirects data ---
64
64
  const filteredRedirects = redirects.filter((r: any) =>
65
- r.from.toLowerCase().includes(redirectSearch.toLowerCase()) ||
66
- r.to.toLowerCase().includes(redirectSearch.toLowerCase())
65
+ (r.from ?? '').toLowerCase().includes(redirectSearch.toLowerCase()) ||
66
+ (r.to ?? '').toLowerCase().includes(redirectSearch.toLowerCase())
67
67
  );
68
68
 
69
69
  const handleScan = async () => {
@@ -36,16 +36,18 @@ const PLACEMENT_COLORS: Record<string, string> = {
36
36
  };
37
37
 
38
38
  function scopeLabel(tag: ScriptTag): string {
39
- if (tag.scope === 'site') return 'Entire Site';
40
- if (tag.scope === 'parents') {
41
- const count = tag.targetPaths?.length ?? 0;
39
+ const scope = String(tag.scope ?? '');
40
+ const targetPaths = Array.isArray(tag.targetPaths) ? tag.targetPaths : [];
41
+ if (scope === 'site') return 'Entire Site';
42
+ if (scope === 'parents') {
43
+ const count = targetPaths.length;
42
44
  return `${count} parent path${count !== 1 ? 's' : ''} + children`;
43
45
  }
44
- if (tag.scope === 'urls') {
45
- const count = tag.targetPaths?.length ?? 0;
46
+ if (scope === 'urls') {
47
+ const count = targetPaths.length;
46
48
  return `${count} URL${count !== 1 ? 's' : ''}`;
47
49
  }
48
- return tag.scope;
50
+ return scope || 'Custom';
49
51
  }
50
52
 
51
53
  export function ScriptTags({ onNavigate }: ScriptTagsProps) {
@@ -28,9 +28,9 @@ export function Users({ onNavigate }: UsersProps) {
28
28
  const filteredAndSorted = useMemo(() => {
29
29
  let results = users.filter((user: any) => {
30
30
  const matchesSearch =
31
- user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
32
- user.email.toLowerCase().includes(searchQuery.toLowerCase());
33
- const matchesRole = filterRole === 'all' || user.role.toLowerCase() === filterRole.toLowerCase();
31
+ (user.name ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
32
+ (user.email ?? '').toLowerCase().includes(searchQuery.toLowerCase());
33
+ const matchesRole = filterRole === 'all' || (user.role ?? '').toLowerCase() === filterRole.toLowerCase();
34
34
  return matchesSearch && matchesRole;
35
35
  });
36
36
 
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+
3
+ import { AlertTriangle, Layers, Loader2, RefreshCw } from 'lucide-react';
4
+ import { useApiData } from '../../lib/useApiData.js';
5
+
6
+ interface PageTemplate {
7
+ id: string;
8
+ name?: string;
9
+ description?: string | null;
10
+ category?: string;
11
+ builtIn?: boolean;
12
+ updatedAt?: string;
13
+ }
14
+
15
+ export interface PageTemplatesProps {
16
+ onNavigate?: (path: string) => void;
17
+ }
18
+
19
+ export function PageTemplates({ onNavigate }: PageTemplatesProps) {
20
+ const { data, loading, error, refetch } = useApiData<PageTemplate[]>('/page-templates');
21
+ const templates = data ?? [];
22
+
23
+ if (loading) {
24
+ return (
25
+ <div className="flex h-64 items-center justify-center p-4" role="status" aria-live="polite">
26
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
27
+ <span className="sr-only">Loading page templates</span>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ return (
33
+ <div className="p-4 pr-8">
34
+ {error && (
35
+ <div className="mb-4 flex items-center gap-3 rounded-lg border border-border bg-card p-3">
36
+ <AlertTriangle className="h-5 w-5 shrink-0 text-muted-foreground" />
37
+ <span className="flex-1 text-sm text-foreground">{error}</span>
38
+ <button
39
+ type="button"
40
+ onClick={refetch}
41
+ className="rounded-md border border-border px-3 py-1 text-sm text-foreground hover:bg-accent"
42
+ >
43
+ Retry
44
+ </button>
45
+ </div>
46
+ )}
47
+
48
+ <div className="mb-4 flex items-center justify-between">
49
+ <div>
50
+ <h1 className="mb-1 text-2xl font-medium text-foreground">Page Templates</h1>
51
+ <p className="text-sm text-muted-foreground">
52
+ {templates.length} saved template{templates.length === 1 ? '' : 's'}
53
+ </p>
54
+ </div>
55
+ <button
56
+ type="button"
57
+ onClick={refetch}
58
+ className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-accent"
59
+ >
60
+ <RefreshCw className="h-4 w-4" />
61
+ Refresh
62
+ </button>
63
+ </div>
64
+
65
+ {templates.length === 0 ? (
66
+ <div className="rounded-lg border border-border bg-card p-8 text-center">
67
+ <Layers className="mx-auto mb-3 h-8 w-8 text-muted-foreground" />
68
+ <h2 className="mb-1 text-lg font-medium text-foreground">No page templates yet</h2>
69
+ <p className="mb-4 text-sm text-muted-foreground">
70
+ Built-in templates are seeded by the CMS when the templates API is available.
71
+ </p>
72
+ <button
73
+ type="button"
74
+ onClick={() => onNavigate?.('/saved-sections')}
75
+ className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90"
76
+ >
77
+ View Saved Sections
78
+ </button>
79
+ </div>
80
+ ) : (
81
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
82
+ {templates.map((template) => (
83
+ <div key={template.id} className="rounded-lg border border-border bg-card p-4">
84
+ <div className="mb-3 flex items-start justify-between gap-3">
85
+ <div>
86
+ <h2 className="text-base font-medium text-foreground">{template.name ?? 'Untitled template'}</h2>
87
+ <p className="mt-1 text-sm text-muted-foreground">{template.description ?? 'No description provided.'}</p>
88
+ </div>
89
+ {template.builtIn && (
90
+ <span className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
91
+ Built-in
92
+ </span>
93
+ )}
94
+ </div>
95
+ <div className="flex items-center justify-between text-sm text-muted-foreground">
96
+ <span>{template.category ?? 'content'}</span>
97
+ <span>{template.updatedAt ? new Date(template.updatedAt).toLocaleDateString() : ''}</span>
98
+ </div>
99
+ </div>
100
+ ))}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }