@kyro-cms/admin 0.1.6 → 0.1.7

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 (163) hide show
  1. package/README.md +149 -51
  2. package/package.json +53 -6
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +136 -27
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +1417 -661
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +3 -3
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +199 -57
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +786 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +191 -53
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +149 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/UserManagement.tsx +204 -0
  26. package/src/components/VersionHistoryPanel.tsx +3 -3
  27. package/src/components/WebhookManager.tsx +608 -0
  28. package/src/components/blocks/AccordionBlock.tsx +97 -0
  29. package/src/components/blocks/ArrayBlock.tsx +75 -0
  30. package/src/components/blocks/BlockEditModal.MARKER +12 -0
  31. package/src/components/blocks/BlockEditModal.tsx +774 -0
  32. package/src/components/blocks/ButtonBlock.tsx +165 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +66 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +151 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +64 -0
  38. package/src/components/blocks/HeadingBlock.tsx +81 -0
  39. package/src/components/blocks/HeroBlock.tsx +157 -0
  40. package/src/components/blocks/ImageBlock.tsx +83 -0
  41. package/src/components/blocks/LinkBlock.tsx +71 -0
  42. package/src/components/blocks/ListBlock.tsx +39 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +279 -0
  45. package/src/components/blocks/VStackBlock.tsx +75 -0
  46. package/src/components/blocks/VideoBlock.tsx +45 -0
  47. package/src/components/blocks/index.ts +10 -0
  48. package/src/components/fields/BlocksField.tsx +323 -0
  49. package/src/components/fields/CheckboxField.tsx +15 -9
  50. package/src/components/fields/CodeField.tsx +234 -0
  51. package/src/components/fields/DateField.tsx +38 -11
  52. package/src/components/fields/EditorClient.tsx +271 -0
  53. package/src/components/fields/FileField.tsx +390 -0
  54. package/src/components/fields/HybridContentField.tsx +109 -0
  55. package/src/components/fields/ImageField.tsx +429 -0
  56. package/src/components/fields/JSONField.tsx +361 -0
  57. package/src/components/fields/MarkdownField.tsx +282 -0
  58. package/src/components/fields/NumberField.tsx +42 -12
  59. package/src/components/fields/PortableTextField.tsx +143 -0
  60. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  61. package/src/components/fields/RelationshipField.tsx +231 -59
  62. package/src/components/fields/SelectField.tsx +25 -15
  63. package/src/components/fields/TextField.tsx +45 -14
  64. package/src/components/fields/extensions/blockComponents.tsx +237 -0
  65. package/src/components/fields/extensions/blocksStore.ts +273 -0
  66. package/src/components/fields/index.ts +13 -0
  67. package/src/components/index.ts +1 -2
  68. package/src/components/layout/Header.tsx +2 -2
  69. package/src/components/layout/Layout.tsx +2 -2
  70. package/src/components/ui/Badge.tsx +9 -4
  71. package/src/components/ui/BlockDrawer.tsx +79 -0
  72. package/src/components/ui/Button.tsx +1 -1
  73. package/src/components/ui/CommandPalette.tsx +362 -0
  74. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  75. package/src/components/ui/Dropdown.tsx +1 -1
  76. package/src/components/ui/Modal.tsx +37 -12
  77. package/src/components/ui/PromptModal.tsx +94 -0
  78. package/src/components/ui/SlidePanel.tsx +43 -16
  79. package/src/components/ui/Toast.tsx +80 -14
  80. package/src/env.d.ts +16 -0
  81. package/src/env.ts +20 -0
  82. package/src/index.ts +0 -1
  83. package/src/layouts/AdminLayout.astro +164 -170
  84. package/src/layouts/AuthLayout.astro +23 -6
  85. package/src/lib/MediaService.ts +541 -0
  86. package/src/lib/auth/sqlite-adapter.ts +319 -0
  87. package/src/lib/config.ts +22 -6
  88. package/src/lib/dataStore.ts +132 -74
  89. package/src/lib/db/adapter.ts +54 -0
  90. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  91. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  92. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  93. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  94. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  95. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  96. package/src/lib/db/index.ts +449 -0
  97. package/src/lib/db/mongodb-adapter.ts +207 -0
  98. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  99. package/src/lib/db/schema/mysql-auth.ts +113 -0
  100. package/src/lib/db/schema/mysql-content.ts +20 -0
  101. package/src/lib/db/schema/postgres-auth.ts +116 -0
  102. package/src/lib/db/schema/postgres-content.ts +35 -0
  103. package/src/lib/db/schema/postgres-media.ts +52 -0
  104. package/src/lib/db/schema/postgres-settings.ts +11 -0
  105. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  106. package/src/lib/db/schema/sqlite-content.ts +20 -0
  107. package/src/lib/graphql/index.ts +1 -0
  108. package/src/lib/graphql/schema.ts +443 -0
  109. package/src/lib/rate-limit.ts +267 -0
  110. package/src/lib/storage.ts +374 -0
  111. package/src/lib/store.ts +85 -0
  112. package/src/middleware.ts +70 -11
  113. package/src/pages/[collection]/[id].astro +178 -122
  114. package/src/pages/[collection]/index.astro +24 -156
  115. package/src/pages/admin/api-explorer.astro +98 -0
  116. package/src/pages/admin/graphql-explorer.astro +40 -0
  117. package/src/pages/admin/graphql.astro +97 -0
  118. package/src/pages/admin/index.astro +200 -139
  119. package/src/pages/admin/keys.astro +8 -0
  120. package/src/pages/admin/rest-playground.astro +44 -0
  121. package/src/pages/admin/webhooks.astro +8 -0
  122. package/src/pages/api/[collection]/[id]/publish.ts +44 -0
  123. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  124. package/src/pages/api/[collection]/[id]/versions.ts +36 -0
  125. package/src/pages/api/[collection]/[id].ts +102 -159
  126. package/src/pages/api/[collection]/index.ts +151 -230
  127. package/src/pages/api/auth/[id].ts +48 -69
  128. package/src/pages/api/auth/audit-logs.ts +20 -43
  129. package/src/pages/api/auth/login.ts +159 -45
  130. package/src/pages/api/auth/logout.ts +42 -24
  131. package/src/pages/api/auth/refresh.ts +119 -0
  132. package/src/pages/api/auth/register.ts +110 -40
  133. package/src/pages/api/auth/users.ts +22 -97
  134. package/src/pages/api/collections.ts +59 -0
  135. package/src/pages/api/globals/[slug]/test.ts +172 -0
  136. package/src/pages/api/globals/[slug].ts +42 -0
  137. package/src/pages/api/graphql.ts +90 -0
  138. package/src/pages/api/health.ts +417 -40
  139. package/src/pages/api/keys/[id].ts +26 -0
  140. package/src/pages/api/keys/index.ts +75 -0
  141. package/src/pages/api/media/[id].ts +309 -0
  142. package/src/pages/api/media/folders.ts +609 -0
  143. package/src/pages/api/media/index.ts +146 -0
  144. package/src/pages/api/media/resize.ts +267 -0
  145. package/src/pages/api/search.ts +82 -0
  146. package/src/pages/api/slug-availability.ts +70 -0
  147. package/src/pages/api/storage-config.ts +20 -0
  148. package/src/pages/api/storage-status.ts +206 -0
  149. package/src/pages/api/upload.ts +334 -0
  150. package/src/pages/api/webhooks/index.ts +71 -0
  151. package/src/pages/audit/index.astro +2 -104
  152. package/src/pages/login.astro +11 -11
  153. package/src/pages/media.astro +10 -0
  154. package/src/pages/preview/[collection]/[id].astro +178 -0
  155. package/src/pages/register.astro +13 -13
  156. package/src/pages/roles/index.astro +21 -21
  157. package/src/pages/settings/[slug].astro +162 -0
  158. package/src/pages/settings/index.astro +9 -0
  159. package/src/pages/users/[id].astro +29 -21
  160. package/src/pages/users/index.astro +22 -17
  161. package/src/pages/users/new.astro +18 -17
  162. package/src/styles/main.css +553 -128
  163. package/src/components/layout/Sidebar.tsx +0 -497
