@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.
Files changed (110) hide show
  1. package/dist/index.cjs +64 -26
  2. package/dist/index.d.cts +2 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +64 -26
  5. package/package.json +8 -2
  6. package/src/components/ActionBar.tsx +16 -16
  7. package/src/components/ApiHealth.tsx +522 -0
  8. package/src/components/ApiKeysManager.tsx +6 -4
  9. package/src/components/AuditLogsPage.tsx +4 -2
  10. package/src/components/AutoForm.tsx +10 -7
  11. package/src/components/BrandingHub.tsx +3 -1
  12. package/src/components/Dashboard.tsx +44 -37
  13. package/src/components/DashboardMetrics.tsx +168 -58
  14. package/src/components/DetailView.tsx +125 -50
  15. package/src/components/DeveloperCenter.tsx +5 -3
  16. package/src/components/GraphQLPlayground.tsx +10 -8
  17. package/src/components/ListView.tsx +66 -45
  18. package/src/components/LoginPage.tsx +21 -19
  19. package/src/components/MarketplaceManager.tsx +3 -1
  20. package/src/components/MediaGallery.tsx +12 -9
  21. package/src/components/PluginsManager.tsx +6 -4
  22. package/src/components/RestPlayground.tsx +8 -6
  23. package/src/components/SessionsManager.tsx +3 -1
  24. package/src/components/Sidebar.astro +180 -92
  25. package/src/components/UserManagement.tsx +8 -6
  26. package/src/components/UserMenu.tsx +19 -17
  27. package/src/components/VersionHistoryPanel.tsx +6 -4
  28. package/src/components/WebhookManager.tsx +143 -15
  29. package/src/components/autoform/AutoFormApiView.tsx +3 -1
  30. package/src/components/autoform/AutoFormEditView.tsx +5 -3
  31. package/src/components/autoform/AutoFormHeader.tsx +6 -6
  32. package/src/components/blocks/AccordionBlock.tsx +5 -3
  33. package/src/components/blocks/ArrayBlock.tsx +5 -3
  34. package/src/components/blocks/BlockWrapper.tsx +5 -3
  35. package/src/components/blocks/CodeBlock.tsx +4 -2
  36. package/src/components/blocks/FileBlock.tsx +5 -3
  37. package/src/components/blocks/HeroBlock.tsx +5 -3
  38. package/src/components/blocks/ImageBlock.tsx +6 -4
  39. package/src/components/blocks/ListBlock.tsx +5 -3
  40. package/src/components/blocks/ParagraphBlock.tsx +3 -1
  41. package/src/components/blocks/RelationshipBlock.tsx +5 -3
  42. package/src/components/blocks/RichTextBlock.tsx +5 -3
  43. package/src/components/blocks/VideoBlock.tsx +5 -3
  44. package/src/components/fields/AccordionField.tsx +10 -51
  45. package/src/components/fields/ArrayField.tsx +9 -7
  46. package/src/components/fields/ArrayLayout.tsx +3 -1
  47. package/src/components/fields/BlocksField.tsx +24 -75
  48. package/src/components/fields/ButtonField.tsx +3 -1
  49. package/src/components/fields/CardField.tsx +6 -4
  50. package/src/components/fields/GroupLayout.tsx +2 -49
  51. package/src/components/fields/HeadingField.tsx +3 -1
  52. package/src/components/fields/HeadingSubheadingField.tsx +4 -2
  53. package/src/components/fields/HeroField.tsx +8 -6
  54. package/src/components/fields/JSONField.tsx +9 -10
  55. package/src/components/fields/LinkField.tsx +4 -2
  56. package/src/components/fields/ListField.tsx +3 -1
  57. package/src/components/fields/MarkdownField.tsx +3 -17
  58. package/src/components/fields/RelationshipBlockField.tsx +7 -5
  59. package/src/components/fields/RelationshipField.tsx +3 -1
  60. package/src/components/fields/RichTextField.tsx +19 -16
  61. package/src/components/fields/SecretField.tsx +9 -69
  62. package/src/components/fields/SelectField.tsx +11 -6
  63. package/src/components/fields/TabsLayout.tsx +2 -48
  64. package/src/components/fields/TextField.tsx +9 -7
  65. package/src/components/fields/UploadField.tsx +3 -1
  66. package/src/components/fields/VideoField.tsx +6 -4
  67. package/src/components/ui/BlockDrawer.tsx +3 -1
  68. package/src/components/ui/CommandPalette.tsx +9 -7
  69. package/src/components/ui/CommandPaletteWrapper.tsx +3 -1
  70. package/src/components/ui/IconPickerModal.tsx +4 -2
  71. package/src/components/ui/PageHeader.tsx +48 -20
  72. package/src/components/users/UserDetail.tsx +3 -1
  73. package/src/components/users/UserForm.tsx +5 -3
  74. package/src/fields/examples/sample-field-2.tsx +3 -1
  75. package/src/fields/examples/sample-field.tsx +3 -1
  76. package/src/hooks/useAutoFormState.ts +19 -11
  77. package/src/integration.ts +21 -0
  78. package/src/layouts/AdminLayout.astro +252 -189
  79. package/src/layouts/AuthLayout.astro +52 -31
  80. package/src/lib/api.ts +7 -7
  81. package/src/lib/autoform-store.ts +4 -0
  82. package/src/lib/deep-equal.ts +3 -3
  83. package/src/lib/i18n.ts +44 -0
  84. package/src/lib/vite-shim-plugin.ts +3 -0
  85. package/src/locales/de.json +163 -0
  86. package/src/locales/en.json +163 -0
  87. package/src/locales/es.json +163 -0
  88. package/src/locales/fr.json +163 -0
  89. package/src/locales/pt.json +163 -0
  90. package/src/pages/403.astro +2 -1
  91. package/src/pages/[collection]/[id].astro +23 -10
  92. package/src/pages/[collection]/index.astro +48 -34
  93. package/src/pages/audit/index.astro +2 -1
  94. package/src/pages/graphql-explorer.astro +2 -1
  95. package/src/pages/graphql.astro +2 -1
  96. package/src/pages/health.astro +9 -0
  97. package/src/pages/index.astro +6 -5
  98. package/src/pages/keys.astro +2 -1
  99. package/src/pages/marketplace.astro +2 -1
  100. package/src/pages/media.astro +2 -1
  101. package/src/pages/plugins.astro +2 -1
  102. package/src/pages/rest-playground.astro +2 -1
  103. package/src/pages/roles/index.astro +16 -52
  104. package/src/pages/sessions.astro +2 -1
  105. package/src/pages/settings/[slug].astro +45 -30
  106. package/src/pages/users/[id].astro +1 -0
  107. package/src/pages/users/index.astro +2 -1
  108. package/src/pages/users/new.astro +2 -1
  109. package/src/pages/webhooks.astro +2 -1
  110. 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-dispatch": "⚙️",
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
- setCreateError("Failed to create webhook");
173
- toast.error("Failed to create webhook");
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-xl border border-[var(--kyro-border)] bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)]/40 transition-all text-left group"
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
- const { showPreview, sidebarCollapsed, formData } = useAutoFormStore();
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={`/${collectionSlug}/${formData.slug || formData.id}?preview=true`}
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
- setFormData(lastSavedData);
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
- setFormData(lastSavedData);
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>