@kyro-cms/admin 0.1.6 → 0.1.7
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 +53 -6
- 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 +136 -27
- 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 +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- 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 +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- 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/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- 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/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -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 +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -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 +553 -128
- package/src/components/layout/Sidebar.tsx +0 -497
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
interface TypeInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
fields?: FieldInfo[];
|
|
8
|
+
inputFields?: FieldInfo[];
|
|
9
|
+
enumValues?: { name: string; description?: string; isDeprecated: boolean }[];
|
|
10
|
+
isDeprecated?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FieldInfo {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
type: string;
|
|
17
|
+
args: ArgInfo[];
|
|
18
|
+
isDeprecated?: boolean;
|
|
19
|
+
deprecationReason?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ArgInfo {
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
type: { name?: string; kind?: string } | string;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SchemaInfo {
|
|
30
|
+
queryType: { name: string };
|
|
31
|
+
mutationType?: { name: string };
|
|
32
|
+
subscriptionType?: { name: string };
|
|
33
|
+
types: TypeInfo[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function GraphQLExplorer({
|
|
37
|
+
endpoint = "/api/graphql",
|
|
38
|
+
}: {
|
|
39
|
+
endpoint?: string;
|
|
40
|
+
}) {
|
|
41
|
+
const [schema, setSchema] = useState<SchemaInfo | null>(null);
|
|
42
|
+
const [loading, setLoading] = useState(true);
|
|
43
|
+
const [error, setError] = useState<string | null>(null);
|
|
44
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
45
|
+
const [selectedType, setSelectedType] = useState<TypeInfo | null>(null);
|
|
46
|
+
const [activeSection, setActiveSection] = useState<
|
|
47
|
+
"types" | "queries" | "mutations"
|
|
48
|
+
>("types");
|
|
49
|
+
const [expandedTypes, setExpandedTypes] = useState<Set<string>>(new Set());
|
|
50
|
+
|
|
51
|
+
const fetchSchema = useCallback(async () => {
|
|
52
|
+
setLoading(true);
|
|
53
|
+
setError(null);
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(endpoint, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
query: `
|
|
60
|
+
{
|
|
61
|
+
__schema {
|
|
62
|
+
queryType { name }
|
|
63
|
+
mutationType { name }
|
|
64
|
+
subscriptionType { name }
|
|
65
|
+
types {
|
|
66
|
+
name
|
|
67
|
+
kind
|
|
68
|
+
description
|
|
69
|
+
fields {
|
|
70
|
+
name
|
|
71
|
+
description
|
|
72
|
+
type { name kind }
|
|
73
|
+
args {
|
|
74
|
+
name
|
|
75
|
+
description
|
|
76
|
+
type { name kind }
|
|
77
|
+
defaultValue
|
|
78
|
+
}
|
|
79
|
+
isDeprecated
|
|
80
|
+
deprecationReason
|
|
81
|
+
}
|
|
82
|
+
inputFields {
|
|
83
|
+
name
|
|
84
|
+
description
|
|
85
|
+
type { name kind }
|
|
86
|
+
defaultValue
|
|
87
|
+
}
|
|
88
|
+
enumValues {
|
|
89
|
+
name
|
|
90
|
+
description
|
|
91
|
+
isDeprecated
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const data = await response.json();
|
|
101
|
+
if (data.errors) {
|
|
102
|
+
setError(data.errors[0]?.message || "Failed to fetch schema");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
setSchema(data.data.__schema);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
setError(err instanceof Error ? err.message : "Failed to connect");
|
|
108
|
+
} finally {
|
|
109
|
+
setLoading(false);
|
|
110
|
+
}
|
|
111
|
+
}, [endpoint]);
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
fetchSchema();
|
|
115
|
+
}, [fetchSchema]);
|
|
116
|
+
|
|
117
|
+
const scalarTypes = [
|
|
118
|
+
"String",
|
|
119
|
+
"Int",
|
|
120
|
+
"Float",
|
|
121
|
+
"Boolean",
|
|
122
|
+
"ID",
|
|
123
|
+
"JSON",
|
|
124
|
+
"DateTime",
|
|
125
|
+
];
|
|
126
|
+
const builtInTypes = [
|
|
127
|
+
"Query",
|
|
128
|
+
"Mutation",
|
|
129
|
+
"Subscription",
|
|
130
|
+
"__Schema",
|
|
131
|
+
"__Type",
|
|
132
|
+
"__Field",
|
|
133
|
+
"__InputValue",
|
|
134
|
+
"__EnumValue",
|
|
135
|
+
"__Directive",
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const filteredTypes =
|
|
139
|
+
schema?.types.filter((type) => {
|
|
140
|
+
if (
|
|
141
|
+
!type.name ||
|
|
142
|
+
builtInTypes.includes(type.name) ||
|
|
143
|
+
type.name.startsWith("__")
|
|
144
|
+
)
|
|
145
|
+
return false;
|
|
146
|
+
if (!searchQuery) return true;
|
|
147
|
+
const query = searchQuery.toLowerCase();
|
|
148
|
+
if (type.name.toLowerCase().includes(query)) return true;
|
|
149
|
+
if (type.description?.toLowerCase().includes(query)) return true;
|
|
150
|
+
return false;
|
|
151
|
+
}) || [];
|
|
152
|
+
|
|
153
|
+
const queryTypes =
|
|
154
|
+
schema?.types.filter((t) => t.name === schema.queryType?.name) || [];
|
|
155
|
+
const mutationTypes =
|
|
156
|
+
schema?.types.filter((t) => t.name === schema.mutationType?.name) || [];
|
|
157
|
+
|
|
158
|
+
const toggleType = (name: string) => {
|
|
159
|
+
setExpandedTypes((prev) => {
|
|
160
|
+
const next = new Set(prev);
|
|
161
|
+
if (next.has(name)) next.delete(name);
|
|
162
|
+
else next.add(name);
|
|
163
|
+
return next;
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const getTypeColor = (kind: string) => {
|
|
168
|
+
switch (kind) {
|
|
169
|
+
case "OBJECT":
|
|
170
|
+
return "text-blue-400";
|
|
171
|
+
case "INPUT_OBJECT":
|
|
172
|
+
return "text-orange-400";
|
|
173
|
+
case "ENUM":
|
|
174
|
+
return "text-purple-400";
|
|
175
|
+
case "INTERFACE":
|
|
176
|
+
return "text-green-400";
|
|
177
|
+
case "SCALAR":
|
|
178
|
+
return "text-yellow-400";
|
|
179
|
+
case "UNION":
|
|
180
|
+
return "text-pink-400";
|
|
181
|
+
default:
|
|
182
|
+
return "text-gray-400";
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const formatType = (type: any): string => {
|
|
187
|
+
if (!type) return "Unknown";
|
|
188
|
+
if (type.name) return type.name;
|
|
189
|
+
if (type.kind === "NON_NULL") return `${formatType(type.ofType)}!`;
|
|
190
|
+
if (type.kind === "LIST") return `[${formatType(type.ofType)}]`;
|
|
191
|
+
return type.kind || "Unknown";
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const generateExample = (
|
|
195
|
+
type: TypeInfo,
|
|
196
|
+
operation: "query" | "mutation" = "query",
|
|
197
|
+
): string => {
|
|
198
|
+
const fields =
|
|
199
|
+
type.fields?.filter((f) => !f.isDeprecated).slice(0, 5) || [];
|
|
200
|
+
const fieldList = fields.map((f) => ` ${f.name}`).join("\n");
|
|
201
|
+
|
|
202
|
+
if (operation === "query") {
|
|
203
|
+
return `query Get${type.name} {
|
|
204
|
+
${type.name.toLowerCase().replace(/_type$/, "")} {
|
|
205
|
+
${fieldList}
|
|
206
|
+
}
|
|
207
|
+
}`;
|
|
208
|
+
} else {
|
|
209
|
+
return `mutation Create${type.name} {
|
|
210
|
+
create${type.name}(input: {}) {
|
|
211
|
+
${fieldList}
|
|
212
|
+
}
|
|
213
|
+
}`;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const copyToClipboard = (text: string) => {
|
|
218
|
+
navigator.clipboard.writeText(text);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (loading) {
|
|
222
|
+
return (
|
|
223
|
+
<div className="flex items-center justify-center h-full">
|
|
224
|
+
<div className="text-center">
|
|
225
|
+
<div className="animate-spin w-12 h-12 border-2 border-pink-500 border-t-transparent rounded-full mx-auto mb-4" />
|
|
226
|
+
<p className="text-[var(--kyro-text-secondary)]">
|
|
227
|
+
Fetching schema...
|
|
228
|
+
</p>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (error) {
|
|
235
|
+
return (
|
|
236
|
+
<div className="flex items-center justify-center h-full">
|
|
237
|
+
<div className="text-center max-w-md">
|
|
238
|
+
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-red-500/10 flex items-center justify-center">
|
|
239
|
+
<svg
|
|
240
|
+
className="w-8 h-8 text-red-500"
|
|
241
|
+
fill="none"
|
|
242
|
+
stroke="currentColor"
|
|
243
|
+
viewBox="0 0 24 24"
|
|
244
|
+
>
|
|
245
|
+
<path
|
|
246
|
+
strokeLinecap="round"
|
|
247
|
+
strokeLinejoin="round"
|
|
248
|
+
strokeWidth="2"
|
|
249
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
250
|
+
/>
|
|
251
|
+
</svg>
|
|
252
|
+
</div>
|
|
253
|
+
<h3 className="text-lg font-bold text-[var(--kyro-text-primary)] mb-2">
|
|
254
|
+
Connection Error
|
|
255
|
+
</h3>
|
|
256
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] mb-4">
|
|
257
|
+
{error}
|
|
258
|
+
</p>
|
|
259
|
+
<button type="button"
|
|
260
|
+
onClick={fetchSchema}
|
|
261
|
+
className="px-4 py-2 bg-pink-500 text-white rounded-lg font-bold text-sm hover:bg-pink-600"
|
|
262
|
+
>
|
|
263
|
+
Retry
|
|
264
|
+
</button>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className="h-full flex gap-6">
|
|
272
|
+
{/* Left Panel - Type List */}
|
|
273
|
+
<div className="w-80 flex-shrink-0 flex flex-col">
|
|
274
|
+
{/* Search */}
|
|
275
|
+
<div className="mb-4">
|
|
276
|
+
<div className="relative">
|
|
277
|
+
<svg
|
|
278
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-muted)]"
|
|
279
|
+
fill="none"
|
|
280
|
+
stroke="currentColor"
|
|
281
|
+
viewBox="0 0 24 24"
|
|
282
|
+
>
|
|
283
|
+
<path
|
|
284
|
+
strokeLinecap="round"
|
|
285
|
+
strokeLinejoin="round"
|
|
286
|
+
strokeWidth="2"
|
|
287
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
288
|
+
/>
|
|
289
|
+
</svg>
|
|
290
|
+
<input
|
|
291
|
+
type="text"
|
|
292
|
+
value={searchQuery}
|
|
293
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
294
|
+
placeholder="Search types..."
|
|
295
|
+
className="w-full pl-10 pr-4 py-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-lg text-sm focus:outline-none focus:border-pink-500"
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
{/* Section Tabs */}
|
|
301
|
+
<div className="flex gap-1 mb-4">
|
|
302
|
+
{(["types", "queries", "mutations"] as const).map((section) => (
|
|
303
|
+
<button type="button"
|
|
304
|
+
key={section}
|
|
305
|
+
onClick={() => setActiveSection(section)}
|
|
306
|
+
className={`flex-1 px-3 py-1.5 text-xs font-bold rounded transition-colors ${
|
|
307
|
+
activeSection === section
|
|
308
|
+
? "bg-pink-500 text-white"
|
|
309
|
+
: "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
|
|
310
|
+
}`}
|
|
311
|
+
>
|
|
312
|
+
{section.charAt(0).toUpperCase() + section.slice(1)}
|
|
313
|
+
</button>
|
|
314
|
+
))}
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{/* Type List */}
|
|
318
|
+
<div className="flex-1 overflow-y-auto space-y-1">
|
|
319
|
+
{activeSection === "types" &&
|
|
320
|
+
filteredTypes.map((type) => (
|
|
321
|
+
<button type="button"
|
|
322
|
+
key={type.name}
|
|
323
|
+
onClick={() => {
|
|
324
|
+
setSelectedType(type);
|
|
325
|
+
toggleType(type.name);
|
|
326
|
+
}}
|
|
327
|
+
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${
|
|
328
|
+
selectedType?.name === type.name
|
|
329
|
+
? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)]"
|
|
330
|
+
: "hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)]"
|
|
331
|
+
}`}
|
|
332
|
+
>
|
|
333
|
+
<div className="flex items-center gap-2">
|
|
334
|
+
<span
|
|
335
|
+
className={`text-xs font-mono ${getTypeColor(type.kind)}`}
|
|
336
|
+
>
|
|
337
|
+
{type.kind === "OBJECT"
|
|
338
|
+
? "{ }"
|
|
339
|
+
: type.kind === "ENUM"
|
|
340
|
+
? "[ ]"
|
|
341
|
+
: type.kind === "INPUT_OBJECT"
|
|
342
|
+
? "( )"
|
|
343
|
+
: "~"}
|
|
344
|
+
</span>
|
|
345
|
+
<span className="font-medium">{type.name}</span>
|
|
346
|
+
</div>
|
|
347
|
+
{type.description && (
|
|
348
|
+
<p className="text-xs text-[var(--kyro-text-muted)] mt-1 line-clamp-2">
|
|
349
|
+
{type.description}
|
|
350
|
+
</p>
|
|
351
|
+
)}
|
|
352
|
+
</button>
|
|
353
|
+
))}
|
|
354
|
+
|
|
355
|
+
{activeSection === "queries" &&
|
|
356
|
+
queryTypes.map((type) => (
|
|
357
|
+
<div key={type.name}>
|
|
358
|
+
<div className="px-3 py-2 text-xs font-bold text-[var(--kyro-text-muted)] uppercase">
|
|
359
|
+
{type.name} Root
|
|
360
|
+
</div>
|
|
361
|
+
{type.fields?.map((field) => (
|
|
362
|
+
<button type="button"
|
|
363
|
+
key={field.name}
|
|
364
|
+
onClick={() => {
|
|
365
|
+
const queryType = schema?.types.find((t) => {
|
|
366
|
+
const fieldType = field.type as any;
|
|
367
|
+
return t.name === (fieldType?.name || fieldType);
|
|
368
|
+
});
|
|
369
|
+
if (queryType) setSelectedType(queryType);
|
|
370
|
+
}}
|
|
371
|
+
className="w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)]"
|
|
372
|
+
>
|
|
373
|
+
<span className="text-green-400 font-mono">
|
|
374
|
+
{field.name}
|
|
375
|
+
</span>
|
|
376
|
+
{field.args.length > 0 && (
|
|
377
|
+
<span className="text-[var(--kyro-text-muted)]">
|
|
378
|
+
({field.args.length} args)
|
|
379
|
+
</span>
|
|
380
|
+
)}
|
|
381
|
+
</button>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
))}
|
|
385
|
+
|
|
386
|
+
{activeSection === "mutations" && (
|
|
387
|
+
<>
|
|
388
|
+
{mutationTypes.length === 0 && (
|
|
389
|
+
<p className="text-sm text-[var(--kyro-text-muted)] px-3 py-4 text-center">
|
|
390
|
+
No mutations available
|
|
391
|
+
</p>
|
|
392
|
+
)}
|
|
393
|
+
{mutationTypes.map((type) => (
|
|
394
|
+
<div key={type.name}>
|
|
395
|
+
<div className="px-3 py-2 text-xs font-bold text-[var(--kyro-text-muted)] uppercase">
|
|
396
|
+
{type.name} Root
|
|
397
|
+
</div>
|
|
398
|
+
{type.fields?.map((field) => (
|
|
399
|
+
<button type="button"
|
|
400
|
+
key={field.name}
|
|
401
|
+
onClick={() => {
|
|
402
|
+
const mutationType = schema?.types.find((t) => {
|
|
403
|
+
const fieldType = field.type as any;
|
|
404
|
+
return t.name === (fieldType?.name || fieldType);
|
|
405
|
+
});
|
|
406
|
+
if (mutationType) setSelectedType(mutationType);
|
|
407
|
+
}}
|
|
408
|
+
className="w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)]"
|
|
409
|
+
>
|
|
410
|
+
<span className="text-red-400 font-mono">
|
|
411
|
+
{field.name}
|
|
412
|
+
</span>
|
|
413
|
+
{field.args.length > 0 && (
|
|
414
|
+
<span className="text-[var(--kyro-text-muted)]">
|
|
415
|
+
({field.args.length} args)
|
|
416
|
+
</span>
|
|
417
|
+
)}
|
|
418
|
+
</button>
|
|
419
|
+
))}
|
|
420
|
+
</div>
|
|
421
|
+
))}
|
|
422
|
+
</>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
{/* Right Panel - Type Details */}
|
|
428
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
429
|
+
{selectedType ? (
|
|
430
|
+
<>
|
|
431
|
+
{/* Type Header */}
|
|
432
|
+
<div className="mb-6">
|
|
433
|
+
<div className="flex items-center gap-3 mb-2">
|
|
434
|
+
<span
|
|
435
|
+
className={`text-xs font-mono px-2 py-1 rounded bg-[var(--kyro-surface-accent)] ${getTypeColor(selectedType.kind)}`}
|
|
436
|
+
>
|
|
437
|
+
{selectedType.kind}
|
|
438
|
+
</span>
|
|
439
|
+
<h2 className="text-2xl font-black text-[var(--kyro-text-primary)]">
|
|
440
|
+
{selectedType.name}
|
|
441
|
+
</h2>
|
|
442
|
+
</div>
|
|
443
|
+
{selectedType.description && (
|
|
444
|
+
<p className="text-sm text-[var(--kyro-text-secondary)]">
|
|
445
|
+
{selectedType.description}
|
|
446
|
+
</p>
|
|
447
|
+
)}
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
{/* Quick Actions */}
|
|
451
|
+
<div className="flex gap-2 mb-6">
|
|
452
|
+
<button type="button"
|
|
453
|
+
onClick={() =>
|
|
454
|
+
copyToClipboard(generateExample(selectedType, "query"))
|
|
455
|
+
}
|
|
456
|
+
className="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-lg text-sm hover:bg-[var(--kyro-surface)] transition-colors"
|
|
457
|
+
>
|
|
458
|
+
<svg
|
|
459
|
+
className="w-4 h-4"
|
|
460
|
+
fill="none"
|
|
461
|
+
stroke="currentColor"
|
|
462
|
+
viewBox="0 0 24 24"
|
|
463
|
+
>
|
|
464
|
+
<path
|
|
465
|
+
strokeLinecap="round"
|
|
466
|
+
strokeLinejoin="round"
|
|
467
|
+
strokeWidth="2"
|
|
468
|
+
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
|
|
469
|
+
/>
|
|
470
|
+
</svg>
|
|
471
|
+
Copy Query
|
|
472
|
+
</button>
|
|
473
|
+
<button type="button"
|
|
474
|
+
onClick={() =>
|
|
475
|
+
copyToClipboard(generateExample(selectedType, "mutation"))
|
|
476
|
+
}
|
|
477
|
+
className="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-lg text-sm hover:bg-[var(--kyro-surface)] transition-colors"
|
|
478
|
+
>
|
|
479
|
+
<svg
|
|
480
|
+
className="w-4 h-4"
|
|
481
|
+
fill="none"
|
|
482
|
+
stroke="currentColor"
|
|
483
|
+
viewBox="0 0 24 24"
|
|
484
|
+
>
|
|
485
|
+
<path
|
|
486
|
+
strokeLinecap="round"
|
|
487
|
+
strokeLinejoin="round"
|
|
488
|
+
strokeWidth="2"
|
|
489
|
+
d="M12 4v16m8-8H4"
|
|
490
|
+
/>
|
|
491
|
+
</svg>
|
|
492
|
+
Copy Mutation
|
|
493
|
+
</button>
|
|
494
|
+
<a
|
|
495
|
+
href={`/admin/graphql?query=${encodeURIComponent(generateExample(selectedType, "query"))}`}
|
|
496
|
+
className="flex items-center gap-2 px-4 py-2 bg-pink-500 text-white rounded-lg text-sm hover:bg-pink-600 transition-colors"
|
|
497
|
+
>
|
|
498
|
+
<svg
|
|
499
|
+
className="w-4 h-4"
|
|
500
|
+
fill="none"
|
|
501
|
+
stroke="currentColor"
|
|
502
|
+
viewBox="0 0 24 24"
|
|
503
|
+
>
|
|
504
|
+
<path
|
|
505
|
+
strokeLinecap="round"
|
|
506
|
+
strokeLinejoin="round"
|
|
507
|
+
strokeWidth="2"
|
|
508
|
+
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
509
|
+
/>
|
|
510
|
+
</svg>
|
|
511
|
+
Open in Playground
|
|
512
|
+
</a>
|
|
513
|
+
</div>
|
|
514
|
+
|
|
515
|
+
{/* Fields */}
|
|
516
|
+
{selectedType.fields && selectedType.fields.length > 0 && (
|
|
517
|
+
<div className="mb-6">
|
|
518
|
+
<h3 className="text-sm font-bold text-[var(--kyro-text-primary)] mb-3">
|
|
519
|
+
Fields
|
|
520
|
+
</h3>
|
|
521
|
+
<div className="space-y-3">
|
|
522
|
+
{selectedType.fields.map((field) => (
|
|
523
|
+
<div
|
|
524
|
+
key={field.name}
|
|
525
|
+
className="p-4 bg-[var(--kyro-surface-accent)] rounded-lg border border-[var(--kyro-border)]"
|
|
526
|
+
>
|
|
527
|
+
<div className="flex items-start justify-between mb-2">
|
|
528
|
+
<div className="flex items-center gap-2">
|
|
529
|
+
<code className="text-sm font-mono text-pink-400">
|
|
530
|
+
{field.name}
|
|
531
|
+
</code>
|
|
532
|
+
{field.args.length > 0 && (
|
|
533
|
+
<span className="text-xs text-[var(--kyro-text-muted)]">
|
|
534
|
+
({field.args.map((a) => a.name).join(", ")})
|
|
535
|
+
</span>
|
|
536
|
+
)}
|
|
537
|
+
<span className="text-xs text-[var(--kyro-text-muted)]">
|
|
538
|
+
:
|
|
539
|
+
</span>
|
|
540
|
+
<code className="text-sm font-mono text-blue-400">
|
|
541
|
+
{formatType(field.type as any)}
|
|
542
|
+
</code>
|
|
543
|
+
</div>
|
|
544
|
+
{field.isDeprecated && (
|
|
545
|
+
<span className="px-2 py-0.5 bg-yellow-500/10 text-yellow-500 text-xs rounded">
|
|
546
|
+
Deprecated
|
|
547
|
+
</span>
|
|
548
|
+
)}
|
|
549
|
+
</div>
|
|
550
|
+
{field.description && (
|
|
551
|
+
<p className="text-xs text-[var(--kyro-text-secondary)] mb-2">
|
|
552
|
+
{field.description}
|
|
553
|
+
</p>
|
|
554
|
+
)}
|
|
555
|
+
{field.deprecationReason && (
|
|
556
|
+
<p className="text-xs text-yellow-500 italic">
|
|
557
|
+
{field.deprecationReason}
|
|
558
|
+
</p>
|
|
559
|
+
)}
|
|
560
|
+
{field.args.length > 0 && (
|
|
561
|
+
<div className="mt-2 pl-4 border-l-2 border-[var(--kyro-border)]">
|
|
562
|
+
<p className="text-xs font-bold text-[var(--kyro-text-muted)] mb-1">
|
|
563
|
+
Arguments:
|
|
564
|
+
</p>
|
|
565
|
+
{field.args.map((arg) => (
|
|
566
|
+
<div
|
|
567
|
+
key={arg.name}
|
|
568
|
+
className="flex items-center gap-2 text-xs"
|
|
569
|
+
>
|
|
570
|
+
<code className="text-[var(--kyro-text-secondary)]">
|
|
571
|
+
{arg.name}
|
|
572
|
+
</code>
|
|
573
|
+
<span className="text-[var(--kyro-text-muted)]">
|
|
574
|
+
:
|
|
575
|
+
</span>
|
|
576
|
+
<code className="text-blue-400">
|
|
577
|
+
{formatType(arg.type as any)}
|
|
578
|
+
</code>
|
|
579
|
+
{arg.defaultValue && (
|
|
580
|
+
<span className="text-[var(--kyro-text-muted)]">
|
|
581
|
+
= {arg.defaultValue}
|
|
582
|
+
</span>
|
|
583
|
+
)}
|
|
584
|
+
</div>
|
|
585
|
+
))}
|
|
586
|
+
</div>
|
|
587
|
+
)}
|
|
588
|
+
</div>
|
|
589
|
+
))}
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
|
|
594
|
+
{/* Input Fields */}
|
|
595
|
+
{selectedType.inputFields &&
|
|
596
|
+
selectedType.inputFields.length > 0 && (
|
|
597
|
+
<div className="mb-6">
|
|
598
|
+
<h3 className="text-sm font-bold text-[var(--kyro-text-primary)] mb-3">
|
|
599
|
+
Input Fields
|
|
600
|
+
</h3>
|
|
601
|
+
<div className="space-y-2">
|
|
602
|
+
{selectedType.inputFields.map((field) => (
|
|
603
|
+
<div
|
|
604
|
+
key={field.name}
|
|
605
|
+
className="flex items-center gap-3 p-3 bg-[var(--kyro-surface-accent)] rounded-lg"
|
|
606
|
+
>
|
|
607
|
+
<code className="text-sm font-mono text-orange-400">
|
|
608
|
+
{field.name}
|
|
609
|
+
</code>
|
|
610
|
+
<span className="text-[var(--kyro-text-muted)]">:</span>
|
|
611
|
+
<code className="text-sm font-mono text-blue-400">
|
|
612
|
+
{formatType(field.type as any)}
|
|
613
|
+
</code>
|
|
614
|
+
</div>
|
|
615
|
+
))}
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
)}
|
|
619
|
+
|
|
620
|
+
{/* Enum Values */}
|
|
621
|
+
{selectedType.enumValues && selectedType.enumValues.length > 0 && (
|
|
622
|
+
<div className="mb-6">
|
|
623
|
+
<h3 className="text-sm font-bold text-[var(--kyro-text-primary)] mb-3">
|
|
624
|
+
Enum Values
|
|
625
|
+
</h3>
|
|
626
|
+
<div className="grid grid-cols-2 gap-2">
|
|
627
|
+
{selectedType.enumValues.map((value) => (
|
|
628
|
+
<div
|
|
629
|
+
key={value.name}
|
|
630
|
+
className={`p-3 rounded-lg border ${
|
|
631
|
+
value.isDeprecated
|
|
632
|
+
? "bg-yellow-500/5 border-yellow-500/20"
|
|
633
|
+
: "bg-[var(--kyro-surface-accent)] border-[var(--kyro-border)]"
|
|
634
|
+
}`}
|
|
635
|
+
>
|
|
636
|
+
<code className="text-sm font-mono text-purple-400">
|
|
637
|
+
{value.name}
|
|
638
|
+
</code>
|
|
639
|
+
{value.isDeprecated && (
|
|
640
|
+
<span className="ml-2 text-xs text-yellow-500">
|
|
641
|
+
Deprecated
|
|
642
|
+
</span>
|
|
643
|
+
)}
|
|
644
|
+
</div>
|
|
645
|
+
))}
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
)}
|
|
649
|
+
</>
|
|
650
|
+
) : (
|
|
651
|
+
<div className="flex-1 flex items-center justify-center">
|
|
652
|
+
<div className="text-center">
|
|
653
|
+
<svg
|
|
654
|
+
className="w-16 h-16 mx-auto mb-4 text-[var(--kyro-text-muted)] opacity-50"
|
|
655
|
+
fill="none"
|
|
656
|
+
stroke="currentColor"
|
|
657
|
+
viewBox="0 0 24 24"
|
|
658
|
+
>
|
|
659
|
+
<path
|
|
660
|
+
strokeLinecap="round"
|
|
661
|
+
strokeLinejoin="round"
|
|
662
|
+
strokeWidth="1.5"
|
|
663
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
664
|
+
/>
|
|
665
|
+
</svg>
|
|
666
|
+
<p className="text-[var(--kyro-text-secondary)]">
|
|
667
|
+
Select a type to view its details
|
|
668
|
+
</p>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
)}
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
);
|
|
675
|
+
}
|