@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
@@ -20,6 +20,7 @@ import {
20
20
  Hexagon,
21
21
  } from "./icons";
22
22
  import { useAuthStore } from "../../lib/stores";
23
+ import { useTranslation } from "react-i18next";
23
24
 
24
25
  interface SearchResult {
25
26
  collection: string;
@@ -44,6 +45,7 @@ export function CommandPalette({
44
45
  globals,
45
46
  onNavigate,
46
47
  }: CommandPaletteProps) {
48
+ const { t } = useTranslation();
47
49
  const { user, permissions } = useAuthStore();
48
50
  const [query, setQuery] = useState("");
49
51
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -107,7 +109,7 @@ export function CommandPalette({
107
109
 
108
110
  const collectionItems = Object.entries(collections)
109
111
  .filter(([slug]) => permissions?.collections?.[slug]?.read !== false)
110
- .map(([slug, config]: [string, Record<string, unknown>]) => ({
112
+ .map(([slug, config]: [string, any]) => ({
111
113
  id: `col-${slug}`,
112
114
  label: config.label || slug,
113
115
  type: "collection",
@@ -117,7 +119,7 @@ export function CommandPalette({
117
119
 
118
120
  const globalItems = Object.entries(globals)
119
121
  .filter(([slug]) => permissions?.globals?.[slug]?.read !== false)
120
- .map(([slug, config]: [string, Record<string, unknown>]) => ({
122
+ .map(([slug, config]: [string, any]) => ({
121
123
  id: `global-${slug}`,
122
124
  label: config.label || slug,
123
125
  type: "global",
@@ -267,7 +269,7 @@ export function CommandPalette({
267
269
  } else if (item.view === "media") {
268
270
  onNavigate("media", "media");
269
271
  } else {
270
- onNavigate(item.view, item.view);
272
+ onNavigate(item.view as string, item.view as string);
271
273
  }
272
274
  }
273
275
  onClose();
@@ -298,7 +300,7 @@ export function CommandPalette({
298
300
  )}
299
301
  <input
300
302
  ref={inputRef}
301
- placeholder="Search anything..."
303
+ placeholder={t("fields.searchAnything", { defaultValue: "Search anything..." })}
302
304
  className="flex-1 bg-transparent border-none focus:outline-none text-lg font-medium text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)]"
303
305
  value={query}
304
306
  onChange={(e) => setQuery(e.target.value)}
@@ -334,12 +336,12 @@ export function CommandPalette({
334
336
  <item.icon className="w-4 h-4" />
335
337
  </div>
336
338
  <div className="flex flex-col">
337
- <span className="font-bold text-sm">{item.label}</span>
338
- {item.type === "document" && item.label2 && (
339
+ <span className="font-bold text-sm">{(item as any).label}</span>
340
+ {item.type === "document" && (item as any).label2 && (
339
341
  <span
340
342
  className={`text-[10px] font-bold tracking-widest ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)]/60" : "opacity-40"}`}
341
343
  >
342
- {item.label2}
344
+ {(item as any).label2}
343
345
  </span>
344
346
  )}
345
347
  </div>
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
2
2
  import { CommandPalette } from "./CommandPalette";
3
3
  import { ConfirmModal } from "./Modal";
4
4
  import { adminPath } from "../../lib/paths";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  interface Props {
7
8
  collections: Record<string, unknown>;
@@ -9,6 +10,7 @@ interface Props {
9
10
  }
10
11
 
11
12
  export function CommandPaletteWrapper({ collections, globals }: Props) {
13
+ const { t } = useTranslation();
12
14
  const [isOpen, setIsOpen] = useState(false);
13
15
  const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
14
16
 
@@ -87,7 +89,7 @@ export function CommandPaletteWrapper({ collections, globals }: Props) {
87
89
  open={showLogoutConfirm}
88
90
  onClose={() => setShowLogoutConfirm(false)}
89
91
  onConfirm={handleLogoutConfirm}
90
- title="Sign Out"
92
+ title={t("tooltips.signOut", { defaultValue: "Sign Out" })}
91
93
  message="Are you sure you want to sign out?"
92
94
  confirmLabel="Sign Out"
93
95
  variant="danger"
@@ -2,6 +2,7 @@ import React, { useState, useMemo } from "react";
2
2
  import { Modal } from "./Modal";
3
3
  import * as LucideIcons from "lucide-react";
4
4
  import { Search } from "./icons";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  // Extract only valid components from lucide-react
7
8
  const availableIcons = Object.keys(LucideIcons).filter((key) => {
@@ -15,6 +16,7 @@ interface IconPickerModalProps {
15
16
  }
16
17
 
17
18
  export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProps) {
19
+ const { t } = useTranslation();
18
20
  const [search, setSearch] = useState("");
19
21
 
20
22
  const filteredIcons = useMemo(() => {
@@ -27,7 +29,7 @@ export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProp
27
29
  <Modal
28
30
  open={open}
29
31
  onClose={onClose}
30
- title="Select an Icon"
32
+ title={t("tooltips.selectAnIcon", { defaultValue: "Select an Icon" })}
31
33
  size="xl"
32
34
  >
33
35
  <div className="flex flex-col h-[60vh]">
@@ -35,7 +37,7 @@ export function IconPickerModal({ open, onClose, onSelect }: IconPickerModalProp
35
37
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-muted)]" />
36
38
  <input
37
39
  type="text"
38
- placeholder="Search icons..."
40
+ placeholder={t("fields.searchIcons", { defaultValue: "Search icons..." })}
39
41
  value={search}
40
42
  onChange={(e) => setSearch(e.target.value)}
41
43
  className="w-full pl-10 pr-4 py-3 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-xl focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)] transition-all text-sm font-bold"
@@ -9,7 +9,8 @@ interface Breadcrumb {
9
9
 
10
10
  interface Action {
11
11
  label: string;
12
- onClick: () => void;
12
+ href?: string;
13
+ onClick?: () => void;
13
14
  icon?: ComponentType<SVGAttributes<SVGSVGElement>>;
14
15
  variant?: "primary" | "outline" | "ghost";
15
16
  className?: string;
@@ -90,24 +91,40 @@ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["action
90
91
  if (Array.isArray(actions)) {
91
92
  return (
92
93
  <div className="flex items-center gap-3">
93
- {actions.map((act, i) => (
94
- <button
95
- key={i}
96
- type="button"
97
- onClick={act.onClick}
98
- disabled={act.disabled}
99
- className={`flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${
100
- act.variant === "outline"
101
- ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
102
- : act.variant === "ghost"
103
- ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
104
- : "kyro-btn-primary hover:opacity-90"
105
- } ${act.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${act.className || ""}`}
106
- >
107
- {act.icon && <act.icon className="w-4 h-4" />}
108
- {act.label}
109
- </button>
110
- ))}
94
+ {actions.map((act, i) => {
95
+ const className = `flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all ${act.variant === "outline"
96
+ ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
97
+ : act.variant === "ghost"
98
+ ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
99
+ : "kyro-btn-primary hover:opacity-90"
100
+ } ${act.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${act.className || ""}`;
101
+
102
+ if (act.href && !act.disabled) {
103
+ return (
104
+ <a
105
+ key={i}
106
+ href={act.href}
107
+ className={className}
108
+ >
109
+ {act.icon && <act.icon className="w-4 h-4" />}
110
+ {act.label}
111
+ </a>
112
+ );
113
+ }
114
+
115
+ return (
116
+ <button
117
+ key={i}
118
+ type="button"
119
+ onClick={act.onClick}
120
+ disabled={act.disabled}
121
+ className={className}
122
+ >
123
+ {act.icon && <act.icon className="w-4 h-4" />}
124
+ {act.label}
125
+ </button>
126
+ );
127
+ })}
111
128
  </div>
112
129
  );
113
130
  }
@@ -115,12 +132,23 @@ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["action
115
132
  }
116
133
 
117
134
  function SingleAction({ action }: { action: NonNullable<PageHeaderProps["action"]> }) {
135
+ const className = `kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all w-full lg:w-auto justify-center ${action.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${action.className || ""}`;
136
+
137
+ if (action.href && !action.disabled) {
138
+ return (
139
+ <a href={action.href} className={className}>
140
+ {action.icon && <action.icon className="w-4 h-4" />}
141
+ {action.label}
142
+ </a>
143
+ );
144
+ }
145
+
118
146
  return (
119
147
  <button
120
148
  type="button"
121
149
  onClick={action.onClick}
122
150
  disabled={action.disabled}
123
- className={`kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10 w-full lg:w-auto justify-center ${action.disabled ? "opacity-50 cursor-wait pointer-events-none" : ""} ${action.className || ""}`}
151
+ className={className}
124
152
  >
125
153
  {action.icon && <action.icon className="w-4 h-4" />}
126
154
  {action.label}
@@ -3,6 +3,7 @@ import { apiGet } from "../../lib/api";
3
3
  import { useUIStore, toast } from "../../lib/stores";
4
4
  import { X } from "../ui/icons";
5
5
  import { UploadField } from "../fields/UploadField";
6
+ import { useTranslation } from "react-i18next";
6
7
 
7
8
  interface User {
8
9
  id: string;
@@ -35,6 +36,7 @@ const roleOptions = [
35
36
  ];
36
37
 
37
38
  export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
39
+ const { t } = useTranslation();
38
40
  const [name, setName] = useState(user.name || "");
39
41
  const [role, setRole] = useState(user.role);
40
42
  const [avatar, setAvatar] = useState<string | undefined>(user.avatar);
@@ -228,7 +230,7 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
228
230
  value={name}
229
231
  onChange={(e) => setName(e.target.value)}
230
232
  className="mt-1 w-full px-3 py-2 border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)]"
231
- placeholder="Enter name"
233
+ placeholder={t("fields.enterName", { defaultValue: "Enter name" })}
232
234
  />
233
235
  </div>
234
236
  <div>
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
2
3
 
3
4
  interface UserFormProps {
4
5
  mode: "create" | "edit";
@@ -25,6 +26,7 @@ const roleOptions = [
25
26
  ];
26
27
 
27
28
  export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
29
+ const { t } = useTranslation();
28
30
  const [email, setEmail] = useState(user?.email || "");
29
31
  const [name, setName] = useState(user?.name || "");
30
32
  const [password, setPassword] = useState("");
@@ -120,7 +122,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
120
122
  value={name}
121
123
  onChange={(e) => setName(e.target.value)}
122
124
  className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
123
- placeholder="John Doe"
125
+ placeholder={t("fields.johnDoe", { defaultValue: "John Doe" })}
124
126
  />
125
127
  </div>
126
128
 
@@ -158,7 +160,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
158
160
  required
159
161
  minLength={12}
160
162
  className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
161
- placeholder="Minimum 12 characters"
163
+ placeholder={t("fields.minimum12Characters", { defaultValue: "Minimum 12 characters" })}
162
164
  />
163
165
  <p className="text-xs text-[var(--kyro-text-secondary)] mt-1">
164
166
  Must contain , lowercase, numbers, and special
@@ -201,7 +203,7 @@ export function UserForm({ mode, apiPath, adminPath, user }: UserFormProps) {
201
203
  value={tenantId}
202
204
  onChange={(e) => setTenantId(e.target.value)}
203
205
  className="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
204
- placeholder="Leave empty for global user"
206
+ placeholder={t("fields.leaveEmptyForGlobal", { defaultValue: "Leave empty for global user" })}
205
207
  />
206
208
  </div>
207
209
 
@@ -2,18 +2,20 @@ import React from "react";
2
2
  import type { FieldEditorProps } from "../types.ts";
3
3
  import { registerField } from "../registry.tsx";
4
4
  import type { KyroField } from "../types.ts";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  const SampleTextareaEditor: React.FC<FieldEditorProps> = ({
7
8
  name,
8
9
  value,
9
10
  onChange,
10
11
  }) => {
12
+ const { t } = useTranslation();
11
13
  return (
12
14
  <textarea
13
15
  name={name}
14
16
  value={typeof value === "string" ? value : ""}
15
17
  onChange={(e) => onChange?.(e.target.value)}
16
- placeholder="Sample textarea"
18
+ placeholder={t("fields.sampleTextarea", { defaultValue: "Sample textarea" })}
17
19
  />
18
20
  );
19
21
  };
@@ -2,18 +2,20 @@ import React from "react";
2
2
  import type { FieldEditorProps } from "../types.ts";
3
3
  import { registerField } from "../registry.tsx";
4
4
  import type { KyroField } from "../types.ts";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  const SampleTextEditor: React.FC<FieldEditorProps> = ({
7
8
  name,
8
9
  value,
9
10
  onChange,
10
11
  }) => {
12
+ const { t } = useTranslation();
11
13
  return (
12
14
  <input
13
15
  name={name}
14
16
  value={typeof value === "string" ? value : ""}
15
17
  onChange={(e) => onChange?.(e.target.value)}
16
- placeholder="Sample text"
18
+ placeholder={t("fields.sampleText", { defaultValue: "Sample text" })}
17
19
  />
18
20
  );
19
21
  };
@@ -107,7 +107,7 @@ const persistBrowserDraft = useCallback(
107
107
  ) => {
108
108
  setDraftCache(documentKey, {
109
109
  data,
110
- baseUpdatedAt: lastSavedData.updatedAt ?? null,
110
+ baseUpdatedAt: (lastSavedData.updatedAt as string) ?? null,
111
111
  draftUpdatedAt: new Date().toISOString(),
112
112
  lastSyncedAt: options?.lastSyncedAt ?? null,
113
113
  });
@@ -140,7 +140,7 @@ const persistBrowserDraft = useCallback(
140
140
  const latestFormData = state.formData;
141
141
  if (autoSaveSkipRef.current || !collectionSlug || !latestFormData.id) return;
142
142
  if (!state.hasDirtyFields()) return;
143
- const documentKey = getDocumentKey(latestFormData.id);
143
+ const documentKey = getDocumentKey(latestFormData.id as string);
144
144
  if (documentKey) {
145
145
  persistBrowserDraft(documentKey, latestFormData);
146
146
  }
@@ -155,7 +155,7 @@ const persistBrowserDraft = useCallback(
155
155
  if (!globalSlug && (!collectionSlug || !latestFormData.id)) return;
156
156
  if (!state.hasDirtyFields()) return;
157
157
 
158
- const documentKey = getDocumentKey(latestFormData.id);
158
+ const documentKey = getDocumentKey(latestFormData.id as string);
159
159
  if (documentKey) {
160
160
  persistBrowserDraft(documentKey, latestFormData);
161
161
  }
@@ -186,7 +186,7 @@ const persistBrowserDraft = useCallback(
186
186
  },
187
187
  keepalive: options?.keepalive,
188
188
  body: JSON.stringify({
189
- ...normalizeUploadFields(latestFormData),
189
+ ...(normalizeUploadFields(latestFormData) as Record<string, unknown>),
190
190
  baseUpdatedAt: currentLastSaved.updatedAt ?? null,
191
191
  }),
192
192
  });
@@ -200,7 +200,7 @@ const persistBrowserDraft = useCallback(
200
200
  if (documentKey) {
201
201
  setDraftCache(documentKey, {
202
202
  data: latestFormData,
203
- baseUpdatedAt: currentLastSaved.updatedAt ?? null,
203
+ baseUpdatedAt: (currentLastSaved.updatedAt as string) ?? null,
204
204
  draftUpdatedAt: new Date().toISOString(),
205
205
  lastSyncedAt: (await response.clone().json()).data?.updatedAt || new Date().toISOString(),
206
206
  });
@@ -321,8 +321,7 @@ const persistBrowserDraft = useCallback(
321
321
  if (response.ok) {
322
322
  const result = await response.json();
323
323
  const savedData = result.data || payload;
324
- state.setFormData({ ...payload, ...savedData });
325
- state.setLastSavedData({ ...payload, ...savedData });
324
+ state.loadDocument(savedData, savedData);
326
325
  setAutoSaveStatus("success");
327
326
  setTimeout(() => {
328
327
  if (useAutoFormStore.getState().autoSaveStatus === "success") {
@@ -352,7 +351,7 @@ const persistBrowserDraft = useCallback(
352
351
  const lastInitialDataRef = useRef<string>("");
353
352
  const initialDataLoadedRef = useRef(false);
354
353
  useEffect(() => {
355
- const currentSlug = globalSlug || initialData?.id;
354
+ const currentSlug = globalSlug || (initialData?.id as string);
356
355
  const serialized = JSON.stringify(initialData);
357
356
  if (initialDataLoadedRef.current && lastLoadedSlugRef.current === currentSlug && lastInitialDataRef.current === serialized) return;
358
357
 
@@ -365,7 +364,7 @@ const persistBrowserDraft = useCallback(
365
364
  useEffect(() => {
366
365
  if (!collectionSlug || !initialData?.id) return;
367
366
 
368
- const documentKey = getDocumentKey(initialData.id);
367
+ const documentKey = getDocumentKey(formData.id as string);
369
368
  if (!documentKey) return;
370
369
  if (restorePromptedRef.current === documentKey) return;
371
370
 
@@ -451,11 +450,20 @@ const persistBrowserDraft = useCallback(
451
450
  }
452
451
  }, [formData, config.fields, setField]);
453
452
 
453
+ interface FieldConfig {
454
+ name?: string;
455
+ admin?: {
456
+ autoGenerate?: string;
457
+ [key: string]: unknown;
458
+ };
459
+ [key: string]: unknown;
460
+ }
461
+
454
462
  // Auto-generate slug
455
463
  useEffect(() => {
456
- const fields = config.fields as Record<string, unknown>[];
464
+ const fields = config.fields as FieldConfig[];
457
465
  const slugField = fields.find(
458
- (f: Record<string, unknown>) => f.name === "slug" && f.admin?.autoGenerate,
466
+ (f: FieldConfig) => f.name === "slug" && f.admin?.autoGenerate,
459
467
  );
460
468
  if (!slugField?.admin?.autoGenerate) return;
461
469
  const sourceField: string = slugField.admin.autoGenerate;
@@ -5,6 +5,7 @@ import { pathToFileURL } from "url";
5
5
  import { build } from "esbuild";
6
6
  import { config as loadDotEnv } from "dotenv";
7
7
  import { Worker } from "worker_threads";
8
+ import { useSyncExternalStoreShimPlugin } from "./lib/vite-shim-plugin";
8
9
 
9
10
  const _shimDir = path.resolve(new URL(".", import.meta.url).pathname, "lib/shim");
10
11
  const _shimUses = path.join(_shimDir, "use-sync-external-store.js");
@@ -163,6 +164,7 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
163
164
  updateConfig({
164
165
  vite: {
165
166
  plugins: [
167
+ useSyncExternalStoreShimPlugin(),
166
168
  {
167
169
  name: "kyro-admin-tsx-loader",
168
170
  enforce: "pre" as const,
@@ -233,6 +235,22 @@ export default debug;
233
235
  optimizeDeps: {
234
236
  include: [
235
237
  '@kyro-cms/admin',
238
+ 'recharts',
239
+ 'recharts > recharts-scale',
240
+ 'recharts > recharts-scale > decimal.js-light',
241
+ 'recharts > react-smooth',
242
+ 'recharts > react-smooth > react-transition-group',
243
+ 'use-sync-external-store',
244
+ 'use-sync-external-store/with-selector.js',
245
+ 'react-i18next > html-parse-stringify > void-elements',
246
+ 'react-i18next > html-parse-stringify',
247
+ ],
248
+ needsInterop: [
249
+ 'decimal.js-light',
250
+ 'use-sync-external-store',
251
+ 'use-sync-external-store/with-selector.js',
252
+ 'react-i18next > html-parse-stringify > void-elements',
253
+ 'react-i18next > html-parse-stringify',
236
254
  ],
237
255
  exclude: ['debug', 'react/compiler-runtime'],
238
256
  },
@@ -292,6 +310,9 @@ export default debug;
292
310
  pattern: "/rest-playground",
293
311
  entrypoint: "./pages/rest-playground.astro",
294
312
  },
313
+ { pattern: "/health", entrypoint: "./pages/health.astro" },
314
+ { pattern: "/403", entrypoint: "./pages/403.astro" },
315
+ { pattern: "/graphql-explorer", entrypoint: "./pages/graphql-explorer.astro" },
295
316
  {
296
317
  pattern: "/[collection]",
297
318
  entrypoint: "./pages/[collection]/index.astro",