@kyro-cms/admin 0.9.0 → 0.9.2

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 (114) hide show
  1. package/dist/index.cjs +11715 -11292
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +67 -65
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +564 -0
  6. package/dist/index.d.ts +11 -10
  7. package/dist/index.js +11326 -10912
  8. package/dist/index.js.map +1 -1
  9. package/package.json +16 -12
  10. package/src/components/ActionBar.tsx +25 -161
  11. package/src/components/Admin.tsx +2 -4
  12. package/src/components/ApiKeysManager.tsx +5 -5
  13. package/src/components/AuditLogsPage.tsx +2 -13
  14. package/src/components/AutoForm.tsx +572 -461
  15. package/src/components/BrandingHub.tsx +7 -4
  16. package/src/components/CreateView.tsx +2 -0
  17. package/src/components/DetailView.tsx +52 -65
  18. package/src/components/DeveloperCenter.tsx +8 -6
  19. package/src/components/FieldRenderer.tsx +94 -19
  20. package/src/components/ListView.tsx +57 -216
  21. package/src/components/MediaGallery.tsx +334 -367
  22. package/src/components/PluginsManager.tsx +197 -70
  23. package/src/components/RestPlayground.tsx +59 -52
  24. package/src/components/SessionsManager.tsx +1 -1
  25. package/src/components/SettingsPage.tsx +22 -0
  26. package/src/components/Sidebar.astro +13 -41
  27. package/src/components/UserManagement.tsx +153 -15
  28. package/src/components/UserMenu.tsx +30 -4
  29. package/src/components/VersionHistoryPanel.tsx +112 -119
  30. package/src/components/WebhookManager.tsx +6 -4
  31. package/src/components/blocks/ArrayBlock.tsx +6 -23
  32. package/src/components/blocks/BlockEditModal.tsx +82 -309
  33. package/src/components/blocks/CardBlock.tsx +35 -0
  34. package/src/components/blocks/ChildBlocksTree.tsx +57 -31
  35. package/src/components/blocks/GenericBlock.tsx +44 -0
  36. package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
  37. package/src/components/blocks/HeroBlock.tsx +5 -14
  38. package/src/components/blocks/RichTextBlock.tsx +5 -5
  39. package/src/components/blocks/index.ts +5 -3
  40. package/src/components/fields/AccordionField.tsx +2 -2
  41. package/src/components/fields/ArrayField.tsx +1 -1
  42. package/src/components/fields/ArrayLayout.tsx +120 -29
  43. package/src/components/fields/BlocksField.tsx +433 -55
  44. package/src/components/fields/CardField.tsx +73 -0
  45. package/src/components/fields/CheckboxField.tsx +7 -3
  46. package/src/components/fields/DateField.tsx +4 -1
  47. package/src/components/fields/GroupLayout.tsx +2 -2
  48. package/src/components/fields/HeadingSubheadingField.tsx +43 -0
  49. package/src/components/fields/ListField.tsx +2 -2
  50. package/src/components/fields/NumberField.tsx +4 -1
  51. package/src/components/fields/RelationshipBlockField.tsx +2 -3
  52. package/src/components/fields/RelationshipField.tsx +155 -90
  53. package/src/components/fields/RichTextField.tsx +781 -0
  54. package/src/components/fields/SecretField.tsx +102 -0
  55. package/src/components/fields/SelectField.tsx +19 -6
  56. package/src/components/fields/TabsLayout.tsx +19 -9
  57. package/src/components/fields/TextField.tsx +4 -1
  58. package/src/components/fields/UploadField.tsx +122 -56
  59. package/src/components/fields/extensions/blockComponents.tsx +103 -174
  60. package/src/components/fields/extensions/blocksStore.ts +8 -1
  61. package/src/components/fields/index.ts +4 -2
  62. package/src/components/fix_imports.cjs +23 -0
  63. package/src/components/fix_imports2.cjs +19 -0
  64. package/src/components/replace_svgs.cjs +63 -0
  65. package/src/components/ui/Dropdown.tsx +7 -2
  66. package/src/components/ui/Modal.tsx +24 -27
  67. package/src/components/ui/PageHeader.tsx +5 -5
  68. package/src/components/ui/PromptModal.tsx +2 -10
  69. package/src/components/ui/SlidePanel.tsx +10 -13
  70. package/src/components/ui/SplitButton.tsx +107 -0
  71. package/src/components/ui/Toaster.tsx +0 -1
  72. package/src/components/ui/icons.tsx +110 -109
  73. package/src/components/users/UserDetail.tsx +79 -16
  74. package/src/components/users/UsersList.tsx +8 -85
  75. package/src/hooks/useAutoFormState.ts +187 -196
  76. package/src/hooks/useQueue.ts +60 -0
  77. package/src/integration.ts +148 -46
  78. package/src/kyro-cms.d.ts +7 -2
  79. package/src/layouts/AdminLayout.astro +22 -2
  80. package/src/layouts/AuthLayout.astro +67 -7
  81. package/src/lib/autoform-store.ts +90 -53
  82. package/src/lib/change-source.ts +9 -0
  83. package/src/lib/config.ts +104 -8
  84. package/src/lib/globals.ts +48 -11
  85. package/src/lib/normalize-upload-fields.ts +41 -0
  86. package/src/lib/paths.ts +2 -2
  87. package/src/lib/resolve-field-value.ts +110 -0
  88. package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
  89. package/src/lib/shim/use-sync-external-store.js +1 -0
  90. package/src/lib/stores/index.ts +1 -0
  91. package/src/lib/useResourceManager.ts +4 -4
  92. package/src/lib/vite-shim-plugin.ts +100 -0
  93. package/src/pages/[collection]/[id].astro +1 -1
  94. package/src/pages/auth/register.astro +5 -2
  95. package/src/pages/preview/[collection]/[id].astro +4 -4
  96. package/src/pages/settings/[slug].astro +2 -2
  97. package/src/styles/main.css +60 -54
  98. package/README.md +0 -46
  99. package/dist/EditorClient-Q23UXR37.cjs +0 -468
  100. package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
  101. package/dist/EditorClient-T5PASFNR.js +0 -466
  102. package/dist/EditorClient-T5PASFNR.js.map +0 -1
  103. package/dist/chunk-3BGDYKTD.cjs +0 -348
  104. package/dist/chunk-3BGDYKTD.cjs.map +0 -1
  105. package/dist/chunk-EEFXLQVT.js +0 -3
  106. package/dist/chunk-EEFXLQVT.js.map +0 -1
  107. package/src/components/blocks/ButtonBlock.tsx +0 -64
  108. package/src/components/blocks/ColumnsBlock.tsx +0 -55
  109. package/src/components/blocks/DividerBlock.tsx +0 -43
  110. package/src/components/blocks/LinkBlock.tsx +0 -65
  111. package/src/components/blocks/VStackBlock.tsx +0 -29
  112. package/src/components/fields/EditorClient.tsx +0 -535
  113. package/src/components/fields/PortableTextField.tsx +0 -155
  114. package/src/components/fields/PortableTextRenderer.tsx +0 -68
