@kyro-cms/admin 0.3.2 → 0.3.5
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/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
-
import { apiGet } from "../../lib/api";
|
|
2
|
+
import { apiGet, resolveApi } from "../../lib/api";
|
|
3
3
|
import {
|
|
4
4
|
Search,
|
|
5
5
|
FileText,
|
|
@@ -16,24 +16,25 @@ import {
|
|
|
16
16
|
Shield,
|
|
17
17
|
Code,
|
|
18
18
|
Database,
|
|
19
|
-
Network,
|
|
19
|
+
Network as NetworkIcon,
|
|
20
20
|
Hexagon,
|
|
21
|
-
} from "
|
|
21
|
+
} from "./icons";
|
|
22
|
+
import { useAuthStore } from "../../lib/stores";
|
|
22
23
|
|
|
23
24
|
interface SearchResult {
|
|
24
25
|
collection: string;
|
|
25
26
|
label: string;
|
|
26
27
|
id: string;
|
|
27
28
|
title: string;
|
|
28
|
-
doc?:
|
|
29
|
+
doc?: Record<string, unknown>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
interface CommandPaletteProps {
|
|
32
33
|
isOpen: boolean;
|
|
33
34
|
onClose: () => void;
|
|
34
|
-
collections:
|
|
35
|
-
globals:
|
|
36
|
-
onNavigate: (view:
|
|
35
|
+
collections: Record<string, unknown>;
|
|
36
|
+
globals: Record<string, unknown>;
|
|
37
|
+
onNavigate: (view: string, collection?: string, id?: string) => void;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export function CommandPalette({
|
|
@@ -43,6 +44,7 @@ export function CommandPalette({
|
|
|
43
44
|
globals,
|
|
44
45
|
onNavigate,
|
|
45
46
|
}: CommandPaletteProps) {
|
|
47
|
+
const { user, permissions } = useAuthStore();
|
|
46
48
|
const [query, setQuery] = useState("");
|
|
47
49
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
48
50
|
const [loading, setLoading] = useState(false);
|
|
@@ -69,7 +71,7 @@ export function CommandPalette({
|
|
|
69
71
|
setLoading(true);
|
|
70
72
|
try {
|
|
71
73
|
const response = await fetch(
|
|
72
|
-
`/api/search?q=${encodeURIComponent(searchQuery)}&limit=15
|
|
74
|
+
resolveApi(`/api/search?q=${encodeURIComponent(searchQuery)}&limit=15`),
|
|
73
75
|
);
|
|
74
76
|
const data = await response.json();
|
|
75
77
|
if (data.results) {
|
|
@@ -103,30 +105,32 @@ export function CommandPalette({
|
|
|
103
105
|
|
|
104
106
|
if (!isOpen) return null;
|
|
105
107
|
|
|
106
|
-
const collectionItems = Object.entries(collections)
|
|
107
|
-
([slug
|
|
108
|
+
const collectionItems = Object.entries(collections)
|
|
109
|
+
.filter(([slug]) => permissions?.collections?.[slug]?.read !== false)
|
|
110
|
+
.map(([slug, config]: [string, Record<string, unknown>]) => ({
|
|
108
111
|
id: `col-${slug}`,
|
|
109
112
|
label: config.label || slug,
|
|
110
113
|
type: "collection",
|
|
111
114
|
slug,
|
|
112
115
|
icon: FileText,
|
|
113
|
-
})
|
|
114
|
-
);
|
|
116
|
+
}));
|
|
115
117
|
|
|
116
|
-
const globalItems = Object.entries(globals)
|
|
117
|
-
([slug
|
|
118
|
+
const globalItems = Object.entries(globals)
|
|
119
|
+
.filter(([slug]) => permissions?.globals?.[slug]?.read !== false)
|
|
120
|
+
.map(([slug, config]: [string, Record<string, unknown>]) => ({
|
|
118
121
|
id: `global-${slug}`,
|
|
119
122
|
label: config.label || slug,
|
|
120
123
|
type: "global",
|
|
121
124
|
slug,
|
|
122
125
|
icon: Settings,
|
|
123
|
-
})
|
|
124
|
-
);
|
|
126
|
+
}));
|
|
125
127
|
|
|
126
128
|
const isDark =
|
|
127
129
|
typeof document !== "undefined" &&
|
|
128
130
|
document.documentElement.classList.contains("dark");
|
|
129
131
|
|
|
132
|
+
const isAdmin = user?.role === "admin";
|
|
133
|
+
|
|
130
134
|
const actionItems = [
|
|
131
135
|
{
|
|
132
136
|
id: "action-media",
|
|
@@ -134,6 +138,7 @@ export function CommandPalette({
|
|
|
134
138
|
type: "action",
|
|
135
139
|
view: "media",
|
|
136
140
|
icon: ImageIcon,
|
|
141
|
+
visible: permissions?.collections?.media?.read !== false,
|
|
137
142
|
},
|
|
138
143
|
{
|
|
139
144
|
id: "action-users",
|
|
@@ -141,6 +146,7 @@ export function CommandPalette({
|
|
|
141
146
|
type: "action",
|
|
142
147
|
view: "users",
|
|
143
148
|
icon: Clock,
|
|
149
|
+
visible: isAdmin,
|
|
144
150
|
},
|
|
145
151
|
{
|
|
146
152
|
id: "action-audit",
|
|
@@ -148,6 +154,7 @@ export function CommandPalette({
|
|
|
148
154
|
type: "action",
|
|
149
155
|
view: "audit",
|
|
150
156
|
icon: File,
|
|
157
|
+
visible: isAdmin,
|
|
151
158
|
},
|
|
152
159
|
{
|
|
153
160
|
id: "action-roles",
|
|
@@ -155,6 +162,7 @@ export function CommandPalette({
|
|
|
155
162
|
type: "action",
|
|
156
163
|
view: "roles",
|
|
157
164
|
icon: Shield,
|
|
165
|
+
visible: isAdmin,
|
|
158
166
|
},
|
|
159
167
|
{
|
|
160
168
|
id: "action-api",
|
|
@@ -162,6 +170,7 @@ export function CommandPalette({
|
|
|
162
170
|
type: "action",
|
|
163
171
|
view: "api-explorer",
|
|
164
172
|
icon: Database,
|
|
173
|
+
visible: isAdmin,
|
|
165
174
|
},
|
|
166
175
|
{
|
|
167
176
|
id: "action-graphql",
|
|
@@ -169,13 +178,15 @@ export function CommandPalette({
|
|
|
169
178
|
type: "action",
|
|
170
179
|
view: "graphql",
|
|
171
180
|
icon: Hexagon,
|
|
181
|
+
visible: isAdmin,
|
|
172
182
|
},
|
|
173
183
|
{
|
|
174
184
|
id: "action-rest",
|
|
175
185
|
label: "REST Playground",
|
|
176
186
|
type: "action",
|
|
177
187
|
view: "rest",
|
|
178
|
-
icon:
|
|
188
|
+
icon: NetworkIcon,
|
|
189
|
+
visible: isAdmin,
|
|
179
190
|
},
|
|
180
191
|
{
|
|
181
192
|
id: "action-theme",
|
|
@@ -183,6 +194,7 @@ export function CommandPalette({
|
|
|
183
194
|
type: "action",
|
|
184
195
|
view: "theme",
|
|
185
196
|
icon: isDark ? Sun : Moon,
|
|
197
|
+
visible: true,
|
|
186
198
|
},
|
|
187
199
|
{
|
|
188
200
|
id: "action-logout",
|
|
@@ -190,10 +202,11 @@ export function CommandPalette({
|
|
|
190
202
|
type: "action",
|
|
191
203
|
view: "logout",
|
|
192
204
|
icon: LogOut,
|
|
205
|
+
visible: true,
|
|
193
206
|
},
|
|
194
|
-
];
|
|
207
|
+
].filter((a) => a.visible);
|
|
195
208
|
|
|
196
|
-
const docResultItems:
|
|
209
|
+
const docResultItems: { id: string; label: string; type: string; collection: string; label2?: string; docId: string; icon: typeof File; doc?: Record<string, unknown> }[] = searchResults.map((result, idx) => ({
|
|
197
210
|
id: `doc-${result.collection}-${result.id}`,
|
|
198
211
|
label: result.title,
|
|
199
212
|
type: "document",
|
|
@@ -213,8 +226,8 @@ export function CommandPalette({
|
|
|
213
226
|
query === ""
|
|
214
227
|
? allItems
|
|
215
228
|
: allItems.filter((item) =>
|
|
216
|
-
|
|
217
|
-
|
|
229
|
+
item.label.toLowerCase().includes(query.toLowerCase()),
|
|
230
|
+
);
|
|
218
231
|
|
|
219
232
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
220
233
|
if (e.key === "ArrowDown") {
|
|
@@ -233,18 +246,28 @@ export function CommandPalette({
|
|
|
233
246
|
}
|
|
234
247
|
};
|
|
235
248
|
|
|
236
|
-
const handleSelect = (item:
|
|
249
|
+
const handleSelect = (item: { type: string; slug?: string; view?: string; collection?: string; docId?: string }) => {
|
|
237
250
|
if (item.type === "collection") {
|
|
238
|
-
|
|
251
|
+
if (item.slug === "users") {
|
|
252
|
+
onNavigate(item.slug, item.slug);
|
|
253
|
+
} else {
|
|
254
|
+
onNavigate("list", item.slug);
|
|
255
|
+
}
|
|
239
256
|
} else if (item.type === "global") {
|
|
240
257
|
onNavigate("settings", item.slug);
|
|
241
258
|
} else if (item.type === "document") {
|
|
242
|
-
|
|
259
|
+
if (item.collection === "users") {
|
|
260
|
+
onNavigate("users", "users", item.docId);
|
|
261
|
+
} else {
|
|
262
|
+
onNavigate("edit", item.collection, item.docId);
|
|
263
|
+
}
|
|
243
264
|
} else if (item.type === "action") {
|
|
244
265
|
if (item.view === "users") {
|
|
245
|
-
onNavigate(
|
|
266
|
+
onNavigate("users", "users");
|
|
246
267
|
} else if (item.view === "media") {
|
|
247
|
-
onNavigate("media",
|
|
268
|
+
onNavigate("media", "media");
|
|
269
|
+
} else {
|
|
270
|
+
onNavigate(item.view, item.view);
|
|
248
271
|
}
|
|
249
272
|
}
|
|
250
273
|
onClose();
|
|
@@ -282,7 +305,7 @@ export function CommandPalette({
|
|
|
282
305
|
onKeyDown={handleKeyDown}
|
|
283
306
|
/>
|
|
284
307
|
<div className="flex items-center gap-2 px-2 py-1 bg-[var(--kyro-bg-secondary)] rounded-lg border border-[var(--kyro-border)]">
|
|
285
|
-
<span className="text-[10px] font-
|
|
308
|
+
<span className="text-[10px] font-bold opacity-40 tracking-widest">
|
|
286
309
|
ESC
|
|
287
310
|
</span>
|
|
288
311
|
</div>
|
|
@@ -291,7 +314,7 @@ export function CommandPalette({
|
|
|
291
314
|
<div className="max-h-[400px] overflow-y-auto py-4">
|
|
292
315
|
{filteredItems.length > 0 ? (
|
|
293
316
|
<div className="space-y-1 px-4">
|
|
294
|
-
<p className="px-4 text-[10px] font-
|
|
317
|
+
<p className="px-4 text-[10px] font-bold tracking-[0.2em] opacity-40 mb-4">
|
|
295
318
|
{getSectionLabel()}
|
|
296
319
|
</p>
|
|
297
320
|
{filteredItems.map((item, index) => (
|
|
@@ -299,11 +322,10 @@ export function CommandPalette({
|
|
|
299
322
|
key={item.id}
|
|
300
323
|
onClick={() => handleSelect(item)}
|
|
301
324
|
onMouseEnter={() => setSelectedIndex(index)}
|
|
302
|
-
className={`flex items-center justify-between px-4 py-4 rounded-2xl cursor-pointer
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}`}
|
|
325
|
+
className={`flex items-center justify-between px-4 py-4 rounded-2xl cursor-pointer ${index === selectedIndex
|
|
326
|
+
? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] shadow-xl shadow-[var(--kyro-primary)]"
|
|
327
|
+
: "hover:bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)]"
|
|
328
|
+
}`}
|
|
307
329
|
>
|
|
308
330
|
<div className="flex items-center gap-4">
|
|
309
331
|
<div
|
|
@@ -315,7 +337,7 @@ export function CommandPalette({
|
|
|
315
337
|
<span className="font-bold text-sm">{item.label}</span>
|
|
316
338
|
{item.type === "document" && item.label2 && (
|
|
317
339
|
<span
|
|
318
|
-
className={`text-[10px] font-
|
|
340
|
+
className={`text-[10px] font-bold tracking-widest ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)]/60" : "opacity-40"}`}
|
|
319
341
|
>
|
|
320
342
|
{item.label2}
|
|
321
343
|
</span>
|
|
@@ -324,7 +346,7 @@ export function CommandPalette({
|
|
|
324
346
|
</div>
|
|
325
347
|
<div className="flex items-center gap-2">
|
|
326
348
|
<span
|
|
327
|
-
className={`text-[10px] font-
|
|
349
|
+
className={`text-[10px] font-bold tracking-widest opacity-40 ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)] p-1" : ""}`}
|
|
328
350
|
>
|
|
329
351
|
{item.type}
|
|
330
352
|
</span>
|
|
@@ -346,7 +368,7 @@ export function CommandPalette({
|
|
|
346
368
|
)}
|
|
347
369
|
</div>
|
|
348
370
|
|
|
349
|
-
<div className="px-8 py-4 bg-[var(--kyro-bg-secondary)] border-t border-[var(--kyro-border)] flex items-center justify-between text-[10px] font-
|
|
371
|
+
<div className="px-8 py-4 bg-[var(--kyro-bg-secondary)] border-t border-[var(--kyro-border)] flex items-center justify-between text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] opacity-60">
|
|
350
372
|
<div className="flex gap-6">
|
|
351
373
|
<span className="flex items-center gap-2 underline underline-offset-4 decoration-2 decoration-[var(--kyro-primary)]">
|
|
352
374
|
↑↓ Navigate
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { CommandPalette } from "./CommandPalette";
|
|
3
3
|
import { ConfirmModal } from "./Modal";
|
|
4
|
+
import { adminPath } from "../../lib/paths";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
|
-
collections:
|
|
7
|
-
globals:
|
|
7
|
+
collections: Record<string, unknown>;
|
|
8
|
+
globals: Record<string, unknown>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export function CommandPaletteWrapper({ collections, globals }: Props) {
|
|
@@ -19,47 +20,47 @@ export function CommandPaletteWrapper({ collections, globals }: Props) {
|
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
(window as
|
|
23
|
+
(window as { openCommandPalette?: () => void }).openCommandPalette = () => setIsOpen(true);
|
|
23
24
|
|
|
24
25
|
window.addEventListener("keydown", handleKeyDown);
|
|
25
26
|
|
|
26
27
|
return () => {
|
|
27
28
|
window.removeEventListener("keydown", handleKeyDown);
|
|
28
|
-
delete (window as
|
|
29
|
+
delete (window as { openCommandPalette?: () => void }).openCommandPalette;
|
|
29
30
|
};
|
|
30
31
|
}, []);
|
|
31
32
|
|
|
32
33
|
const handleClose = () => setIsOpen(false);
|
|
33
34
|
|
|
34
35
|
const handleLogoutConfirm = () => {
|
|
35
|
-
localStorage
|
|
36
|
-
|
|
36
|
+
// Clear in-memory auth (not localStorage)
|
|
37
|
+
(window as { __kyroAuth?: { user: unknown; verified: boolean } }).__kyroAuth = { user: null, verified: false };
|
|
37
38
|
window.location.href = "/login";
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
const handleNavigate = (view: string, collection?: string, id?: string) => {
|
|
41
42
|
if (view === "list" && collection) {
|
|
42
|
-
window.location.href =
|
|
43
|
+
window.location.href = `${adminPath}/${collection}`;
|
|
43
44
|
} else if (view === "edit" && collection && id) {
|
|
44
|
-
window.location.href =
|
|
45
|
+
window.location.href = `${adminPath}/${collection}/${id}`;
|
|
45
46
|
} else if (view === "create" && collection) {
|
|
46
|
-
window.location.href =
|
|
47
|
+
window.location.href = `${adminPath}/${collection}/new`;
|
|
47
48
|
} else if (view === "settings" && collection) {
|
|
48
|
-
window.location.href =
|
|
49
|
+
window.location.href = `${adminPath}/settings/${collection}`;
|
|
49
50
|
} else if (view === "media") {
|
|
50
|
-
window.location.href =
|
|
51
|
+
window.location.href = `${adminPath}/media`;
|
|
51
52
|
} else if (view === "users") {
|
|
52
|
-
window.location.href =
|
|
53
|
+
window.location.href = `${adminPath}/users`;
|
|
53
54
|
} else if (view === "audit") {
|
|
54
|
-
window.location.href =
|
|
55
|
+
window.location.href = `${adminPath}/audit`;
|
|
55
56
|
} else if (view === "roles") {
|
|
56
|
-
window.location.href =
|
|
57
|
+
window.location.href = `${adminPath}/roles`;
|
|
57
58
|
} else if (view === "api-explorer") {
|
|
58
|
-
window.location.href =
|
|
59
|
+
window.location.href = `${adminPath}/api-explorer`;
|
|
59
60
|
} else if (view === "graphql") {
|
|
60
|
-
window.location.href =
|
|
61
|
+
window.location.href = `${adminPath}/graphql`;
|
|
61
62
|
} else if (view === "rest") {
|
|
62
|
-
window.location.href =
|
|
63
|
+
window.location.href = `${adminPath}/rest-playground`;
|
|
63
64
|
} else if (view === "theme") {
|
|
64
65
|
const isDark = document.documentElement.classList.contains("dark");
|
|
65
66
|
if (isDark) {
|
|
@@ -34,9 +34,8 @@ export function Dropdown({
|
|
|
34
34
|
</div>
|
|
35
35
|
{open && (
|
|
36
36
|
<div
|
|
37
|
-
className={`absolute z-
|
|
38
|
-
|
|
39
|
-
}`}
|
|
37
|
+
className={`absolute z-[100] mt-2 min-w-[200px] py-2 bg-[var(--kyro-surface)] rounded-2xl shadow-2xl border border-[var(--kyro-border)] animate-in fade-in zoom-in-95 duration-100 ${align === "right" ? "right-0 bottom-full mb-2" : "left-0 bottom-full mb-2"
|
|
38
|
+
}`}
|
|
40
39
|
onClick={() => setOpen(false)}
|
|
41
40
|
>
|
|
42
41
|
{children}
|
|
@@ -52,6 +51,7 @@ interface DropdownItemProps {
|
|
|
52
51
|
icon?: ReactNode;
|
|
53
52
|
danger?: boolean;
|
|
54
53
|
disabled?: boolean;
|
|
54
|
+
className?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function DropdownItem({
|
|
@@ -60,23 +60,25 @@ export function DropdownItem({
|
|
|
60
60
|
icon,
|
|
61
61
|
danger,
|
|
62
62
|
disabled,
|
|
63
|
+
className = "",
|
|
63
64
|
}: DropdownItemProps) {
|
|
64
65
|
return (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
danger
|
|
70
|
-
? "text-red-
|
|
71
|
-
: "text-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
<div className="px-1.5">
|
|
67
|
+
<button type="button"
|
|
68
|
+
onClick={onClick}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
className={`w-full flex items-center gap-3 px-3 py-2.5 text-[11px] font-medium tracking-wide text-left transition-all rounded-xl ${danger
|
|
71
|
+
? "text-red-500 hover:bg-red-500/10"
|
|
72
|
+
: "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
|
|
73
|
+
} ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`}
|
|
74
|
+
>
|
|
75
|
+
{icon && <span className="w-4 h-4 opacity-70">{icon}</span>}
|
|
76
|
+
<span className="flex-1">{children}</span>
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
77
79
|
);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
export function DropdownSeparator() {
|
|
81
|
-
return <div className="my-1 border-t border-
|
|
83
|
+
return <div className="my-1 border-t border-[var(--kyro-border)] opacity-50" />;
|
|
82
84
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface EmptyStateProps {
|
|
4
|
+
icon?: ReactNode;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
action?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function EmptyState({ icon, title, description, action }: EmptyStateProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col items-center gap-3 justify-center py-16 px-8">
|
|
13
|
+
{icon && (
|
|
14
|
+
<div className="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center mb-4">
|
|
15
|
+
{icon}
|
|
16
|
+
</div>
|
|
17
|
+
)}
|
|
18
|
+
<p className="font-medium text-[var(--kyro-text-primary)] text-base">{title}</p>
|
|
19
|
+
{description && (
|
|
20
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] mt-1">{description}</p>
|
|
21
|
+
)}
|
|
22
|
+
{action}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { useUIStore } from "../../lib/stores";
|
|
3
|
+
import { ConfirmModal } from "./Modal";
|
|
4
|
+
|
|
5
|
+
export function GlobalModal() {
|
|
6
|
+
const { modal, closeModal } = useUIStore();
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
|
|
9
|
+
if (!modal.open || !modal.config) return null;
|
|
10
|
+
|
|
11
|
+
const { config } = modal;
|
|
12
|
+
|
|
13
|
+
const handleConfirm = async () => {
|
|
14
|
+
if (config.onConfirm) {
|
|
15
|
+
try {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
await config.onConfirm();
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("Modal confirm action failed:", error);
|
|
20
|
+
} finally {
|
|
21
|
+
setLoading(false);
|
|
22
|
+
closeModal();
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
closeModal();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleClose = () => {
|
|
30
|
+
if (config.onCancel) {
|
|
31
|
+
config.onCancel();
|
|
32
|
+
}
|
|
33
|
+
closeModal();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ConfirmModal
|
|
38
|
+
open={modal.open}
|
|
39
|
+
onClose={handleClose}
|
|
40
|
+
onConfirm={handleConfirm}
|
|
41
|
+
title={config.title}
|
|
42
|
+
message={config.message}
|
|
43
|
+
confirmLabel={config.confirmLabel}
|
|
44
|
+
cancelLabel={config.cancelLabel}
|
|
45
|
+
variant={config.variant === "danger" ? "danger" : "default"}
|
|
46
|
+
loading={loading}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { ButtonVariant, ButtonSize } from './Button';
|
|
3
|
+
|
|
4
|
+
export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
icon: ReactNode;
|
|
6
|
+
size?: ButtonSize;
|
|
7
|
+
variant?: ButtonVariant;
|
|
8
|
+
label: string;
|
|
9
|
+
active?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function IconButton({
|
|
13
|
+
icon,
|
|
14
|
+
size = 'md',
|
|
15
|
+
variant = 'ghost',
|
|
16
|
+
label,
|
|
17
|
+
active = false,
|
|
18
|
+
className = '',
|
|
19
|
+
disabled,
|
|
20
|
+
...props
|
|
21
|
+
}: IconButtonProps) {
|
|
22
|
+
const classes = [
|
|
23
|
+
'kyro-btn',
|
|
24
|
+
`kyro-btn-${variant}`,
|
|
25
|
+
`kyro-btn-${size}`,
|
|
26
|
+
'kyro-btn-icon',
|
|
27
|
+
active ? 'kyro-btn-active' : '',
|
|
28
|
+
className,
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join(' ');
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button type="button"
|
|
35
|
+
className={classes}
|
|
36
|
+
aria-label={label}
|
|
37
|
+
title={label}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{icon}
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -51,30 +51,29 @@ export function Modal({
|
|
|
51
51
|
const sizeClasses = {
|
|
52
52
|
sm: "max-w-sm",
|
|
53
53
|
md: "max-w-md",
|
|
54
|
-
lg: "max-w-
|
|
54
|
+
lg: "max-w-lg",
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
return createPortal(
|
|
58
|
-
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
|
|
58
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
|
|
59
59
|
<div
|
|
60
|
-
className="absolute inset-0 bg-black/
|
|
60
|
+
className="absolute inset-0 bg-[var(--kyro-black)]/40 backdrop-blur-md transition-all duration-500"
|
|
61
61
|
onClick={onClose}
|
|
62
62
|
/>
|
|
63
63
|
<div
|
|
64
|
-
className={`relative w-full ${sizeClasses[size]}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}`}
|
|
64
|
+
className={`relative w-full ${sizeClasses[size]} bg-[var(--kyro-surface)] rounded-[var(--kyro-radius-lg)] shadow-2xl animate-in fade-in zoom-in-95 duration-300 border ${variant === "danger"
|
|
65
|
+
? "border-red-500/30"
|
|
66
|
+
: "border-[var(--kyro-border)]"
|
|
67
|
+
} overflow-hidden`}
|
|
69
68
|
>
|
|
70
|
-
<div className="flex items-center justify-between px-
|
|
71
|
-
<h2 className="text-
|
|
69
|
+
<div className="flex items-center justify-between px-8 py-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50 backdrop-blur-md">
|
|
70
|
+
<h2 className="text-xl font-bold text-[var(--kyro-text-primary)]">
|
|
72
71
|
{title}
|
|
73
72
|
</h2>
|
|
74
73
|
<button
|
|
75
74
|
type="button"
|
|
76
75
|
onClick={onClose}
|
|
77
|
-
className="p-
|
|
76
|
+
className="p-2 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all duration-200"
|
|
78
77
|
>
|
|
79
78
|
<svg
|
|
80
79
|
width="20"
|
|
@@ -82,15 +81,15 @@ export function Modal({
|
|
|
82
81
|
viewBox="0 0 24 24"
|
|
83
82
|
fill="none"
|
|
84
83
|
stroke="currentColor"
|
|
85
|
-
strokeWidth="2"
|
|
84
|
+
strokeWidth="2.5"
|
|
86
85
|
>
|
|
87
86
|
<path d="M18 6L6 18M6 6l12 12" />
|
|
88
87
|
</svg>
|
|
89
88
|
</button>
|
|
90
89
|
</div>
|
|
91
|
-
<div className="px-
|
|
90
|
+
<div className="px-8 py-8">{children}</div>
|
|
92
91
|
{footer && (
|
|
93
|
-
<div className="flex items-center justify-end gap-3 px-
|
|
92
|
+
<div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50">
|
|
94
93
|
{footer}
|
|
95
94
|
</div>
|
|
96
95
|
)}
|
|
@@ -98,6 +97,7 @@ export function Modal({
|
|
|
98
97
|
</div>,
|
|
99
98
|
document.body,
|
|
100
99
|
);
|
|
100
|
+
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
interface ConfirmModalProps {
|
|
@@ -135,7 +135,7 @@ export function ConfirmModal({
|
|
|
135
135
|
type="button"
|
|
136
136
|
onClick={onClose}
|
|
137
137
|
disabled={loading}
|
|
138
|
-
className="
|
|
138
|
+
className="kyro-btn kyro-btn-md kyro-btn-secondary"
|
|
139
139
|
>
|
|
140
140
|
{cancelLabel}
|
|
141
141
|
</button>
|
|
@@ -143,11 +143,10 @@ export function ConfirmModal({
|
|
|
143
143
|
type="button"
|
|
144
144
|
onClick={onConfirm}
|
|
145
145
|
disabled={loading}
|
|
146
|
-
className={`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}`}
|
|
146
|
+
className={`kyro-btn kyro-btn-md ${variant === "danger"
|
|
147
|
+
? "kyro-btn-danger"
|
|
148
|
+
: "kyro-btn-primary"
|
|
149
|
+
}`}
|
|
151
150
|
>
|
|
152
151
|
{loading ? "Loading..." : confirmLabel}
|
|
153
152
|
</button>
|