@kyro-cms/admin 0.1.6 → 0.1.8
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/README.md +149 -51
- package/package.json +54 -5
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +137 -28
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +2155 -770
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +4 -4
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +200 -58
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +890 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +192 -54
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +206 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/ThemeProvider.tsx +8 -2
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +65 -0
- package/src/components/blocks/ArrayBlock.tsx +84 -0
- package/src/components/blocks/BlockEditModal.tsx +363 -0
- package/src/components/blocks/ButtonBlock.tsx +64 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +114 -0
- package/src/components/blocks/ColumnsBlock.tsx +93 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +63 -0
- package/src/components/blocks/HeadingBlock.tsx +59 -0
- package/src/components/blocks/HeroBlock.tsx +99 -0
- package/src/components/blocks/ImageBlock.tsx +82 -0
- package/src/components/blocks/LinkBlock.tsx +65 -0
- package/src/components/blocks/ListBlock.tsx +60 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +72 -0
- package/src/components/blocks/RichTextBlock.tsx +66 -0
- package/src/components/blocks/VStackBlock.tsx +61 -0
- package/src/components/blocks/VideoBlock.tsx +65 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/AccordionField.tsx +213 -0
- package/src/components/fields/ArrayField.tsx +241 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/ButtonField.tsx +53 -0
- package/src/components/fields/CheckboxField.tsx +18 -8
- package/src/components/fields/ChildrenField.tsx +48 -0
- package/src/components/fields/CodeField.tsx +294 -0
- package/src/components/fields/ColumnsField.tsx +137 -0
- package/src/components/fields/DateField.tsx +24 -12
- package/src/components/fields/EditorClient.tsx +537 -0
- package/src/components/fields/HeadingField.tsx +31 -0
- package/src/components/fields/HeroField.tsx +101 -0
- package/src/components/fields/JSONField.tsx +341 -0
- package/src/components/fields/LinkField.tsx +81 -0
- package/src/components/fields/ListField.tsx +74 -0
- package/src/components/fields/MarkdownField.tsx +260 -0
- package/src/components/fields/NumberField.tsx +25 -13
- package/src/components/fields/PortableTextField.tsx +155 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipBlockField.tsx +233 -0
- package/src/components/fields/RelationshipField.tsx +278 -60
- package/src/components/fields/SelectField.tsx +28 -16
- package/src/components/fields/TextField.tsx +31 -15
- package/src/components/fields/UploadField.tsx +613 -0
- package/src/components/fields/VideoField.tsx +73 -0
- package/src/components/fields/extensions/blockComponents.tsx +247 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +24 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +3 -3
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +23 -6
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/api.ts +163 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +23 -7
- package/src/lib/dataStore.ts +188 -73
- package/src/lib/date-utils.ts +69 -0
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/db/version-adapter.ts +248 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/i18n.tsx +353 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/slugify.ts +15 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/lib/validation.ts +250 -0
- package/src/middleware.ts +70 -11
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +200 -139
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +52 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +66 -0
- package/src/pages/api/[collection]/[id].ts +114 -159
- package/src/pages/api/[collection]/index.ts +150 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +42 -24
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +11 -11
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +13 -13
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +563 -128
- package/src/components/layout/Sidebar.tsx +0 -497
|
@@ -34,6 +34,28 @@ export function ActionBar({
|
|
|
34
34
|
publishedAt,
|
|
35
35
|
updatedAt,
|
|
36
36
|
}: ActionBarProps) {
|
|
37
|
+
const styles = {
|
|
38
|
+
btn: {
|
|
39
|
+
padding: "0.5rem 1rem",
|
|
40
|
+
borderRadius: "0.75rem",
|
|
41
|
+
fontWeight: 600,
|
|
42
|
+
fontSize: "0.875rem",
|
|
43
|
+
transition: "all 0.2s",
|
|
44
|
+
border: "none",
|
|
45
|
+
cursor: "pointer",
|
|
46
|
+
},
|
|
47
|
+
idle: { backgroundColor: "#6b7280", color: "white" },
|
|
48
|
+
saving: {
|
|
49
|
+
backgroundColor: "#9ca3af",
|
|
50
|
+
color: "white",
|
|
51
|
+
opacity: 0.7,
|
|
52
|
+
cursor: "not-allowed",
|
|
53
|
+
},
|
|
54
|
+
saved: { backgroundColor: "#22c55e", color: "white" },
|
|
55
|
+
error: { backgroundColor: "#ef4444", color: "white" },
|
|
56
|
+
changes: { backgroundColor: "#eab308", color: "black" },
|
|
57
|
+
};
|
|
58
|
+
|
|
37
59
|
const getSaveStatusText = () => {
|
|
38
60
|
if (saveStatus === "saving") return "Saving...";
|
|
39
61
|
if (saveStatus === "saved") return "Saved";
|
|
@@ -42,18 +64,92 @@ export function ActionBar({
|
|
|
42
64
|
return null;
|
|
43
65
|
};
|
|
44
66
|
|
|
67
|
+
const getSaveButtonClass = () => {
|
|
68
|
+
const base = "kyro-btn kyro-btn-md";
|
|
69
|
+
if (saveStatus === "saving") return `${base} opacity-50 cursor-wait`;
|
|
70
|
+
if (saveStatus === "saved")
|
|
71
|
+
return `${base} bg-green-500 hover:bg-green-600 text-white`;
|
|
72
|
+
if (saveStatus === "error")
|
|
73
|
+
return `${base} bg-red-500 hover:bg-red-600 text-white`;
|
|
74
|
+
if (hasChanges)
|
|
75
|
+
return `${base} bg-yellow-500 hover:bg-yellow-600 text-black`;
|
|
76
|
+
return `${base} bg-gray-500 hover:bg-gray-600 text-white`;
|
|
77
|
+
};
|
|
78
|
+
|
|
45
79
|
const getStatusBadge = () => {
|
|
46
80
|
const statusConfig = {
|
|
47
|
-
draft: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
81
|
+
draft: {
|
|
82
|
+
label: "Draft",
|
|
83
|
+
class: "bg-gray-100 text-gray-600 border-gray-200",
|
|
84
|
+
icon: (
|
|
85
|
+
<svg
|
|
86
|
+
width="12"
|
|
87
|
+
height="12"
|
|
88
|
+
viewBox="0 0 24 24"
|
|
89
|
+
fill="none"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
strokeWidth="2.5"
|
|
92
|
+
>
|
|
93
|
+
<path d="M12 2v20M2 12h20" />
|
|
94
|
+
</svg>
|
|
95
|
+
),
|
|
96
|
+
},
|
|
97
|
+
published: {
|
|
98
|
+
label: "Published",
|
|
99
|
+
class: "bg-green-100 text-green-700 border-green-200",
|
|
100
|
+
icon: (
|
|
101
|
+
<svg
|
|
102
|
+
width="12"
|
|
103
|
+
height="12"
|
|
104
|
+
viewBox="0 0 24 24"
|
|
105
|
+
fill="none"
|
|
106
|
+
stroke="currentColor"
|
|
107
|
+
strokeWidth="2.5"
|
|
108
|
+
>
|
|
109
|
+
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" />
|
|
110
|
+
</svg>
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
scheduled: {
|
|
114
|
+
label: "Scheduled",
|
|
115
|
+
class: "bg-blue-100 text-blue-700 border-blue-200",
|
|
116
|
+
icon: (
|
|
117
|
+
<svg
|
|
118
|
+
width="12"
|
|
119
|
+
height="12"
|
|
120
|
+
viewBox="0 0 24 24"
|
|
121
|
+
fill="none"
|
|
122
|
+
stroke="currentColor"
|
|
123
|
+
strokeWidth="2.5"
|
|
124
|
+
>
|
|
125
|
+
<circle cx="12" cy="12" r="10" />
|
|
126
|
+
<path d="M12 6v6l4 2" />
|
|
127
|
+
</svg>
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
archived: {
|
|
131
|
+
label: "Archived",
|
|
132
|
+
class: "bg-yellow-100 text-yellow-700 border-yellow-200",
|
|
133
|
+
icon: (
|
|
134
|
+
<svg
|
|
135
|
+
width="12"
|
|
136
|
+
height="12"
|
|
137
|
+
viewBox="0 0 24 24"
|
|
138
|
+
fill="none"
|
|
139
|
+
stroke="currentColor"
|
|
140
|
+
strokeWidth="2.5"
|
|
141
|
+
>
|
|
142
|
+
<path d="M21 8v13H3V8M1 3h22v5H1zM10 12h4" />
|
|
143
|
+
</svg>
|
|
144
|
+
),
|
|
145
|
+
},
|
|
51
146
|
};
|
|
52
147
|
const config = statusConfig[status];
|
|
53
148
|
return (
|
|
54
149
|
<span
|
|
55
|
-
className={`inline-flex items-center px-2 py-
|
|
150
|
+
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold border ${config.class}`}
|
|
56
151
|
>
|
|
152
|
+
{config.icon}
|
|
57
153
|
{config.label}
|
|
58
154
|
</span>
|
|
59
155
|
);
|
|
@@ -80,46 +176,87 @@ export function ActionBar({
|
|
|
80
176
|
</span>
|
|
81
177
|
)}
|
|
82
178
|
</div>
|
|
83
|
-
<div className="text-xs
|
|
84
|
-
{updatedAt &&
|
|
179
|
+
<div className="text-xs space-y-0.5">
|
|
180
|
+
{updatedAt && (
|
|
181
|
+
<div className="text-gray-400">
|
|
182
|
+
Updated: {formatDate(updatedAt)}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
85
185
|
{publishedAt && status === "published" && (
|
|
86
|
-
<div
|
|
186
|
+
<div className="text-[var(--kyro-primary)] font-medium">
|
|
187
|
+
Published: {formatDate(publishedAt)}
|
|
188
|
+
</div>
|
|
87
189
|
)}
|
|
88
190
|
</div>
|
|
89
191
|
</div>
|
|
90
192
|
|
|
91
193
|
<div className="flex items-center gap-2">
|
|
92
194
|
{status === "draft" && onPublish && (
|
|
93
|
-
<button
|
|
195
|
+
<button type="button"
|
|
94
196
|
onClick={onPublish}
|
|
95
197
|
disabled={saveStatus === "saving"}
|
|
96
|
-
className="kyro-
|
|
198
|
+
className="bg-[var(--kyro-primary)] hover:opacity-90 text-white kyro-btn-md flex items-center gap-2"
|
|
97
199
|
>
|
|
200
|
+
<svg
|
|
201
|
+
width="16"
|
|
202
|
+
height="16"
|
|
203
|
+
viewBox="0 0 24 24"
|
|
204
|
+
fill="none"
|
|
205
|
+
stroke="currentColor"
|
|
206
|
+
strokeWidth="2.5"
|
|
207
|
+
>
|
|
208
|
+
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" />
|
|
209
|
+
</svg>
|
|
98
210
|
Publish
|
|
99
211
|
</button>
|
|
100
212
|
)}
|
|
101
213
|
{status === "published" && onUnpublish && (
|
|
102
|
-
<button
|
|
214
|
+
<button type="button"
|
|
103
215
|
onClick={onUnpublish}
|
|
104
216
|
disabled={saveStatus === "saving"}
|
|
105
|
-
className="kyro-btn kyro-btn-
|
|
217
|
+
className="kyro-btn kyro-btn-warning kyro-btn-md flex items-center gap-2"
|
|
106
218
|
>
|
|
219
|
+
<svg
|
|
220
|
+
width="16"
|
|
221
|
+
height="16"
|
|
222
|
+
viewBox="0 0 24 24"
|
|
223
|
+
fill="none"
|
|
224
|
+
stroke="currentColor"
|
|
225
|
+
strokeWidth="2.5"
|
|
226
|
+
>
|
|
227
|
+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8M3 3v5h5" />
|
|
228
|
+
</svg>
|
|
107
229
|
Unpublish
|
|
108
230
|
</button>
|
|
109
231
|
)}
|
|
110
|
-
<button
|
|
232
|
+
<button type="button"
|
|
111
233
|
onClick={onSave}
|
|
112
234
|
disabled={
|
|
113
235
|
saveStatus === "saving" || (!hasChanges && saveStatus !== "error")
|
|
114
236
|
}
|
|
115
|
-
|
|
237
|
+
style={{
|
|
238
|
+
...styles.btn,
|
|
239
|
+
...(saveStatus === "saving"
|
|
240
|
+
? styles.saving
|
|
241
|
+
: saveStatus === "saved"
|
|
242
|
+
? styles.saved
|
|
243
|
+
: saveStatus === "error"
|
|
244
|
+
? styles.error
|
|
245
|
+
: hasChanges
|
|
246
|
+
? styles.changes
|
|
247
|
+
: styles.idle),
|
|
248
|
+
}}
|
|
116
249
|
>
|
|
117
|
-
{saveStatus === "saving"
|
|
250
|
+
{saveStatus === "saving"
|
|
251
|
+
? "Saving..."
|
|
252
|
+
: saveStatus === "saved"
|
|
253
|
+
? "Saved"
|
|
254
|
+
: "Save"}
|
|
118
255
|
</button>
|
|
119
256
|
|
|
120
257
|
<Dropdown
|
|
121
258
|
trigger={
|
|
122
|
-
<button className="kyro-btn kyro-btn-ghost kyro-btn-md p-2">
|
|
259
|
+
<button type="button" className="kyro-btn kyro-btn-ghost kyro-btn-md p-2">
|
|
123
260
|
<svg
|
|
124
261
|
width="16"
|
|
125
262
|
height="16"
|
package/src/components/Admin.tsx
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
-
import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core";
|
|
3
|
-
import { Sidebar } from "./layout/Sidebar";
|
|
2
|
+
import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core/client";
|
|
4
3
|
import { ListView } from "./ListView";
|
|
5
4
|
import { DetailView } from "./DetailView";
|
|
6
5
|
import { CreateView } from "./CreateView";
|
|
7
6
|
import { LoginPage } from "./LoginPage";
|
|
7
|
+
import { Dashboard } from "./Dashboard";
|
|
8
|
+
import { UserManagement } from "./UserManagement";
|
|
9
|
+
import { BrandingHub } from "./BrandingHub";
|
|
10
|
+
import { DeveloperCenter } from "./DeveloperCenter";
|
|
11
|
+
import { WebhookManager } from "./WebhookManager";
|
|
12
|
+
import { MediaGallery } from "./MediaGallery";
|
|
13
|
+
import { CommandPalette } from "./ui/CommandPalette";
|
|
8
14
|
import { Toast, ToastProvider } from "./ui/Toast";
|
|
9
15
|
import { ThemeProvider, type ThemeMode } from "./ThemeProvider";
|
|
10
16
|
import "../styles/main.css";
|
|
11
17
|
|
|
12
|
-
type View =
|
|
18
|
+
type View =
|
|
19
|
+
| "list"
|
|
20
|
+
| "detail"
|
|
21
|
+
| "create"
|
|
22
|
+
| "settings"
|
|
23
|
+
| "users"
|
|
24
|
+
| "roles"
|
|
25
|
+
| "audit"
|
|
26
|
+
| "media"
|
|
27
|
+
| "branding"
|
|
28
|
+
| "developer"
|
|
29
|
+
| "webhooks";
|
|
13
30
|
|
|
14
31
|
export interface KyroAdminConfig {
|
|
15
32
|
collections?: CollectionConfig[] | Record<string, CollectionConfig>;
|
|
@@ -82,10 +99,22 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
|
|
|
82
99
|
const [currentView, setCurrentView] = useState<View>("list");
|
|
83
100
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
84
101
|
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
|
102
|
+
const [showCommandPalette, setShowCommandPalette] = useState(false);
|
|
85
103
|
|
|
86
104
|
const collections = normalizeCollections(config.collections);
|
|
87
105
|
const globals = normalizeGlobals(config.globals);
|
|
88
106
|
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
109
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
setShowCommandPalette((prev) => !prev);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
115
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
89
118
|
useEffect(() => {
|
|
90
119
|
const token = localStorage.getItem("kyro_token");
|
|
91
120
|
const userStr = localStorage.getItem("kyro_user");
|
|
@@ -102,10 +131,7 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
|
|
|
102
131
|
}, []);
|
|
103
132
|
|
|
104
133
|
useEffect(() => {
|
|
105
|
-
|
|
106
|
-
if (collectionKeys.length > 0 && !activeCollection) {
|
|
107
|
-
setActiveCollection(collectionKeys[0]);
|
|
108
|
-
}
|
|
134
|
+
// No longer auto-selecting the first collection to allow Dashboard as home
|
|
109
135
|
}, [authenticated]);
|
|
110
136
|
|
|
111
137
|
const handleAuth = (token: string, user: AuthUser) => {
|
|
@@ -133,7 +159,37 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
|
|
|
133
159
|
}, 5000);
|
|
134
160
|
};
|
|
135
161
|
|
|
162
|
+
const removeToast = (id: string) => {
|
|
163
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
164
|
+
};
|
|
165
|
+
|
|
136
166
|
const handleCollectionChange = (collectionName: string) => {
|
|
167
|
+
if (!collectionName) {
|
|
168
|
+
setActiveCollection(null);
|
|
169
|
+
setActiveGlobal(null);
|
|
170
|
+
setCurrentView("list");
|
|
171
|
+
setSelectedId(null);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if it's a special governance or developer view
|
|
176
|
+
if (
|
|
177
|
+
[
|
|
178
|
+
"users",
|
|
179
|
+
"roles",
|
|
180
|
+
"audit",
|
|
181
|
+
"media",
|
|
182
|
+
"branding",
|
|
183
|
+
"developer",
|
|
184
|
+
"webhooks",
|
|
185
|
+
].includes(collectionName)
|
|
186
|
+
) {
|
|
187
|
+
setActiveCollection(null);
|
|
188
|
+
setActiveGlobal(null);
|
|
189
|
+
setCurrentView(collectionName as any);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
137
193
|
setActiveCollection(collectionName);
|
|
138
194
|
setActiveGlobal(null);
|
|
139
195
|
setCurrentView("list");
|
|
@@ -178,13 +234,54 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
|
|
|
178
234
|
config={{} as any}
|
|
179
235
|
global={global}
|
|
180
236
|
onBack={() => setCurrentView("list")}
|
|
181
|
-
onSave={() =>
|
|
237
|
+
onSave={() => addToast("success", "Configuration saved successfully")}
|
|
182
238
|
onError={handleError}
|
|
183
239
|
mode="global"
|
|
184
240
|
/>
|
|
185
241
|
);
|
|
186
242
|
}
|
|
187
243
|
|
|
244
|
+
if (!activeCollection && !activeGlobal) {
|
|
245
|
+
if (currentView === "users") return <UserManagement />;
|
|
246
|
+
if (currentView === "roles")
|
|
247
|
+
return (
|
|
248
|
+
<div className="p-6">
|
|
249
|
+
<h2 className="text-xl font-bold">Roles Management</h2>
|
|
250
|
+
<p className="text-gray-500 mt-2">
|
|
251
|
+
Configure user roles and permissions.
|
|
252
|
+
</p>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
if (currentView === "audit")
|
|
256
|
+
return (
|
|
257
|
+
<div className="p-6">
|
|
258
|
+
<h2 className="text-xl font-bold">Audit Logs</h2>
|
|
259
|
+
<p className="text-gray-500 mt-2">
|
|
260
|
+
View system activity and changes.
|
|
261
|
+
</p>
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
if (currentView === "media") return <MediaGallery />;
|
|
265
|
+
if (currentView === "branding") return <BrandingHub />;
|
|
266
|
+
if (currentView === "developer")
|
|
267
|
+
return <DeveloperCenter collections={collections} />;
|
|
268
|
+
if (currentView === "webhooks") return <WebhookManager />;
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<Dashboard
|
|
272
|
+
collections={collections}
|
|
273
|
+
onNavigate={(view, collection) => {
|
|
274
|
+
if (collection) {
|
|
275
|
+
setActiveCollection(collection);
|
|
276
|
+
setActiveGlobal(null);
|
|
277
|
+
}
|
|
278
|
+
setCurrentView(view as any);
|
|
279
|
+
}}
|
|
280
|
+
user={currentUser}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
188
285
|
if (!activeCollection) return null;
|
|
189
286
|
|
|
190
287
|
const collection = collections[activeCollection];
|
|
@@ -231,33 +328,45 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
|
|
|
231
328
|
|
|
232
329
|
return (
|
|
233
330
|
<ThemeProvider defaultMode={theme}>
|
|
234
|
-
<ToastProvider
|
|
331
|
+
<ToastProvider
|
|
332
|
+
toasts={toasts}
|
|
333
|
+
addToast={addToast}
|
|
334
|
+
removeToast={removeToast}
|
|
335
|
+
>
|
|
235
336
|
<div className="kyro-admin">
|
|
236
|
-
<Sidebar
|
|
237
|
-
collections={collections}
|
|
238
|
-
globals={globals}
|
|
239
|
-
activeCollection={activeCollection}
|
|
240
|
-
activeGlobal={activeGlobal}
|
|
241
|
-
onCollectionClick={handleCollectionChange}
|
|
242
|
-
onGlobalClick={handleGlobalChange}
|
|
243
|
-
user={currentUser}
|
|
244
|
-
onLogout={handleLogout}
|
|
245
|
-
/>
|
|
246
337
|
<div className="kyro-main">
|
|
247
338
|
<div className="kyro-content">{renderContent()}</div>
|
|
248
339
|
</div>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
340
|
+
<CommandPalette
|
|
341
|
+
isOpen={showCommandPalette}
|
|
342
|
+
onClose={() => setShowCommandPalette(false)}
|
|
343
|
+
collections={collections}
|
|
344
|
+
globals={globals}
|
|
345
|
+
onNavigate={(view, collection, id) => {
|
|
346
|
+
if (view === "list" && collection) {
|
|
347
|
+
handleCollectionChange(collection);
|
|
348
|
+
} else if (view === "settings" && collection) {
|
|
349
|
+
handleGlobalChange(collection);
|
|
350
|
+
} else if (view === "media") {
|
|
351
|
+
setActiveCollection(null);
|
|
352
|
+
setActiveGlobal(null);
|
|
353
|
+
setCurrentView("list"); // This might need a separate 'media' view state if implemented
|
|
354
|
+
} else if (view === "create" && collection) {
|
|
355
|
+
setActiveCollection(collection);
|
|
356
|
+
setCurrentView("create");
|
|
256
357
|
}
|
|
257
|
-
|
|
258
|
-
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
259
360
|
</div>
|
|
260
361
|
</ToastProvider>
|
|
362
|
+
{toasts.map((toast) => (
|
|
363
|
+
<Toast
|
|
364
|
+
key={toast.id}
|
|
365
|
+
type={toast.type}
|
|
366
|
+
message={toast.message}
|
|
367
|
+
onClose={() => removeToast(toast.id)}
|
|
368
|
+
/>
|
|
369
|
+
))}
|
|
261
370
|
</ThemeProvider>
|
|
262
371
|
);
|
|
263
372
|
}
|