@@ -1,11 +1,15 @@
1
- import React, { useState } from "react";
2
- import { useUIStore } from "../../lib/stores";
1
+ import React, { useState, useEffect } from "react";
2
+ import { apiGet } from "../../lib/api";
3
+ import { useUIStore, toast } from "../../lib/stores";
4
+ import { X } from "../ui/icons";
5
+ import { UploadField } from "../fields/UploadField";
3
6
 
4
7
  interface User {
5
8
  id: string;
6
9
  name?: string;
7
10
  email: string;
8
11
  role: string;
12
+ avatar?: string;
9
13
  tenantId?: string;
10
14
  emailVerified?: boolean;
11
15
  locked?: boolean;
@@ -33,12 +37,41 @@ const roleOptions = [
33
37
  export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
34
38
  const [name, setName] = useState(user.name || "");
35
39
  const [role, setRole] = useState(user.role);
40
+ const [avatar, setAvatar] = useState<string | undefined>(user.avatar);
41
+ const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
42
+ const [avatarMedia, setAvatarMedia] = useState<any>(user.avatar ? { id: user.avatar } : null);
36
43
  const [saving, setSaving] = useState(false);
37
44
  const { confirm, alert } = useUIStore();
38
45
  const [deleting, setDeleting] = useState(false);
39
46
  const [locking, setLocking] = useState(false);
40
47
  const [isLocked, setIsLocked] = useState(user.locked || false);
41
48
 
49
+ useEffect(() => {
50
+ if (typeof avatar === "string" && /^[0-9a-f-]+$/i.test(avatar)) {
51
+ apiGet<any>(`/api/media/${avatar}`)
52
+ .then((media) => {
53
+ setAvatarUrl(media?.thumbnailUrl || media?.url || null);
54
+ setAvatarMedia({ id: avatar, url: media.url, thumbnailUrl: media.thumbnailUrl, filename: media.filename, originalName: media.originalName, mimeType: media.mimeType });
55
+ })
56
+ .catch(() => { setAvatarUrl(null); setAvatarMedia(null); });
57
+ } else {
58
+ setAvatarUrl(null);
59
+ setAvatarMedia(null);
60
+ }
61
+ }, [avatar]);
62
+
63
+ const handleAvatarChange = (val: any) => {
64
+ if (val && typeof val === "object") {
65
+ setAvatar(val.id);
66
+ setAvatarUrl(val.url || val.thumbnailUrl || null);
67
+ setAvatarMedia(val);
68
+ } else {
69
+ setAvatar(undefined);
70
+ setAvatarUrl(null);
71
+ setAvatarMedia(null);
72
+ }
73
+ };
74
+
42
75
  const handleSave = async () => {
43
76
  setSaving(true);
44
77
  try {
@@ -50,6 +83,9 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
50
83
  if (name !== user.name) {
51
84
  body.name = name.trim() === "" ? null : name.trim();
52
85
  }
86
+ if (avatar !== user.avatar) {
87
+ body.avatar = avatar || null;
88
+ }
53
89
 
54
90
  if (Object.keys(body).length === 0) {
55
91
  window.location.href = adminPath + "/users";
@@ -66,12 +102,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
66
102
  const data = await res.json();
67
103
 
68
104
  if (res.ok) {
105
+ toast.success("User updated");
69
106
  window.location.href = adminPath + "/users";
70
107
  } else {
71
- console.error("Save failed:", data.error);
108
+ toast.error(data.error || "Failed to save user");
72
109
  }
73
110
  } catch (e) {
74
- console.error("Failed to save user:", e);
111
+ toast.error("Failed to save user");
75
112
  } finally {
76
113
  setSaving(false);
77
114
  }
@@ -96,9 +133,12 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
96
133
  });
97
134
  if (res.ok) {
98
135
  setIsLocked(!isLocked);
136
+ toast.success(isLocked ? "User unlocked" : "User locked");
137
+ } else {
138
+ toast.error("Failed to update lock status");
99
139
  }
100
140
  } catch (e) {
101
- console.error("Failed to toggle lock:", e);
141
+ toast.error("Failed to toggle lock");
102
142
  } finally {
103
143
  setLocking(false);
104
144
  }
@@ -119,10 +159,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
119
159
  credentials: "include",
120
160
  });
