@kyro-cms/admin 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +11715 -11292
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11326 -10912
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/src/components/ActionBar.tsx +25 -161
- package/src/components/Admin.tsx +2 -4
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +572 -461
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +52 -65
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +57 -216
- package/src/components/MediaGallery.tsx +334 -367
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +59 -52
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +433 -55
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +155 -90
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +10 -13
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +110 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +187 -196
- package/src/hooks/useQueue.ts +60 -0
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +67 -7
- package/src/lib/autoform-store.ts +90 -53
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +48 -11
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/auth/register.astro +5 -2
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { apiGet, apiPatch } from "../lib/api";
|
|
3
|
+
import { toast } from "../lib/stores";
|
|
3
4
|
import {
|
|
4
5
|
Palette,
|
|
5
6
|
Tag,
|
|
@@ -25,7 +26,7 @@ export function BrandingHub() {
|
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
const fetchBranding = async () => {
|
|
27
28
|
try {
|
|
28
|
-
const result = await apiGet("/api/globals/site");
|
|
29
|
+
const result = await apiGet("/api/globals/site-settings");
|
|
29
30
|
const data = result.data || result;
|
|
30
31
|
if (data && Object.keys(data).length > 0) {
|
|
31
32
|
if (data.siteName) setSiteName(data.siteName);
|
|
@@ -44,19 +45,21 @@ export function BrandingHub() {
|
|
|
44
45
|
const handleSave = async () => {
|
|
45
46
|
setSaving(true);
|
|
46
47
|
try {
|
|
47
|
-
await apiPatch("/api/globals/site", {
|
|
48
|
+
await apiPatch("/api/globals/site-settings", {
|
|
48
49
|
siteName,
|
|
49
50
|
adminTitle,
|
|
50
51
|
primaryColor,
|
|
51
52
|
dashboardGreeting,
|
|
52
53
|
});
|
|
53
54
|
setSaved(true);
|
|
54
|
-
|
|
55
|
+
toast.success("Branding updated");
|
|
55
56
|
document.documentElement.style.setProperty(
|
|
56
57
|
"--kyro-primary",
|
|
57
58
|
primaryColor,
|
|
58
59
|
);
|
|
60
|
+
setTimeout(() => window.location.reload(), 800);
|
|
59
61
|
} catch (e) {
|
|
62
|
+
toast.error("Failed to save branding");
|
|
60
63
|
console.error(e);
|
|
61
64
|
} finally {
|
|
62
65
|
setSaving(false);
|
|
@@ -92,7 +95,7 @@ export function BrandingHub() {
|
|
|
92
95
|
disabled={saving}
|
|
93
96
|
className={`flex items-center gap-2 px-8 py-3 rounded-2xl font-bold text-sm shadow-xl transition-all active:scale-95 ${saved
|
|
94
97
|
? "bg-green-500 text-white"
|
|
95
|
-
: "
|
|
98
|
+
: "kyro-btn-primary hover:shadow-[var(--kyro-primary)]"
|
|
96
99
|
}`}
|
|
97
100
|
>
|
|
98
101
|
{saving ? (
|
|
@@ -5,6 +5,7 @@ import { AutoForm } from "./AutoForm";
|
|
|
5
5
|
import { Spinner } from "./ui/Spinner";
|
|
6
6
|
import { PageHeader } from "./ui/PageHeader";
|
|
7
7
|
import { adminPath } from "../lib/paths";
|
|
8
|
+
import { toast } from "../lib/stores";
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
interface CreateViewProps {
|
|
@@ -34,6 +35,7 @@ export function CreateView({
|
|
|
34
35
|
try {
|
|
35
36
|
setSaving(true);
|
|
36
37
|
await apiPost(`/api/${collection.slug}`, data);
|
|
38
|
+
toast.success(`${collection.singularLabel || collection.label || "Document"} created`);
|
|
37
39
|
onSuccess();
|
|
38
40
|
} catch (err) {
|
|
39
41
|
onError(err instanceof Error ? err.message : "Failed to create");
|
|
@@ -9,11 +9,12 @@ import { AutoForm } from "./AutoForm";
|
|
|
9
9
|
import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
|
|
10
10
|
import { Spinner } from "./ui/Spinner";
|
|
11
11
|
import { Shimmer } from "./ui/Shimmer";
|
|
12
|
-
import {
|
|
13
|
-
import { useUIStore } from "../lib/stores";
|
|
12
|
+
import { useUIStore, toast } from "../lib/stores";
|
|
14
13
|
import { PageHeader } from "./ui/PageHeader";
|
|
15
14
|
import { Badge } from "./ui/Badge";
|
|
15
|
+
import { SplitButton } from "./ui/SplitButton";
|
|
16
16
|
import { adminPath } from "../lib/paths";
|
|
17
|
+
import { resolveFieldValue } from "../lib/resolve-field-value";
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
interface DetailViewProps {
|
|
@@ -39,7 +40,6 @@ export function DetailView({
|
|
|
39
40
|
onError,
|
|
40
41
|
mode = "collection",
|
|
41
42
|
}: DetailViewProps) {
|
|
42
|
-
const { addToast } = useToast();
|
|
43
43
|
const { confirm, alert } = useUIStore();
|
|
44
44
|
const [data, setData] = useState<Record<string, unknown>>({});
|
|
45
45
|
const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
|
|
@@ -94,10 +94,10 @@ export function DetailView({
|
|
|
94
94
|
const docData = result.data || {};
|
|
95
95
|
setData(docData);
|
|
96
96
|
setOriginalData(docData);
|
|
97
|
-
setStatus((result.status || "draft") as DocumentStatus);
|
|
98
|
-
setCreatedAt(result.createdAt || null);
|
|
99
|
-
setUpdatedAt(result.updatedAt || null);
|
|
100
|
-
setPublishedAt(result.publishedAt || null);
|
|
97
|
+
setStatus(((docData as any)?.status || result.status || "draft") as DocumentStatus);
|
|
98
|
+
setCreatedAt(result.createdAt || (docData.createdAt as string) || null);
|
|
99
|
+
setUpdatedAt(result.updatedAt || (docData.updatedAt as string) || null);
|
|
100
|
+
setPublishedAt(result.publishedAt || (docData.publishedAt as string) || null);
|
|
101
101
|
} catch {
|
|
102
102
|
onError("Failed to load document");
|
|
103
103
|
} finally {
|
|
@@ -134,7 +134,8 @@ export function DetailView({
|
|
|
134
134
|
? `/api/globals/${slug}`
|
|
135
135
|
: `/api/${slug}/${documentId}`;
|
|
136
136
|
|
|
137
|
-
const
|
|
137
|
+
const isDraft = status === "draft" || (data as any)?.status === "draft";
|
|
138
|
+
const result = (await apiPatch(endpoint, data, { autoToast: false, headers: { "X-Draft": String(isDraft) } }) as { data?: Record<string, unknown> });
|
|
138
139
|
const savedData = (result && (result.data || result)) || data;
|
|
139
140
|
|
|
140
141
|
if (!isAutosave) {
|
|
@@ -143,6 +144,7 @@ export function DetailView({
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
setData(savedData);
|
|
147
|
+
setStatus((savedData as any)?.status || status);
|
|
146
148
|
setSaveStatus("saved");
|
|
147
149
|
setUpdatedAt(new Date().toISOString());
|
|
148
150
|
|
|
@@ -151,7 +153,9 @@ export function DetailView({
|
|
|
151
153
|
setTimeout(() => setJustSaved(false), 3000);
|
|
152
154
|
|
|
153
155
|
if (!isAutosave) {
|
|
154
|
-
|
|
156
|
+
const isDraft = status === "draft" || (savedData as any)?.status === "draft";
|
|
157
|
+
if (isDraft) toast.warning("Draft saved");
|
|
158
|
+
else toast.success("Updated");
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
setTimeout(() => {
|
|
@@ -161,25 +165,29 @@ export function DetailView({
|
|
|
161
165
|
setSaveStatus("error");
|
|
162
166
|
if (!isAutosave) {
|
|
163
167
|
onError("Failed to save changes");
|
|
164
|
-
|
|
168
|
+
toast.error("Failed to save changes");
|
|
165
169
|
}
|
|
166
170
|
} finally {
|
|
167
171
|
setSaving(false);
|
|
168
172
|
}
|
|
169
173
|
},
|
|
170
|
-
[data, mode, slug, documentId, onSave, onError],
|
|
174
|
+
[data, mode, slug, documentId, status, onSave, onError],
|
|
171
175
|
);
|
|
172
176
|
|
|
173
177
|
const handlePublish = async () => {
|
|
174
178
|
try {
|
|
175
179
|
setSaving(true);
|
|
176
|
-
await
|
|
180
|
+
await apiPatch(`/api/${slug}/${documentId}`, data, {
|
|
181
|
+
autoToast: false,
|
|
182
|
+
headers: { "X-Draft": "false" },
|
|
183
|
+
} as any);
|
|
177
184
|
setStatus("published");
|
|
178
185
|
setPublishedAt(new Date().toISOString());
|
|
179
|
-
|
|
186
|
+
toast.success("Published successfully");
|
|
187
|
+
onSave();
|
|
180
188
|
} catch {
|
|
181
189
|
onError("Failed to publish");
|
|
182
|
-
|
|
190
|
+
toast.error("Failed to publish");
|
|
183
191
|
} finally {
|
|
184
192
|
setSaving(false);
|
|
185
193
|
}
|
|
@@ -188,10 +196,16 @@ export function DetailView({
|
|
|
188
196
|
const handleUnpublish = async () => {
|
|
189
197
|
try {
|
|
190
198
|
setSaving(true);
|
|
191
|
-
await
|
|
199
|
+
await apiPatch(`/api/${slug}/${documentId}`, { status: 'draft' }, {
|
|
200
|
+
autoToast: false,
|
|
201
|
+
headers: { "X-Draft": "false" },
|
|
202
|
+
} as any);
|
|
192
203
|
setStatus("draft");
|
|
204
|
+
toast.warning("Document unpublished");
|
|
205
|
+
onSave();
|
|
193
206
|
} catch {
|
|
194
207
|
onError("Failed to unpublish");
|
|
208
|
+
toast.error("Failed to unpublish");
|
|
195
209
|
} finally {
|
|
196
210
|
setSaving(false);
|
|
197
211
|
}
|
|
@@ -199,14 +213,15 @@ export function DetailView({
|
|
|
199
213
|
|
|
200
214
|
const handleDuplicate = async () => {
|
|
201
215
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
216
|
+
setSaving(true);
|
|
217
|
+
await apiPost(`/api/${slug}/${documentId}/duplicate`, undefined, { autoToast: false });
|
|
218
|
+
toast.success("Document duplicated");
|
|
219
|
+
} catch (err: unknown) {
|
|
220
|
+
const message = err instanceof Error ? err.message : "Failed to duplicate document";
|
|
221
|
+
onError(message);
|
|
222
|
+
toast.error(message);
|
|
223
|
+
} finally {
|
|
224
|
+
setSaving(false);
|
|
210
225
|
}
|
|
211
226
|
};
|
|
212
227
|
|
|
@@ -218,12 +233,12 @@ export function DetailView({
|
|
|
218
233
|
onConfirm: async () => {
|
|
219
234
|
try {
|
|
220
235
|
setDeleting(true);
|
|
221
|
-
await apiDelete(`/api/${slug}/${documentId}
|
|
236
|
+
await apiDelete(`/api/${slug}/${documentId}`, { autoToast: false });
|
|
222
237
|
onDelete?.();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
toast.error("Document deleted");
|
|
239
|
+
} catch (err: unknown) {
|
|
240
|
+
const message = err instanceof Error ? err.message : "Failed to delete document";
|
|
241
|
+
toast.error(message);
|
|
227
242
|
} finally {
|
|
228
243
|
setDeleting(false);
|
|
229
244
|
}
|
|
@@ -263,7 +278,7 @@ export function DetailView({
|
|
|
263
278
|
{ label: mode === "global" ? "Edit" : documentId ? "Edit" : "New" }
|
|
264
279
|
]}
|
|
265
280
|
title={
|
|
266
|
-
(mode === "global" ? label : (data
|
|
281
|
+
(mode === "global" ? label : ((resolveFieldValue(collection?.fields as any, data, collection?.admin?.useAsTitle || "title") as string) || data.name as string || documentId || `New ${collection?.singularLabel || label}`))
|
|
267
282
|
}
|
|
268
283
|
metadata={[
|
|
269
284
|
<Badge
|
|
@@ -325,8 +340,8 @@ export function DetailView({
|
|
|
325
340
|
layout={isSingleLayout ? "single" : "split"}
|
|
326
341
|
globalSlug={mode === "global" ? slug : undefined}
|
|
327
342
|
collectionSlug={mode === "collection" ? slug : undefined}
|
|
328
|
-
onActionSuccess={(message) =>
|
|
329
|
-
onActionError={(message) =>
|
|
343
|
+
onActionSuccess={(message) => toast.success(message)}
|
|
344
|
+
onActionError={(message) => toast.error(message)}
|
|
330
345
|
documentStatus={status}
|
|
331
346
|
justSaved={justSaved}
|
|
332
347
|
/>
|
|
@@ -341,41 +356,13 @@ export function DetailView({
|
|
|
341
356
|
Delete
|
|
342
357
|
</button>
|
|
343
358
|
)}
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
359
|
+
<SplitButton
|
|
360
|
+
status={status}
|
|
361
|
+
saveStatus={saving ? "saving" : "idle"}
|
|
362
|
+
hasChanges={hasChanges}
|
|
363
|
+
onPublish={() => handleSave(false)}
|
|
347
364
|
disabled={saving}
|
|
348
|
-
|
|
349
|
-
>
|
|
350
|
-
{saving ? (
|
|
351
|
-
<svg
|
|
352
|
-
className="w-4 h-4 animate-spin"
|
|
353
|
-
viewBox="0 0 24 24"
|
|
354
|
-
fill="none"
|
|
355
|
-
stroke="currentColor"
|
|
356
|
-
strokeWidth="2"
|
|
357
|
-
>
|
|
358
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
359
|
-
</svg>
|
|
360
|
-
) : (
|
|
361
|
-
<svg
|
|
362
|
-
className="w-4 h-4"
|
|
363
|
-
viewBox="0 0 24 24"
|
|
364
|
-
fill="none"
|
|
365
|
-
stroke="currentColor"
|
|
366
|
-
strokeWidth="2"
|
|
367
|
-
>
|
|
368
|
-
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
|
369
|
-
<polyline points="17 21 17 13 7 13 7 21" />
|
|
370
|
-
<polyline points="7 3 7 8 15 8" />
|
|
371
|
-
</svg>
|
|
372
|
-
)}
|
|
373
|
-
{saving
|
|
374
|
-
? "Saving..."
|
|
375
|
-
: mode === "global"
|
|
376
|
-
? "Save Configuration"
|
|
377
|
-
: "Save Document"}
|
|
378
|
-
</button>
|
|
365
|
+
/>
|
|
379
366
|
</div>
|
|
380
367
|
)}
|
|
381
368
|
</div>
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import CodeMirror from "@uiw/react-codemirror";
|
|
18
18
|
import { json } from "@codemirror/lang-json";
|
|
19
19
|
import { aura } from "@uiw/codemirror-theme-aura";
|
|
20
|
-
import { useUIStore } from "../lib/stores";
|
|
20
|
+
import { useUIStore, toast } from "../lib/stores";
|
|
21
21
|
import { Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
22
22
|
import { PageHeader } from "./ui/PageHeader";
|
|
23
23
|
import { Badge } from "./ui/Badge";
|
|
@@ -70,9 +70,10 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
70
70
|
loadKeys();
|
|
71
71
|
setShowCreateModal(false);
|
|
72
72
|
setNewKeyName("");
|
|
73
|
+
toast.success("API key generated");
|
|
73
74
|
} catch (e) {
|
|
74
75
|
console.error(e);
|
|
75
|
-
|
|
76
|
+
toast.error("Failed to generate API key");
|
|
76
77
|
}
|
|
77
78
|
};
|
|
78
79
|
|
|
@@ -85,9 +86,10 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
85
86
|
try {
|
|
86
87
|
await apiDelete(`/api/keys/${id}`);
|
|
87
88
|
loadKeys();
|
|
89
|
+
toast.success("API key revoked");
|
|
88
90
|
} catch (e) {
|
|
89
91
|
console.error(e);
|
|
90
|
-
|
|
92
|
+
toast.error("Failed to revoke API key");
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
});
|
|
@@ -180,7 +182,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
180
182
|
className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-all text-[var(--kyro-text-secondary)]"
|
|
181
183
|
onClick={() => {
|
|
182
184
|
navigator.clipboard.writeText(key.key);
|
|
183
|
-
|
|
185
|
+
toast.success("API key copied to clipboard");
|
|
184
186
|
}}
|
|
185
187
|
>
|
|
186
188
|
<Copy className="w-4 h-4" />
|
|
@@ -311,7 +313,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
311
313
|
type="button"
|
|
312
314
|
onClick={handleRunTest}
|
|
313
315
|
disabled={exploring || !testEndpoint}
|
|
314
|
-
className="px-8 py-4
|
|
316
|
+
className="kyro-btn kyro-btn-primary px-8 py-4 rounded-[1.5rem] font-bold text-sm shadow-xl disabled:opacity-50 disabled:cursor-not-allowed hover:scale-[1.02] transition-all flex items-center gap-3 shrink-0"
|
|
315
317
|
>
|
|
316
318
|
{exploring ? (
|
|
317
319
|
<RefreshCcw className="w-4 h-4 animate-spin" />
|
|
@@ -398,7 +400,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
398
400
|
<button
|
|
399
401
|
type="button"
|
|
400
402
|
onClick={confirmGenerateKey}
|
|
401
|
-
className="px-8 py-3 rounded-xl font-bold text-sm
|
|
403
|
+
className="kyro-btn kyro-btn-primary px-8 py-3 rounded-xl font-bold text-sm hover:opacity-90 shadow-lg shadow-[var(--kyro-primary)]/20 transition-all"
|
|
402
404
|
>
|
|
403
405
|
Generate Token
|
|
404
406
|
</button>
|
|
@@ -9,10 +9,15 @@ import DateField from "./fields/DateField";
|
|
|
9
9
|
import { MarkdownField } from "./fields/MarkdownField";
|
|
10
10
|
import TextField from "./fields/TextField";
|
|
11
11
|
import { BlocksField } from "./fields/BlocksField";
|
|
12
|
-
import
|
|
12
|
+
import { RichTextField } from "./fields";
|
|
13
13
|
import { ListField } from "./fields/ListField";
|
|
14
14
|
import RelationshipField from "./fields/RelationshipField";
|
|
15
|
+
import SecretField from "./fields/SecretField";
|
|
15
16
|
import FieldLayout from "./fields/FieldLayout";
|
|
17
|
+
import ArrayField from "./fields/ArrayField";
|
|
18
|
+
import { GroupLayout } from "./fields/GroupLayout";
|
|
19
|
+
import { ArrayLayout } from "./fields/ArrayLayout";
|
|
20
|
+
import { setChangeSource } from "../lib/change-source";
|
|
16
21
|
|
|
17
22
|
interface FieldRendererProps {
|
|
18
23
|
field: Field;
|
|
@@ -29,7 +34,12 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
29
34
|
error,
|
|
30
35
|
disabled,
|
|
31
36
|
}) => {
|
|
32
|
-
if (field.admin?.hidden) return null;
|
|
37
|
+
if (field.hidden === true || field.admin?.hidden === true) return null;
|
|
38
|
+
|
|
39
|
+
const onChangeKeystroke = (val: unknown) => {
|
|
40
|
+
setChangeSource("keystroke");
|
|
41
|
+
onChange(val);
|
|
42
|
+
};
|
|
33
43
|
|
|
34
44
|
switch (field.type) {
|
|
35
45
|
case "text":
|
|
@@ -39,7 +49,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
39
49
|
<TextField
|
|
40
50
|
field={field as any}
|
|
41
51
|
value={value}
|
|
42
|
-
onChange={
|
|
52
|
+
onChange={onChangeKeystroke}
|
|
43
53
|
error={error}
|
|
44
54
|
disabled={disabled}
|
|
45
55
|
/>
|
|
@@ -49,7 +59,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
49
59
|
<TextField
|
|
50
60
|
field={{ ...field, variant: "textarea" } as any}
|
|
51
61
|
value={value}
|
|
52
|
-
onChange={
|
|
62
|
+
onChange={onChangeKeystroke}
|
|
53
63
|
error={error}
|
|
54
64
|
disabled={disabled}
|
|
55
65
|
/>
|
|
@@ -59,7 +69,17 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
59
69
|
<TextField
|
|
60
70
|
field={{ ...field, variant: "password" } as any}
|
|
61
71
|
value={value}
|
|
62
|
-
onChange={
|
|
72
|
+
onChange={onChangeKeystroke}
|
|
73
|
+
error={error}
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
case "secret":
|
|
78
|
+
return (
|
|
79
|
+
<SecretField
|
|
80
|
+
field={field as any}
|
|
81
|
+
value={value as string | null | undefined}
|
|
82
|
+
onChange={onChange as (value: string) => void}
|
|
63
83
|
error={error}
|
|
64
84
|
disabled={disabled}
|
|
65
85
|
/>
|
|
@@ -105,19 +125,11 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
105
125
|
/>
|
|
106
126
|
);
|
|
107
127
|
case "richtext":
|
|
108
|
-
return (
|
|
109
|
-
<
|
|
110
|
-
field={field
|
|
111
|
-
value={value}
|
|
112
|
-
onChange={onChange}
|
|
113
|
-
disabled={disabled}
|
|
114
|
-
error={error}
|
|
115
|
-
/>
|
|
116
|
-
) : (
|
|
117
|
-
<BlocksField
|
|
118
|
-
field={field as any}
|
|
128
|
+
return (
|
|
129
|
+
<RichTextField
|
|
130
|
+
field={field}
|
|
119
131
|
value={value}
|
|
120
|
-
onChange={
|
|
132
|
+
onChange={onChangeKeystroke}
|
|
121
133
|
disabled={disabled}
|
|
122
134
|
error={error}
|
|
123
135
|
/>
|
|
@@ -127,7 +139,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
127
139
|
<MarkdownField
|
|
128
140
|
field={field as any}
|
|
129
141
|
value={value}
|
|
130
|
-
onChange={
|
|
142
|
+
onChange={onChangeKeystroke}
|
|
131
143
|
disabled={disabled}
|
|
132
144
|
error={error}
|
|
133
145
|
/>
|
|
@@ -137,7 +149,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
137
149
|
<CodeField
|
|
138
150
|
field={field as any}
|
|
139
151
|
value={value}
|
|
140
|
-
onChange={
|
|
152
|
+
onChange={onChangeKeystroke}
|
|
141
153
|
disabled={disabled}
|
|
142
154
|
error={error}
|
|
143
155
|
/>
|
|
@@ -174,6 +186,69 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
174
186
|
/>
|
|
175
187
|
</FieldLayout>
|
|
176
188
|
);
|
|
189
|
+
case "array":
|
|
190
|
+
return (
|
|
191
|
+
<ArrayLayout
|
|
192
|
+
field={field}
|
|
193
|
+
value={Array.isArray(value) ? value : []}
|
|
194
|
+
onChange={onChange}
|
|
195
|
+
disabled={disabled}
|
|
196
|
+
renderField={(nestedField, parentData, onNestedChange) => {
|
|
197
|
+
const nestedValue = parentData[nestedField.name];
|
|
198
|
+
return (
|
|
199
|
+
<FieldRenderer
|
|
200
|
+
key={nestedField.name}
|
|
201
|
+
field={nestedField}
|
|
202
|
+
value={nestedValue}
|
|
203
|
+
onChange={(val) => {
|
|
204
|
+
onNestedChange({
|
|
205
|
+
...parentData,
|
|
206
|
+
[nestedField.name]: val,
|
|
207
|
+
});
|
|
208
|
+
}}
|
|
209
|
+
disabled={disabled}
|
|
210
|
+
error={error}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
case "blocks":
|
|
217
|
+
return (
|
|
218
|
+
<BlocksField
|
|
219
|
+
field={field as any}
|
|
220
|
+
value={value}
|
|
221
|
+
onChange={onChangeKeystroke}
|
|
222
|
+
disabled={disabled}
|
|
223
|
+
error={error}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
case "group":
|
|
227
|
+
return (
|
|
228
|
+
<GroupLayout
|
|
229
|
+
field={field}
|
|
230
|
+
value={value as Record<string, unknown> | null}
|
|
231
|
+
onChange={onChange}
|
|
232
|
+
renderField={(nestedField, parentData, onNestedChange) => {
|
|
233
|
+
const nestedValue = parentData[nestedField.name];
|
|
234
|
+
return (
|
|
235
|
+
<FieldRenderer
|
|
236
|
+
key={nestedField.name}
|
|
237
|
+
field={nestedField}
|
|
238
|
+
value={nestedValue}
|
|
239
|
+
onChange={(val) => {
|
|
240
|
+
onNestedChange({
|
|
241
|
+
...parentData,
|
|
242
|
+
[nestedField.name]: val,
|
|
243
|
+
});
|
|
244
|
+
}}
|
|
245
|
+
disabled={disabled}
|
|
246
|
+
error={error}
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
177
252
|
case "color":
|
|
178
253
|
return (
|
|
179
254
|
<FieldLayout field={field} error={error}>
|