@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
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Terminal,
|
|
4
|
+
Key,
|
|
5
|
+
PlayCircle,
|
|
6
|
+
Copy,
|
|
7
|
+
RefreshCcw,
|
|
8
|
+
Trash2,
|
|
9
|
+
ExternalLink,
|
|
10
|
+
ChevronRight,
|
|
11
|
+
Code2,
|
|
12
|
+
Lock,
|
|
13
|
+
Eye,
|
|
14
|
+
EyeOff,
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
import CodeMirror from "@uiw/react-codemirror";
|
|
17
|
+
import { json } from "@codemirror/lang-json";
|
|
18
|
+
import { aura } from "@uiw/codemirror-theme-aura";
|
|
19
|
+
import { Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
20
|
+
|
|
21
|
+
interface ApiKey {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
key: string;
|
|
25
|
+
lastUsed?: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function DeveloperCenter({ collections }: { collections: any }) {
|
|
30
|
+
const [keys, setKeys] = useState<ApiKey[]>([]);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const [showKey, setShowKey] = useState<string | null>(null);
|
|
33
|
+
const [testEndpoint, setTestEndpoint] = useState("");
|
|
34
|
+
const [playgroundResult, setPlaygroundResult] = useState<unknown>(null);
|
|
35
|
+
const [exploring, setExploring] = useState(false);
|
|
36
|
+
|
|
37
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
38
|
+
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
39
|
+
const [deleteKeyId, setDeleteKeyId] = useState<string | null>(null);
|
|
40
|
+
const [newKeyName, setNewKeyName] = useState("");
|
|
41
|
+
|
|
42
|
+
const loadKeys = async () => {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch("/api/keys");
|
|
45
|
+
if (res.ok) {
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
setKeys(data);
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(e);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
loadKeys();
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const handleGenerateKey = async () => {
|
|
59
|
+
setNewKeyName("");
|
|
60
|
+
setShowCreateModal(true);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const confirmGenerateKey = async () => {
|
|
64
|
+
if (!newKeyName.trim()) return;
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch("/api/keys", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({ name: newKeyName }),
|
|
70
|
+
});
|
|
71
|
+
if (res.ok) {
|
|
72
|
+
loadKeys();
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error(e);
|
|
76
|
+
}
|
|
77
|
+
setShowCreateModal(false);
|
|
78
|
+
setNewKeyName("");
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleRevokeKey = async (id: string) => {
|
|
82
|
+
setDeleteKeyId(id);
|
|
83
|
+
setShowDeleteModal(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const confirmRevokeKey = async () => {
|
|
87
|
+
if (!deleteKeyId) return;
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`/api/keys/${deleteKeyId}`, { method: "DELETE" });
|
|
90
|
+
if (res.ok) {
|
|
91
|
+
loadKeys();
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(e);
|
|
95
|
+
}
|
|
96
|
+
setShowDeleteModal(false);
|
|
97
|
+
setDeleteKeyId(null);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleRunTest = async () => {
|
|
101
|
+
if (!testEndpoint) return;
|
|
102
|
+
setExploring(true);
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`/api/${testEndpoint}`);
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
setPlaygroundResult(data);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
setPlaygroundResult({
|
|
109
|
+
error: "Failed to fetch. Ensure the endpoint exists.",
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
setExploring(false);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 px-8 pb-32">
|
|
118
|
+
{/* Header */}
|
|
119
|
+
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6 pt-4">
|
|
120
|
+
<div>
|
|
121
|
+
<h1 className="text-4xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
|
|
122
|
+
Developer <span className="text-[var(--kyro-primary)]">Center</span>
|
|
123
|
+
</h1>
|
|
124
|
+
<p className="text-[var(--kyro-text-secondary)] mt-1 font-medium opacity-60">
|
|
125
|
+
Provision access keys and explore the headless API ecosystem.
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="flex items-center gap-3">
|
|
129
|
+
<button type="button"
|
|
130
|
+
onClick={handleGenerateKey}
|
|
131
|
+
className="flex items-center gap-2 px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-full font-black text-sm shadow-xl hover:shadow-[var(--kyro-primary)] active:scale-95 transition-all"
|
|
132
|
+
>
|
|
133
|
+
<Key className="w-4 h-4" />
|
|
134
|
+
Generate New Key
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
|
|
140
|
+
{/* API Key List */}
|
|
141
|
+
<section className="xl:col-span-2 space-y-6">
|
|
142
|
+
<div className="flex items-center justify-between px-2">
|
|
143
|
+
<div className="flex items-center gap-2">
|
|
144
|
+
<Lock className="w-4 h-4 text-[var(--kyro-primary)]" />
|
|
145
|
+
<span className="text-[10px] font-black uppercase tracking-widest opacity-40">
|
|
146
|
+
Access Credentials
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className="space-y-4">
|
|
152
|
+
{keys.map((key) => (
|
|
153
|
+
<div
|
|
154
|
+
key={key.id}
|
|
155
|
+
className="surface-tile p-6 group transition-all duration-300 hover:border-[var(--kyro-primary)] hover:shadow-xl"
|
|
156
|
+
>
|
|
157
|
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
158
|
+
<div className="flex-1 min-w-0">
|
|
159
|
+
<h3 className="text-lg font-black tracking-tight mb-1">
|
|
160
|
+
{key.name}
|
|
161
|
+
</h3>
|
|
162
|
+
<div className="flex items-center gap-3">
|
|
163
|
+
<div className="flex-1 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-xl px-4 py-2 flex items-center justify-between group/key overflow-hidden">
|
|
164
|
+
<code className="text-xs font-mono opacity-80 truncate mr-4">
|
|
165
|
+
{showKey === key.id
|
|
166
|
+
? key.key
|
|
167
|
+
: "••••••••••••••••••••••••••••••••"}
|
|
168
|
+
</code>
|
|
169
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
170
|
+
<button type="button"
|
|
171
|
+
onClick={() =>
|
|
172
|
+
setShowKey(showKey === key.id ? null : key.id)
|
|
173
|
+
}
|
|
174
|
+
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-md transition-all text-[var(--kyro-text-secondary)]"
|
|
175
|
+
>
|
|
176
|
+
{showKey === key.id ? (
|
|
177
|
+
<EyeOff className="w-3.5 h-3.5" />
|
|
178
|
+
) : (
|
|
179
|
+
<Eye className="w-3.5 h-3.5" />
|
|
180
|
+
)}
|
|
181
|
+
</button>
|
|
182
|
+
<button type="button" className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-md transition-all text-[var(--kyro-text-secondary)]">
|
|
183
|
+
<Copy className="w-3.5 h-3.5" />
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<div className="flex items-center gap-4 mt-3 text-[10px] font-black uppercase tracking-widest opacity-30">
|
|
189
|
+
<span>
|
|
190
|
+
Created {new Date(key.createdAt).toLocaleDateString()}
|
|
191
|
+
</span>
|
|
192
|
+
<span>
|
|
193
|
+
Last used{" "}
|
|
194
|
+
{key.lastUsed
|
|
195
|
+
? new Date(key.lastUsed).toLocaleDateString()
|
|
196
|
+
: "Never"}
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="flex items-center gap-3">
|
|
201
|
+
<button type="button"
|
|
202
|
+
onClick={() => handleRevokeKey(key.id)}
|
|
203
|
+
className="p-3 bg-red-500/5 text-red-500 rounded-xl hover:bg-red-500/10 transition-all border border-transparent hover:border-red-500/20"
|
|
204
|
+
title="Revoke Key"
|
|
205
|
+
>
|
|
206
|
+
<Trash2 className="w-4 h-4" />
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
</section>
|
|
214
|
+
|
|
215
|
+
{/* Sidebar Stats/Info */}
|
|
216
|
+
<section className="space-y-6">
|
|
217
|
+
<div className="surface-tile p-8 bg-[var(--kyro-primary)] group overflow-hidden relative">
|
|
218
|
+
<div className="relative z-10">
|
|
219
|
+
<Terminal className="w-8 h-8 text-white mb-4 opacity-40" />
|
|
220
|
+
<h3 className="text-white text-xl font-black tracking-tight mb-2">
|
|
221
|
+
Endpoint Hub
|
|
222
|
+
</h3>
|
|
223
|
+
<p className="text-white/70 text-sm font-medium leading-relaxed mb-6">
|
|
224
|
+
All content is delivered via our optimized REST API. Use your
|
|
225
|
+
keys to authorize requests.
|
|
226
|
+
</p>
|
|
227
|
+
<button type="button" className="w-full py-3 bg-white text-[var(--kyro-primary)] rounded-xl font-black text-xs uppercase tracking-widest hover:shadow-2xl transition-all flex items-center justify-center gap-2">
|
|
228
|
+
Review API Docs
|
|
229
|
+
<ExternalLink className="w-3.5 h-3.5" />
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
{/* Decorative glow */}
|
|
233
|
+
<div className="absolute -bottom-10 -right-10 w-40 h-40 bg-white opacity-10 blur-3xl group-hover:scale-150 transition-transform duration-700" />
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div className="surface-tile p-8">
|
|
237
|
+
<h4 className="text-[10px] font-black uppercase tracking-widest opacity-40 mb-6">
|
|
238
|
+
Base URLs
|
|
239
|
+
</h4>
|
|
240
|
+
<div className="space-y-4">
|
|
241
|
+
<div>
|
|
242
|
+
<p className="text-xs font-bold mb-2">
|
|
243
|
+
Content Delivery (Production)
|
|
244
|
+
</p>
|
|
245
|
+
<div className="bg-[var(--kyro-bg-secondary)] p-3 rounded-xl border border-[var(--kyro-border)] flex items-center justify-between group">
|
|
246
|
+
<code className="text-[10px] font-mono opacity-60">
|
|
247
|
+
https://api.kyro.io/v1
|
|
248
|
+
</code>
|
|
249
|
+
<Copy className="w-3.5 h-3.5 opacity-0 group-hover:opacity-40 cursor-pointer transition-opacity" />
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
<div>
|
|
253
|
+
<p className="text-xs font-bold mb-2">
|
|
254
|
+
Editor Snapshots (Preview)
|
|
255
|
+
</p>
|
|
256
|
+
<div className="bg-[var(--kyro-bg-secondary)] p-3 rounded-xl border border-[var(--kyro-border)] flex items-center justify-between group">
|
|
257
|
+
<code className="text-[10px] font-mono opacity-60">
|
|
258
|
+
https://preview.kyro.io/v1
|
|
259
|
+
</code>
|
|
260
|
+
<Copy className="w-3.5 h-3.5 opacity-0 group-hover:opacity-40 cursor-pointer transition-opacity" />
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</section>
|
|
266
|
+
|
|
267
|
+
{/* Playground Explorer */}
|
|
268
|
+
<section className="xl:col-span-3 surface-tile p-8 space-y-8">
|
|
269
|
+
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6">
|
|
270
|
+
<div>
|
|
271
|
+
<h2 className="text-2xl font-black tracking-tighter flex items-center gap-3">
|
|
272
|
+
<PlayCircle className="w-6 h-6 text-[var(--kyro-primary)]" />
|
|
273
|
+
API{" "}
|
|
274
|
+
<span className="text-[var(--kyro-primary)]">Playground</span>
|
|
275
|
+
</h2>
|
|
276
|
+
<p className="text-[var(--kyro-text-secondary)] text-sm font-medium opacity-60">
|
|
277
|
+
Test your endpoints and analyze response payloads in real-time.
|
|
278
|
+
</p>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="flex items-center gap-3 flex-1 lg:max-w-xl">
|
|
281
|
+
<div className="relative flex-1 group">
|
|
282
|
+
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-[10px] font-black opacity-40 mt-0.5">
|
|
283
|
+
GET /api/
|
|
284
|
+
</span>
|
|
285
|
+
<input
|
|
286
|
+
type="text"
|
|
287
|
+
value={testEndpoint}
|
|
288
|
+
onChange={(e) => setTestEndpoint(e.target.value)}
|
|
289
|
+
placeholder="collection-slug"
|
|
290
|
+
className="w-full pl-20 pr-4 py-3 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-2xl focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)] transition-all font-mono text-xs font-bold"
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
<button type="button"
|
|
294
|
+
onClick={handleRunTest}
|
|
295
|
+
disabled={exploring || !testEndpoint}
|
|
296
|
+
className="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-2xl font-black text-xs uppercase tracking-widest shadow-xl disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-[var(--kyro-primary)] transition-all flex items-center gap-2 shrink-0"
|
|
297
|
+
>
|
|
298
|
+
{exploring ? (
|
|
299
|
+
<RefreshCcw className="w-3.5 h-3.5 animate-spin" />
|
|
300
|
+
) : (
|
|
301
|
+
<ChevronRight className="w-4 h-4" />
|
|
302
|
+
)}
|
|
303
|
+
Fire
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{playgroundResult ? (
|
|
309
|
+
<div className="animate-in fade-in slide-in-from-top-4 duration-500">
|
|
310
|
+
<div className="flex items-center gap-2 mb-4">
|
|
311
|
+
<Code2 className="w-4 h-4 opacity-40" />
|
|
312
|
+
<span className="text-[10px] font-black uppercase tracking-widest opacity-40">
|
|
313
|
+
JSON Response Payload
|
|
314
|
+
</span>
|
|
315
|
+
</div>
|
|
316
|
+
<div className="rounded-3xl overflow-hidden border border-[var(--kyro-border)] shadow-2xl">
|
|
317
|
+
<CodeMirror
|
|
318
|
+
value={JSON.stringify(playgroundResult, null, 2)}
|
|
319
|
+
height="350px"
|
|
320
|
+
theme={aura}
|
|
321
|
+
extensions={[json()]}
|
|
322
|
+
editable={false}
|
|
323
|
+
className="text-sm"
|
|
324
|
+
/>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
) : (
|
|
328
|
+
<div className="h-64 rounded-3xl border-2 border-dashed border-[var(--kyro-border)] flex flex-col items-center justify-center opacity-30 select-none">
|
|
329
|
+
<Code2 className="w-12 h-12 mb-4" />
|
|
330
|
+
<p className="font-bold text-sm">
|
|
331
|
+
Enter an endpoint above to begin testing.
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
</section>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* Create Modal */}
|
|
339
|
+
<Modal
|
|
340
|
+
open={showCreateModal}
|
|
341
|
+
onClose={() => setShowCreateModal(false)}
|
|
342
|
+
title="Create API Key"
|
|
343
|
+
>
|
|
344
|
+
<ModalContent>
|
|
345
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] mb-4">
|
|
346
|
+
Give your API key a name to identify its purpose.
|
|
347
|
+
</p>
|
|
348
|
+
<input
|
|
349
|
+
type="text"
|
|
350
|
+
value={newKeyName}
|
|
351
|
+
onChange={(e) => setNewKeyName(e.target.value)}
|
|
352
|
+
placeholder="e.g., Production API"
|
|
353
|
+
className="w-full px-4 py-3 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-xl text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
354
|
+
onKeyDown={(e) => e.key === "Enter" && confirmGenerateKey()}
|
|
355
|
+
/>
|
|
356
|
+
</ModalContent>
|
|
357
|
+
<ModalActions>
|
|
358
|
+
<button type="button"
|
|
359
|
+
onClick={() => setShowCreateModal(false)}
|
|
360
|
+
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
361
|
+
>
|
|
362
|
+
Cancel
|
|
363
|
+
</button>
|
|
364
|
+
<button type="button"
|
|
365
|
+
onClick={confirmGenerateKey}
|
|
366
|
+
className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors"
|
|
367
|
+
>
|
|
368
|
+
Create
|
|
369
|
+
</button>
|
|
370
|
+
</ModalActions>
|
|
371
|
+
</Modal>
|
|
372
|
+
|
|
373
|
+
{/* Delete Confirmation Modal */}
|
|
374
|
+
<Modal
|
|
375
|
+
open={showDeleteModal}
|
|
376
|
+
onClose={() => setShowDeleteModal(false)}
|
|
377
|
+
title="Revoke API Key"
|
|
378
|
+
variant="danger"
|
|
379
|
+
>
|
|
380
|
+
<ModalContent>
|
|
381
|
+
<p className="text-sm text-[var(--kyro-text-secondary)]">
|
|
382
|
+
Are you sure you want to revoke this key? Any integrations using it
|
|
383
|
+
will stop working.
|
|
384
|
+
</p>
|
|
385
|
+
</ModalContent>
|
|
386
|
+
<ModalActions>
|
|
387
|
+
<button type="button"
|
|
388
|
+
onClick={() => setShowDeleteModal(false)}
|
|
389
|
+
className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
|
|
390
|
+
>
|
|
391
|
+
Cancel
|
|
392
|
+
</button>
|
|
393
|
+
<button type="button"
|
|
394
|
+
onClick={confirmRevokeKey}
|
|
395
|
+
className="px-4 py-2 rounded-lg font-medium text-sm bg-red-500 text-white hover:bg-red-600 transition-colors"
|
|
396
|
+
>
|
|
397
|
+
Revoke
|
|
398
|
+
</button>
|
|
399
|
+
</ModalActions>
|
|
400
|
+
</Modal>
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
}
|