121
161
  if (res.ok) {
162
+ toast.success("User deleted");
122
163
  window.location.href = adminPath + "/users";
164
+ } else {
165
+ toast.error("Failed to delete user");
123
166
  }
124
167
  } catch (e) {
125
- console.error("Failed to delete user:", e);
168
+ toast.error("Failed to delete user");
126
169
  } finally {
127
170
  setDeleting(false);
128
171
  }
@@ -139,14 +182,18 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
139
182
  <div className="flex-1 overflow-y-auto space-y-8">
140
183
  <div className="surface-tile p-6 flex items-center justify-between">
141
184
  <div className="flex items-center gap-4">
142
- <div className="w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl">
143
- {(name || user.email).charAt(0).toUpperCase()}
185
+ <div className="relative w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl overflow-hidden flex-shrink-0">
186
+ {avatarUrl ? (
187
+ <img src={avatarUrl} alt="" className="w-full h-full object-cover" />
188
+ ) : (
189
+ (name || user.email).charAt(0).toUpperCase()
190
+ )}
144
191
  </div>
145
192
  <div>
146
193
  <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
147
194
  {name || user.email}
148
195
  </h1>
149
- <p className="text-sm text-[var(--kyro-text-secondary)] font-medium">
196
+ <p className="text-sm text-[var(--kyro-text-secondary)] font-medium mb-2">
150
197
  {name ? user.email : `User ID: ${user.id}`}
