@kyro-cms/admin 0.8.0 → 0.9.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/index.cjs +11960 -11006
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +563 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12183 -11238
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/ActionBar.tsx +27 -14
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AutoForm.tsx +585 -369
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +71 -56
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +33 -20
- package/src/components/MediaGallery.tsx +219 -194
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +7 -7
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +430 -50
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipField.tsx +153 -87
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/SlidePanel.tsx +8 -3
- package/src/components/ui/icons.tsx +109 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/hooks/useAutoFormState.ts +125 -62
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AuthLayout.astro +14 -2
- package/src/lib/autoform-store.ts +85 -52
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +44 -9
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Blocks,
|
|
4
4
|
Settings,
|
|
5
|
-
ToggleRight,
|
|
6
|
-
ToggleLeft,
|
|
7
5
|
CheckCircle2,
|
|
8
6
|
Clock,
|
|
9
7
|
RefreshCw,
|
|
@@ -11,6 +9,7 @@ import {
|
|
|
11
9
|
Search,
|
|
12
10
|
Plus,
|
|
13
11
|
X,
|
|
12
|
+
AlertTriangle,
|
|
14
13
|
} from "./ui/icons";
|
|
15
14
|
import { Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
16
15
|
import { PageHeader } from "./ui/PageHeader";
|
|
@@ -22,71 +21,118 @@ interface Plugin {
|
|
|
22
21
|
description: string;
|
|
23
22
|
version: string;
|
|
24
23
|
enabled: boolean;
|
|
25
|
-
|
|
26
|
-
updatedAt: string;
|
|
27
|
-
icon: string;
|
|
28
|
-
status: "active" | "error" | "update_available";
|
|
24
|
+
status: "active" | "disabled" | "error" | "update_available";
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"Advanced meta tags, sitemaps, and rich snippets for all collections.",
|
|
37
|
-
version: "2.1.4",
|
|
38
|
-
enabled: true,
|
|
39
|
-
author: "Kyro Team",
|
|
40
|
-
updatedAt: "2024-05-10T14:30:00Z",
|
|
41
|
-
icon: "search",
|
|
42
|
-
status: "active",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "analytics-dashboard",
|
|
46
|
-
name: "Analytics Integration",
|
|
47
|
-
description:
|
|
48
|
-
"Connect to Google Analytics, Plausible, or Mixpanel for traffic insights.",
|
|
49
|
-
version: "1.0.2",
|
|
50
|
-
enabled: true,
|
|
51
|
-
author: "Kyro Team",
|
|
52
|
-
updatedAt: "2024-04-20T09:15:00Z",
|
|
53
|
-
icon: "activity",
|
|
54
|
-
status: "update_available",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "aws-s3-adapter",
|
|
58
|
-
name: "AWS S3 Storage",
|
|
59
|
-
description: "Cloud storage adapter for Media Library with S3 integration.",
|
|
60
|
-
version: "3.0.0",
|
|
61
|
-
enabled: false,
|
|
62
|
-
author: "AWS",
|
|
63
|
-
updatedAt: "2024-01-15T11:00:00Z",
|
|
64
|
-
icon: "database",
|
|
65
|
-
status: "active",
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: "ai-writer",
|
|
69
|
-
name: "AI Content Writer",
|
|
70
|
-
description:
|
|
71
|
-
"Generate blog posts and product descriptions with AI assistance.",
|
|
72
|
-
version: "1.2.0",
|
|
73
|
-
enabled: true,
|
|
74
|
-
author: "Kyro Team",
|
|
75
|
-
updatedAt: "2024-05-01T10:00:00Z",
|
|
76
|
-
icon: "sparkles",
|
|
77
|
-
status: "active",
|
|
78
|
-
},
|
|
79
|
-
];
|
|
27
|
+
interface ToggleError {
|
|
28
|
+
error: string;
|
|
29
|
+
requiresAction?: boolean;
|
|
30
|
+
activeProvider?: string;
|
|
31
|
+
}
|
|
80
32
|
|
|
81
33
|
export function PluginsManager() {
|
|
82
|
-
const [plugins, setPlugins] = useState<Plugin[]>(
|
|
34
|
+
const [plugins, setPlugins] = useState<Plugin[]>([]);
|
|
35
|
+
const [loading, setLoading] = useState(true);
|
|
36
|
+
const [toggleLoading, setToggleLoading] = useState<string | null>(null);
|
|
83
37
|
const [searchQuery, setSearchQuery] = useState("");
|
|
84
38
|
const [showConfigModal, setShowConfigModal] = useState<string | null>(null);
|
|
39
|
+
const [confirmDisable, setConfirmDisable] = useState<{
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
activeProvider: string;
|
|
43
|
+
} | null>(null);
|
|
44
|
+
const [error, setError] = useState<string | null>(null);
|
|
85
45
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
46
|
+
const fetchPlugins = async () => {
|
|
47
|
+
try {
|
|
48
|
+
setLoading(true);
|
|
49
|
+
const res = await fetch("/api/plugins");
|
|
50
|
+
if (res.ok) {
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
setPlugins(data);
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error("Failed to fetch plugins:", e);
|
|
56
|
+
} finally {
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
fetchPlugins();
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const togglePlugin = async (id: string) => {
|
|
66
|
+
setError(null);
|
|
67
|
+
setToggleLoading(id);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(`/api/plugins/${encodeURIComponent(id)}/toggle`, {
|
|
71
|
+
method: "PUT",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (res.status === 409) {
|
|
75
|
+
const errData: ToggleError = await res.json();
|
|
76
|
+
const plugin = plugins.find((p) => p.id === id);
|
|
77
|
+
if (errData.requiresAction && plugin) {
|
|
78
|
+
setConfirmDisable({
|
|
79
|
+
id,
|
|
80
|
+
name: plugin.name,
|
|
81
|
+
activeProvider: errData.activeProvider || "unknown",
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
setError(errData.error);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
const errData = await res.json().catch(() => ({ error: "Toggle failed" }));
|
|
91
|
+
setError(errData.error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = await res.json();
|
|
96
|
+
setPlugins((prev) =>
|
|
97
|
+
prev.map((p) =>
|
|
98
|
+
p.id === id ? { ...p, enabled: result.enabled } : p,
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
} catch (e: any) {
|
|
102
|
+
setError(e.message || "Network error");
|
|
103
|
+
} finally {
|
|
104
|
+
setToggleLoading(null);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleForceDisable = async () => {
|
|
109
|
+
if (!confirmDisable) return;
|
|
110
|
+
setError(null);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(
|
|
114
|
+
`/api/plugins/${encodeURIComponent(confirmDisable.id)}/toggle?force=1`,
|
|
115
|
+
{ method: "PUT" },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (res.ok) {
|
|
119
|
+
const result = await res.json();
|
|
120
|
+
setPlugins((prev) =>
|
|
121
|
+
prev.map((p) =>
|
|
122
|
+
p.id === confirmDisable.id
|
|
123
|
+
? { ...p, enabled: result.enabled }
|
|
124
|
+
: p,
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
const errData = await res.json().catch(() => ({ error: "Toggle failed" }));
|
|
129
|
+
setError(errData.error);
|
|
130
|
+
}
|
|
131
|
+
} catch (e: any) {
|
|
132
|
+
setError(e.message || "Network error");
|
|
133
|
+
} finally {
|
|
134
|
+
setConfirmDisable(null);
|
|
135
|
+
}
|
|
90
136
|
};
|
|
91
137
|
|
|
92
138
|
const filteredPlugins = plugins.filter(
|
|
@@ -100,6 +146,21 @@ export function PluginsManager() {
|
|
|
100
146
|
(p) => p.status === "update_available",
|
|
101
147
|
).length;
|
|
102
148
|
|
|
149
|
+
if (loading) {
|
|
150
|
+
return (
|
|
151
|
+
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
|
|
152
|
+
<PageHeader
|
|
153
|
+
title="Plugins"
|
|
154
|
+
description="Extend Kyro CMS with modular features and integrations."
|
|
155
|
+
icon={Blocks}
|
|
156
|
+
/>
|
|
157
|
+
<div className="flex items-center justify-center p-12">
|
|
158
|
+
<RefreshCw className="w-6 h-6 animate-spin text-[var(--kyro-text-secondary)] opacity-40" />
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
103
164
|
return (
|
|
104
165
|
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
|
|
105
166
|
<PageHeader
|
|
@@ -115,6 +176,20 @@ export function PluginsManager() {
|
|
|
115
176
|
]}
|
|
116
177
|
/>
|
|
117
178
|
|
|
179
|
+
{error && (
|
|
180
|
+
<div className="p-4 rounded-2xl bg-red-500/10 border border-red-500/20 flex items-center gap-3">
|
|
181
|
+
<AlertTriangle className="w-4 h-4 text-red-500 shrink-0" />
|
|
182
|
+
<p className="text-xs text-red-500">{error}</p>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
onClick={() => setError(null)}
|
|
186
|
+
className="ml-auto p-1 hover:bg-red-500/20 rounded-lg transition-colors"
|
|
187
|
+
>
|
|
188
|
+
<X className="w-3 h-3 text-red-500" />
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
118
193
|
<div className="flex flex-col gap-8 surface-tile p-8">
|
|
119
194
|
{/* Stats Summary */}
|
|
120
195
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@@ -155,14 +230,14 @@ export function PluginsManager() {
|
|
|
155
230
|
<Search className="w-4 h-4 absolute left-3.5 top-1/2 -translate-y-1/2 text-[var(--kyro-text-secondary)] opacity-40" />
|
|
156
231
|
<input
|
|
157
232
|
type="text"
|
|
158
|
-
placeholder="Search
|
|
233
|
+
placeholder="Search plugins..."
|
|
159
234
|
value={searchQuery}
|
|
160
235
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
161
236
|
className="w-full pl-10 pr-4 py-3 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-xl text-xs focus:outline-none focus:border-[var(--kyro-primary)]/50 transition-all"
|
|
162
237
|
/>
|
|
163
238
|
</div>
|
|
164
239
|
</div>
|
|
165
|
-
|
|
240
|
+
|
|
166
241
|
<div className="flex items-center gap-2 px-1">
|
|
167
242
|
<div className="w-0.5 h-3 bg-[var(--kyro-primary)] rounded-full" />
|
|
168
243
|
<h2 className="text-[10px] font-bold tracking-[0.2em] opacity-40 uppercase">Installed Extensions</h2>
|
|
@@ -199,7 +274,7 @@ export function PluginsManager() {
|
|
|
199
274
|
</div>
|
|
200
275
|
|
|
201
276
|
<p className="text-xs text-[var(--kyro-text-secondary)] opacity-70 leading-relaxed min-h-[32px] line-clamp-2">
|
|
202
|
-
{plugin.description}
|
|
277
|
+
{plugin.description || "No description available."}
|
|
203
278
|
</p>
|
|
204
279
|
|
|
205
280
|
<div className="flex items-center justify-between pt-3 border-t border-[var(--kyro-border)]/50">
|
|
@@ -222,12 +297,19 @@ export function PluginsManager() {
|
|
|
222
297
|
<button
|
|
223
298
|
type="button"
|
|
224
299
|
onClick={() => togglePlugin(plugin.id)}
|
|
300
|
+
disabled={toggleLoading === plugin.id}
|
|
225
301
|
className={`p-2 border rounded-lg transition-all shadow-sm ${plugin.enabled
|
|
226
302
|
? "bg-red-500/5 border-red-500/10 text-red-500/40 hover:text-red-500 hover:border-red-500/30"
|
|
227
303
|
: "bg-green-500/5 border-green-500/10 text-green-500/40 hover:text-green-500 hover:border-green-500/30"
|
|
228
|
-
}`}
|
|
304
|
+
} disabled:opacity-30 disabled:cursor-not-allowed`}
|
|
229
305
|
>
|
|
230
|
-
{plugin.
|
|
306
|
+
{toggleLoading === plugin.id ? (
|
|
307
|
+
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
|
|
308
|
+
) : plugin.enabled ? (
|
|
309
|
+
<X className="w-3.5 h-3.5" />
|
|
310
|
+
) : (
|
|
311
|
+
<Plus className="w-3.5 h-3.5" />
|
|
312
|
+
)}
|
|
231
313
|
</button>
|
|
232
314
|
</div>
|
|
233
315
|
</div>
|
|
@@ -244,11 +326,56 @@ export function PluginsManager() {
|
|
|
244
326
|
</div>
|
|
245
327
|
</div>
|
|
246
328
|
|
|
247
|
-
{/* Modal */}
|
|
329
|
+
{/* Confirmation Modal for Disabling Active Storage Plugin */}
|
|
330
|
+
<Modal
|
|
331
|
+
open={!!confirmDisable}
|
|
332
|
+
onClose={() => setConfirmDisable(null)}
|
|
333
|
+
title="Disable Storage Plugin?"
|
|
334
|
+
size="md"
|
|
335
|
+
>
|
|
336
|
+
<ModalContent>
|
|
337
|
+
<div className="p-6 text-center space-y-4">
|
|
338
|
+
<div className="w-16 h-16 mx-auto bg-amber-500/10 rounded-2xl flex items-center justify-center border border-amber-500/20">
|
|
339
|
+
<AlertTriangle className="w-8 h-8 text-amber-500" />
|
|
340
|
+
</div>
|
|
341
|
+
<div>
|
|
342
|
+
<h4 className="text-lg font-bold mb-2">Storage Plugin In Use</h4>
|
|
343
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] opacity-70 leading-relaxed">
|
|
344
|
+
"{confirmDisable?.name}" is currently the active storage provider.
|
|
345
|
+
Disabling it will switch storage to <strong>Local</strong>.
|
|
346
|
+
</p>
|
|
347
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] opacity-50 mt-2">
|
|
348
|
+
Existing media URLs will remain accessible, but new uploads will
|
|
349
|
+
use Local storage. You can re-enable the plugin at any time.
|
|
350
|
+
</p>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</ModalContent>
|
|
354
|
+
<ModalActions>
|
|
355
|
+
<div className="flex gap-3 w-full">
|
|
356
|
+
<button
|
|
357
|
+
type="button"
|
|
358
|
+
onClick={() => setConfirmDisable(null)}
|
|
359
|
+
className="kyro-btn kyro-btn-primary flex-1 py-3 rounded-xl font-bold text-sm"
|
|
360
|
+
>
|
|
361
|
+
Cancel
|
|
362
|
+
</button>
|
|
363
|
+
<button
|
|
364
|
+
type="button"
|
|
365
|
+
onClick={handleForceDisable}
|
|
366
|
+
className="kyro-btn flex-1 py-3 rounded-xl font-bold text-sm bg-amber-500 hover:bg-amber-600 text-white transition-all"
|
|
367
|
+
>
|
|
368
|
+
Switch to Local & Disable
|
|
369
|
+
</button>
|
|
370
|
+
</div>
|
|
371
|
+
</ModalActions>
|
|
372
|
+
</Modal>
|
|
373
|
+
|
|
374
|
+
{/* Config Modal */}
|
|
248
375
|
<Modal
|
|
249
376
|
open={!!showConfigModal}
|
|
250
377
|
onClose={() => setShowConfigModal(null)}
|
|
251
|
-
title="Plugin
|
|
378
|
+
title="Plugin Configuration"
|
|
252
379
|
size="lg"
|
|
253
380
|
>
|
|
254
381
|
<ModalContent>
|
|
@@ -266,9 +393,9 @@ export function PluginsManager() {
|
|
|
266
393
|
<button
|
|
267
394
|
type="button"
|
|
268
395
|
onClick={() => setShowConfigModal(null)}
|
|
269
|
-
className="w-full py-3 rounded-xl font-bold text-sm
|
|
396
|
+
className="kyro-btn kyro-btn-primary w-full py-3 rounded-xl font-bold text-sm hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/20"
|
|
270
397
|
>
|
|
271
|
-
|
|
398
|
+
Close
|
|
272
399
|
</button>
|
|
273
400
|
</ModalActions>
|
|
274
401
|
</Modal>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import { useUIStore } from "../lib/stores";
|
|
2
|
+
import { useUIStore, toast } from "../lib/stores";
|
|
3
3
|
import { apiPath } from "../lib/paths";
|
|
4
4
|
|
|
5
5
|
interface EnvVariable {
|
|
@@ -82,7 +82,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
|
|
|
82
82
|
const [newFolderName, setNewFolderName] = useState("");
|
|
83
83
|
const [saveToFolderId, setSaveToFolderId] = useState("");
|
|
84
84
|
const [saveRequestName, setSaveRequestName] = useState("");
|
|
85
|
-
const { confirm
|
|
85
|
+
const { confirm } = useUIStore();
|
|
86
86
|
|
|
87
87
|
// Load from localStorage
|
|
88
88
|
useEffect(() => {
|
|
@@ -325,9 +325,9 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
|
|
|
325
325
|
setEnvVars((prev) => [...prev, ...data.envVars]);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
} catch (
|
|
330
|
-
|
|
328
|
+
toast.success("Your playground data has been imported.");
|
|
329
|
+
} catch (err) {
|
|
330
|
+
toast.error("Invalid JSON file structure.");
|
|
331
331
|
}
|
|
332
332
|
};
|
|
333
333
|
reader.readAsText(file);
|
|
@@ -744,7 +744,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
|
|
|
744
744
|
/>
|
|
745
745
|
<div className="p-4 border-t border-[var(--kyro-border)] flex justify-end gap-2 bg-[var(--kyro-surface-accent)]">
|
|
746
746
|
<button type="button" onClick={() => setShowFolderModal(false)} className="kyro-btn kyro-btn-md kyro-btn-ghost">Cancel</button>
|
|
747
|
-
<button type="button" onClick={createFolder} className="kyro-btn kyro-btn-md
|
|
747
|
+
<button type="button" onClick={createFolder} className="kyro-btn kyro-btn-md bg-pink-500 border-pink-500 text-white hover:bg-pink-600 hover:border-pink-600">Create</button>
|
|
748
748
|
</div>
|
|
749
749
|
</div>
|
|
750
750
|
</div>
|
|
@@ -779,7 +779,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
|
|
|
779
779
|
</div>
|
|
780
780
|
<div className="p-4 border-t border-[var(--kyro-border)] flex justify-end gap-2 bg-[var(--kyro-surface-accent)]">
|
|
781
781
|
<button type="button" onClick={() => setShowSaveModal(false)} className="kyro-btn kyro-btn-md kyro-btn-ghost">Cancel</button>
|
|
782
|
-
<button type="button" onClick={saveRequest} className="kyro-btn kyro-btn-md
|
|
782
|
+
<button type="button" onClick={saveRequest} className="kyro-btn kyro-btn-md bg-pink-500 border-pink-500 text-white hover:bg-pink-600 hover:border-pink-600 disabled:opacity-50 disabled:cursor-not-allowed" disabled={!saveRequestName || !saveToFolderId}>Save</button>
|
|
783
783
|
</div>
|
|
784
784
|
</div>
|
|
785
785
|
</div>
|
|
@@ -151,7 +151,7 @@ export function SessionsManager() {
|
|
|
151
151
|
>
|
|
152
152
|
<div className="flex items-start justify-between gap-4 relative z-10">
|
|
153
153
|
<div className="flex items-center gap-3">
|
|
154
|
-
<div className={`p-2.5 rounded-xl transition-colors shadow-sm ${s.currentSession ? "
|
|
154
|
+
<div className={`kyro-btn-primary p-2.5 rounded-xl transition-colors shadow-sm ${s.currentSession ? "" : "bg-[var(--kyro-surface)] text-[var(--kyro-text-secondary)] border border-[var(--kyro-border)]"}`}>
|
|
155
155
|
{s.deviceInfo?.platform?.toLowerCase().includes("android") || s.deviceInfo?.platform?.toLowerCase().includes("ios")
|
|
156
156
|
? <Smartphone className="w-4 h-4" />
|
|
157
157
|
: <Laptop className="w-4 h-4" />}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AutoForm } from "./AutoForm";
|
|
2
|
+
import { toast } from "../lib/stores";
|
|
3
|
+
|
|
4
|
+
interface SettingsPageProps {
|
|
5
|
+
config: any;
|
|
6
|
+
globalSlug?: string;
|
|
7
|
+
data?: Record<string, unknown>;
|
|
8
|
+
layout?: "split" | "single";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SettingsPage(props: SettingsPageProps) {
|
|
12
|
+
return (
|
|
13
|
+
<AutoForm
|
|
14
|
+
{...props}
|
|
15
|
+
onActionSuccess={(msg: string) => {
|
|
16
|
+
toast.success(msg);
|
|
17
|
+
setTimeout(() => window.location.reload(), 800);
|
|
18
|
+
}}
|
|
19
|
+
onActionError={(msg: string) => toast.error(msg)}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -3,25 +3,7 @@ import "../styles/main.css";
|
|
|
3
3
|
import { nonAuthCollections } from "../lib/config";
|
|
4
4
|
import { adminPath } from "../lib/paths";
|
|
5
5
|
import { getSiteSettings } from "../lib/globals";
|
|
6
|
-
import
|
|
7
|
-
Home,
|
|
8
|
-
Database,
|
|
9
|
-
Settings,
|
|
10
|
-
Users,
|
|
11
|
-
Shield,
|
|
12
|
-
FileText,
|
|
13
|
-
Clock,
|
|
14
|
-
Blocks,
|
|
15
|
-
Key,
|
|
16
|
-
Webhook,
|
|
17
|
-
Grid,
|
|
18
|
-
User,
|
|
19
|
-
LogOut,
|
|
20
|
-
Sun,
|
|
21
|
-
Moon,
|
|
22
|
-
Menu,
|
|
23
|
-
Dot,
|
|
24
|
-
} from "./ui/icons";
|
|
6
|
+
import * as Icons from "lucide-react";
|
|
25
7
|
import { UserMenu } from "./UserMenu";
|
|
26
8
|
|
|
27
9
|
interface NavItem {
|
|
@@ -49,7 +31,7 @@ const collectionItems: NavItem[] = nonAuthCollections
|
|
|
49
31
|
.map((col) => ({
|
|
50
32
|
href: `${adminPath}/${col.slug}`,
|
|
51
33
|
label: col.label || col.slug,
|
|
52
|
-
icon: "collection",
|
|
34
|
+
icon: col.admin?.icon || "collection",
|
|
53
35
|
}));
|
|
54
36
|
|
|
55
37
|
const navSections: { label: string; items: NavItem[] }[] = [
|
|
@@ -82,24 +64,13 @@ const navSections: { label: string; items: NavItem[] }[] = [
|
|
|
82
64
|
},
|
|
83
65
|
];
|
|
84
66
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
media: Grid,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
shield: Shield,
|
|
93
|
-
audit: FileText,
|
|
94
|
-
plugins: Blocks,
|
|
95
|
-
keys: Key,
|
|
96
|
-
webhooks: Webhook,
|
|
97
|
-
marketplace: Grid,
|
|
98
|
-
user: User,
|
|
99
|
-
logout: LogOut,
|
|
100
|
-
sessions: Clock,
|
|
101
|
-
sun: Sun,
|
|
102
|
-
moon: Moon,
|
|
67
|
+
const iconAliases: Record<string, string> = {
|
|
68
|
+
collection: "Dot",
|
|
69
|
+
media: "Grid",
|
|
70
|
+
home: "Home",
|
|
71
|
+
users: "Users",
|
|
72
|
+
plugins: "Blocks",
|
|
73
|
+
settings: "Settings",
|
|
103
74
|
};
|
|
104
75
|
|
|
105
76
|
function isActive(item: NavItem): boolean {
|
|
@@ -177,7 +148,8 @@ function isActive(item: NavItem): boolean {
|
|
|
177
148
|
}`}
|
|
178
149
|
>
|
|
179
150
|
{(() => {
|
|
180
|
-
const
|
|
151
|
+
const iconName = iconAliases[item.icon] || item.icon;
|
|
152
|
+
const Icon = (Icons as any)[iconName] || Icons.Dot;
|
|
181
153
|
return <Icon className="w-4 h-4" strokeWidth={2.5} />;
|
|
182
154
|
})()}
|
|
183
155
|
<span>{item.label}</span>
|
|
@@ -245,14 +217,14 @@ function isActive(item: NavItem): boolean {
|
|
|
245
217
|
class="p-2 rounded-lg transition-all active:scale-95"
|
|
246
218
|
title="Light Mode"
|
|
247
219
|
>
|
|
248
|
-
<Sun className="w-4 h-4" strokeWidth={2.5} />
|
|
220
|
+
<Icons.Sun className="w-4 h-4" strokeWidth={2.5} />
|
|
249
221
|
</button>
|
|
250
222
|
<button
|
|
251
223
|
id="theme-dark-btn"
|
|
252
224
|
class="p-2 rounded-lg transition-all active:scale-95"
|
|
253
225
|
title="Dark Mode"
|
|
254
226
|
>
|
|
255
|
-
<Moon className="w-4 h-4" strokeWidth={2.5} />
|
|
227
|
+
<Icons.Moon className="w-4 h-4" strokeWidth={2.5} />
|
|
256
228
|
</button>
|
|
257
229
|
</div>
|
|
258
230
|
|