@actuate-media/cms-admin 0.4.0 → 0.6.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 +22 -0
- package/dist/AdminRoot.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +1 -0
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/index.d.ts +4 -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/ForgotPassword.d.ts +5 -0
- package/dist/views/ForgotPassword.d.ts.map +1 -0
- package/dist/views/ForgotPassword.js +41 -0
- package/dist/views/ForgotPassword.js.map +1 -0
- package/dist/views/ResetPassword.d.ts +6 -0
- package/dist/views/ResetPassword.d.ts.map +1 -0
- package/dist/views/ResetPassword.js +46 -0
- package/dist/views/ResetPassword.js.map +1 -0
- package/dist/views/ScriptTagEditor.d.ts +6 -0
- package/dist/views/ScriptTagEditor.d.ts.map +1 -0
- package/dist/views/ScriptTagEditor.js +109 -0
- package/dist/views/ScriptTagEditor.js.map +1 -0
- package/dist/views/ScriptTags.d.ts +5 -0
- package/dist/views/ScriptTags.d.ts.map +1 -0
- package/dist/views/ScriptTags.js +54 -0
- package/dist/views/ScriptTags.js.map +1 -0
- package/package.json +5 -3
- package/src/AdminRoot.tsx +25 -0
- package/src/components/Breadcrumbs.tsx +1 -0
- package/src/index.ts +4 -0
- package/src/layout/Sidebar.tsx +2 -0
- package/src/views/ForgotPassword.tsx +136 -0
- package/src/views/ResetPassword.tsx +192 -0
- package/src/views/ScriptTagEditor.tsx +361 -0
- package/src/views/ScriptTags.tsx +174 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Code2, Plus, Loader2, AlertTriangle } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { useApiData } from '../lib/useApiData.js';
|
|
7
|
+
import { cmsApi } from '../lib/api.js';
|
|
8
|
+
|
|
9
|
+
interface ScriptTag {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
code: string;
|
|
13
|
+
placement: string;
|
|
14
|
+
scope: string;
|
|
15
|
+
targetPaths: string[];
|
|
16
|
+
priority: number;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ScriptTagsProps {
|
|
23
|
+
onNavigate?: (path: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PLACEMENT_LABELS: Record<string, string> = {
|
|
27
|
+
head: 'Head',
|
|
28
|
+
body_open: 'Body Open',
|
|
29
|
+
body_close: 'Body Close',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const PLACEMENT_COLORS: Record<string, string> = {
|
|
33
|
+
head: 'bg-purple-100 text-purple-700',
|
|
34
|
+
body_open: 'bg-blue-100 text-blue-700',
|
|
35
|
+
body_close: 'bg-green-100 text-green-700',
|
|
36
|
+
};
|
|
37
|
+
|
|
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;
|
|
42
|
+
return `${count} parent path${count !== 1 ? 's' : ''} + children`;
|
|
43
|
+
}
|
|
44
|
+
if (tag.scope === 'urls') {
|
|
45
|
+
const count = tag.targetPaths?.length ?? 0;
|
|
46
|
+
return `${count} URL${count !== 1 ? 's' : ''}`;
|
|
47
|
+
}
|
|
48
|
+
return tag.scope;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ScriptTags({ onNavigate }: ScriptTagsProps) {
|
|
52
|
+
const { data, loading, error, refetch } = useApiData<ScriptTag[]>('/script-tags');
|
|
53
|
+
const [togglingId, setTogglingId] = useState<string | null>(null);
|
|
54
|
+
|
|
55
|
+
const tags = data ?? [];
|
|
56
|
+
|
|
57
|
+
const toggleEnabled = async (tag: ScriptTag) => {
|
|
58
|
+
setTogglingId(tag.id);
|
|
59
|
+
const res = await cmsApi(`/script-tags/${tag.id}`, {
|
|
60
|
+
method: 'PUT',
|
|
61
|
+
body: JSON.stringify({ enabled: !tag.enabled }),
|
|
62
|
+
});
|
|
63
|
+
setTogglingId(null);
|
|
64
|
+
if (res.error) {
|
|
65
|
+
toast.error(res.error);
|
|
66
|
+
} else {
|
|
67
|
+
refetch();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (loading) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="p-3 pr-6 sm:p-4 sm:pr-8 flex items-center justify-center h-64">
|
|
74
|
+
<Loader2 className="w-6 h-6 animate-spin text-blue-600" />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="p-3 pr-6 sm:p-4 sm:pr-8">
|
|
81
|
+
{error && (
|
|
82
|
+
<div className="mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
|
|
83
|
+
<AlertTriangle className="w-5 h-5 text-red-600 shrink-0" />
|
|
84
|
+
<span className="text-sm text-red-800 flex-1">{error}</span>
|
|
85
|
+
<button onClick={refetch} className="px-3 py-1 text-sm text-red-700 border border-red-300 rounded-lg hover:bg-red-100 transition-colors">Retry</button>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
<div className="mb-4 flex items-center justify-between">
|
|
90
|
+
<div>
|
|
91
|
+
<h1 className="mb-1 text-2xl font-semibold text-gray-900">Script Tags</h1>
|
|
92
|
+
<p className="text-sm text-gray-600">Manage tracking codes, analytics, and custom scripts injected into your site</p>
|
|
93
|
+
</div>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={() => onNavigate?.('/script-tags/new')}
|
|
97
|
+
className="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
|
98
|
+
>
|
|
99
|
+
<Plus className="w-4 h-4" />
|
|
100
|
+
New Tag
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{tags.length === 0 && !error ? (
|
|
105
|
+
<div className="rounded-lg border border-gray-200 bg-white p-12 text-center">
|
|
106
|
+
<Code2 className="mx-auto mb-3 h-10 w-10 text-gray-300" />
|
|
107
|
+
<h3 className="text-sm font-semibold text-gray-900">No script tags yet</h3>
|
|
108
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
109
|
+
Add tracking codes like Google Analytics, Tag Manager, or Facebook Pixel.
|
|
110
|
+
</p>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
onClick={() => onNavigate?.('/script-tags/new')}
|
|
114
|
+
className="mt-4 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
|
115
|
+
>
|
|
116
|
+
<Plus className="w-4 h-4" />
|
|
117
|
+
Add Your First Tag
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
) : (
|
|
121
|
+
<div className="rounded-lg border border-gray-200 bg-white overflow-hidden">
|
|
122
|
+
<table className="w-full text-sm">
|
|
123
|
+
<thead>
|
|
124
|
+
<tr className="border-b border-gray-200 bg-gray-50">
|
|
125
|
+
<th className="px-4 py-3 text-left font-medium text-gray-600">Name</th>
|
|
126
|
+
<th className="px-4 py-3 text-left font-medium text-gray-600">Placement</th>
|
|
127
|
+
<th className="px-4 py-3 text-left font-medium text-gray-600">Scope</th>
|
|
128
|
+
<th className="px-4 py-3 text-center font-medium text-gray-600">Priority</th>
|
|
129
|
+
<th className="px-4 py-3 text-center font-medium text-gray-600">Enabled</th>
|
|
130
|
+
</tr>
|
|
131
|
+
</thead>
|
|
132
|
+
<tbody>
|
|
133
|
+
{tags.map((tag) => (
|
|
134
|
+
<tr key={tag.id} className="border-b border-gray-100 last:border-0 hover:bg-gray-50 transition-colors">
|
|
135
|
+
<td className="px-4 py-3">
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => onNavigate?.(`/script-tags/${tag.id}`)}
|
|
139
|
+
className="font-medium text-blue-600 hover:text-blue-800 hover:underline"
|
|
140
|
+
>
|
|
141
|
+
{tag.name}
|
|
142
|
+
</button>
|
|
143
|
+
</td>
|
|
144
|
+
<td className="px-4 py-3">
|
|
145
|
+
<span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${PLACEMENT_COLORS[tag.placement] ?? 'bg-gray-100 text-gray-700'}`}>
|
|
146
|
+
{PLACEMENT_LABELS[tag.placement] ?? tag.placement}
|
|
147
|
+
</span>
|
|
148
|
+
</td>
|
|
149
|
+
<td className="px-4 py-3 text-gray-600">{scopeLabel(tag)}</td>
|
|
150
|
+
<td className="px-4 py-3 text-center text-gray-600 font-mono">{tag.priority}</td>
|
|
151
|
+
<td className="px-4 py-3 text-center">
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={() => toggleEnabled(tag)}
|
|
155
|
+
disabled={togglingId === tag.id}
|
|
156
|
+
className={`relative h-6 w-11 shrink-0 rounded-full transition-colors ${tag.enabled ? 'bg-blue-600' : 'bg-gray-300'}`}
|
|
157
|
+
aria-pressed={tag.enabled}
|
|
158
|
+
>
|
|
159
|
+
<span
|
|
160
|
+
className={`absolute top-0.5 block h-5 w-5 rounded-full bg-white transition-transform ${
|
|
161
|
+
tag.enabled ? 'translate-x-[22px]' : 'translate-x-0.5'
|
|
162
|
+
}`}
|
|
163
|
+
/>
|
|
164
|
+
</button>
|
|
165
|
+
</td>
|
|
166
|
+
</tr>
|
|
167
|
+
))}
|
|
168
|
+
</tbody>
|
|
169
|
+
</table>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|