151
198
  </p>
152
199
  </div>
@@ -248,14 +295,20 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
248
295
  : "Never"}
249
296
  </p>
250
297
  </div>
251
- <div>
252
- <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
253
- Failed Attempts
298
+ <div className="col-span-2">
299
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
300
+ Photo
254
301
  </label>
255
- <p className="mt-1 text-sm font-medium text-[var(--kyro-text-primary)]">
256
- {user.failedLoginAttempts || 0}
257
- </p>
302
+ <div className="mt-1 flex items-center gap-2">
303
+ <UploadField
304
+ field={{ label: "Photo", name: "avatar", maxCount: 1, allowedTypes: ["image/*"] }}
305
+ value={avatarMedia}
306
+ onChange={handleAvatarChange}
307
+ disabled={saving}
308
+ />
309
+ </div>
258
310
  </div>
311
+
259
312
  <div>
260
313
  <label className="text-xs font-bold text-[#64748b] tracking-wider">
261
314
  Created
@@ -264,6 +317,16 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
264
317
  {formatDate(user.createdAt)}
265
318
  </p>
266
319
  </div>
320
+
321
+ <div>
322
+ <label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
323
+ Failed Attempts
324
+ </label>
325
+ <p className="mt-1 text-sm font-medium text-[var(--kyro-text-primary)]">
326
+ {user.failedLoginAttempts || 0}
327
+ </p>
328
+ </div>
329
+
267
330
  <div>
268
331
  <label className="text-xs font-bold text-[#64748b] tracking-wider">
269
332
  Updated
@@ -278,7 +341,7 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
278
341
  <button
279
342
  onClick={handleSave}
280
343
  disabled={saving}
281
- className="px-6 py-2 bg-[#0b1222] text-white rounded-xl text-sm font-bold hover:bg-[#1a2332] transition-colors disabled:opacity-50"
344
+ className="kyro-btn kyro-btn-primary px-6 py-2 rounded-xl text-sm font-bold transition-colors disabled:opacity-50"
282
345
  >
283
346
  {saving ? "Saving…" : "Save Changes"}
284
347
  </button>
@@ -1,3 +1,4 @@
1
+ import { Plus, Lock, CheckCircle2, Edit2, Trash2, XCircle, X } from "../ui/icons";
1
2
  import React, { useState } from "react";
2
3
  import { useUIStore } from "../../lib/stores";
3
4
 
