@kyro-cms/admin 0.3.1 → 0.3.4

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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,8 +1,11 @@
1
1
  import type { TextField as TextFieldType } from "@kyro-cms/core/client";
2
+ import FieldLayout from "./FieldLayout";
3
+ import { useAutoFormStore } from "../../lib/autoform-store";
4
+ import { slugifyText } from "../../lib/slugify";
2
5
 
3
6
  interface TextFieldComponentProps {
4
7
  field: TextFieldType;
5
- value?: string;
8
+ value?: string | null;
6
9
  onChange?: (value: string) => void;
7
10
  error?: string;
8
11
  disabled?: boolean;
@@ -10,11 +13,17 @@ interface TextFieldComponentProps {
10
13
 
11
14
  export default function TextField({
12
15
  field,
13
- value = "",
16
+ value,
14
17
  onChange,
15
18
  error,
16
19
  disabled,
17
20
  }: TextFieldComponentProps) {
21
+ const isReadOnly = field.admin?.readOnly;
22
+ const isTextarea = (field as TextFieldType).variant === "textarea";
23
+ const isSlug = field.name === "slug";
24
+
25
+ const { isSlugLocked, setIsSlugLocked, formData } = useAutoFormStore();
26
+
18
27
  const inputType =
19
28
  field.variant === "email"
20
29
  ? "email"
@@ -24,42 +33,80 @@ export default function TextField({
24
33
  ? "url"
25
34
  : "text";
26
35
 
36
+ const normalizedValue = value == null ? "" : String(value);
37
+
38
+ const commonProps = {
39
+ id: field.name,
40
+ value: normalizedValue,
41
+ onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => onChange?.(e.target.value),
42
+ placeholder: field.admin?.placeholder,
43
+ disabled: disabled || isReadOnly || (isSlug && isSlugLocked),
44
+ minLength: field.minLength,
45
+ maxLength: field.maxLength,
46
+ required: field.required,
47
+ className: `kyro-form-input ${isSlug ? "pr-24" : ""
48
+ } ${disabled || isReadOnly || (isSlug && isSlugLocked) ? "opacity-70 bg-[var(--kyro-bg-secondary)] cursor-not-allowed" : ""}`,
49
+ };
50
+
27
51
  return (
28
- <div className="space-y-1">
29
- {field.label && (
30
- <label className="block text-sm font-medium text-[var(--kyro-text-primary)]">
31
- {field.label}
32
- {field.required && (
33
- <span className="text-[var(--kyro-error)] ml-1">*</span>
34
- )}
35
- </label>
36
- )}
37
- <input
38
- type={inputType}
39
- value={value}
40
- onChange={(e) => onChange?.(e.target.value)}
41
- placeholder={field.admin?.placeholder}
42
- disabled={disabled || field.admin?.readOnly}
43
- minLength={field.minLength}
44
- maxLength={field.maxLength}
45
- pattern={field.pattern}
46
- required={field.required}
47
- className={`w-full px-3 py-2 border rounded-md text-sm transition-colors ${
48
- error
49
- ? "border-[var(--kyro-error)] focus:border-[var(--kyro-error)] focus:ring-[var(--kyro-error)]"
50
- : "border-[var(--kyro-border)] focus:border-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)]"
51
- } ${
52
- disabled || field.admin?.readOnly
53
- ? "bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)] opacity-50"
54
- : "bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)]"
55
- }`}
56
- />
57
- {field.admin?.description && !error && (
58
- <p className="text-xs text-[var(--kyro-text-muted)]">
59
- {field.admin.description}
60
- </p>
61
- )}
62
- {error && <p className="text-xs text-[var(--kyro-error)]">{error}</p>}
63
- </div>
52
+ <FieldLayout field={field} error={error}>
53
+ <div className="relative">
54
+ {isTextarea ? (
55
+ <textarea {...commonProps} rows={(field as TextFieldType).rows || 4} />
56
+ ) : (
57
+ <input type={inputType} {...commonProps} pattern={field.pattern} />
58
+ )}
59
+
60
+ {isSlug && (
61
+ <div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
62
+ {!isSlugLocked && (
63
+ <button
64
+ type="button"
65
+ onClick={() =>
66
+ onChange?.(
67
+ slugifyText(
68
+ formData[field.admin?.autoGenerate || "title"] || "",
69
+ ),
70
+ )
71
+ }
72
+ className="p-1 text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)]"
73
+ title="Regenerate slug"
74
+ >
75
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
76
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
77
+ <path d="M21 3v5h-5" />
78
+ </svg>
79
+ </button>
80
+ )}
81
+ <button
82
+ type="button"
83
+ onClick={() => setIsSlugLocked(!isSlugLocked)}
84
+ className={`p-1.5 rounded transition-colors ${isSlugLocked ? "text-[var(--kyro-primary)] bg-[var(--kyro-primary-alpha)]" : "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"}`}
85
+ title={isSlugLocked ? "Unlock slug" : "Lock slug"}
86
+ >
87
+ {isSlugLocked ? (
88
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
89
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
90
+ <path d="M7 11V7a5 5 0 0 1 10 0v4" />
91
+ </svg>
92
+ ) : (
93
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
94
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
95
+ <path d="M7 11V7a5 5 0 0 1 9.9-1" />
96
+ </svg>
97
+ )}
98
+ </button>
99
+ </div>
100
+ )}
101
+
102
+ {field.name?.toLowerCase().includes("metatitle") && (
103
+ <div className="flex items-center justify-between mt-1 text-[10px] font-bold tracking-wider">
104
+ <span className={(normalizedValue.length) > 60 ? "text-red-500" : normalizedValue.length >= 40 ? "text-green-500" : "text-amber-600"}>
105
+ {normalizedValue.length} / 60 — {normalizedValue.length > 60 ? "Too Long" : normalizedValue.length >= 40 ? "Ideal" : "Short"}
106
+ </span>
107
+ </div>
108
+ )}
109
+ </div>
110
+ </FieldLayout>
64
111
  );
65
112
  }
@@ -1,7 +1,8 @@
1
1
  import React, { useState, useEffect, useRef, useMemo } from "react";
2
2
  import { createPortal } from "react-dom";
3
- import { Image, Film, FileText, Music, File, X, Loader2 } from "lucide-react";
4
- import { apiGet, withCacheBust, apiPost, apiUpload } from "../../lib/api";
3
+ import { Image as ImageIcon, Film, FileText, Music, File, X, Loader2 } from "../ui/icons";
4
+ import { apiGet, withCacheBust, apiPost, apiUpload, resolveApi, resolveMedia } from "../../lib/api";
5
+ import { toast } from "../../lib/stores";
5
6
 
6
7
  interface UploadFieldProps {
7
8
  field: any;
@@ -55,7 +56,7 @@ const FileIcon = ({
55
56
  }) => {
56
57
  switch (type) {
57
58
  case "image":
58
- return <Image className={className} />;
59
+ return <ImageIcon className={className} />;
59
60
  case "video":
60
61
  return <Film className={className} />;
61
62
  case "audio":
@@ -94,6 +95,36 @@ export function UploadField({
94
95
  const currentValue = Array.isArray(value) ? value : value ? [value] : [];
95
96
  const canAddMore = currentValue.length < maxCount;
96
97
 
98
+ useEffect(() => {
99
+ const fetchMissingDetails = async () => {
100
+ const idsToFetch = currentValue
101
+ .filter(item => typeof item === 'string')
102
+ .map(id => id as string);
103
+
104
+ if (idsToFetch.length === 0) return;
105
+
106
+ try {
107
+ const fetchedItems = await Promise.all(
108
+ idsToFetch.map(id => apiGet<any>(`/api/media/${id}`))
109
+ );
110
+
111
+ const newItems = [...currentValue];
112
+ fetchedItems.forEach(fetchedItem => {
113
+ const index = newItems.findIndex(item => item === (fetchedItem.id || fetchedItem));
114
+ if (index !== -1) {
115
+ newItems[index] = fetchedItem;
116
+ }
117
+ });
118
+
119
+ onChange(isMultiple ? newItems : newItems[0]);
120
+ } catch (err) {
121
+ console.error("Failed to fetch media details:", err);
122
+ }
123
+ };
124
+
125
+ fetchMissingDetails();
126
+ }, [value]);
127
+
97
128
  useEffect(() => {
98
129
  if (showPicker) {
99
130
  loadFolders();
@@ -103,7 +134,7 @@ export function UploadField({
103
134
 
104
135
  const loadFolders = async () => {
105
136
  try {
106
- const result = await apiGet(withCacheBust("/api/media/folders"));
137
+ const result = await apiGet<any>(withCacheBust("/api/media/folders"));
107
138
  setFolders(result.folders || []);
108
139
  } catch {
109
140
  setFolders([]);
@@ -119,7 +150,7 @@ export function UploadField({
119
150
  if (selectedFolder) {
120
151
  url += "&folder=" + encodeURIComponent(selectedFolder);
121
152
  }
122
- const result = await apiGet(url);
153
+ const result = await apiGet<any>(url);
123
154
  setMediaItems(result.docs || []);
124
155
  } catch {
125
156
  setMediaItems([]);
@@ -136,7 +167,7 @@ export function UploadField({
136
167
  if (selectedFolder) {
137
168
  formData.append("folder", selectedFolder);
138
169
  }
139
- const result = await apiUpload("/api/upload", formData);
170
+ const result = await apiUpload<any>("/api/media/upload", formData);
140
171
  const newImage = {
141
172
  id: result.id,
142
173
  filename: result.filename,
@@ -149,6 +180,7 @@ export function UploadField({
149
180
  } else {
150
181
  onChange(newImage);
151
182
  }
183
+ toast.success(`Asset synchronized: ${newImage.filename}`);
152
184
  } catch (err) {
153
185
  console.error("Upload failed:", err);
154
186
  } finally {
@@ -162,7 +194,7 @@ export function UploadField({
162
194
 
163
195
  setUrlError("");
164
196
  try {
165
- const result = await apiPost("/api/upload", { url });
197
+ const result = await apiPost<any>("/api/media/upload", { url });
166
198
  const originalName = (() => {
167
199
  try {
168
200
  return (
@@ -186,10 +218,12 @@ export function UploadField({
186
218
  } else {
187
219
  onChange(newImage);
188
220
  }
221
+ toast.success(`URL asset established: ${newImage.filename}`);
189
222
  setUrlValue("");
190
223
  setShowUrlInput(false);
191
- } catch (err: any) {
192
- setUrlError(err.message || "Invalid URL");
224
+ } catch (err: unknown) {
225
+ const message = err instanceof Error ? err.message : "Invalid URL";
226
+ setUrlError(message);
193
227
  }
194
228
  };
195
229
 
@@ -234,7 +268,8 @@ export function UploadField({
234
268
  }
235
269
 
236
270
  const renderImagePreview = (img: any, index?: number) => {
237
- const fileType = getFileType(img?.mimeType, img?.filename || img?.url);
271
+ if (!img) return null;
272
+ const fileType = getFileType(img.mimeType, img.filename || img.url);
238
273
  const isImage = fileType === "image";
239
274
 
240
275
  return (
@@ -245,7 +280,7 @@ export function UploadField({
245
280
  <div className="w-10 h-10 rounded-md overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex items-center justify-center flex-shrink-0">
246
281
  {isImage ? (
247
282
  <img
248
- src={img.url}
283
+ src={resolveMedia(img.url)}
249
284
  alt={img.filename || "Preview"}
250
285
  className="w-full h-full object-cover"
251
286
  />
@@ -258,9 +293,9 @@ export function UploadField({
258
293
  </div>
259
294
  <div className="flex-1 min-w-0">
260
295
  <div className="text-[11px] font-medium truncate text-[var(--kyro-text-primary)]">
261
- {img?.originalName || img?.filename || "Unnamed File"}
296
+ {img.originalName || img.filename || "Unnamed File"}
262
297
  </div>
263
- <div className="text-[10px] text-[var(--kyro-text-muted)] uppercase tracking-wider font-bold">
298
+ <div className="text-[10px] text-[var(--kyro-text-muted)] tracking-wider font-bold">
264
299
  {fieldLabel}
265
300
  </div>
266
301
  </div>
@@ -298,7 +333,7 @@ export function UploadField({
298
333
  )}
299
334
  </div>
300
335
  ) : (
301
- renderImagePreview(value, fieldLabel)
336
+ renderImagePreview(value)
302
337
  )}
303
338
  <input
304
339
  ref={inputRef}
@@ -316,7 +351,7 @@ export function UploadField({
316
351
  }
317
352
 
318
353
  return (
319
- <div className="space-y-2">
354
+ <div className="space-y-2 relative">
320
355
  <input
321
356
  ref={inputRef}
322
357
  type="file"
@@ -448,11 +483,10 @@ function MediaPickerContent({
448
483
  }) {
449
484
  return (
450
485
  <div
451
- className={`${
452
- isFullscreen
453
- ? "fixed inset-0 z-[9999]"
454
- : "absolute z-50 w-[360px] max-h-[400px] mt-1 rounded-lg shadow-lg"
455
- } overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex flex-col`}
486
+ className={`${isFullscreen
487
+ ? "fixed inset-0 z-[9999]"
488
+ : "absolute z-[9999] w-[360px] max-h-[400px] mt-1 rounded-lg shadow-lg"
489
+ } overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex flex-col`}
456
490
  >
457
491
  <div className="p-2 border-b border-[var(--kyro-border)] flex flex-col gap-2">
458
492
  <input
@@ -467,11 +501,10 @@ function MediaPickerContent({
467
501
  <button
468
502
  type="button"
469
503
  onClick={() => setSelectedFolder("")}
470
- className={`px-2 py-1 text-xs rounded transition-colors ${
471
- selectedFolder === ""
472
- ? "bg-[var(--kyro-primary)] text-white"
473
- : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
474
- }`}
504
+ className={`px-2 py-1 text-xs rounded transition-colors ${selectedFolder === ""
505
+ ? "bg-[var(--kyro-primary)] text-white"
506
+ : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
507
+ }`}
475
508
  >
476
509
  All
477
510
  </button>
@@ -480,11 +513,10 @@ function MediaPickerContent({
480
513
  key={folder.path}
481
514
  type="button"
482
515
  onClick={() => setSelectedFolder(folder.path)}
483
- className={`px-2 py-1 text-xs rounded transition-colors ${
484
- selectedFolder === folder.path
485
- ? "bg-[var(--kyro-primary)] text-white"
486
- : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
487
- }`}
516
+ className={`px-2 py-1 text-xs rounded transition-colors ${selectedFolder === folder.path
517
+ ? "bg-[var(--kyro-primary)] text-white"
518
+ : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
519
+ }`}
488
520
  >
489
521
  {folder.name}
490
522
  </button>
@@ -505,11 +537,10 @@ function MediaPickerContent({
505
537
  </div>
506
538
  ) : (
507
539
  <div
508
- className={`grid gap-1 ${
509
- isFullscreen
510
- ? "grid-cols-[repeat(auto-fill,minmax(140px,1fr))]"
511
- : "grid-cols-3"
512
- }`}
540
+ className={`grid gap-1 ${isFullscreen
541
+ ? "grid-cols-[repeat(auto-fill,minmax(140px,1fr))]"
542
+ : "grid-cols-3"
543
+ }`}
513
544
  >
514
545
  {filteredMedia.map((item) => (
515
546
  <button
@@ -519,13 +550,12 @@ function MediaPickerContent({
519
550
  className="border border-[var(--kyro-border)] rounded-md overflow-hidden cursor-pointer p-0 bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)] transition-all relative group"
520
551
  >
521
552
  <div
522
- className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${
523
- isFullscreen ? "h-[120px]" : "h-[80px]"
524
- }`}
553
+ className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${isFullscreen ? "h-[120px]" : "h-[80px]"
554
+ }`}
525
555
  >
526
556
  {getFileType(item.mimeType, item.filename) === "image" ? (
527
557
  <img
528
- src={item.thumbnailUrl || item.url}
558
+ src={resolveMedia(item.thumbnailUrl || item.url)}
529
559
  alt={item.filename}
530
560
  className="w-full h-full object-cover"
531
561
  />
@@ -536,11 +566,11 @@ function MediaPickerContent({
536
566
  />
537
567
  )}
538
568
  </div>
539
- <div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex flex-col items-center justify-center p-2">
569
+ <div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-0 transition-opacity flex flex-col items-center justify-center p-2">
540
570
  <span className="text-white text-[10px] font-medium text-center line-clamp-2 mb-1">
541
571
  {item.filename}
542
572
  </span>
543
- <span className="text-white/70 text-[9px] uppercase font-bold tracking-tighter">
573
+ <span className="text-white/70 text-[9px] font-bold tracking-tighter">
544
574
  {getFileType(item.mimeType, item.filename)}
545
575
  </span>
546
576
  </div>
@@ -5,7 +5,7 @@ interface VideoFieldProps {
5
5
  src?: string;
6
6
  title?: string;
7
7
  onChange: (field: string, value: string) => void;
8
- onUploadChange?: (value: any) => void;
8
+ onUploadChange?: (value: unknown) => void;
9
9
  compact?: boolean;
10
10
  }
11
11
 
@@ -35,10 +35,10 @@ import {
35
35
  Star,
36
36
  ListOrdered,
37
37
  Link2,
38
- } from "lucide-react";
38
+ } from "../../ui/icons";
39
39
 
40
40
  // Block component registry
41
- export const BLOCK_COMPONENTS: Record<string, React.ComponentType<any>> = {
41
+ export const BLOCK_COMPONENTS: Record<string, React.ComponentType<{ block: Record<string, unknown>; index: number }>> = {
42
42
  columns: ColumnsBlock,
43
43
  heading: HeadingBlock,
44
44
  paragraph: ParagraphBlock,