@actuate-media/cms-admin 0.6.0 → 0.7.1
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 +17 -0
- package/dist/AdminRoot.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorBoundary.js.map +1 -1
- package/dist/hooks/useBuilderState.d.ts +49 -0
- package/dist/hooks/useBuilderState.d.ts.map +1 -0
- package/dist/hooks/useBuilderState.js +238 -0
- package/dist/hooks/useBuilderState.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +2 -2
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/views/FormSubmissions.js +11 -11
- package/dist/views/FormSubmissions.js.map +1 -1
- package/dist/views/Forms.js +1 -1
- package/dist/views/Forms.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +28 -8
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Posts.js +1 -1
- package/dist/views/Posts.js.map +1 -1
- package/dist/views/Redirects.js +2 -2
- package/dist/views/Redirects.js.map +1 -1
- package/dist/views/SEO.js +3 -3
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/Users.js +3 -3
- package/dist/views/Users.js.map +1 -1
- package/dist/views/page-builder/AIBlockAssist.d.ts +9 -0
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +1 -0
- package/dist/views/page-builder/AIBlockAssist.js +40 -0
- package/dist/views/page-builder/AIBlockAssist.js.map +1 -0
- package/dist/views/page-builder/AIGenerateDialog.d.ts +8 -0
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -0
- package/dist/views/page-builder/AIGenerateDialog.js +170 -0
- package/dist/views/page-builder/AIGenerateDialog.js.map +1 -0
- package/dist/views/page-builder/BlockEditor.d.ts +11 -0
- package/dist/views/page-builder/BlockEditor.d.ts.map +1 -0
- package/dist/views/page-builder/BlockEditor.js +67 -0
- package/dist/views/page-builder/BlockEditor.js.map +1 -0
- package/dist/views/page-builder/BlockPicker.d.ts +7 -0
- package/dist/views/page-builder/BlockPicker.d.ts.map +1 -0
- package/dist/views/page-builder/BlockPicker.js +102 -0
- package/dist/views/page-builder/BlockPicker.js.map +1 -0
- package/dist/views/page-builder/BottomBar.d.ts +9 -0
- package/dist/views/page-builder/BottomBar.d.ts.map +1 -0
- package/dist/views/page-builder/BottomBar.js +13 -0
- package/dist/views/page-builder/BottomBar.js.map +1 -0
- package/dist/views/page-builder/BuilderToolbar.d.ts +21 -0
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +1 -0
- package/dist/views/page-builder/BuilderToolbar.js +18 -0
- package/dist/views/page-builder/BuilderToolbar.js.map +1 -0
- package/dist/views/page-builder/ContextPanel.d.ts +20 -0
- package/dist/views/page-builder/ContextPanel.d.ts.map +1 -0
- package/dist/views/page-builder/ContextPanel.js +40 -0
- package/dist/views/page-builder/ContextPanel.js.map +1 -0
- package/dist/views/page-builder/DesignScore.d.ts +6 -0
- package/dist/views/page-builder/DesignScore.d.ts.map +1 -0
- package/dist/views/page-builder/DesignScore.js +93 -0
- package/dist/views/page-builder/DesignScore.js.map +1 -0
- package/dist/views/page-builder/NodeSettings.d.ts +12 -0
- package/dist/views/page-builder/NodeSettings.d.ts.map +1 -0
- package/dist/views/page-builder/NodeSettings.js +80 -0
- package/dist/views/page-builder/NodeSettings.js.map +1 -0
- package/dist/views/page-builder/PageBuilder.d.ts +8 -0
- package/dist/views/page-builder/PageBuilder.d.ts.map +1 -0
- package/dist/views/page-builder/PageBuilder.js +126 -0
- package/dist/views/page-builder/PageBuilder.js.map +1 -0
- package/dist/views/page-builder/PageSettings.d.ts +7 -0
- package/dist/views/page-builder/PageSettings.d.ts.map +1 -0
- package/dist/views/page-builder/PageSettings.js +27 -0
- package/dist/views/page-builder/PageSettings.js.map +1 -0
- package/dist/views/page-builder/PageTemplates.d.ts +5 -0
- package/dist/views/page-builder/PageTemplates.d.ts.map +1 -0
- package/dist/views/page-builder/PageTemplates.js +13 -0
- package/dist/views/page-builder/PageTemplates.js.map +1 -0
- package/dist/views/page-builder/SEOPanel.d.ts +10 -0
- package/dist/views/page-builder/SEOPanel.d.ts.map +1 -0
- package/dist/views/page-builder/SEOPanel.js +105 -0
- package/dist/views/page-builder/SEOPanel.js.map +1 -0
- package/dist/views/page-builder/SavedSections.d.ts +6 -0
- package/dist/views/page-builder/SavedSections.d.ts.map +1 -0
- package/dist/views/page-builder/SavedSections.js +145 -0
- package/dist/views/page-builder/SavedSections.js.map +1 -0
- package/dist/views/page-builder/TemplatePicker.d.ts +7 -0
- package/dist/views/page-builder/TemplatePicker.d.ts.map +1 -0
- package/dist/views/page-builder/TemplatePicker.js +68 -0
- package/dist/views/page-builder/TemplatePicker.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.js +19 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.js +22 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CodePreview.js +16 -0
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.js +24 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +6 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +7 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FormPreview.js +14 -0
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +21 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.js +19 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.js +17 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/TextPreview.js +26 -0
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.js +21 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/index.d.ts +9 -0
- package/dist/views/page-builder/block-renderers/index.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/index.js +25 -0
- package/dist/views/page-builder/block-renderers/index.js.map +1 -0
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/BlockRenderer.js +30 -0
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +10 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.js +26 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +1 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.js +36 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.js +33 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/RowRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/RowRenderer.js +32 -0
- package/dist/views/page-builder/canvas/RowRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/SectionRenderer.js +54 -0
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/index.d.ts +3 -0
- package/dist/views/page-builder/canvas/index.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/index.js +2 -0
- package/dist/views/page-builder/canvas/index.js.map +1 -0
- package/package.json +3 -2
- package/src/AdminRoot.tsx +21 -0
- package/src/components/ErrorBoundary.tsx +3 -3
- package/src/hooks/useBuilderState.ts +328 -0
- package/src/index.ts +4 -0
- package/src/layout/Sidebar.tsx +5 -0
- package/src/views/FormSubmissions.tsx +12 -12
- package/src/views/Forms.tsx +1 -1
- package/src/views/MediaBrowser.tsx +46 -15
- package/src/views/Posts.tsx +1 -1
- package/src/views/Redirects.tsx +2 -2
- package/src/views/SEO.tsx +3 -3
- package/src/views/Users.tsx +3 -3
- package/src/views/page-builder/AIBlockAssist.tsx +68 -0
- package/src/views/page-builder/AIGenerateDialog.tsx +574 -0
- package/src/views/page-builder/BlockEditor.tsx +352 -0
- package/src/views/page-builder/BlockPicker.tsx +338 -0
- package/src/views/page-builder/BottomBar.tsx +64 -0
- package/src/views/page-builder/BuilderToolbar.tsx +218 -0
- package/src/views/page-builder/ContextPanel.tsx +145 -0
- package/src/views/page-builder/DesignScore.tsx +258 -0
- package/src/views/page-builder/NodeSettings.tsx +515 -0
- package/src/views/page-builder/PageBuilder.tsx +288 -0
- package/src/views/page-builder/PageSettings.tsx +161 -0
- package/src/views/page-builder/PageTemplates.tsx +105 -0
- package/src/views/page-builder/SEOPanel.tsx +485 -0
- package/src/views/page-builder/SavedSections.tsx +486 -0
- package/src/views/page-builder/TemplatePicker.tsx +201 -0
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +81 -0
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +71 -0
- package/src/views/page-builder/block-renderers/CodePreview.tsx +46 -0
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +90 -0
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +18 -0
- package/src/views/page-builder/block-renderers/FormPreview.tsx +69 -0
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +93 -0
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +103 -0
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +54 -0
- package/src/views/page-builder/block-renderers/TextPreview.tsx +81 -0
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +78 -0
- package/src/views/page-builder/block-renderers/index.ts +34 -0
- package/src/views/page-builder/canvas/BlockRenderer.tsx +62 -0
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +90 -0
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +86 -0
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +71 -0
- package/src/views/page-builder/canvas/RowRenderer.tsx +72 -0
- package/src/views/page-builder/canvas/SectionRenderer.tsx +97 -0
- package/src/views/page-builder/canvas/index.ts +2 -0
|
@@ -35,7 +35,7 @@ interface Submission {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function sourceColor(source: string): string {
|
|
38
|
-
const s = source.toLowerCase();
|
|
38
|
+
const s = (source ?? '').toLowerCase();
|
|
39
39
|
if (s === 'google') return 'bg-blue-100 text-blue-800';
|
|
40
40
|
if (s === 'facebook' || s === 'meta') return 'bg-indigo-100 text-indigo-800';
|
|
41
41
|
if (s === 'linkedin') return 'bg-sky-100 text-sky-800';
|
|
@@ -45,7 +45,7 @@ function sourceColor(source: string): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function mediumColor(medium: string): string {
|
|
48
|
-
const m = medium.toLowerCase();
|
|
48
|
+
const m = (medium ?? '').toLowerCase();
|
|
49
49
|
if (m === 'cpc' || m === 'paid') return 'bg-orange-100 text-orange-800';
|
|
50
50
|
if (m === 'organic') return 'bg-green-100 text-green-800';
|
|
51
51
|
if (m === 'social') return 'bg-pink-100 text-pink-800';
|
|
@@ -66,22 +66,22 @@ export interface FormSubmissionsProps {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function FormSubmissions({ formId, onNavigate }: FormSubmissionsProps) {
|
|
69
|
-
const { data, loading, error, refetch } = useApiData<Submission[]>(`/forms/${formId}/submissions`);
|
|
69
|
+
const { data, loading, error, refetch } = useApiData<Submission[] | { submissions: Submission[] }>(`/forms/${formId}/submissions`);
|
|
70
70
|
const [searchQuery, setSearchQuery] = useState('');
|
|
71
71
|
const [filterStatus, setFilterStatus] = useState('all');
|
|
72
72
|
const [filterSource, setFilterSource] = useState('all');
|
|
73
73
|
const [expandedId, setExpandedId] = useState<number | null>(null);
|
|
74
74
|
|
|
75
|
-
const submissions = data ?? [];
|
|
75
|
+
const submissions = Array.isArray(data) ? data : data?.submissions ?? [];
|
|
76
76
|
|
|
77
77
|
const filteredSubmissions = useMemo(() => {
|
|
78
78
|
let results = submissions.filter((s) => {
|
|
79
79
|
const matchesSearch =
|
|
80
|
-
s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
81
|
-
s.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
82
|
-
s.message.toLowerCase().includes(searchQuery.toLowerCase());
|
|
80
|
+
(s.name ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
81
|
+
(s.email ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
82
|
+
(s.message ?? '').toLowerCase().includes(searchQuery.toLowerCase());
|
|
83
83
|
const matchesStatus = filterStatus === 'all' || s.status === filterStatus;
|
|
84
|
-
const matchesSource = filterSource === 'all' || s.attribution
|
|
84
|
+
const matchesSource = filterSource === 'all' || s.attribution?.source === filterSource;
|
|
85
85
|
return matchesSearch && matchesStatus && matchesSource;
|
|
86
86
|
});
|
|
87
87
|
|
|
@@ -91,19 +91,19 @@ export function FormSubmissions({ formId, onNavigate }: FormSubmissionsProps) {
|
|
|
91
91
|
return results;
|
|
92
92
|
}, [submissions, searchQuery, filterStatus, filterSource]);
|
|
93
93
|
|
|
94
|
-
const uniqueSources = [...new Set(submissions.map(s => s.attribution
|
|
94
|
+
const uniqueSources = [...new Set(submissions.map(s => s.attribution?.source ?? '(direct)'))];
|
|
95
95
|
|
|
96
96
|
const sourceSummary = useMemo(() => {
|
|
97
97
|
const counts: Record<string, number> = {};
|
|
98
98
|
for (const s of submissions) {
|
|
99
|
-
const key = `${s.attribution
|
|
99
|
+
const key = `${s.attribution?.source ?? '(direct)'} / ${s.attribution?.medium ?? '(none)'}`;
|
|
100
100
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
101
101
|
}
|
|
102
102
|
return Object.entries(counts).sort(([, a], [, b]) => b - a);
|
|
103
103
|
}, [submissions]);
|
|
104
104
|
|
|
105
|
-
const paidCount = submissions.filter(s => ['cpc', 'paid'].includes(s.attribution
|
|
106
|
-
const organicCount = submissions.filter(s => s.attribution
|
|
105
|
+
const paidCount = submissions.filter(s => ['cpc', 'paid'].includes(s.attribution?.medium ?? '')).length;
|
|
106
|
+
const organicCount = submissions.filter(s => s.attribution?.medium === 'organic').length;
|
|
107
107
|
|
|
108
108
|
const handleExport = () => {
|
|
109
109
|
toast.success('Submissions exported to CSV (includes attribution data)');
|
package/src/views/Forms.tsx
CHANGED
|
@@ -17,7 +17,7 @@ export function Forms({ onNavigate }: FormsProps) {
|
|
|
17
17
|
|
|
18
18
|
const filteredAndSorted = useMemo(() => {
|
|
19
19
|
let results = forms.filter((form: any) =>
|
|
20
|
-
form.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
20
|
+
(form.name ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
21
21
|
(form.description ?? '').toLowerCase().includes(searchQuery.toLowerCase())
|
|
22
22
|
);
|
|
23
23
|
|
|
@@ -15,7 +15,7 @@ import { FolderTree, type FolderSelection } from '../components/FolderTree.js';
|
|
|
15
15
|
import { FocalPointPicker } from '../components/FocalPointPicker.js';
|
|
16
16
|
|
|
17
17
|
interface MediaItem {
|
|
18
|
-
id: number;
|
|
18
|
+
id: number | string;
|
|
19
19
|
name: string;
|
|
20
20
|
type: string;
|
|
21
21
|
size: string;
|
|
@@ -29,6 +29,33 @@ interface MediaItem {
|
|
|
29
29
|
usedOn?: { page: string; path: string }[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function isImageMedia(item: MediaItem): boolean {
|
|
33
|
+
return Boolean(item.url) && (item.type?.startsWith('image/') || /\.(avif|gif|jpe?g|png|webp|svg)$/i.test(item.url));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function matchesMediaType(item: MediaItem, filterType: string): boolean {
|
|
37
|
+
if (filterType === 'all') return true;
|
|
38
|
+
if (filterType === 'image') return item.type?.startsWith('image/') || isImageMedia(item);
|
|
39
|
+
if (filterType === 'video') return item.type?.startsWith('video/');
|
|
40
|
+
if (filterType === 'document') return !item.type?.startsWith('image/') && !item.type?.startsWith('video/');
|
|
41
|
+
return item.type === filterType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function MediaPreview({ item }: { item: MediaItem }) {
|
|
45
|
+
if (isImageMedia(item)) {
|
|
46
|
+
return (
|
|
47
|
+
<img
|
|
48
|
+
src={item.url}
|
|
49
|
+
alt={item.altTag || item.title || item.name || 'Media preview'}
|
|
50
|
+
className="h-full w-full object-cover"
|
|
51
|
+
loading="lazy"
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <FileImage className="w-6 h-6 sm:w-8 sm:h-8 text-muted-foreground" />;
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
type MediaSortKey = 'name' | 'type' | 'size' | 'date';
|
|
33
60
|
|
|
34
61
|
export interface MediaBrowserProps {
|
|
@@ -50,7 +77,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
50
77
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
51
78
|
|
|
52
79
|
const apiUrl = useMemo(() => buildMediaApiUrl(folderSel), [folderSel]);
|
|
53
|
-
const { data, loading, error, refetch } = useApiData<{ data
|
|
80
|
+
const { data, loading, error, refetch } = useApiData<{ data?: MediaItem[]; items?: MediaItem[]; total: number }>(apiUrl);
|
|
54
81
|
|
|
55
82
|
const allData = useApiData<{ data: MediaItem[]; total: number }>('/media?pageSize=1');
|
|
56
83
|
const uncatData = useApiData<{ data: MediaItem[]; total: number }>('/media?pageSize=1&folderId=none');
|
|
@@ -58,7 +85,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
58
85
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
|
59
86
|
const [searchQuery, setSearchQuery] = useState('');
|
|
60
87
|
const [filterType, setFilterType] = useState('all');
|
|
61
|
-
const [selectedMedia, setSelectedMedia] = useState<number
|
|
88
|
+
const [selectedMedia, setSelectedMedia] = useState<Array<number | string>>([]);
|
|
62
89
|
const [sortConfig, setSortConfig] = useState<SortConfig<MediaSortKey> | null>(null);
|
|
63
90
|
const [activeItem, setActiveItem] = useState<MediaItem | null>(null);
|
|
64
91
|
|
|
@@ -72,12 +99,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
72
99
|
const [uploading, setUploading] = useState(false);
|
|
73
100
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
74
101
|
|
|
75
|
-
const mediaItems = data?.data ?? [];
|
|
102
|
+
const mediaItems = data?.data ?? data?.items ?? [];
|
|
76
103
|
|
|
77
104
|
const filteredAndSorted = useMemo(() => {
|
|
78
105
|
let results = mediaItems.filter((item) => {
|
|
79
|
-
const matchesSearch = item.name.toLowerCase().includes(searchQuery.toLowerCase());
|
|
80
|
-
const matchesType =
|
|
106
|
+
const matchesSearch = (item.name ?? '').toLowerCase().includes(searchQuery.toLowerCase());
|
|
107
|
+
const matchesType = matchesMediaType(item, filterType);
|
|
81
108
|
return matchesSearch && matchesType;
|
|
82
109
|
});
|
|
83
110
|
|
|
@@ -110,7 +137,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
110
137
|
setActiveItem(null);
|
|
111
138
|
};
|
|
112
139
|
|
|
113
|
-
const handleCheckbox = (e: React.MouseEvent, id: number) => {
|
|
140
|
+
const handleCheckbox = (e: React.MouseEvent, id: number | string) => {
|
|
114
141
|
e.stopPropagation();
|
|
115
142
|
setSelectedMedia(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id]);
|
|
116
143
|
};
|
|
@@ -141,7 +168,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
141
168
|
}
|
|
142
169
|
};
|
|
143
170
|
|
|
144
|
-
const deleteMedia = async (id: number) => {
|
|
171
|
+
const deleteMedia = async (id: number | string) => {
|
|
145
172
|
const res = await cmsApi(`/media/${id}`, { method: 'DELETE' });
|
|
146
173
|
if (res.error) {
|
|
147
174
|
toast.error(res.error);
|
|
@@ -185,7 +212,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
185
212
|
|
|
186
213
|
await new Promise(r => setTimeout(r, 1500));
|
|
187
214
|
if (field === 'alt') {
|
|
188
|
-
const generated = `A ${activeItem?.format
|
|
215
|
+
const generated = `A ${(activeItem?.format ?? 'media').toLowerCase()} image showing ${(activeItem?.name ?? 'uploaded').replace(/[-_]/g, ' ').replace(/\.\w+$/, '')} content`;
|
|
189
216
|
setEditAlt(generated);
|
|
190
217
|
toast.success('Alt tag generated by AI');
|
|
191
218
|
} else if (field === 'title') {
|
|
@@ -257,7 +284,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
257
284
|
}
|
|
258
285
|
}, [refetch]);
|
|
259
286
|
|
|
260
|
-
const handleDragStart = (e: React.DragEvent, id: number) => {
|
|
287
|
+
const handleDragStart = (e: React.DragEvent, id: number | string) => {
|
|
261
288
|
e.dataTransfer.setData('text/actuate-item-id', String(id));
|
|
262
289
|
e.dataTransfer.effectAllowed = 'move';
|
|
263
290
|
};
|
|
@@ -432,7 +459,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
432
459
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
433
460
|
>
|
|
434
461
|
<div className="w-full h-full bg-gray-100 flex items-center justify-center">
|
|
435
|
-
<
|
|
462
|
+
<MediaPreview item={item} />
|
|
436
463
|
</div>
|
|
437
464
|
<div className="absolute inset-0 bg-linear-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
|
|
438
465
|
<div className="absolute bottom-0 left-0 right-0 p-2">
|
|
@@ -494,7 +521,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
494
521
|
</td>
|
|
495
522
|
<td className="px-3 py-2">
|
|
496
523
|
<div className="flex items-center gap-3">
|
|
497
|
-
<div className="w-10 h-10 bg-gray-100 rounded flex items-center justify-center"><
|
|
524
|
+
<div className="w-10 h-10 bg-gray-100 rounded flex items-center justify-center overflow-hidden"><MediaPreview item={item} /></div>
|
|
498
525
|
<span className="text-sm font-medium text-gray-900">{item.name}</span>
|
|
499
526
|
</div>
|
|
500
527
|
</td>
|
|
@@ -530,8 +557,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
530
557
|
|
|
531
558
|
<div className="flex-1 overflow-y-auto">
|
|
532
559
|
<div className="p-4 border-b border-gray-200">
|
|
533
|
-
<div className="aspect-video bg-gray-100 rounded-lg flex items-center justify-center">
|
|
534
|
-
|
|
560
|
+
<div className="aspect-video bg-gray-100 rounded-lg flex items-center justify-center overflow-hidden">
|
|
561
|
+
{isImageMedia(activeItem) ? (
|
|
562
|
+
<MediaPreview item={activeItem} />
|
|
563
|
+
) : (
|
|
564
|
+
<ImageIcon className="w-12 h-12 text-gray-300" />
|
|
565
|
+
)}
|
|
535
566
|
</div>
|
|
536
567
|
</div>
|
|
537
568
|
|
|
@@ -661,7 +692,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
661
692
|
</div>
|
|
662
693
|
</div>
|
|
663
694
|
|
|
664
|
-
{activeItem
|
|
695
|
+
{isImageMedia(activeItem) && activeItem.url && (
|
|
665
696
|
<div className="p-4 border-b border-gray-200">
|
|
666
697
|
<FocalPointPicker
|
|
667
698
|
imageUrl={activeItem.url}
|
package/src/views/Posts.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export function Posts({ onNavigate }: PostsProps) {
|
|
|
25
25
|
|
|
26
26
|
const filteredAndSorted = useMemo(() => {
|
|
27
27
|
let results = posts.filter((post: any) => {
|
|
28
|
-
const matchesSearch = post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
28
|
+
const matchesSearch = (post.title ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
29
29
|
(post.author ?? '').toLowerCase().includes(searchQuery.toLowerCase());
|
|
30
30
|
const matchesStatus = filterStatus === 'all' || (post.status ?? '').toLowerCase() === filterStatus.toLowerCase();
|
|
31
31
|
const matchesCategory = filterCategory === 'all' || (post.category ?? '').toLowerCase() === filterCategory.toLowerCase();
|
package/src/views/Redirects.tsx
CHANGED
|
@@ -27,8 +27,8 @@ export function Redirects({ onNavigate }: RedirectsProps) {
|
|
|
27
27
|
const filteredAndSorted = useMemo(() => {
|
|
28
28
|
let results = redirects.filter((r) => {
|
|
29
29
|
const matchesSearch =
|
|
30
|
-
r.from.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
31
|
-
r.to.toLowerCase().includes(searchQuery.toLowerCase());
|
|
30
|
+
(r.from ?? '').toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
31
|
+
(r.to ?? '').toLowerCase().includes(searchQuery.toLowerCase());
|
|
32
32
|
const matchesType = filterType === 'all' || r.type === filterType;
|
|
33
33
|
return matchesSearch && matchesType;
|
|
34
34
|
});
|
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 () => {
|
package/src/views/Users.tsx
CHANGED
|
@@ -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,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { Sparkles, Loader2 } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { cmsApi } from '../../lib/api.js';
|
|
7
|
+
import type { BlockNode } from '@actuate-media/cms-core';
|
|
8
|
+
|
|
9
|
+
export interface AIBlockAssistProps {
|
|
10
|
+
block: BlockNode;
|
|
11
|
+
pageTitle?: string;
|
|
12
|
+
businessName?: string;
|
|
13
|
+
onUpdateData: (data: Record<string, unknown>) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AIBlockAssist({
|
|
17
|
+
block,
|
|
18
|
+
pageTitle,
|
|
19
|
+
businessName,
|
|
20
|
+
onUpdateData,
|
|
21
|
+
}: AIBlockAssistProps) {
|
|
22
|
+
const [generating, setGenerating] = useState(false);
|
|
23
|
+
|
|
24
|
+
const handleGenerate = useCallback(async () => {
|
|
25
|
+
setGenerating(true);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const res = await cmsApi<Record<string, unknown>>('/page-builder/generate-block', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
blockType: block.settings.blockType,
|
|
32
|
+
variant: block.settings.variant,
|
|
33
|
+
pageContext: {
|
|
34
|
+
pageTitle,
|
|
35
|
+
businessName,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (res.error) {
|
|
41
|
+
toast.error(res.error);
|
|
42
|
+
} else if (res.data) {
|
|
43
|
+
onUpdateData(res.data);
|
|
44
|
+
toast.success('Content generated');
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
toast.error('Failed to generate content');
|
|
48
|
+
} finally {
|
|
49
|
+
setGenerating(false);
|
|
50
|
+
}
|
|
51
|
+
}, [block, pageTitle, businessName, onUpdateData]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
onClick={handleGenerate}
|
|
56
|
+
disabled={generating}
|
|
57
|
+
className="flex items-center gap-1.5 px-2.5 py-1 text-xs text-primary hover:bg-primary/10 rounded-md transition-colors disabled:opacity-50"
|
|
58
|
+
title="Generate content with AI"
|
|
59
|
+
>
|
|
60
|
+
{generating ? (
|
|
61
|
+
<Loader2 size={12} className="animate-spin" />
|
|
62
|
+
) : (
|
|
63
|
+
<Sparkles size={12} />
|
|
64
|
+
)}
|
|
65
|
+
{generating ? 'Generating...' : 'AI Fill'}
|
|
66
|
+
</button>
|
|
67
|
+
);
|
|
68
|
+
}
|