@kyro-cms/admin 0.12.3 → 0.12.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/index.cjs +64 -26
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +64 -26
- package/package.json +8 -2
- package/src/components/ActionBar.tsx +16 -16
- package/src/components/ApiHealth.tsx +522 -0
- package/src/components/ApiKeysManager.tsx +6 -4
- package/src/components/AuditLogsPage.tsx +4 -2
- package/src/components/AutoForm.tsx +10 -7
- package/src/components/BrandingHub.tsx +3 -1
- package/src/components/Dashboard.tsx +44 -37
- package/src/components/DashboardMetrics.tsx +168 -58
- package/src/components/DetailView.tsx +125 -50
- package/src/components/DeveloperCenter.tsx +5 -3
- package/src/components/GraphQLPlayground.tsx +10 -8
- package/src/components/ListView.tsx +66 -45
- package/src/components/LoginPage.tsx +21 -19
- package/src/components/MarketplaceManager.tsx +3 -1
- package/src/components/MediaGallery.tsx +12 -9
- package/src/components/PluginsManager.tsx +6 -4
- package/src/components/RestPlayground.tsx +8 -6
- package/src/components/SessionsManager.tsx +3 -1
- package/src/components/Sidebar.astro +180 -92
- package/src/components/UserManagement.tsx +8 -6
- package/src/components/UserMenu.tsx +19 -17
- package/src/components/VersionHistoryPanel.tsx +6 -4
- package/src/components/WebhookManager.tsx +143 -15
- package/src/components/autoform/AutoFormApiView.tsx +3 -1
- package/src/components/autoform/AutoFormEditView.tsx +5 -3
- package/src/components/autoform/AutoFormHeader.tsx +6 -6
- package/src/components/blocks/AccordionBlock.tsx +5 -3
- package/src/components/blocks/ArrayBlock.tsx +5 -3
- package/src/components/blocks/BlockWrapper.tsx +5 -3
- package/src/components/blocks/CodeBlock.tsx +4 -2
- package/src/components/blocks/FileBlock.tsx +5 -3
- package/src/components/blocks/HeroBlock.tsx +5 -3
- package/src/components/blocks/ImageBlock.tsx +6 -4
- package/src/components/blocks/ListBlock.tsx +5 -3
- package/src/components/blocks/ParagraphBlock.tsx +3 -1
- package/src/components/blocks/RelationshipBlock.tsx +5 -3
- package/src/components/blocks/RichTextBlock.tsx +5 -3
- package/src/components/blocks/VideoBlock.tsx +5 -3
- package/src/components/fields/AccordionField.tsx +10 -51
- package/src/components/fields/ArrayField.tsx +9 -7
- package/src/components/fields/ArrayLayout.tsx +3 -1
- package/src/components/fields/BlocksField.tsx +24 -75
- package/src/components/fields/ButtonField.tsx +3 -1
- package/src/components/fields/CardField.tsx +6 -4
- package/src/components/fields/GroupLayout.tsx +2 -49
- package/src/components/fields/HeadingField.tsx +3 -1
- package/src/components/fields/HeadingSubheadingField.tsx +4 -2
- package/src/components/fields/HeroField.tsx +8 -6
- package/src/components/fields/JSONField.tsx +9 -10
- package/src/components/fields/LinkField.tsx +4 -2
- package/src/components/fields/ListField.tsx +3 -1
- package/src/components/fields/MarkdownField.tsx +3 -17
- package/src/components/fields/RelationshipBlockField.tsx +7 -5
- package/src/components/fields/RelationshipField.tsx +3 -1
- package/src/components/fields/RichTextField.tsx +19 -16
- package/src/components/fields/SecretField.tsx +9 -69
- package/src/components/fields/SelectField.tsx +11 -6
- package/src/components/fields/TabsLayout.tsx +2 -48
- package/src/components/fields/TextField.tsx +9 -7
- package/src/components/fields/UploadField.tsx +3 -1
- package/src/components/fields/VideoField.tsx +6 -4
- package/src/components/ui/BlockDrawer.tsx +3 -1
- package/src/components/ui/CommandPalette.tsx +9 -7
- package/src/components/ui/CommandPaletteWrapper.tsx +3 -1
- package/src/components/ui/IconPickerModal.tsx +4 -2
- package/src/components/ui/PageHeader.tsx +48 -20
- package/src/components/users/UserDetail.tsx +3 -1
- package/src/components/users/UserForm.tsx +5 -3
- package/src/fields/examples/sample-field-2.tsx +3 -1
- package/src/fields/examples/sample-field.tsx +3 -1
- package/src/hooks/useAutoFormState.ts +19 -11
- package/src/integration.ts +21 -0
- package/src/layouts/AdminLayout.astro +252 -189
- package/src/layouts/AuthLayout.astro +52 -31
- package/src/lib/api.ts +7 -7
- package/src/lib/autoform-store.ts +4 -0
- package/src/lib/deep-equal.ts +3 -3
- package/src/lib/i18n.ts +44 -0
- package/src/lib/vite-shim-plugin.ts +3 -0
- package/src/locales/de.json +163 -0
- package/src/locales/en.json +163 -0
- package/src/locales/es.json +163 -0
- package/src/locales/fr.json +163 -0
- package/src/locales/pt.json +163 -0
- package/src/pages/403.astro +2 -1
- package/src/pages/[collection]/[id].astro +23 -10
- package/src/pages/[collection]/index.astro +48 -34
- package/src/pages/audit/index.astro +2 -1
- package/src/pages/graphql-explorer.astro +2 -1
- package/src/pages/graphql.astro +2 -1
- package/src/pages/health.astro +9 -0
- package/src/pages/index.astro +6 -5
- package/src/pages/keys.astro +2 -1
- package/src/pages/marketplace.astro +2 -1
- package/src/pages/media.astro +2 -1
- package/src/pages/plugins.astro +2 -1
- package/src/pages/rest-playground.astro +2 -1
- package/src/pages/roles/index.astro +16 -52
- package/src/pages/sessions.astro +2 -1
- package/src/pages/settings/[slug].astro +45 -30
- package/src/pages/users/[id].astro +1 -0
- package/src/pages/users/index.astro +2 -1
- package/src/pages/users/new.astro +2 -1
- package/src/pages/webhooks.astro +2 -1
- package/src/pages/index_ALT.astro +0 -3
|
@@ -10,9 +10,12 @@ import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
|
|
|
10
10
|
import { Spinner } from "./ui/Spinner";
|
|
11
11
|
import { Shimmer } from "./ui/Shimmer";
|
|
12
12
|
import { useUIStore, toast } from "../lib/stores";
|
|
13
|
+
import { useAutoFormStore } from "../lib/autoform-store";
|
|
13
14
|
import { PageHeader } from "./ui/PageHeader";
|
|
14
15
|
import { Badge } from "./ui/Badge";
|
|
15
16
|
import { SplitButton } from "./ui/SplitButton";
|
|
17
|
+
import { useTranslation } from "react-i18next";
|
|
18
|
+
import "../lib/i18n";
|
|
16
19
|
import { adminPath } from "../lib/paths";
|
|
17
20
|
import { resolveFieldValue } from "../lib/resolve-field-value";
|
|
18
21
|
|
|
@@ -40,6 +43,7 @@ export function DetailView({
|
|
|
40
43
|
onError,
|
|
41
44
|
mode = "collection",
|
|
42
45
|
}: DetailViewProps) {
|
|
46
|
+
const { t } = useTranslation();
|
|
43
47
|
const { confirm, alert } = useUIStore();
|
|
44
48
|
const [data, setData] = useState<Record<string, unknown>>({});
|
|
45
49
|
const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
|
|
@@ -53,6 +57,10 @@ export function DetailView({
|
|
|
53
57
|
const [updatedAt, setUpdatedAt] = useState<string | null>(null);
|
|
54
58
|
const [publishedAt, setPublishedAt] = useState<string | null>(null);
|
|
55
59
|
const [justSaved, setJustSaved] = useState(false);
|
|
60
|
+
const showPreview = useAutoFormStore((state) => state.showPreview);
|
|
61
|
+
const setShowPreview = useAutoFormStore((state) => state.setShowPreview);
|
|
62
|
+
const previewUrl = useAutoFormStore((state) => state.previewUrl);
|
|
63
|
+
const setPreviewUrl = useAutoFormStore((state) => state.setPreviewUrl);
|
|
56
64
|
|
|
57
65
|
const fields = global?.fields || collection?.fields || [];
|
|
58
66
|
const label = global?.label || collection?.label || "Document";
|
|
@@ -155,18 +163,23 @@ export function DetailView({
|
|
|
155
163
|
|
|
156
164
|
if (!isAutosave) {
|
|
157
165
|
const isDraft = status === "draft" || (savedData as any)?.status === "draft";
|
|
158
|
-
if (isDraft) toast.warning("Draft saved");
|
|
159
|
-
else toast.success("Updated");
|
|
166
|
+
if (isDraft) toast.warning(t("toast.draftSaved", { defaultValue: "Draft saved" }));
|
|
167
|
+
else toast.success(t("toast.updated", { defaultValue: "Updated" }));
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
setTimeout(() => {
|
|
163
171
|
setSaveStatus("idle");
|
|
164
172
|
}, 2000);
|
|
165
|
-
|
|
173
|
+
|
|
174
|
+
if (showPreview) {
|
|
175
|
+
refreshPreviewUrl(savedData);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
} catch (e: any) {
|
|
166
179
|
setSaveStatus("error");
|
|
167
180
|
if (!isAutosave) {
|
|
168
|
-
onError("Failed to save changes");
|
|
169
|
-
toast.error("Failed to save changes");
|
|
181
|
+
onError(t("toast.saveError", { defaultValue: "Failed to save changes" }));
|
|
182
|
+
toast.error(t("toast.saveError", { defaultValue: "Failed to save changes" }));
|
|
170
183
|
}
|
|
171
184
|
} finally {
|
|
172
185
|
setSaving(false);
|
|
@@ -184,11 +197,11 @@ export function DetailView({
|
|
|
184
197
|
} as any);
|
|
185
198
|
setStatus("published");
|
|
186
199
|
setPublishedAt(new Date().toISOString());
|
|
187
|
-
toast.success("Published successfully");
|
|
200
|
+
toast.success(t("toast.published", { defaultValue: "Published successfully" }));
|
|
188
201
|
onSave();
|
|
189
202
|
} catch {
|
|
190
|
-
onError("Failed to publish");
|
|
191
|
-
toast.error("Failed to publish");
|
|
203
|
+
onError(t("toast.publishError", { defaultValue: "Failed to publish" }));
|
|
204
|
+
toast.error(t("toast.publishError", { defaultValue: "Failed to publish" }));
|
|
192
205
|
} finally {
|
|
193
206
|
setSaving(false);
|
|
194
207
|
}
|
|
@@ -202,11 +215,11 @@ export function DetailView({
|
|
|
202
215
|
headers: { "X-Draft": "false" },
|
|
203
216
|
} as any);
|
|
204
217
|
setStatus("draft");
|
|
205
|
-
toast.warning("Document unpublished");
|
|
218
|
+
toast.warning(t("toast.unpublished", { defaultValue: "Document unpublished" }));
|
|
206
219
|
onSave();
|
|
207
220
|
} catch {
|
|
208
|
-
onError("Failed to unpublish");
|
|
209
|
-
toast.error("Failed to unpublish");
|
|
221
|
+
onError(t("toast.unpublishError", { defaultValue: "Failed to unpublish" }));
|
|
222
|
+
toast.error(t("toast.unpublishError", { defaultValue: "Failed to unpublish" }));
|
|
210
223
|
} finally {
|
|
211
224
|
setSaving(false);
|
|
212
225
|
}
|
|
@@ -216,14 +229,14 @@ export function DetailView({
|
|
|
216
229
|
try {
|
|
217
230
|
setIsDuplicating(true);
|
|
218
231
|
const response = await apiPost(`/api/${slug}/${documentId}/duplicate`, undefined, { autoToast: false }) as { data?: { id?: string } };
|
|
219
|
-
toast.success("Document duplicated");
|
|
232
|
+
toast.success(t("toast.duplicated", { defaultValue: "Document duplicated" }));
|
|
220
233
|
if (response?.data?.id) {
|
|
221
234
|
window.location.href = `${adminPath}/${slug}/${response.data.id}`;
|
|
222
235
|
} else {
|
|
223
236
|
window.location.href = `${adminPath}/${slug}`;
|
|
224
237
|
}
|
|
225
238
|
} catch (e) {
|
|
226
|
-
toast.error((e as Error).message || "Failed to duplicate document");
|
|
239
|
+
toast.error((e as Error).message || t("toast.duplicateError", { defaultValue: "Failed to duplicate document" }));
|
|
227
240
|
} finally {
|
|
228
241
|
setIsDuplicating(false);
|
|
229
242
|
}
|
|
@@ -239,9 +252,9 @@ export function DetailView({
|
|
|
239
252
|
delete (copyPayload as any).status;
|
|
240
253
|
|
|
241
254
|
await navigator.clipboard.writeText(JSON.stringify(copyPayload));
|
|
242
|
-
toast.success("Document data copied to clipboard");
|
|
255
|
+
toast.success(t("toast.dataCopied", { defaultValue: "Document data copied to clipboard" }));
|
|
243
256
|
} catch (e) {
|
|
244
|
-
toast.error("Failed to copy document data");
|
|
257
|
+
toast.error(t("toast.copyError", { defaultValue: "Failed to copy document data" }));
|
|
245
258
|
}
|
|
246
259
|
};
|
|
247
260
|
|
|
@@ -262,16 +275,16 @@ export function DetailView({
|
|
|
262
275
|
updatedAt: prev.updatedAt,
|
|
263
276
|
status: prev.status,
|
|
264
277
|
}));
|
|
265
|
-
toast.success("Document data pasted");
|
|
278
|
+
toast.success(t("toast.dataPasted", { defaultValue: "Document data pasted" }));
|
|
266
279
|
} catch (e) {
|
|
267
|
-
toast.error("Clipboard does not contain valid document JSON");
|
|
280
|
+
toast.error(t("toast.pasteError", { defaultValue: "Clipboard does not contain valid document JSON" }));
|
|
268
281
|
}
|
|
269
282
|
};
|
|
270
283
|
|
|
271
284
|
const handleDeleteTrigger = () => {
|
|
272
285
|
confirm({
|
|
273
|
-
title:
|
|
274
|
-
message: "This action cannot be undone.",
|
|
286
|
+
title: t("detailView.deleteTitle", { defaultValue: "Delete {{label}}?", label }),
|
|
287
|
+
message: t("detailView.deleteMessage", { defaultValue: "This action cannot be undone." }),
|
|
275
288
|
variant: "danger",
|
|
276
289
|
onConfirm: async () => {
|
|
277
290
|
try {
|
|
@@ -284,7 +297,37 @@ export function DetailView({
|
|
|
284
297
|
}
|
|
285
298
|
});
|
|
286
299
|
};
|
|
300
|
+
const refreshPreviewUrl = async (docData: any = data) => {
|
|
301
|
+
try {
|
|
302
|
+
const endpoint = mode === "global" ? `/api/globals/${slug}/preview-url` : `/api/${slug}/preview-url`;
|
|
303
|
+
console.log("[Kyro Preview] Calling endpoint:", endpoint, "with data keys:", Object.keys(docData || {}), "documentId:", documentId);
|
|
304
|
+
const res = await apiPost(endpoint, { ...docData, id: documentId }, { autoToast: false });
|
|
305
|
+
console.log("[Kyro Preview] Response:", JSON.stringify(res));
|
|
306
|
+
if (res && (res as any).url) {
|
|
307
|
+
console.log("[Kyro Preview] Setting previewUrl:", (res as any).url);
|
|
308
|
+
setPreviewUrl((res as any).url);
|
|
309
|
+
} else {
|
|
310
|
+
console.warn("[Kyro Preview] No url in response:", res);
|
|
311
|
+
}
|
|
312
|
+
} catch (e: any) {
|
|
313
|
+
console.error("[Kyro Preview] Error:", e.message, e);
|
|
314
|
+
toast.error(e.message || "Failed to generate preview URL");
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (showPreview) {
|
|
320
|
+
refreshPreviewUrl(data);
|
|
321
|
+
}
|
|
322
|
+
}, [showPreview]);
|
|
287
323
|
|
|
324
|
+
const togglePreview = () => {
|
|
325
|
+
const nextState = !showPreview;
|
|
326
|
+
setShowPreview(nextState);
|
|
327
|
+
if (nextState) {
|
|
328
|
+
refreshPreviewUrl(data);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
288
331
|
if (loading) {
|
|
289
332
|
return (
|
|
290
333
|
<div className="kyro-detail">
|
|
@@ -309,15 +352,15 @@ export function DetailView({
|
|
|
309
352
|
<PageHeader
|
|
310
353
|
back={{ onClick: onBack }}
|
|
311
354
|
breadcrumbs={[
|
|
312
|
-
{ label: mode === "global" ? "Globals" : "Collections" },
|
|
355
|
+
{ label: mode === "global" ? t("detailView.globals", { defaultValue: "Globals" }) : t("detailView.collections", { defaultValue: "Collections" }) },
|
|
313
356
|
{
|
|
314
357
|
label: label,
|
|
315
358
|
href: mode === "collection" ? `${adminPath}/${slug}` : undefined
|
|
316
359
|
},
|
|
317
|
-
{ label: mode === "global" ? "Edit" : documentId ? "Edit" : "New" }
|
|
360
|
+
{ label: mode === "global" ? t("actions.edit", { defaultValue: "Edit" }) : documentId ? t("actions.edit", { defaultValue: "Edit" }) : t("actions.new", { defaultValue: "New" }) }
|
|
318
361
|
]}
|
|
319
362
|
title={
|
|
320
|
-
(mode === "global" ? label : ((resolveFieldValue(collection?.fields as any, data, collection?.admin?.useAsTitle || "title") as string) || data.name as string || documentId ||
|
|
363
|
+
(mode === "global" ? label : ((resolveFieldValue(collection?.fields as any, data, collection?.admin?.useAsTitle || "title") as string) || data.name as string || documentId || t("detailView.newDocument", { defaultValue: "New {{label}}", label: collection?.singularLabel || label })))
|
|
321
364
|
}
|
|
322
365
|
metadata={[
|
|
323
366
|
<Badge
|
|
@@ -326,7 +369,7 @@ export function DetailView({
|
|
|
326
369
|
dot
|
|
327
370
|
className="text-[10px] font-bold "
|
|
328
371
|
>
|
|
329
|
-
{status}
|
|
372
|
+
{t(`status.${status}`, { defaultValue: status })}
|
|
330
373
|
</Badge>
|
|
331
374
|
]}
|
|
332
375
|
/>
|
|
@@ -344,9 +387,7 @@ export function DetailView({
|
|
|
344
387
|
onViewHistory={() => {
|
|
345
388
|
window.dispatchEvent(new CustomEvent('kyro:show-version-history'));
|
|
346
389
|
}}
|
|
347
|
-
onPreview={()
|
|
348
|
-
window.open(`/preview/${slug}/${documentId}`, "_blank")
|
|
349
|
-
}
|
|
390
|
+
onPreview={((collection as any)?.admin?.disablePreview || (global as any)?.admin?.disablePreview) ? undefined : togglePreview}
|
|
350
391
|
onDelete={handleDeleteTrigger}
|
|
351
392
|
onBack={onBack}
|
|
352
393
|
onToggleSidebar={() => window.dispatchEvent(new CustomEvent("toggle-sidebar"))}
|
|
@@ -356,16 +397,18 @@ export function DetailView({
|
|
|
356
397
|
|
|
357
398
|
<div
|
|
358
399
|
className={
|
|
359
|
-
|
|
360
|
-
? "w-full pt-4 md:pt-
|
|
361
|
-
:
|
|
400
|
+
showPreview
|
|
401
|
+
? "w-full mx-auto grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-8 pt-4 md:pt-0 h-[calc(100vh-140px)]"
|
|
402
|
+
: isSingleLayout
|
|
403
|
+
? "w-full pt-4 md:pt-8"
|
|
404
|
+
: "w-full mx-auto grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-4 md:gap-8 pt-4 md:pt-0"
|
|
362
405
|
}
|
|
363
406
|
>
|
|
364
|
-
<div className=
|
|
407
|
+
<div className={`space-y-4 md:space-y-8 min-w-0 ${showPreview ? "overflow-y-auto pr-2 pb-20" : ""}`}>
|
|
365
408
|
<div className="surface-tile p-4 md:p-8">
|
|
366
409
|
<div className="flex items-center justify-between mb-8 px-1">
|
|
367
410
|
<h2 className="text-[10px] font-bold tracking-[0.2em] opacity-40">
|
|
368
|
-
Core Configuration
|
|
411
|
+
{t("detailView.coreConfiguration", { defaultValue: "Core Configuration" })}
|
|
369
412
|
</h2>
|
|
370
413
|
<div className="h-px flex-1 bg-[var(--kyro-border)] ml-6 opacity-30" />
|
|
371
414
|
</div>
|
|
@@ -394,7 +437,7 @@ export function DetailView({
|
|
|
394
437
|
disabled={isDeleting || saving}
|
|
395
438
|
className="kyro-btn kyro-btn-sm text-[var(--kyro-danger)] hover:bg-[var(--kyro-danger)]/10 w-full justify-start mt-2"
|
|
396
439
|
>
|
|
397
|
-
{isDeleting ? "Deleting..." : "Delete Document"}
|
|
440
|
+
{isDeleting ? t("actions.deleting", { defaultValue: "Deleting..." }) : t("actions.deleteDocument", { defaultValue: "Delete Document" })}
|
|
398
441
|
</button>
|
|
399
442
|
)}
|
|
400
443
|
<SplitButton
|
|
@@ -409,16 +452,46 @@ export function DetailView({
|
|
|
409
452
|
</div>
|
|
410
453
|
</div>
|
|
411
454
|
|
|
412
|
-
{
|
|
455
|
+
{showPreview && (
|
|
456
|
+
<div className="surface-tile flex flex-col overflow-hidden h-full border border-[var(--kyro-border)] rounded-xl animate-in fade-in slide-in-from-right-4 duration-500 shadow-xl bg-white dark:bg-black">
|
|
457
|
+
<div className="bg-[var(--kyro-surface-accent)] border-b border-[var(--kyro-border)] p-2 flex items-center justify-between shrink-0">
|
|
458
|
+
<div className="flex items-center gap-2 pl-2">
|
|
459
|
+
<div className="w-3 h-3 rounded-full bg-red-400/80"></div>
|
|
460
|
+
<div className="w-3 h-3 rounded-full bg-amber-400/80"></div>
|
|
461
|
+
<div className="w-3 h-3 rounded-full bg-green-400/80"></div>
|
|
462
|
+
</div>
|
|
463
|
+
<div className="text-[10px] font-mono opacity-50 truncate max-w-[60%] bg-[var(--kyro-bg)] px-3 py-1 rounded">
|
|
464
|
+
{previewUrl || t("detailView.generatingPreview", { defaultValue: "Generating preview URL..." })}
|
|
465
|
+
</div>
|
|
466
|
+
<div className="w-16"></div>
|
|
467
|
+
</div>
|
|
468
|
+
<div className="flex-1 bg-white relative">
|
|
469
|
+
{!previewUrl ? (
|
|
470
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
471
|
+
<Spinner className="w-6 h-6 text-[var(--kyro-primary)]" />
|
|
472
|
+
</div>
|
|
473
|
+
) : (
|
|
474
|
+
<iframe
|
|
475
|
+
src={previewUrl}
|
|
476
|
+
className="w-full h-full border-0 bg-white"
|
|
477
|
+
title={t("tooltips.preview", { defaultValue: "Preview" })}
|
|
478
|
+
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
|
|
479
|
+
/>
|
|
480
|
+
)}
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
)}
|
|
484
|
+
|
|
485
|
+
{!isSingleLayout && !showPreview && (
|
|
413
486
|
<div className="space-y-4 md:space-y-6 animate-in fade-in slide-in-from-right-4 duration-500">
|
|
414
487
|
<div className="surface-tile p-4 md:p-8">
|
|
415
488
|
<h3 className="text-[10px] font-bold tracking-[0.2em] opacity-40 mb-4 md:mb-6">
|
|
416
|
-
Metadata
|
|
489
|
+
{t("detailView.metadata", { defaultValue: "Metadata" })}
|
|
417
490
|
</h3>
|
|
418
491
|
<div className="space-y-4 md:space-y-6">
|
|
419
492
|
<div className="flex flex-col gap-2">
|
|
420
493
|
<span className="text-[10px] font-bold tracking-widest opacity-40">
|
|
421
|
-
Dynamic Status
|
|
494
|
+
{t("detailView.dynamicStatus", { defaultValue: "Dynamic Status" })}
|
|
422
495
|
</span>
|
|
423
496
|
<div>
|
|
424
497
|
<span
|
|
@@ -430,7 +503,7 @@ export function DetailView({
|
|
|
430
503
|
</div>
|
|
431
504
|
<div className="flex flex-col gap-2">
|
|
432
505
|
<span className="text-[10px] font-bold tracking-widest opacity-40">
|
|
433
|
-
Date Created
|
|
506
|
+
{t("detailView.dateCreated", { defaultValue: "Date Created" })}
|
|
434
507
|
</span>
|
|
435
508
|
<span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
|
|
436
509
|
{createdAt
|
|
@@ -438,12 +511,12 @@ export function DetailView({
|
|
|
438
511
|
dateStyle: "medium",
|
|
439
512
|
timeStyle: "short",
|
|
440
513
|
})
|
|
441
|
-
: "N/A"}
|
|
514
|
+
: t("common.na", { defaultValue: "N/A" })}
|
|
442
515
|
</span>
|
|
443
516
|
</div>
|
|
444
517
|
<div className="flex flex-col gap-2">
|
|
445
518
|
<span className="text-[10px] font-bold tracking-widest opacity-40">
|
|
446
|
-
Last Modified
|
|
519
|
+
{t("detailView.lastModified", { defaultValue: "Last Modified" })}
|
|
447
520
|
</span>
|
|
448
521
|
<span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
|
|
449
522
|
{updatedAt
|
|
@@ -451,13 +524,13 @@ export function DetailView({
|
|
|
451
524
|
dateStyle: "medium",
|
|
452
525
|
timeStyle: "short",
|
|
453
526
|
})
|
|
454
|
-
: "Just now"}
|
|
527
|
+
: t("detailView.justNow", { defaultValue: "Just now" })}
|
|
455
528
|
</span>
|
|
456
529
|
</div>
|
|
457
530
|
{publishedAt && (
|
|
458
531
|
<div className="flex flex-col gap-2">
|
|
459
532
|
<span className="text-[10px] font-bold tracking-widest opacity-40">
|
|
460
|
-
Public At
|
|
533
|
+
{t("detailView.publicAt", { defaultValue: "Public At" })}
|
|
461
534
|
</span>
|
|
462
535
|
<span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
|
|
463
536
|
{new Date(publishedAt).toLocaleString("en-US", {
|
|
@@ -472,7 +545,7 @@ export function DetailView({
|
|
|
472
545
|
|
|
473
546
|
<div className="surface-tile p-4 md:p-8 bg-[var(--kyro-bg-secondary)]">
|
|
474
547
|
<h3 className="text-[10px] font-bold tracking-[0.2em] opacity-40 mb-3 md:mb-4">
|
|
475
|
-
Quick Links
|
|
548
|
+
{t("detailView.quickLinks", { defaultValue: "Quick Links" })}
|
|
476
549
|
</h3>
|
|
477
550
|
<div className="space-y-2 md:space-y-3">
|
|
478
551
|
<button
|
|
@@ -481,21 +554,23 @@ export function DetailView({
|
|
|
481
554
|
disabled={isDuplicating || saving}
|
|
482
555
|
className="kyro-btn kyro-btn-sm kyro-btn-ghost w-full justify-start"
|
|
483
556
|
>
|
|
484
|
-
{isDuplicating ? "Duplicating..." : "Duplicate Document"}
|
|
485
|
-
</button>
|
|
486
|
-
<button
|
|
487
|
-
type="button"
|
|
488
|
-
onClick={() => window.open(`/preview/${slug}/${documentId}`, "_blank")}
|
|
489
|
-
className="kyro-btn kyro-btn-sm kyro-btn-ghost w-full justify-start"
|
|
490
|
-
>
|
|
491
|
-
View Public Page
|
|
557
|
+
{isDuplicating ? t("actions.duplicating", { defaultValue: "Duplicating..." }) : t("actions.duplicateDocument", { defaultValue: "Duplicate Document" })}
|
|
492
558
|
</button>
|
|
559
|
+
{previewUrl && (
|
|
560
|
+
<button
|
|
561
|
+
type="button"
|
|
562
|
+
onClick={() => window.open(previewUrl, "_blank")}
|
|
563
|
+
className="kyro-btn kyro-btn-sm kyro-btn-ghost w-full justify-start"
|
|
564
|
+
>
|
|
565
|
+
{t("detailView.openPreview", { defaultValue: "Open Preview in New Tab" })}
|
|
566
|
+
</button>
|
|
567
|
+
)}
|
|
493
568
|
<button
|
|
494
569
|
type="button"
|
|
495
570
|
onClick={handleDeleteTrigger}
|
|
496
571
|
className="kyro-btn kyro-btn-sm kyro-btn-ghost w-full justify-start text-[var(--kyro-error)] hover:bg-[var(--kyro-danger-bg)]"
|
|
497
572
|
>
|
|
498
|
-
Delete Entry
|
|
573
|
+
{t("actions.deleteEntry", { defaultValue: "Delete Entry" })}
|
|
499
574
|
</button>
|
|
500
575
|
</div>
|
|
501
576
|
</div>
|
|
@@ -21,6 +21,7 @@ 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";
|
|
24
|
+
import { useTranslation } from "react-i18next";
|
|
24
25
|
|
|
25
26
|
// @ts-ignore
|
|
26
27
|
const API_BASE = typeof __KYRO_API_PATH__ !== 'undefined' ? __KYRO_API_PATH__ : '/api';
|
|
@@ -34,6 +35,7 @@ interface ApiKey {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function DeveloperCenter({ collections }: { collections: Record<string, unknown> }) {
|
|
38
|
+
const { t } = useTranslation();
|
|
37
39
|
const [keys, setKeys] = useState<ApiKey[]>([]);
|
|
38
40
|
const [loading, setLoading] = useState(false);
|
|
39
41
|
const [showKey, setShowKey] = useState<string | null>(null);
|
|
@@ -114,7 +116,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
114
116
|
return (
|
|
115
117
|
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
|
|
116
118
|
<PageHeader
|
|
117
|
-
title="Developer Center"
|
|
119
|
+
title={t("tooltips.developerCenter", { defaultValue: "Developer Center" })}
|
|
118
120
|
description="Provision access keys and explore the headless API ecosystem."
|
|
119
121
|
icon={Code2}
|
|
120
122
|
actions={[
|
|
@@ -206,7 +208,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
206
208
|
type="button"
|
|
207
209
|
onClick={() => handleRevokeKey(key.id)}
|
|
208
210
|
className="p-3.5 bg-red-500/5 text-red-500 rounded-2xl hover:bg-red-500/10 transition-all border border-transparent hover:border-red-500/30 self-start md:self-center"
|
|
209
|
-
title="Revoke Key"
|
|
211
|
+
title={t("tooltips.revokeKey", { defaultValue: "Revoke Key" })}
|
|
210
212
|
>
|
|
211
213
|
<Trash2 className="w-5 h-5" />
|
|
212
214
|
</button>
|
|
@@ -367,7 +369,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
|
|
|
367
369
|
<Modal
|
|
368
370
|
open={showCreateModal}
|
|
369
371
|
onClose={() => setShowCreateModal(false)}
|
|
370
|
-
title="Generate Access Token"
|
|
372
|
+
title={t("tooltips.generateAccessToken", { defaultValue: "Generate Access Token" })}
|
|
371
373
|
size="lg"
|
|
372
374
|
>
|
|
373
375
|
<ModalContent>
|
|
@@ -197,6 +197,7 @@ import { javascript } from "@codemirror/lang-javascript";
|
|
|
197
197
|
import { json } from "@codemirror/lang-json";
|
|
198
198
|
import { CompletionContext, autocompletion } from "@codemirror/autocomplete";
|
|
199
199
|
import { aura } from "@uiw/codemirror-theme-aura";
|
|
200
|
+
import { useTranslation } from "react-i18next";
|
|
200
201
|
|
|
201
202
|
function prettifyQuery(query: string): string {
|
|
202
203
|
let indent = 0;
|
|
@@ -421,6 +422,7 @@ export function GraphQLPlayground({
|
|
|
421
422
|
initialVariables,
|
|
422
423
|
initialShowDocs = false,
|
|
423
424
|
}: GraphQLPlaygroundProps) {
|
|
425
|
+
const { t } = useTranslation();
|
|
424
426
|
const [token, setToken] = useState<string>("");
|
|
425
427
|
const [showToken, setShowToken] = useState(false);
|
|
426
428
|
const [isConnected, setIsConnected] = useState(false);
|
|
@@ -837,7 +839,7 @@ export function GraphQLPlayground({
|
|
|
837
839
|
type={showToken ? "text" : "password"}
|
|
838
840
|
value={token}
|
|
839
841
|
onChange={(e) => setToken(e.target.value)}
|
|
840
|
-
placeholder="API key"
|
|
842
|
+
placeholder={t("fields.apiKey", { defaultValue: "API key" })}
|
|
841
843
|
className="w-24 px-2 py-1 text-[10px] font-mono bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-md text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
842
844
|
/>
|
|
843
845
|
<button onClick={() => setShowToken(!showToken)} className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)]">
|
|
@@ -846,22 +848,22 @@ export function GraphQLPlayground({
|
|
|
846
848
|
</div>
|
|
847
849
|
)}
|
|
848
850
|
<div className="ml-auto flex items-center gap-1 flex-wrap justify-end">
|
|
849
|
-
<button onClick={() => { setShowDocs(!showDocs); setRightTab("docs"); }} className={`p-1.5 rounded-lg transition-all ${rightTab === "docs" && showDocs ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="Schema docs">
|
|
851
|
+
<button onClick={() => { setShowDocs(!showDocs); setRightTab("docs"); }} className={`p-1.5 rounded-lg transition-all ${rightTab === "docs" && showDocs ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title={t("tooltips.schemaDocs", { defaultValue: "Schema docs" })}>
|
|
850
852
|
<Book className="w-3.5 h-3.5" />
|
|
851
853
|
</button>
|
|
852
|
-
<button onClick={() => setRightTab("history")} className={`p-1.5 rounded-lg transition-all ${rightTab === "history" ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="History">
|
|
854
|
+
<button onClick={() => setRightTab("history")} className={`p-1.5 rounded-lg transition-all ${rightTab === "history" ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)]" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title={t("tooltips.history", { defaultValue: "History" })}>
|
|
853
855
|
<Clock className="w-3.5 h-3.5" />
|
|
854
856
|
</button>
|
|
855
|
-
<button onClick={handlePrettify} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title="Prettify (Cmd+Shift+P)">
|
|
857
|
+
<button onClick={handlePrettify} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title={t("tooltips.prettifyCmdshiftp", { defaultValue: "Prettify (Cmd+Shift+P)" })}>
|
|
856
858
|
<Code2 className="w-3.5 h-3.5" />
|
|
857
859
|
</button>
|
|
858
|
-
<button onClick={handleCopyResponse} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title="Copy response">
|
|
860
|
+
<button onClick={handleCopyResponse} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title={t("tooltips.copyResponse", { defaultValue: "Copy response" })}>
|
|
859
861
|
{copied ? <Check className="w-3.5 h-3.5 text-[var(--kyro-success)]" /> : <Copy className="w-3.5 h-3.5" />}
|
|
860
862
|
</button>
|
|
861
|
-
<button onClick={handleDownloadResponse} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title="Download response">
|
|
863
|
+
<button onClick={handleDownloadResponse} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]" title={t("tooltips.downloadResponse", { defaultValue: "Download response" })}>
|
|
862
864
|
<Download className="w-3.5 h-3.5" />
|
|
863
865
|
</button>
|
|
864
|
-
<button onClick={handleClearEditor} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-danger)] hover:bg-[var(--kyro-danger-bg)]" title="Clear editor">
|
|
866
|
+
<button onClick={handleClearEditor} className="p-1.5 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-danger)] hover:bg-[var(--kyro-danger-bg)]" title={t("tooltips.clearEditor", { defaultValue: "Clear editor" })}>
|
|
865
867
|
<Trash2 className="w-3.5 h-3.5" />
|
|
866
868
|
</button>
|
|
867
869
|
<div className="h-4 w-px bg-[var(--kyro-border)] mx-1" />
|
|
@@ -977,7 +979,7 @@ export function GraphQLPlayground({
|
|
|
977
979
|
type="text"
|
|
978
980
|
value={searchQuery}
|
|
979
981
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
980
|
-
placeholder="Search types and fields..."
|
|
982
|
+
placeholder={t("fields.searchTypesAndFields", { defaultValue: "Search types and fields..." })}
|
|
981
983
|
className="w-full pl-7 pr-6 py-1 text-[10px] bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-md text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
982
984
|
/>
|
|
983
985
|
{searchQuery && (
|