@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
|
@@ -17,12 +17,14 @@ import {
|
|
|
17
17
|
ExternalLink,
|
|
18
18
|
Zap,
|
|
19
19
|
Shield,
|
|
20
|
+
Activity,
|
|
21
|
+
XCircle,
|
|
20
22
|
} from "./ui/icons";
|
|
21
23
|
import { useUIStore, toast } from "../lib/stores";
|
|
22
24
|
import { Modal, ModalContent, ModalActions } from "./ui/Modal";
|
|
23
25
|
import { Badge } from "./ui/Badge";
|
|
24
26
|
import { PageHeader } from "./ui/PageHeader";
|
|
25
|
-
|
|
27
|
+
import { useTranslation } from "react-i18next";
|
|
26
28
|
|
|
27
29
|
interface WebhookConfigField {
|
|
28
30
|
name: string;
|
|
@@ -52,17 +54,29 @@ interface WebhookItem {
|
|
|
52
54
|
action?: string;
|
|
53
55
|
config?: Record<string, string>;
|
|
54
56
|
lastTriggered?: string;
|
|
57
|
+
lastError?: string;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface WebhookDelivery {
|
|
62
|
+
id: string;
|
|
63
|
+
webhookId: string;
|
|
64
|
+
event: string;
|
|
65
|
+
status: "pending" | "success" | "failed" | "retrying";
|
|
66
|
+
responseStatus?: number;
|
|
67
|
+
responseBody?: string;
|
|
68
|
+
error?: string;
|
|
69
|
+
duration?: number;
|
|
55
70
|
createdAt: string;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
const ACTION_ICONS: Record<string, string> = {
|
|
59
74
|
generic: "🔗",
|
|
60
|
-
"github-
|
|
61
|
-
"dokploy-deploy": "🚀",
|
|
62
|
-
"coolify-deploy": "🎯",
|
|
75
|
+
"github-push": "⚙️",
|
|
63
76
|
};
|
|
64
77
|
|
|
65
78
|
export function WebhookManager() {
|
|
79
|
+
const { t } = useTranslation();
|
|
66
80
|
const {
|
|
67
81
|
items: webhooks,
|
|
68
82
|
loading,
|
|
@@ -97,6 +111,11 @@ export function WebhookManager() {
|
|
|
97
111
|
});
|
|
98
112
|
const [createError, setCreateError] = useState("");
|
|
99
113
|
|
|
114
|
+
const [showHistoryModal, setShowHistoryModal] = useState(false);
|
|
115
|
+
const [selectedWebhookId, setSelectedWebhookId] = useState<string | null>(null);
|
|
116
|
+
const [deliveryHistory, setDeliveryHistory] = useState<WebhookDelivery[]>([]);
|
|
117
|
+
const [loadingHistory, setLoadingHistory] = useState(false);
|
|
118
|
+
|
|
100
119
|
useEffect(() => {
|
|
101
120
|
loadActions();
|
|
102
121
|
}, []);
|
|
@@ -168,9 +187,10 @@ export function WebhookManager() {
|
|
|
168
187
|
await create(formData);
|
|
169
188
|
resetCreateModal();
|
|
170
189
|
toast.success(`Webhook created: ${formData.name}`);
|
|
171
|
-
} catch (e) {
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
} catch (e: any) {
|
|
191
|
+
const errorMsg = e.message || "Failed to create webhook";
|
|
192
|
+
setCreateError(errorMsg);
|
|
193
|
+
toast.error(errorMsg);
|
|
174
194
|
}
|
|
175
195
|
};
|
|
176
196
|
|
|
@@ -204,6 +224,21 @@ export function WebhookManager() {
|
|
|
204
224
|
}
|
|
205
225
|
};
|
|
206
226
|
|
|
227
|
+
const handleViewHistory = async (id: string) => {
|
|
228
|
+
setSelectedWebhookId(id);
|
|
229
|
+
setShowHistoryModal(true);
|
|
230
|
+
setLoadingHistory(true);
|
|
231
|
+
try {
|
|
232
|
+
const data = await apiGet<{ docs: WebhookDelivery[] }>(`/api/webhooks/${id}/history`);
|
|
233
|
+
setDeliveryHistory(data.docs || []);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.error("Failed to load history:", e);
|
|
236
|
+
toast.error("Failed to load delivery history");
|
|
237
|
+
} finally {
|
|
238
|
+
setLoadingHistory(false);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
207
242
|
const eventOptions = [
|
|
208
243
|
{
|
|
209
244
|
label: "Create",
|
|
@@ -231,7 +266,7 @@ export function WebhookManager() {
|
|
|
231
266
|
return (
|
|
232
267
|
<div className="w-full space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-32">
|
|
233
268
|
<PageHeader
|
|
234
|
-
title="Webhooks"
|
|
269
|
+
title={t("tooltips.webhooks", { defaultValue: "Webhooks" })}
|
|
235
270
|
description="Get notified when your content changes."
|
|
236
271
|
icon={Webhook}
|
|
237
272
|
actions={[
|
|
@@ -359,16 +394,34 @@ export function WebhookManager() {
|
|
|
359
394
|
? `Last triggered ${new Date(webhook.lastTriggered).toLocaleDateString()}`
|
|
360
395
|
: "Never triggered"}
|
|
361
396
|
</div>
|
|
397
|
+
{webhook.lastError && (
|
|
398
|
+
<div className="text-[10px] text-red-500/80 flex items-center gap-1.5 mt-1">
|
|
399
|
+
<XCircle className="w-3 h-3" />
|
|
400
|
+
<span className="truncate max-w-[150px]" title={webhook.lastError}>
|
|
401
|
+
{webhook.lastError}
|
|
402
|
+
</span>
|
|
403
|
+
</div>
|
|
404
|
+
)}
|
|
362
405
|
</div>
|
|
363
406
|
</div>
|
|
364
407
|
</div>
|
|
365
408
|
|
|
366
409
|
<div className="flex items-center gap-1.5 lg:opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
|
410
|
+
<button
|
|
411
|
+
type="button"
|
|
412
|
+
onClick={() => handleViewHistory(webhook.id)}
|
|
413
|
+
className="p-2 rounded-lg border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)] transition-colors flex items-center gap-1.5"
|
|
414
|
+
title={t("tooltips.viewDeliveryHistory", { defaultValue: "View delivery history" })}
|
|
415
|
+
>
|
|
416
|
+
<Activity className="w-3.5 h-3.5" />
|
|
417
|
+
<span className="text-[11px] font-medium hidden sm:inline">History</span>
|
|
418
|
+
</button>
|
|
419
|
+
|
|
367
420
|
<button
|
|
368
421
|
type="button"
|
|
369
422
|
onClick={() => handleTest(webhook.id)}
|
|
370
423
|
className="p-2 rounded-lg border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)] transition-colors flex items-center gap-1.5"
|
|
371
|
-
title="Send test request"
|
|
424
|
+
title={t("tooltips.sendTestRequest", { defaultValue: "Send test request" })}
|
|
372
425
|
>
|
|
373
426
|
<Send className="w-3.5 h-3.5" />
|
|
374
427
|
<span className="text-[11px] font-medium">Test</span>
|
|
@@ -387,7 +440,7 @@ export function WebhookManager() {
|
|
|
387
440
|
type="button"
|
|
388
441
|
onClick={() => remove(webhook.id, "Webhook")}
|
|
389
442
|
className="p-2 rounded-lg border border-[var(--kyro-border)] hover:border-red-500/30 hover:bg-red-500/5 transition-colors"
|
|
390
|
-
title="Delete"
|
|
443
|
+
title={t("tooltips.delete", { defaultValue: "Delete" })}
|
|
391
444
|
>
|
|
392
445
|
<Trash2 className="w-3.5 h-3.5 text-red-500/50 hover:text-red-500" />
|
|
393
446
|
</button>
|
|
@@ -403,7 +456,7 @@ export function WebhookManager() {
|
|
|
403
456
|
<Modal
|
|
404
457
|
open={showCreateModal && step === "action"}
|
|
405
458
|
onClose={() => setShowCreateModal(false)}
|
|
406
|
-
title="New webhook"
|
|
459
|
+
title={t("tooltips.newWebhook", { defaultValue: "New webhook" })}
|
|
407
460
|
>
|
|
408
461
|
<ModalContent>
|
|
409
462
|
<div className="space-y-4">
|
|
@@ -418,7 +471,7 @@ export function WebhookManager() {
|
|
|
418
471
|
type="button"
|
|
419
472
|
key={key}
|
|
420
473
|
onClick={() => handleActionSelect(key)}
|
|
421
|
-
className="flex flex-col items-start p-4 rounded-
|
|
474
|
+
className="flex flex-col items-start p-4 rounded-md border border-[var(--kyro-border)] bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)]/40 transition-all text-left group"
|
|
422
475
|
>
|
|
423
476
|
<div className="text-lg mb-2">{ACTION_ICONS[key] || "🔗"}</div>
|
|
424
477
|
<h3 className="text-sm font-medium mb-0.5 group-hover:text-[var(--kyro-primary)] transition-colors">
|
|
@@ -500,7 +553,7 @@ export function WebhookManager() {
|
|
|
500
553
|
type="text"
|
|
501
554
|
value={formData.secret}
|
|
502
555
|
onChange={(e) => setFormData({ ...formData, secret: e.target.value })}
|
|
503
|
-
placeholder="Optional"
|
|
556
|
+
placeholder={t("fields.optional", { defaultValue: "Optional" })}
|
|
504
557
|
className="w-full px-3 py-2.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg text-sm text-[var(--kyro-text-primary)] focus:outline-none focus:border-[var(--kyro-primary)]"
|
|
505
558
|
/>
|
|
506
559
|
</div>
|
|
@@ -587,7 +640,7 @@ export function WebhookManager() {
|
|
|
587
640
|
<Modal
|
|
588
641
|
open={showTestModal}
|
|
589
642
|
onClose={() => setShowTestModal(false)}
|
|
590
|
-
title="Test webhook"
|
|
643
|
+
title={t("tooltips.testWebhook", { defaultValue: "Test webhook" })}
|
|
591
644
|
>
|
|
592
645
|
<ModalContent>
|
|
593
646
|
<div className="p-6 rounded-xl bg-[var(--kyro-surface)] border border-[var(--kyro-border)] text-center">
|
|
@@ -629,7 +682,7 @@ export function WebhookManager() {
|
|
|
629
682
|
<Modal
|
|
630
683
|
open={showHelpModal}
|
|
631
684
|
onClose={() => setShowHelpModal(false)}
|
|
632
|
-
title="How webhooks work"
|
|
685
|
+
title={t("tooltips.howWebhooksWork", { defaultValue: "How webhooks work" })}
|
|
633
686
|
>
|
|
634
687
|
<ModalContent>
|
|
635
688
|
<div className="space-y-6">
|
|
@@ -680,6 +733,81 @@ export function WebhookManager() {
|
|
|
680
733
|
</button>
|
|
681
734
|
</ModalActions>
|
|
682
735
|
</Modal>
|
|
736
|
+
|
|
737
|
+
{/* History Modal */}
|
|
738
|
+
<Modal
|
|
739
|
+
open={showHistoryModal}
|
|
740
|
+
onClose={() => setShowHistoryModal(false)}
|
|
741
|
+
title={t("tooltips.deliveryHistory", { defaultValue: "Delivery History" })}
|
|
742
|
+
>
|
|
743
|
+
<ModalContent>
|
|
744
|
+
<div className="space-y-4">
|
|
745
|
+
{loadingHistory ? (
|
|
746
|
+
<div className="flex items-center justify-center p-12 opacity-50">
|
|
747
|
+
<RefreshCw className="w-5 h-5 animate-spin text-[var(--kyro-primary)]" />
|
|
748
|
+
</div>
|
|
749
|
+
) : deliveryHistory.length === 0 ? (
|
|
750
|
+
<div className="text-center p-8 text-[var(--kyro-text-secondary)] opacity-60 text-sm">
|
|
751
|
+
No delivery history available for this webhook.
|
|
752
|
+
</div>
|
|
753
|
+
) : (
|
|
754
|
+
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-1">
|
|
755
|
+
{deliveryHistory.map((delivery) => (
|
|
756
|
+
<div key={delivery.id} className="p-4 rounded-md border border-[var(--kyro-border)] bg-[var(--kyro-surface)] text-sm">
|
|
757
|
+
<div className="flex items-center justify-between mb-2">
|
|
758
|
+
<div className="flex items-center gap-2">
|
|
759
|
+
{delivery.status === "success" ? (
|
|
760
|
+
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
|
761
|
+
) : (
|
|
762
|
+
<XCircle className="w-4 h-4 text-red-500" />
|
|
763
|
+
)}
|
|
764
|
+
<span className="font-medium">{delivery.event}</span>
|
|
765
|
+
</div>
|
|
766
|
+
<span className="text-[10px] text-[var(--kyro-text-secondary)] opacity-50">
|
|
767
|
+
{new Date(delivery.createdAt).toLocaleString()}
|
|
768
|
+
</span>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<div className="grid grid-cols-2 gap-4 text-xs text-[var(--kyro-text-secondary)] mb-3">
|
|
772
|
+
<div>
|
|
773
|
+
<span className="opacity-50">Status: </span>
|
|
774
|
+
<span className={`font-medium ${delivery.status === "success" ? "text-green-500" : "text-red-500"}`}>
|
|
775
|
+
{delivery.responseStatus || delivery.status}
|
|
776
|
+
</span>
|
|
777
|
+
</div>
|
|
778
|
+
<div>
|
|
779
|
+
<span className="opacity-50">Duration: </span>
|
|
780
|
+
{delivery.duration ? `${delivery.duration}ms` : "-"}
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
{delivery.error && (
|
|
785
|
+
<div className="text-xs text-red-500 bg-red-500/5 p-2 rounded border border-red-500/10 font-mono break-all mb-2">
|
|
786
|
+
{delivery.error}
|
|
787
|
+
</div>
|
|
788
|
+
)}
|
|
789
|
+
|
|
790
|
+
{delivery.responseBody && (
|
|
791
|
+
<div className="text-[10px] font-mono bg-[var(--kyro-bg)] p-2 rounded border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] opacity-70 max-h-24 overflow-y-auto break-all">
|
|
792
|
+
{delivery.responseBody}
|
|
793
|
+
</div>
|
|
794
|
+
)}
|
|
795
|
+
</div>
|
|
796
|
+
))}
|
|
797
|
+
</div>
|
|
798
|
+
)}
|
|
799
|
+
</div>
|
|
800
|
+
</ModalContent>
|
|
801
|
+
<ModalActions>
|
|
802
|
+
<button
|
|
803
|
+
type="button"
|
|
804
|
+
onClick={() => setShowHistoryModal(false)}
|
|
805
|
+
className="w-full py-2.5 rounded-lg text-sm font-medium bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] hover:bg-[var(--kyro-surface)] transition-colors"
|
|
806
|
+
>
|
|
807
|
+
Close
|
|
808
|
+
</button>
|
|
809
|
+
</ModalActions>
|
|
810
|
+
</Modal>
|
|
683
811
|
</div>
|
|
684
812
|
);
|
|
685
813
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useAutoFormStore } from "../../lib/autoform-store";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
3
4
|
|
|
4
5
|
interface AutoFormApiViewProps {
|
|
5
6
|
collectionSlug?: string;
|
|
@@ -10,6 +11,7 @@ export function AutoFormApiView({
|
|
|
10
11
|
collectionSlug,
|
|
11
12
|
globalSlug,
|
|
12
13
|
}: AutoFormApiViewProps) {
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
const { formData } = useAutoFormStore();
|
|
14
16
|
|
|
15
17
|
return (
|
|
@@ -57,7 +59,7 @@ export function AutoFormApiView({
|
|
|
57
59
|
navigator.clipboard.writeText(String(formData.id));
|
|
58
60
|
}}
|
|
59
61
|
className="p-1.5 hover:bg-[var(--kyro-bg-secondary)] rounded-lg transition-colors text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
60
|
-
title="Copy ID"
|
|
62
|
+
title={t("tooltips.copyId", { defaultValue: "Copy ID" })}
|
|
61
63
|
>
|
|
62
64
|
<svg
|
|
63
65
|
width="14"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { CollectionConfig, GlobalConfig, Field } from "@kyro-cms/core/client";
|
|
3
3
|
import { useAutoFormStore } from "../../lib/autoform-store";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
4
5
|
|
|
5
6
|
interface AutoFormEditViewProps {
|
|
6
7
|
config: CollectionConfig | GlobalConfig;
|
|
@@ -15,7 +16,8 @@ export function AutoFormEditView({
|
|
|
15
16
|
collectionSlug,
|
|
16
17
|
renderField,
|
|
17
18
|
}: AutoFormEditViewProps) {
|
|
18
|
-
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const { showPreview, sidebarCollapsed, formData, previewUrl } = useAutoFormStore();
|
|
19
21
|
|
|
20
22
|
if (layout === "single") {
|
|
21
23
|
return (
|
|
@@ -65,9 +67,9 @@ export function AutoFormEditView({
|
|
|
65
67
|
</span>
|
|
66
68
|
</div>
|
|
67
69
|
<iframe
|
|
68
|
-
src={
|
|
70
|
+
src={previewUrl || ""}
|
|
69
71
|
className="w-full h-full border-none"
|
|
70
|
-
title="Live Preview"
|
|
72
|
+
title={t("tooltips.livePreview", { defaultValue: "Live Preview" })}
|
|
71
73
|
/>
|
|
72
74
|
<div className="absolute inset-0 bg-transparent pointer-events-none border-[12px] border-[var(--kyro-surface)] rounded-3xl" />
|
|
73
75
|
</div>
|
|
@@ -5,6 +5,7 @@ import { SplitButton } from "../ui/SplitButton";
|
|
|
5
5
|
import type { SplitButtonStatus } from "../ui/SplitButton";
|
|
6
6
|
import { useAutoFormStore } from "../../lib/autoform-store";
|
|
7
7
|
import { adminPath as ADMIN_BASE } from "../../lib/paths";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
8
9
|
|
|
9
10
|
type View = "edit" | "version" | "api";
|
|
10
11
|
|
|
@@ -41,6 +42,7 @@ export function AutoFormHeader({
|
|
|
41
42
|
handleSchedulePublish,
|
|
42
43
|
handleConflictOverride,
|
|
43
44
|
}: AutoFormHeaderProps) {
|
|
45
|
+
const { t } = useTranslation();
|
|
44
46
|
const [now, setNow] = useState(Date.now());
|
|
45
47
|
const [showSchedulePicker, setShowSchedulePicker] = useState(false);
|
|
46
48
|
const scheduleRef = useRef<HTMLDivElement>(null);
|
|
@@ -194,7 +196,7 @@ export function AutoFormHeader({
|
|
|
194
196
|
<button
|
|
195
197
|
type="button"
|
|
196
198
|
className="kyro-btn p-2 md:p-2.5 rounded-xl border border-[var(--kyro-border)] hover:bg-[var(--kyro-bg-secondary)] transition-all"
|
|
197
|
-
title="More actions"
|
|
199
|
+
title={t("tooltips.moreActions", { defaultValue: "More actions" })}
|
|
198
200
|
>
|
|
199
201
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
200
202
|
<circle cx="12" cy="5" r="1.5" />
|
|
@@ -377,8 +379,7 @@ export function AutoFormHeader({
|
|
|
377
379
|
<button
|
|
378
380
|
type="button"
|
|
379
381
|
onClick={() => {
|
|
380
|
-
|
|
381
|
-
markSaved();
|
|
382
|
+
useAutoFormStore.getState().loadDocument(lastSavedData, lastSavedData);
|
|
382
383
|
}}
|
|
383
384
|
className="text-[var(--kyro-primary)] text-[10px] font-medium hover:underline"
|
|
384
385
|
>
|
|
@@ -424,8 +425,7 @@ export function AutoFormHeader({
|
|
|
424
425
|
<button
|
|
425
426
|
type="button"
|
|
426
427
|
onClick={() => {
|
|
427
|
-
|
|
428
|
-
markSaved();
|
|
428
|
+
useAutoFormStore.getState().loadDocument(lastSavedData, lastSavedData);
|
|
429
429
|
}}
|
|
430
430
|
className="text-[var(--kyro-primary)] hover:underline"
|
|
431
431
|
>
|
|
@@ -475,7 +475,7 @@ export function AutoFormHeader({
|
|
|
475
475
|
type="button"
|
|
476
476
|
onClick={() => setShowPreview(!showPreview)}
|
|
477
477
|
className={`kyro-btn p-2.5 rounded-xl transition-all flex items-center gap-2 ${showPreview ? "shadow-lg" : "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-bg-secondary)]"}`}
|
|
478
|
-
title="Live Preview"
|
|
478
|
+
title={t("tooltips.livePreview", { defaultValue: "Live Preview" })}
|
|
479
479
|
>
|
|
480
480
|
<ExternalLink className="w-4 h-4" />
|
|
481
481
|
{showPreview && (
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "../ui/icons";
|
|
7
7
|
import { AccordionField } from "../fields/AccordionField";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
8
9
|
|
|
9
10
|
export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
10
11
|
block,
|
|
11
12
|
index,
|
|
12
13
|
}) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
const blockData = useBlockById(block.id);
|
|
14
16
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
17
|
|
|
@@ -36,7 +38,7 @@ export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: n
|
|
|
36
38
|
type="button"
|
|
37
39
|
onClick={() => moveBlock(block.id, "up")}
|
|
38
40
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
39
|
-
title="Move up"
|
|
41
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
40
42
|
>
|
|
41
43
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
42
44
|
</button>
|
|
@@ -44,7 +46,7 @@ export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: n
|
|
|
44
46
|
type="button"
|
|
45
47
|
onClick={() => moveBlock(block.id, "down")}
|
|
46
48
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
47
|
-
title="Move down"
|
|
49
|
+
title={t("tooltips.moveDown", { defaultValue: "Move down" })}
|
|
48
50
|
>
|
|
49
51
|
<ChevronRight className="w-3 h-3" />
|
|
50
52
|
</button>
|
|
@@ -52,7 +54,7 @@ export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: n
|
|
|
52
54
|
type="button"
|
|
53
55
|
onClick={() => removeBlock(block.id)}
|
|
54
56
|
className="p-1 hover:bg-[var(--kyro-danger-bg)] rounded text-[var(--kyro-danger)]"
|
|
55
|
-
title="Remove"
|
|
57
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
56
58
|
>
|
|
57
59
|
<X className="w-3 h-3" />
|
|
58
60
|
</button>
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "../ui/icons";
|
|
7
7
|
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
8
9
|
|
|
9
10
|
export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
10
11
|
block,
|
|
11
12
|
index,
|
|
12
13
|
}) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
const blockData = useBlockById(block.id);
|
|
14
16
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
17
|
|
|
@@ -32,7 +34,7 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
32
34
|
type="button"
|
|
33
35
|
onClick={() => moveBlock(block.id, "up")}
|
|
34
36
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
35
|
-
title="Move up"
|
|
37
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
36
38
|
>
|
|
37
39
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
38
40
|
</button>
|
|
@@ -40,7 +42,7 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
40
42
|
type="button"
|
|
41
43
|
onClick={() => moveBlock(block.id, "down")}
|
|
42
44
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
43
|
-
title="Move down"
|
|
45
|
+
title={t("tooltips.moveDown", { defaultValue: "Move down" })}
|
|
44
46
|
>
|
|
45
47
|
<ChevronRight className="w-3 h-3" />
|
|
46
48
|
</button>
|
|
@@ -48,7 +50,7 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
48
50
|
type="button"
|
|
49
51
|
onClick={() => removeBlock(block.id)}
|
|
50
52
|
className="p-1 hover:bg-[var(--kyro-danger-bg)] rounded text-[var(--kyro-danger)]"
|
|
51
|
-
title="Remove"
|
|
53
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
52
54
|
>
|
|
53
55
|
<X className="w-3 h-3" />
|
|
54
56
|
</button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ChevronRight, X } from "../ui/icons";
|
|
3
3
|
import { useBlockActions } from "../fields/extensions/blocksStore";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
4
5
|
|
|
5
6
|
interface BlockWrapperProps {
|
|
6
7
|
id: string;
|
|
@@ -17,6 +18,7 @@ export const BlockWrapper: React.FC<BlockWrapperProps> = ({
|
|
|
17
18
|
children,
|
|
18
19
|
className = "",
|
|
19
20
|
}) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
20
22
|
const { moveBlock, removeBlock } = useBlockActions();
|
|
21
23
|
|
|
22
24
|
return (
|
|
@@ -30,7 +32,7 @@ export const BlockWrapper: React.FC<BlockWrapperProps> = ({
|
|
|
30
32
|
type="button"
|
|
31
33
|
onClick={() => moveBlock(id, "up")}
|
|
32
34
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded text-[var(--kyro-text-muted)] hover:text-[var(--kyro-primary)]"
|
|
33
|
-
title="Move up"
|
|
35
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
34
36
|
>
|
|
35
37
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
36
38
|
</button>
|
|
@@ -38,7 +40,7 @@ export const BlockWrapper: React.FC<BlockWrapperProps> = ({
|
|
|
38
40
|
type="button"
|
|
39
41
|
onClick={() => moveBlock(id, "down")}
|
|
40
42
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded text-[var(--kyro-text-muted)] hover:text-[var(--kyro-primary)]"
|
|
41
|
-
title="Move down"
|
|
43
|
+
title={t("tooltips.moveDown", { defaultValue: "Move down" })}
|
|
42
44
|
>
|
|
43
45
|
<ChevronRight className="w-3 h-3" />
|
|
44
46
|
</button>
|
|
@@ -46,7 +48,7 @@ export const BlockWrapper: React.FC<BlockWrapperProps> = ({
|
|
|
46
48
|
type="button"
|
|
47
49
|
onClick={() => removeBlock(id)}
|
|
48
50
|
className="p-1 hover:bg-[var(--kyro-error)]/10 rounded text-[var(--kyro-error)]"
|
|
49
|
-
title="Remove"
|
|
51
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
50
52
|
>
|
|
51
53
|
<X className="w-3 h-3" />
|
|
52
54
|
</button>
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X, Code2 } from "../ui/icons";
|
|
7
7
|
import { CodeField } from "../fields/CodeField";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
8
9
|
|
|
9
10
|
export const CodeBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
10
11
|
block,
|
|
11
12
|
index,
|
|
12
13
|
}) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
const blockData = useBlockById(block.id);
|
|
14
16
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
17
|
const data = blockData?.data ?? block.data ?? {};
|
|
@@ -39,7 +41,7 @@ export const CodeBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
39
41
|
type="button"
|
|
40
42
|
onClick={() => moveBlock(block.id, "up")}
|
|
41
43
|
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-lg transition-all text-[var(--kyro-text-muted)] hover:text-[var(--kyro-primary)]"
|
|
42
|
-
title="Move up"
|
|
44
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
43
45
|
>
|
|
44
46
|
<ChevronRight className="w-4 h-4 rotate-[-90deg]" />
|
|
45
47
|
</button>
|
|
@@ -48,7 +50,7 @@ export const CodeBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
48
50
|
type="button"
|
|
49
51
|
onClick={() => removeBlock(block.id)}
|
|
50
52
|
className="p-1.5 hover:bg-[var(--kyro-danger-bg)] rounded-lg transition-all text-[var(--kyro-text-muted)] hover:text-[var(--kyro-danger)]"
|
|
51
|
-
title="Remove"
|
|
53
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
52
54
|
>
|
|
53
55
|
<X className="w-4 h-4" />
|
|
54
56
|
</button>
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "../ui/icons";
|
|
7
7
|
import { UploadField } from "../fields/UploadField";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
8
9
|
|
|
9
10
|
export const FileBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
10
11
|
block,
|
|
11
12
|
index,
|
|
12
13
|
}) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
13
15
|
const blockData = useBlockById(block.id);
|
|
14
16
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
17
|
|
|
@@ -30,7 +32,7 @@ export const FileBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
30
32
|
type="button"
|
|
31
33
|
onClick={() => moveBlock(block.id, "up")}
|
|
32
34
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
33
|
-
title="Move up"
|
|
35
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
34
36
|
>
|
|
35
37
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
36
38
|
</button>
|
|
@@ -38,7 +40,7 @@ export const FileBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
38
40
|
type="button"
|
|
39
41
|
onClick={() => moveBlock(block.id, "down")}
|
|
40
42
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
-
title="Move down"
|
|
43
|
+
title={t("tooltips.moveDown", { defaultValue: "Move down" })}
|
|
42
44
|
>
|
|
43
45
|
<ChevronRight className="w-3 h-3" />
|
|
44
46
|
</button>
|
|
@@ -46,7 +48,7 @@ export const FileBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
46
48
|
type="button"
|
|
47
49
|
onClick={() => removeBlock(block.id)}
|
|
48
50
|
className="p-1 hover:bg-[var(--kyro-danger-bg)] rounded text-[var(--kyro-danger)]"
|
|
49
|
-
title="Remove"
|
|
51
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
50
52
|
>
|
|
51
53
|
<X className="w-3 h-3" />
|
|
52
54
|
</button>
|
|
@@ -7,11 +7,13 @@ import { ChevronRight, X } from "../ui/icons";
|
|
|
7
7
|
import { HeroField } from "../fields/HeroField";
|
|
8
8
|
import { UploadField } from "../fields/UploadField";
|
|
9
9
|
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
10
|
+
import { useTranslation } from "react-i18next";
|
|
10
11
|
|
|
11
12
|
export const HeroBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
12
13
|
block,
|
|
13
14
|
index,
|
|
14
15
|
}) => {
|
|
16
|
+
const { t } = useTranslation();
|
|
15
17
|
const blockData = useBlockById(block.id);
|
|
16
18
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
19
|
|
|
@@ -35,7 +37,7 @@ export const HeroBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
35
37
|
type="button"
|
|
36
38
|
onClick={() => moveBlock(block.id, "up")}
|
|
37
39
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
38
|
-
title="Move up"
|
|
40
|
+
title={t("tooltips.moveUp", { defaultValue: "Move up" })}
|
|
39
41
|
>
|
|
40
42
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
41
43
|
</button>
|
|
@@ -43,7 +45,7 @@ export const HeroBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
43
45
|
type="button"
|
|
44
46
|
onClick={() => moveBlock(block.id, "down")}
|
|
45
47
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
46
|
-
title="Move down"
|
|
48
|
+
title={t("tooltips.moveDown", { defaultValue: "Move down" })}
|
|
47
49
|
>
|
|
48
50
|
<ChevronRight className="w-3 h-3" />
|
|
49
51
|
</button>
|
|
@@ -51,7 +53,7 @@ export const HeroBlock: React.FC<{ block: Record<string, unknown>; index: number
|
|
|
51
53
|
type="button"
|
|
52
54
|
onClick={() => removeBlock(block.id)}
|
|
53
55
|
className="p-1 hover:bg-[var(--kyro-danger-bg)] rounded text-[var(--kyro-danger)]"
|
|
54
|
-
title="Remove"
|
|
56
|
+
title={t("tooltips.remove", { defaultValue: "Remove" })}
|
|
55
57
|
>
|
|
56
58
|
<X className="w-3 h-3" />
|
|
57
59
|
</button>
|