@kyro-cms/admin 0.3.2 → 0.3.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 (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,15 +1,14 @@
1
1
  ---
2
- import AdminLayout from '../../../layouts/AdminLayout.astro';
3
- import {
4
- renderRichText,
5
- richTextStyles,
6
- } from "../../../../../src/fields/richtext.js";
7
- import { collections } from '@/lib/config';
2
+ import AdminLayout from "../../../layouts/AdminLayout.astro";
3
+ import { renderRichText, richTextStyles } from "@kyro-cms/core/client";
4
+ import { collections } from "../../../lib/config";
5
+
6
+ import { adminPath, apiPath } from "../../../lib/paths";
8
7
 
9
8
  const { collection, id } = Astro.params;
10
9
 
11
10
  if (!collection || !collections[collection]) {
12
- return Astro.redirect('/');
11
+ return Astro.redirect(adminPath);
13
12
  }
14
13
 
15
14
  const config = collections[collection];
@@ -19,10 +18,13 @@ let error = null;
19
18
 
20
19
  if (id) {
21
20
  try {
22
- const response = await fetch(`${Astro.url.origin}/api/${collection}/${id}`, {
23
- headers: Astro.request.headers,
24
- credentials: 'include'
25
- });
21
+ const response = await fetch(
22
+ `${Astro.url.origin}${apiPath}/${collection}/${id}`,
23
+ {
24
+ headers: Astro.request.headers,
25
+ credentials: "include",
26
+ },
27
+ );
26
28
  if (response.ok) {
27
29
  const result = await response.json();
28
30
  doc = result.data || null;
@@ -30,34 +32,53 @@ if (id) {
30
32
  error = `Failed to load document: ${response.status}`;
31
33
  }
32
34
  } catch (e) {
33
- error = 'Failed to fetch document';
35
+ error = "Failed to fetch document";
34
36
  }
35
37
  }
36
38
 
37
- const title = doc ? `Preview: ${doc.title || doc.name || doc.slug || id}` : 'Preview';
39
+ const title = doc
40
+ ? `Preview: ${doc.title || doc.name || doc.slug || id}`
41
+ : "Preview";
38
42
  ---
39
43
 
40
44
  <AdminLayout title={title}>
41
45
  <Fragment set:html={`<style>${richTextStyles}</style>`} />
42
- <div class="flex-1 overflow-y-auto p-8 space-y-6">
46
+ <div class="flex-1 overflow-y-auto space-y-6">
43
47
  <div class="surface-tile p-6 flex items-center justify-between">
44
48
  <div class="flex items-center gap-4">
45
49
  <a
46
- href={`/${collection}/${id}`}
50
+ href={`${adminPath}/${collection}/${id}`}
47
51
  class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
48
52
  >
49
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 19l-7-7 7-7"></path>
53
+ <svg
54
+ class="w-4 h-4"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ viewBox="0 0 24 24"
58
+ >
59
+ <path
60
+ stroke-linecap="round"
61
+ stroke-linejoin="round"
62
+ stroke-width="2.5"
63
+ d="M15 19l-7-7 7-7"></path>
51
64
  </svg>
52
65
  </a>
53
66
  <div>
54
- <h1 class="text-xl font-black tracking-tighter text-[var(--kyro-text-primary)]">Preview Mode</h1>
55
- <p class="text-sm text-[var(--kyro-text-secondary)] mt-0.5">Showing draft content</p>
67
+ <h1
68
+ class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
69
+ >
70
+ Preview Mode
71
+ </h1>
72
+ <p class="text-sm text-[var(--kyro-text-secondary)] mt-0.5">
73
+ Showing draft content
74
+ </p>
56
75
  </div>
57
76
  </div>
58
77
  <div class="flex items-center gap-3">
59
- <span class="px-3 py-1.5 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] text-xs font-bold rounded-lg border border-[var(--kyro-border)]">
60
- {doc?.status || 'draft'}
78
+ <span
79
+ class="px-3 py-1.5 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] text-xs font-bold rounded-lg border border-[var(--kyro-border)]"
80
+ >
81
+ {doc?.status || "draft"}
61
82
  </span>
62
83
  <a
63
84
  href={`/${collection}/${id}`}
@@ -68,111 +89,155 @@ const title = doc ? `Preview: ${doc.title || doc.name || doc.slug || id}` : 'Pre
68
89
  </div>
69
90
  </div>
70
91
 
71
- {error && (
72
- <div class="surface-tile p-6 border border-red-500/30 bg-red-500/10">
73
- <p class="text-red-400">{error}</p>
74
- </div>
75
- )}
92
+ {
93
+ error && (
94
+ <div class="surface-tile p-6 border border-red-500/30 bg-red-500/10">
95
+ <p class="text-red-400">{error}</p>
96
+ </div>
97
+ )
98
+ }
76
99
 
77
- {doc && (
78
- <div class="space-y-6">
79
- {config.fields.map((field: any) => {
80
- const value = doc[field.name];
81
- if (value === undefined || value === null) return null;
100
+ {
101
+ doc && (
102
+ <div class="space-y-6">
103
+ {config.fields.map((field: any) => {
104
+ const value = doc[field.name];
105
+ if (value === undefined || value === null) return null;
82
106
 
83
- return (
84
- <div class="surface-tile p-6">
85
- <h3 class="text-xs font-bold text-[var(--kyro-text-muted)] uppercase tracking-[0.15em] mb-3">
86
- {field.label || field.name}
87
- </h3>
88
- <div class="text-sm text-[var(--kyro-text-primary)]">
89
- {field.type === 'text' && (
90
- <p class="whitespace-pre-wrap">{value}</p>
91
- )}
92
- {field.type === 'textarea' && (
93
- <p class="whitespace-pre-wrap">{value}</p>
94
- )}
95
- {field.type === 'richtext' && (
96
- <div set:html={renderRichText(value)} />
97
- )}
98
- {field.type === 'markdown' && (
99
- <div class="prose prose-sm max-w-none">
100
- <pre class="whitespace-pre-wrap bg-transparent p-0">{value}</pre>
101
- </div>
102
- )}
103
- {field.type === 'number' && (
104
- <p>{value}</p>
105
- )}
106
- {field.type === 'boolean' && (
107
- <span class={value ? 'text-green-400' : 'text-red-400'}>{value ? 'Yes' : 'No'}</span>
108
- )}
109
- {field.type === 'select' && (
110
- <span class="px-2 py-1 bg-[var(--kyro-surface-accent)] rounded text-xs">{value}</span>
111
- )}
112
- {field.type === 'date' && (
113
- <p>{new Date(value).toLocaleString()}</p>
114
- )}
115
- {field.type === 'upload' && value.url && (
116
- <div>
117
- {value.url.match(/\.(jpe?g|png|gif|webp|avif|svg)(\?|$)/i) ? (
118
- <img src={value.url} alt={value.filename || 'Image'} class="max-w-md rounded-lg border border-[var(--kyro-border)]" />
119
- ) : (
120
- <a href={value.url} class="text-blue-400 hover:underline">{value.filename || 'Download'}</a>
121
- )}
122
- </div>
123
- )}
124
- {field.type === 'gallery' && Array.isArray(value) && (
125
- <div class="grid grid-cols-4 gap-3">
126
- {value.map((item: any, idx: number) => (
127
- item?.url ? (
128
- item.url.match(/\.(jpe?g|png|gif|webp|avif|svg)(\?|$)/i) ? (
129
- <img key={idx} src={item.url} alt={item.filename || `Image ${idx + 1}`} class="w-full h-24 object-cover rounded-lg border border-[var(--kyro-border)]" />
130
- ) : (
131
- <div key={idx} class="h-24 flex items-center justify-center bg-[var(--kyro-surface-accent)] rounded-lg border border-[var(--kyro-border)] text-xs">
132
- {item.filename || 'File'}
133
- </div>
134
- )
135
- ) : null
136
- ))}
137
- </div>
138
- )}
139
- {field.type === 'array' && Array.isArray(value) && (
140
- <div class="space-y-2">
141
- {value.map((item: any, idx: number) => (
142
- <div key={idx} class="p-3 bg-[var(--kyro-surface-accent)] rounded border border-[var(--kyro-border)] text-xs">
143
- {Object.entries(item).filter(([k, v]) => v !== undefined).map(([k, v]) => (
144
- <div key={k} class="flex gap-2">
145
- <span class="text-[var(--kyro-text-muted)]">{k}:</span>
146
- <span>{typeof v === 'object' ? JSON.stringify(v) : String(v)}</span>
147
- </div>
148
- ))}
149
- </div>
150
- ))}
151
- </div>
152
- )}
153
- {field.type === 'relationship' && (
154
- <p class="text-[var(--kyro-text-secondary)]">{value.title || value.name || value.slug || JSON.stringify(value)}</p>
155
- )}
156
- {field.type === 'json' && (
157
- <pre class="text-xs bg-[var(--kyro-surface-accent)] p-3 rounded overflow-x-auto">{JSON.stringify(value, null, 2)}</pre>
158
- )}
159
- {field.type === 'code' && (
160
- <pre class="text-xs bg-[var(--kyro-surface-accent)] p-3 rounded overflow-x-auto">{value}</pre>
161
- )}
162
- {(!field.type || ['text', 'string'].includes(field.type)) && (
163
- <p>{value}</p>
164
- )}
107
+ return (
108
+ <div class="surface-tile p-6">
109
+ <h3 class="text-xs font-bold text-[var(--kyro-text-muted)] tracking-[0.15em] mb-3">
110
+ {field.label || field.name}
111
+ </h3>
112
+ <div class="text-sm text-[var(--kyro-text-primary)]">
113
+ {field.type === "text" && (
114
+ <p class="whitespace-pre-wrap">{value}</p>
115
+ )}
116
+ {field.type === "textarea" && (
117
+ <p class="whitespace-pre-wrap">{value}</p>
118
+ )}
119
+ {field.type === "richtext" && (
120
+ <div set:html={renderRichText(value)} />
121
+ )}
122
+ {field.type === "markdown" && (
123
+ <div class="prose prose-sm max-w-none">
124
+ <pre class="whitespace-pre-wrap bg-transparent p-0">
125
+ {value}
126
+ </pre>
127
+ </div>
128
+ )}
129
+ {field.type === "number" && <p>{value}</p>}
130
+ {field.type === "boolean" && (
131
+ <span class={value ? "text-green-400" : "text-red-400"}>
132
+ {value ? "Yes" : "No"}
133
+ </span>
134
+ )}
135
+ {field.type === "select" && (
136
+ <span class="px-2 py-1 bg-[var(--kyro-surface-accent)] rounded text-xs">
137
+ {value}
138
+ </span>
139
+ )}
140
+ {field.type === "date" && (
141
+ <p>{new Date(value).toLocaleString()}</p>
142
+ )}
143
+ {field.type === "upload" && value.url && (
144
+ <div>
145
+ {value.url.match(
146
+ /\.(jpe?g|png|gif|webp|avif|svg)(\?|$)/i,
147
+ ) ? (
148
+ <img
149
+ src={value.url}
150
+ alt={value.filename || "Image"}
151
+ class="max-w-md rounded-lg border border-[var(--kyro-border)]"
152
+ />
153
+ ) : (
154
+ <a
155
+ href={value.url}
156
+ class="text-blue-400 hover:underline"
157
+ >
158
+ {value.filename || "Download"}
159
+ </a>
160
+ )}
161
+ </div>
162
+ )}
163
+ {field.type === "gallery" && Array.isArray(value) && (
164
+ <div class="grid grid-cols-4 gap-3">
165
+ {value.map((item: any, idx: number) =>
166
+ item?.url ? (
167
+ item.url.match(
168
+ /\.(jpe?g|png|gif|webp|avif|svg)(\?|$)/i,
169
+ ) ? (
170
+ <img
171
+ src={item.url}
172
+ alt={item.filename || `Image ${idx + 1}`}
173
+ class="w-full h-24 object-cover rounded-lg border border-[var(--kyro-border)]"
174
+ />
175
+ ) : (
176
+ <div class="h-24 flex items-center justify-center bg-[var(--kyro-surface-accent)] rounded-lg border border-[var(--kyro-border)] text-xs">
177
+ {item.filename || "File"}
178
+ </div>
179
+ )
180
+ ) : null,
181
+ )}
182
+ </div>
183
+ )}
184
+ {field.type === "array" && Array.isArray(value) && (
185
+ <div class="space-y-2">
186
+ {value.map((item: any) => (
187
+ <div class="p-3 bg-[var(--kyro-surface-accent)] rounded border border-[var(--kyro-border)] text-xs">
188
+ {Object.entries(item)
189
+ .filter(([, v]) => v !== undefined)
190
+ .map(([k, v]) => (
191
+ <div class="flex gap-2">
192
+ <span class="text-[var(--kyro-text-muted)]">
193
+ {k}:
194
+ </span>
195
+ <span>
196
+ {typeof v === "object"
197
+ ? JSON.stringify(v)
198
+ : String(v)}
199
+ </span>
200
+ </div>
201
+ ))}
202
+ </div>
203
+ ))}
204
+ </div>
205
+ )}
206
+ {field.type === "relationship" && (
207
+ <p class="text-[var(--kyro-text-secondary)]">
208
+ {value.title ||
209
+ value.name ||
210
+ value.slug ||
211
+ JSON.stringify(value)}
212
+ </p>
213
+ )}
214
+ {field.type === "json" && (
215
+ <pre class="text-xs bg-[var(--kyro-surface-accent)] p-3 rounded overflow-x-auto">
216
+ {JSON.stringify(value, null, 2)}
217
+ </pre>
218
+ )}
219
+ {field.type === "code" && (
220
+ <pre class="text-xs bg-[var(--kyro-surface-accent)] p-3 rounded overflow-x-auto">
221
+ {value}
222
+ </pre>
223
+ )}
224
+ {(!field.type || ["text", "string"].includes(field.type)) && (
225
+ <p>{value}</p>
226
+ )}
227
+ </div>
165
228
  </div>
166
- </div>
167
- );
168
- })}
169
- </div>
170
- )}
229
+ );
230
+ })}
231
+ </div>
232
+ )
233
+ }
171
234
 
172
- {!doc && !error && (
173
- <div class="surface-tile p-6">
174
- <p class="text-[var(--kyro-text-muted)]">Document not found</p>
175
- </div>
176
- )}
235
+ {
236
+ !doc && !error && (
237
+ <div class="surface-tile p-6">
238
+ <p class="text-[var(--kyro-text-muted)]">Document not found</p>
239
+ </div>
240
+ )
241
+ }
177
242
  </div>
178
243
  </AdminLayout>
@@ -0,0 +1,62 @@
1
+ ---
2
+ import AdminLayout from "../layouts/AdminLayout.astro";
3
+ import { RestPlayground } from "../components/RestPlayground";
4
+
5
+ import { adminPath, apiPath } from "../lib/paths";
6
+
7
+ const collectionsResponse = await fetch(
8
+ `${Astro.url.origin}${apiPath}/collections`,
9
+ );
10
+ const collectionsData = await collectionsResponse.json();
11
+ const collections = collectionsData.collections || [];
12
+ ---
13
+
14
+ <AdminLayout title="REST Playground">
15
+ <div class="flex-1 overflow-hidden">
16
+ <!-- Header -->
17
+ <div class="mb-6 surface-tile">
18
+ <div class="flex items-center justify-between mb-4">
19
+ <div>
20
+ <h1
21
+ class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
22
+ >
23
+ REST Playground
24
+ </h1>
25
+ <p
26
+ class="text-[var(--kyro-text-secondary)] font-bold mt-2 text-sm tracking-wider"
27
+ >
28
+ Saved collections, history, and environment variables
29
+ </p>
30
+ </div>
31
+ <div class="flex items-center gap-4">
32
+ <a
33
+ href={`${adminPath}/api-explorer`}
34
+ class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
35
+ >
36
+ <svg
37
+ class="w-4 h-4"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ viewBox="0 0 24 24"
41
+ >
42
+ <path
43
+ stroke-linecap="round"
44
+ stroke-linejoin="round"
45
+ stroke-width="2"
46
+ d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
47
+ ></path>
48
+ </svg>
49
+ Explorer
50
+ </a>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Playground Container -->
56
+ <div class="h-[calc(100vh-200px)] surface-tile overflow-hidden">
57
+ <div class="surface-tile h-full overflow-hidden">
58
+ <RestPlayground client:load collections={collections} />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </AdminLayout>