@@ -144,19 +145,7 @@ export function UsersList({
144
145
  href={`${adminPath}/users/new`}
145
146
  className="mt-2 inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-lg font-bold text-sm shadow-md"
146
147
  >
147
- <svg
148
- className="w-3.5 h-3.5"
149
- fill="none"
150
- stroke="currentColor"
151
- viewBox="0 0 24 24"
152
- >
153
- <path
154
- strokeLinecap="round"
155
- strokeLinejoin="round"
156
- strokeWidth="2.5"
157
- d="M12 5v14M5 12h14"
158
- />
159
- </svg>
148
+ <Plus className="w-4 h-4" />
160
149
  Add User
161
150
  </a>
162
151
  </div>
@@ -214,32 +203,12 @@ export function UsersList({
214
203
  <td className="px-6 py-5">
215
204
  {user.locked ? (
216
205
  <span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600">
217
- <svg
218
- className="w-3 h-3"
219
- fill="currentColor"
220
- viewBox="0 0 20 20"
221
- >
222
- <path
223
- fillRule="evenodd"
224
- d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
225
- clipRule="evenodd"
226
- />
227
- </svg>
206
+ <Lock className="w-4 h-4" />
228
207
  Locked
229
208
  </span>
230
209
  ) : (
231
210
  <span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600">
232
- <svg
233
- className="w-3 h-3"
234
- fill="currentColor"
235
- viewBox="0 0 20 20"
236
- >
237
- <path
238
- fillRule="evenodd"
239
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
240
- clipRule="evenodd"
241
- />
242
- </svg>
211
+ <CheckCircle2 className="w-4 h-4" />
243
212
  Active
244
213
  </span>
245
214
  )}
@@ -254,19 +223,7 @@ export function UsersList({
254
223
  className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors"
255
224
  onClick={(e) => e.stopPropagation()}
256
225
  >
257
- <svg
258
- className="w-4 h-4"
259
- fill="none"
260
- stroke="currentColor"
261
- viewBox="0 0 24 24"
262
- >
263
- <path
264
- strokeLinecap="round"
265
- strokeLinejoin="round"
266
- strokeWidth="2"
267
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
268
- />
269
- </svg>
226
+ <Edit2 className="w-4 h-4" />
270
227
  </a>
271
228
  <button
272
229
  onClick={(e) => {
@@ -275,19 +232,7 @@ export function UsersList({
275
232
  }}
276
233
  className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-red-50 hover:text-red-600 transition-colors"
277
234
  >
278
- <svg
279
- className="w-4 h-4"
280
- fill="none"
281
- stroke="currentColor"
282
- viewBox="0 0 24 24"
283
- >
284
- <path
285
- strokeLinecap="round"
286
- strokeLinejoin="round"
287
- strokeWidth="2"
288
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
289
- />
290
- </svg>
235
+ <Trash2 className="w-4 h-4" />
291
236
  </button>
292
237
  </div>
293
238
  </td>
@@ -300,35 +245,13 @@ export function UsersList({
300
245
 
301
246
  {errorMsg && (
302
247
  <div className="surface-tile p-4 flex items-center gap-3 border border-red-200">
303
- <svg
304
- className="w-5 h-5 text-red-500 flex-shrink-0"
305
- fill="currentColor"
306
- viewBox="0 0 20 20"
307
- >
308
- <path
309
- fillRule="evenodd"
310
- d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
311
- clipRule="evenodd"
312
- />
313
- </svg>
248
+ <XCircle className="w-4 h-4" />
314
249
  <p className="text-sm font-medium text-red-500">{errorMsg}</p>
315
250
  <button
316
251
  onClick={() => setErrorMsg(null)}
317
252
  className="ml-auto text-red-400 hover:text-red-600"
318
253
  >
319
- <svg
320
- className="w-4 h-4"
321
- fill="none"
322
- stroke="currentColor"
323
- viewBox="0 0 24 24"
324
- >
325
- <path
326
- strokeLinecap="round"
327
- strokeLinejoin="round"
328
- strokeWidth="2"
329
- d="M6 18L18 6M6 6l12 12"
330
- />
331
- </svg>
254
+ <X className="w-4 h-4" />
332
255
  </button>
333
256
  </div>
334
257
  )}