@@ -8,6 +8,7 @@ import { AutoForm } from "./AutoForm";
8
8
  import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
9
9
  import { ConfirmModal } from "./ui/Modal";
10
10
  import { Spinner } from "./ui/Spinner";
11
+ import { useToast } from "./ui/Toast";
11
12
 
12
13
  interface DetailViewProps {
13
14
  config: KyroConfig;
@@ -36,6 +37,7 @@ export function DetailView({
36
37
  autosave = true,
37
38
  autosaveDelay = 2000,
38
39
  }: DetailViewProps) {
40
+ const { addToast } = useToast();
39
41
  const [data, setData] = useState<Record<string, unknown>>({});
40
42
  const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
41
43
  const [loading, setLoading] = useState(true);
@@ -47,6 +49,7 @@ export function DetailView({
47
49
  const [createdAt, setCreatedAt] = useState<string | null>(null);
48
50
  const [updatedAt, setUpdatedAt] = useState<string | null>(null);
49
51
  const [publishedAt, setPublishedAt] = useState<string | null>(null);
52
+ const [justSaved, setJustSaved] = useState(false);
50
53
 
51
54
  const autosaveTimerRef = useRef<NodeJS.Timeout | null>(null);
52
55
  const hasChangesRef = useRef(false);
@@ -57,6 +60,19 @@ export function DetailView({
57
60
 
58
61
  const hasChanges = JSON.stringify(data) !== JSON.stringify(originalData);
59
62
 
63
+ // Auto-set status to draft when there are changes from a published state
64
+ useEffect(() => {
65
+ if (hasChanges && status === "published") {
66
+ setStatus("draft");
67
+ }
68
+ }, [hasChanges, status]);
69
+
70
+ useEffect(() => {
71
+ if (hasChanges && saveStatus === "saved") {
72
+ setSaveStatus("idle");
73
+ }
74
+ }, [hasChanges, saveStatus]);
75
+
60
76
  useEffect(() => {
61
77
  if (mode === "global") {
62
78
  loadGlobal();
@@ -150,6 +166,14 @@ export function DetailView({
150
166
  setSaveStatus("saved");
151
167
  setUpdatedAt(new Date().toISOString());
152
168
 
169
+ // Show green border for 3 seconds after any save
170
+ setJustSaved(true);
171
+ setTimeout(() => setJustSaved(false), 3000);
172
+
173
+ if (!isAutosave) {
174
+ addToast("success", "Saved successfully");
175
+ }
176
+
153
177
  setTimeout(() => {
154
178
  setSaveStatus("idle");
155
179
  }, 2000);
@@ -157,6 +181,7 @@ export function DetailView({
157
181
  setSaveStatus("error");
158
182
  if (!isAutosave) {
159
183
  onError("Failed to save changes");
184
+ addToast("error", "Failed to save changes");
160
185
  }
161
186
  } finally {
162
187
  setSaving(false);
@@ -174,8 +199,10 @@ export function DetailView({
174
199
  if (!response.ok) throw new Error("Failed to publish");
175
200
  setStatus("published");
176
201
  setPublishedAt(new Date().toISOString());
202
+ addToast("success", "Published successfully");
177
203
  } catch {
178
204
  onError("Failed to publish");
205
+ addToast("error", "Failed to publish");
179
206
  } finally {
180
207
  setSaving(false);
181
208
  }
@@ -235,30 +262,45 @@ export function DetailView({
235
262
  );
236
263
  }
237
264
 
265
+ const isSingleLayout =
266
+ mode === "global" || collection?.admin?.layout === "single";
267
+
238
268
  return (
239
269
  <div className="kyro-detail">
240
- <ActionBar
241
- status={status}
242
- saveStatus={saveStatus}
243
- hasChanges={hasChanges}
244
- onSave={() => handleSave(false)}
245
- onPublish={mode === "collection" ? handlePublish : undefined}
246
- onUnpublish={status === "published" ? handleUnpublish : undefined}
247
- onDuplicate={mode === "collection" ? handleDuplicate : undefined}
248
- onViewHistory={() => {}}
249
- onPreview={() =>
250
- window.open(`/preview/${slug}/${documentId}`, "_blank")
251
- }
252
- onDelete={
253
- mode === "collection" ? () => setShowDeleteConfirm(true) : undefined
254
- }
255
- publishedAt={publishedAt}
256
- updatedAt={updatedAt}
257
- />
270
+ {!isSingleLayout && (
271
+ <ActionBar
272
+ status={status}
273
+ saveStatus={saveStatus}
274
+ hasChanges={hasChanges}
275
+ onSave={() => handleSave(false)}
276
+ onPublish={handlePublish}
277
+ onUnpublish={status === "published" ? handleUnpublish : undefined}
278
+ onDuplicate={handleDuplicate}
279
+ onViewHistory={() => {}}
280
+ onPreview={() =>
281
+ window.open(`/preview/${slug}/${documentId}`, "_blank")
282
+ }
283
+ onDelete={() => setShowDeleteConfirm(true)}
284
+ publishedAt={publishedAt}
285
+ updatedAt={updatedAt}
286
+ />
287
+ )}
258
288
 
259
- <div className="kyro-detail-body">
260
- <div className="kyro-card">
261
- <div className="kyro-card-content">
289
+ <div
290
+ className={
291
+ isSingleLayout
292
+ ? "w-full pb-32 pt-8"
293
+ : "w-full mx-auto grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-8 pb-32"
294
+ }
295
+ >
296
+ <div className="space-y-8 min-w-0">
297
+ <div className="surface-tile p-8">
298
+ <div className="flex items-center justify-between mb-8 px-1">
299
+ <h2 className="text-[10px] font-black uppercase tracking-[0.2em] opacity-40">
300
+ Core Configuration
301
+ </h2>
302
+ <div className="h-px flex-1 bg-[var(--kyro-border)] ml-6 opacity-30" />
303
+ </div>
262
304
  <AutoForm
263
305
  config={
264
306
  collection
@@ -267,51 +309,151 @@ export function DetailView({
267
309
  }
268
310
  data={data}
269
311
  onChange={setData}
312
+ layout={isSingleLayout ? "single" : "split"}
313
+ onActionSuccess={(message) => addToast("success", message)}
314
+ onActionError={(message) => addToast("error", message)}
315
+ documentStatus={status}
316
+ justSaved={justSaved}
270
317
  />
318
+ {isSingleLayout && (
319
+ <div className="mt-8 pt-8 border-t border-[var(--kyro-border)] flex justify-end gap-3">
320
+ {mode === "collection" && documentId && (
321
+ <button
322
+ type="button"
323
+ onClick={() => setShowDeleteConfirm(true)}
324
+ className="px-6 py-3 text-red-500 rounded-2xl font-black text-sm hover:bg-red-500/10 active:scale-95 transition-all text-left"
325
+ >
326
+ Delete
327
+ </button>
328
+ )}
329
+ <button
330
+ type="button"
331
+ onClick={() => handleSave(false)}
332
+ disabled={saving}
333
+ className="px-8 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-2xl font-black text-sm shadow-xl hover:shadow-[var(--kyro-primary)] active:scale-95 transition-all flex items-center gap-2"
334
+ >
335
+ {saving ? (
336
+ <svg
337
+ className="w-4 h-4 animate-spin"
338
+ viewBox="0 0 24 24"
339
+ fill="none"
340
+ stroke="currentColor"
341
+ strokeWidth="2"
342
+ >
343
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
344
+ </svg>
345
+ ) : (
346
+ <svg
347
+ className="w-4 h-4"
348
+ viewBox="0 0 24 24"
349
+ fill="none"
350
+ stroke="currentColor"
351
+ strokeWidth="2"
352
+ >
353
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
354
+ <polyline points="17 21 17 13 7 13 7 21" />
355
+ <polyline points="7 3 7 8 15 8" />
356
+ </svg>
357
+ )}
358
+ {saving
359
+ ? "Saving..."
360
+ : mode === "global"
361
+ ? "Save Configuration"
362
+ : "Save Document"}
363
+ </button>
364
+ </div>
365
+ )}
271
366
  </div>
272
367
  </div>
273
368
 
274
- <div className="kyro-meta-card">
275
- <div className="kyro-card">
276
- <div className="kyro-card-header">
277
- <h3 className="kyro-card-title">Info</h3>
369
+ {!isSingleLayout && (
370
+ <div className="space-y-6 animate-in fade-in slide-in-from-right-4 duration-500">
371
+ <div className="surface-tile p-8">
372
+ <h3 className="text-[10px] font-black uppercase tracking-[0.2em] opacity-40 mb-6">
373
+ Metadata
374
+ </h3>
375
+ <div className="space-y-6">
376
+ <div className="flex flex-col gap-2">
377
+ <span className="text-[10px] font-black uppercase tracking-widest opacity-40">
378
+ Dynamic Status
379
+ </span>
380
+ <div>
381
+ <span
382
+ className={`inline-flex items-center px-3 py-1 rounded-full text-[9px] font-black uppercase tracking-widest ${status === "published" ? "bg-green-500/10 text-green-500" : "bg-amber-500/10 text-amber-500"}`}
383
+ >
384
+ {status || "draft"}
385
+ </span>
386
+ </div>
387
+ </div>
388
+ <div className="flex flex-col gap-2">
389
+ <span className="text-[10px] font-black uppercase tracking-widest opacity-40">
390
+ Date Created
391
+ </span>
392
+ <span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
393
+ {createdAt
394
+ ? new Date(createdAt).toLocaleString("en-US", {
395
+ dateStyle: "medium",
396
+ timeStyle: "short",
397
+ })
398
+ : "N/A"}
399
+ </span>
400
+ </div>
401
+ <div className="flex flex-col gap-2">
402
+ <span className="text-[10px] font-black uppercase tracking-widest opacity-40">
403
+ Last Modified
404
+ </span>
405
+ <span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
406
+ {updatedAt
407
+ ? new Date(updatedAt).toLocaleString("en-US", {
408
+ dateStyle: "medium",
409
+ timeStyle: "short",
410
+ })
411
+ : "Just now"}
412
+ </span>
413
+ </div>
414
+ {publishedAt && (
415
+ <div className="flex flex-col gap-2">
416
+ <span className="text-[10px] font-black uppercase tracking-widest opacity-40">
417
+ Public At
418
+ </span>
419
+ <span className="text-sm font-bold text-[var(--kyro-text-secondary)]">
420
+ {new Date(publishedAt).toLocaleString("en-US", {
421
+ dateStyle: "medium",
422
+ timeStyle: "short",
423
+ })}
424
+ </span>
425
+ </div>
426
+ )}
427
+ </div>
278
428
  </div>
279
- <div className="kyro-card-content">
280
- <div className="kyro-meta-item">
281
- <span className="kyro-meta-label">Status</span>
282
- <span
283
- className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
284
- status === "published"
285
- ? "bg-green-100 text-green-700"
286
- : "bg-gray-100 text-gray-600"
287
- }`}
429
+
430
+ <div className="surface-tile p-8 bg-[var(--kyro-bg-secondary)]">
431
+ <h3 className="text-[10px] font-black uppercase tracking-[0.2em] opacity-40 mb-4">
432
+ Quick Links
433
+ </h3>
434
+ <div className="space-y-3">
435
+ <button
436
+ type="button"
437
+ className="w-full text-left px-4 py-2 rounded-xl text-xs font-bold hover:bg-[var(--kyro-bg-secondary)] transition-colors"
288
438
  >
289
- {status === "published" ? "Published" : "Draft"}
290
- </span>
291
- </div>
292
- <div className="kyro-meta-item">
293
- <span className="kyro-meta-label">Created</span>
294
- <span className="kyro-meta-value">
295
- {createdAt ? new Date(createdAt).toLocaleString() : "N/A"}
296
- </span>
297
- </div>
298
- <div className="kyro-meta-item">
299
- <span className="kyro-meta-label">Updated</span>
300
- <span className="kyro-meta-value">
301
- {updatedAt ? new Date(updatedAt).toLocaleString() : "N/A"}
302
- </span>
439
+ Duplicate Document
440
+ </button>
441
+ <button
442
+ type="button"
443
+ className="w-full text-left px-4 py-2 rounded-xl text-xs font-bold hover:bg-[var(--kyro-bg-secondary)] transition-colors"
444
+ >
445
+ View Public Page
446
+ </button>
447
+ <button
448
+ type="button"
449
+ className="w-full text-left px-4 py-2 rounded-xl text-xs font-bold text-red-500 hover:bg-red-500/10 transition-colors"
450
+ >
451
+ Delete Entry
452
+ </button>
303
453
  </div>
304
- {publishedAt && (
305
- <div className="kyro-meta-item">
306
- <span className="kyro-meta-label">Published</span>
307
- <span className="kyro-meta-value">
308
- {new Date(publishedAt).toLocaleString()}
309
- </span>
310
- </div>
311
- )}
312
454
  </div>
313
455
  </div>
314
- </div>
456
+ )}
315
457
  </div>
316
458
 
317
459
  <ConfirmModal