@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,5 +1,8 @@
1
1
  ---
2
- import AdminLayout from '../../layouts/AdminLayout.astro';
2
+ import AdminLayout from "../../layouts/AdminLayout.astro";
3
+ import { UserDetail } from "../../components/users/UserDetail";
4
+
5
+ import { adminPath, apiPath } from "../../lib/paths";
3
6
 
4
7
  const { id } = Astro.params;
5
8
  let user: any = null;
@@ -7,176 +10,44 @@ let error: string | null = null;
7
10
 
8
11
  if (id) {
9
12
  try {
10
- const response = await fetch(`${Astro.url.origin}/api/users/${id}`, {
13
+ const response = await fetch(`${Astro.url.origin}${apiPath}/users/${id}`, {
11
14
  headers: Astro.request.headers,
12
- credentials: 'include'
15
+ credentials: "include",
13
16
  });
14
17
  if (response.ok) {
15
18
  const data = await response.json();
16
19
  user = data.data;
17
20
  } else {
18
- error = 'User not found';
21
+ error = "User not found";
19
22
  }
20
23
  } catch (e) {
21
- error = 'Failed to fetch user';
24
+ error = "Failed to fetch user";
22
25
  }
23
26
  }
24
-
25
- const roleOptions = ['super_admin', 'admin', 'editor', 'author', 'customer', 'guest'];
26
27
  ---
27
28
 
28
- <AdminLayout title={user ? user.email : 'User'}>
29
- <div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8">
30
- {error ? (
29
+ <AdminLayout title={user ? user.email : "User"}>
30
+ {error ? (
31
+ <div class="flex-1 overflow-y-auto pr-12 space-y-8">
31
32
  <div class="surface-tile p-8">
32
33
  <div class="text-center">
33
34
  <p class="text-lg font-bold text-red-500">{error}</p>
34
- <a href="/users" class="mt-4 inline-block text-[var(--kyro-text-primary)] font-bold underline">← Back to users</a>
35
+ <a href={`${adminPath}/users`} class="mt-4 inline-block text-[var(--kyro-text-primary)] font-bold underline">← Back to users</a>
35
36
  </div>
36
37
  </div>
37
- ) : user ? (
38
- <>
39
- <!-- Header -->
40
- <div class="surface-tile p-6 flex items-center justify-between">
41
- <div class="flex items-center gap-4">
42
- <div class="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">
43
- {user.email.charAt(0).toUpperCase()}
44
- </div>
45
- <div>
46
- <h1 class="text-2xl font-black tracking-tighter text-[var(--kyro-text-primary)]">{user.email}</h1>
47
- <p class="text-sm text-[var(--kyro-text-secondary)] font-medium">User ID: {user.id}</p>
48
- </div>
49
- </div>
50
- <div class="flex gap-2">
51
- <button id="btn-toggle-lock" class="px-4 py-2 border border-[var(--kyro-border)] rounded-xl text-sm font-bold text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
52
- {user.locked ? 'Unlock User' : 'Lock User'}
53
- </button>
54
- <button id="btn-delete-user" class="px-4 py-2 border border-red-200 rounded-xl text-sm font-bold text-red-600 hover:bg-red-50 transition-colors">
55
- Delete
56
- </button>
57
- </div>
58
- </div>
59
-
60
- <!-- User Details -->
61
- <div class="surface-tile p-6">
62
- <h2 class="text-lg font-black text-[var(--kyro-text-primary)] tracking-tighter mb-6">Details</h2>
63
- <div class="grid grid-cols-2 gap-6">
64
- <div>
65
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Email</label>
66
- <p class="mt-1 font-bold text-[var(--kyro-text-primary)]" id="field-email">{user.email}</p>
67
- </div>
68
- <div>
69
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Role</label>
70
- <select id="field-role" class="mt-1 w-full px-3 py-2 border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-bold text-[var(--kyro-text-primary)]">
71
- {roleOptions.map((role) => (
72
- <option value={role} selected={user.role === role}>{role}</option>
73
- ))}
74
- </select>
75
- </div>
76
- <div>
77
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Email Verified</label>
78
- <p class="mt-1">
79
- <span class={`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${user.emailVerified ? 'bg-green-500/10 text-green-500' : 'bg-yellow-500/10 text-yellow-500'}`}>
80
- {user.emailVerified ? 'Verified' : 'Not verified'}
81
- </span>
82
- </p>
83
- </div>
84
- <div>
85
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Status</label>
86
- <p class="mt-1">
87
- <span class={`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${user.locked ? 'bg-red-500/10 text-red-500' : 'bg-green-500/10 text-green-500'}`}>
88
- {user.locked ? 'Locked' : 'Active'}
89
- </span>
90
- </p>
91
- </div>
92
- <div>
93
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Last Login</label>
94
- <p class="mt-1 text-sm text-[var(--kyro-text-secondary)]" id="field-last-login">
95
- {user.lastLogin ? new Date(user.lastLogin).toLocaleString() : 'Never'}
96
- </p>
97
- </div>
98
- <div>
99
- <label class="text-xs font-bold text-[var(--kyro-text-secondary)] uppercase tracking-wider">Failed Attempts</label>
100
- <p class="mt-1 text-sm font-bold text-[var(--kyro-text-primary)]" id="field-failed-attempts">
101
- {user.failedLoginAttempts || 0}
102
- </p>
103
- </div>
104
- <div>
105
- <label class="text-xs font-bold text-[#64748b] uppercase tracking-wider">Created</label>
106
- <p class="mt-1 text-sm text-[#64748b]">
107
- {user.createdAt ? new Date(user.createdAt).toLocaleString() : '—'}
108
- </p>
109
- </div>
110
- <div>
111
- <label class="text-xs font-bold text-[#64748b] uppercase tracking-wider">Updated</label>
112
- <p class="mt-1 text-sm text-[#64748b]">
113
- {user.updatedAt ? new Date(user.updatedAt).toLocaleString() : '—'}
114
- </p>
115
- </div>
116
- </div>
117
-
118
- <div class="mt-6 flex justify-end">
119
- <button id="btn-save" class="px-6 py-2 bg-[#0b1222] text-white rounded-xl text-sm font-bold hover:bg-[#1a2332] transition-colors">
120
- Save Changes
121
- </button>
122
- </div>
123
- </div>
124
- </>
125
- ) : (
38
+ </div>
39
+ ) : user ? (
40
+ <UserDetail
41
+ client:load
42
+ user={user}
43
+ apiPath={apiPath}
44
+ adminPath={adminPath}
45
+ />
46
+ ) : (
47
+ <div class="flex-1 overflow-y-auto pr-12 space-y-8">
126
48
  <div class="surface-tile p-8 text-center">
127
- <p class="text-lg font-bold text-[#64748b]">Loading...</p>
49
+ <p class="text-lg font-bold text-[var(--kyro-text-secondary)]">Loading...</p>
128
50
  </div>
129
- )}
130
- </div>
131
-
132
- <script is:inline>
133
- document.addEventListener('DOMContentLoaded', () => {
134
- const userId = window.location.pathname.split('/').pop();
135
- const saveBtn = document.getElementById('btn-save');
136
- const lockBtn = document.getElementById('btn-toggle-lock');
137
- const deleteBtn = document.getElementById('btn-delete-user');
138
- const roleSelect = document.getElementById('field-role');
139
-
140
- if (saveBtn && roleSelect) {
141
- saveBtn.addEventListener('click', async () => {
142
- const role = roleSelect.value;
143
- const res = await fetch(`/api/users/${userId}`, {
144
- method: 'PATCH',
145
- credentials: 'include',
146
- headers: { 'Content-Type': 'application/json' },
147
- body: JSON.stringify({ role }),
148
- });
149
- if (res.ok) {
150
- saveBtn.textContent = 'Saved!';
151
- setTimeout(() => { saveBtn.textContent = 'Save Changes'; }, 2000);
152
- }
153
- });
154
- }
155
-
156
- if (lockBtn) {
157
- lockBtn.addEventListener('click', async () => {
158
- const isLocked = lockBtn.textContent?.includes('Unlock');
159
- const res = await fetch(`/api/users/${userId}`, {
160
- method: 'PATCH',
161
- credentials: 'include',
162
- headers: { 'Content-Type': 'application/json' },
163
- body: JSON.stringify({ locked: isLocked }),
164
- });
165
- if (res.ok) location.reload();
166
- });
167
- }
168
-
169
- if (deleteBtn) {
170
- deleteBtn.addEventListener('click', async () => {
171
- if (confirm('Delete this user? This cannot be undone.')) {
172
- const res = await fetch(`/api/users/${userId}`, {
173
- method: 'DELETE',
174
- credentials: 'include'
175
- });
176
- if (res.ok) window.location.href = '/users';
177
- }
178
- });
179
- }
180
- });
181
- </script>
51
+ </div>
52
+ )}
182
53
  </AdminLayout>
@@ -1,147 +1,36 @@
1
1
  ---
2
- import AdminLayout from '../../layouts/AdminLayout.astro';
2
+ import AdminLayout from "../../layouts/AdminLayout.astro";
3
+ import { UsersList } from "../../components/users/UsersList";
4
+
5
+ import { adminPath, apiPath } from "../../lib/paths";
3
6
 
4
7
  let users: any[] = [];
5
8
  let totalUsers = 0;
6
9
 
7
10
  try {
8
- const response = await fetch(`${Astro.url.origin}/api/users?page=1&limit=100`, {
9
- headers: Astro.request.headers,
10
- credentials: 'include'
11
- });
11
+ const response = await fetch(
12
+ `${Astro.url.origin}${apiPath}/users?page=1&limit=100`,
13
+ {
14
+ headers: Astro.request.headers,
15
+ credentials: "include",
16
+ },
17
+ );
12
18
  if (response.ok) {
13
19
  const data = await response.json();
14
20
  users = data.docs || [];
15
21
  totalUsers = data.totalDocs || 0;
16
22
  }
17
23
  } catch (error) {
18
- console.error('Failed to fetch users:', error);
24
+ console.error("Failed to fetch users:", error);
19
25
  }
20
-
21
- users = users || [];
22
-
23
- const roleColors: Record<string, string> = {
24
- super_admin: 'bg-red-50 text-red-600',
25
- admin: 'bg-purple-50 text-purple-600',
26
- editor: 'bg-blue-50 text-blue-600',
27
- author: 'bg-green-50 text-green-600',
28
- customer: 'bg-gray-50 text-gray-600',
29
- guest: 'bg-yellow-50 text-yellow-600',
30
- };
31
26
  ---
32
27
 
33
28
  <AdminLayout title="Users">
34
- <div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8">
35
- <!-- Header -->
36
- <div class="surface-tile p-6 flex items-center justify-between">
37
- <div>
38
- <h1 class="text-3xl font-black tracking-tighter text-[var(--kyro-text-primary)]">Users</h1>
39
- <p class="text-sm text-[var(--kyro-text-secondary)] mt-1 font-medium">
40
- Manage user accounts and permissions
41
- <span class="ml-2 text-[var(--kyro-text-primary)] font-bold">· {totalUsers} users</span>
42
- </p>
43
- </div>
44
- <a href="/users/new" class="flex items-center gap-2 px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold transition-all hover:opacity-90 active:scale-95 shadow-lg">
45
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
46
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path>
47
- </svg>
48
- Add User
49
- </a>
50
- </div>
51
-
52
- <!-- Users Table -->
53
- <div class="surface-tile overflow-hidden">
54
- {users.length === 0 ? (
55
- <div class="px-8 py-16 text-center">
56
- <div class="flex flex-col items-center gap-4">
57
- <div class="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center">
58
- <svg class="w-8 h-8 text-[var(--kyro-text-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
59
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>
60
- </svg>
61
- </div>
62
- <p class="font-bold text-[var(--kyro-text-primary)] text-base">No users yet</p>
63
- <p class="text-sm text-[var(--kyro-text-secondary)]">Create your first user to get started.</p>
64
- <a href="/users/new" class="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">
65
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
66
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path>
67
- </svg>
68
- Add User
69
- </a>
70
- </div>
71
- </div>
72
- ) : (
73
- <table class="w-full text-left">
74
- <thead>
75
- <tr class="text-[var(--kyro-text-secondary)] font-bold text-[10px] uppercase tracking-[0.3em] border-b border-[var(--kyro-border)]">
76
- <th class="px-8 py-6">Email</th>
77
- <th class="px-6 py-6">Role</th>
78
- <th class="px-6 py-6">Status</th>
79
- <th class="px-6 py-6">Last Login</th>
80
- <th class="px-6 py-6">Created</th>
81
- <th class="px-6 py-6 text-right">Actions</th>
82
- </tr>
83
- </thead>
84
- <tbody class="divide-y divide-[var(--kyro-border)]">
85
- {users.map((user) => (
86
- <tr class="group hover:bg-gray-50/50 transition-colors cursor-pointer" onclick={`window.location='/users/${user.id}'`}>
87
- <td class="px-8 py-5">
88
- <div class="flex items-center gap-4">
89
- <div class="w-10 h-10 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-sm">
90
- {user.email ? user.email.charAt(0).toUpperCase() : '?'}
91
- </div>
92
- <div>
93
- <div class="font-bold text-[var(--kyro-text-primary)]">{user.email}</div>
94
- {user.tenantId && <div class="text-xs text-[var(--kyro-text-secondary)]">Tenant: {user.tenantId}</div>}
95
- </div>
96
- </div>
97
- </td>
98
- <td class="px-6 py-5">
99
- <span class={`inline-flex items-center px-3 py-1 rounded-lg text-xs font-bold ${roleColors[user.role] || 'bg-gray-50 text-gray-600'}`}>
100
- {user.role}
101
- </span>
102
- </td>
103
- <td class="px-6 py-5">
104
- {user.locked ? (
105
- <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600">
106
- <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
107
- <path fill-rule="evenodd" 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" clip-rule="evenodd"/>
108
- </svg>
109
- Locked
110
- </span>
111
- ) : (
112
- <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600">
113
- <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
114
- <path fill-rule="evenodd" 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" clip-rule="evenodd"/>
115
- </svg>
116
- Active
117
- </span>
118
- )}
119
- </td>
120
- <td class="px-6 py-5 text-sm text-[#64748b]">
121
- {user.lastLogin ? new Date(user.lastLogin).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : 'Never'}
122
- </td>
123
- <td class="px-6 py-5 text-sm text-[#64748b]">
124
- {user.createdAt ? new Date(user.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '—'}
125
- </td>
126
- <td class="px-6 py-5 text-right">
127
- <div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
128
- <a href={`/users/${user.id}`} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors" onclick="event.stopPropagation()">
129
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"></path>
131
- </svg>
132
- </a>
133
- <button onclick={`event.stopPropagation(); if(confirm('Delete this user?')) { fetch('/api/users/${user.id}', { method: 'DELETE', credentials: 'include' }).then(() => location.reload()); }`} class="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-red-50 hover:text-red-600 transition-colors">
134
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
135
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"></path>
136
- </svg>
137
- </button>
138
- </div>
139
- </td>
140
- </tr>
141
- ))}
142
- </tbody>
143
- </table>
144
- )}
145
- </div>
146
- </div>
29
+ <UsersList
30
+ client:load
31
+ initialUsers={users}
32
+ initialTotal={totalUsers}
33
+ apiPath={apiPath}
34
+ adminPath={adminPath}
35
+ />
147
36
  </AdminLayout>
@@ -1,92 +1,15 @@
1
1
  ---
2
- import AdminLayout from '../../layouts/AdminLayout.astro';
2
+ import AdminLayout from "../../layouts/AdminLayout.astro";
3
+ import { UserForm } from "../../components/users/UserForm";
3
4
 
4
- const roleOptions = ['super_admin', 'admin', 'editor', 'author', 'customer', 'guest'];
5
+ import { adminPath, apiPath } from "../../lib/paths";
5
6
  ---
6
7
 
7
8
  <AdminLayout title="New User">
8
- <div class="flex-1 overflow-y-auto p-8 pr-12 space-y-8">
9
- <!-- Header -->
10
- <div class="surface-tile p-6 flex items-center justify-between">
11
- <div>
12
- <h1 class="text-3xl font-black tracking-tighter text-[var(--kyro-text-primary)]">Create User</h1>
13
- <p class="text-sm text-[var(--kyro-text-secondary)] mt-1 font-medium">Add a new user to the system</p>
14
- </div>
15
- <a href="/users" class="text-sm font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-colors">
16
- ← Back to users
17
- </a>
18
- </div>
19
-
20
- <!-- Form -->
21
- <div class="surface-tile p-6">
22
- <form id="create-user-form" class="space-y-6 max-w-2xl">
23
- <div>
24
- <label for="email" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2">Email Address</label>
25
- <input type="email" id="email" name="email" required class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]" placeholder="user@example.com" />
26
- </div>
27
-
28
- <div>
29
- <label for="password" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2">Password</label>
30
- <input type="password" id="password" name="password" required minlength="12" class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]" placeholder="Minimum 12 characters" />
31
- <p class="text-xs text-[var(--kyro-text-secondary)] mt-1">Must contain uppercase, lowercase, numbers, and special characters</p>
32
- </div>
33
-
34
- <div>
35
- <label for="role" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2">Role</label>
36
- <select id="role" name="role" class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]">
37
- {roleOptions.map((role) => (
38
- <option value={role}>{role}</option>
39
- ))}
40
- </select>
41
- </div>
42
-
43
- <div>
44
- <label for="tenantId" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2">Tenant ID (optional)</label>
45
- <input type="text" id="tenantId" name="tenantId" class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]" placeholder="Leave empty for global user" />
46
- </div>
47
-
48
- <div class="flex items-center justify-end gap-3 pt-4 border-t border-[var(--kyro-border)]">
49
- <a href="/users" class="px-6 py-3 border border-[var(--kyro-border)] rounded-xl text-sm font-bold text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
50
- Cancel
51
- </a>
52
- <button type="submit" class="px-6 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl text-sm font-bold hover:bg-opacity-90 transition-colors shadow-lg shadow-black/10">
53
- Create User
54
- </button>
55
- </div>
56
- </form>
57
-
58
- <div id="form-message" class="hidden mt-4 p-4 rounded-xl text-sm font-bold"></div>
59
- </div>
60
- </div>
61
-
62
- <script is:inline>
63
- document.addEventListener('DOMContentLoaded', () => {
64
- const form = document.getElementById('create-user-form');
65
- const message = document.getElementById('form-message');
66
-
67
- form?.addEventListener('submit', async (e) => {
68
- e.preventDefault();
69
- const formData = new FormData(form);
70
- const body = Object.fromEntries(formData.entries());
71
-
72
- const res = await fetch('/api/users', {
73
- method: 'POST',
74
- credentials: 'include',
75
- headers: { 'Content-Type': 'application/json' },
76
- body: JSON.stringify(body),
77
- });
78
-
79
- const data = await res.json();
80
-
81
- if (res.ok) {
82
- message.textContent = 'User created successfully!';
83
- message.className = 'mt-4 p-4 rounded-xl text-sm font-bold bg-green-500/10 text-green-500';
84
- setTimeout(() => { window.location.href = `/users/${data.data.id}`; }, 1000);
85
- } else {
86
- message.textContent = data.error || 'Failed to create user';
87
- message.className = 'mt-4 p-4 rounded-xl text-sm font-bold bg-red-500/10 text-red-500';
88
- }
89
- });
90
- });
91
- </script>
9
+ <UserForm
10
+ client:load
11
+ mode="create"
12
+ apiPath={apiPath}
13
+ adminPath={adminPath}
14
+ />
92
15
  </AdminLayout>
@@ -0,0 +1,11 @@
1
+ ---
2
+ import AdminLayout from "../layouts/AdminLayout.astro";
3
+ import { WebhookManager } from "../components/WebhookManager";
4
+
5
+ import { adminPath, apiPath } from '../lib/paths';
6
+
7
+ ---
8
+
9
+ <AdminLayout title="Webhooks">
10
+ <WebhookManager client:only="react" />
11
+ </AdminLayout>
package/src/routes.ts ADDED
@@ -0,0 +1,80 @@
1
+ export const ADMIN_ROUTES = [
2
+ { pattern: "/", entrypoint: "@kyro-cms/admin/src/pages/admin/index.astro" },
3
+ {
4
+ pattern: "/api-explorer",
5
+ entrypoint: "@kyro-cms/admin/src/pages/admin/api-explorer.astro",
6
+ },
7
+ {
8
+ pattern: "/graphql",
9
+ entrypoint: "@kyro-cms/admin/src/pages/admin/graphql.astro",
10
+ },
11
+ {
12
+ pattern: "/graphql-explorer",
13
+ entrypoint: "@kyro-cms/admin/src/pages/admin/graphql-explorer.astro",
14
+ },
15
+ {
16
+ pattern: "/rest-playground",
17
+ entrypoint: "@kyro-cms/admin/src/pages/admin/rest-playground.astro",
18
+ },
19
+ {
20
+ pattern: "/webhooks",
21
+ entrypoint: "@kyro-cms/admin/src/pages/admin/webhooks.astro",
22
+ },
23
+ {
24
+ pattern: "/keys",
25
+ entrypoint: "@kyro-cms/admin/src/pages/admin/keys.astro",
26
+ },
27
+ {
28
+ pattern: "/login",
29
+ entrypoint: "@kyro-cms/admin/src/pages/auth/login.astro",
30
+ },
31
+ {
32
+ pattern: "/register",
33
+ entrypoint: "@kyro-cms/admin/src/pages/auth/register.astro",
34
+ },
35
+ { pattern: "/media", entrypoint: "@kyro-cms/admin/src/pages/media.astro" },
36
+ {
37
+ pattern: "/users",
38
+ entrypoint: "@kyro-cms/admin/src/pages/users/index.astro",
39
+ },
40
+ {
41
+ pattern: "/users/new",
42
+ entrypoint: "@kyro-cms/admin/src/pages/users/new.astro",
43
+ },
44
+ {
45
+ pattern: "/users/[id]",
46
+ entrypoint: "@kyro-cms/admin/src/pages/users/[id].astro",
47
+ },
48
+ {
49
+ pattern: "/roles",
50
+ entrypoint: "@kyro-cms/admin/src/pages/roles/index.astro",
51
+ },
52
+ {
53
+ pattern: "/audit",
54
+ entrypoint: "@kyro-cms/admin/src/pages/audit/index.astro",
55
+ },
56
+ {
57
+ pattern: "/settings",
58
+ entrypoint: "@kyro-cms/admin/src/pages/settings/index.astro",
59
+ },
60
+ {
61
+ pattern: "/settings/[slug]",
62
+ entrypoint: "@kyro-cms/admin/src/pages/settings/[slug].astro",
63
+ },
64
+ {
65
+ pattern: "/sessions",
66
+ entrypoint: "@kyro-cms/admin/src/pages/sessions.astro",
67
+ },
68
+ {
69
+ pattern: "/preview/[collection]/[id]",
70
+ entrypoint: "@kyro-cms/admin/src/pages/preview/[collection]/[id].astro",
71
+ },
72
+ {
73
+ pattern: "/[collection]",
74
+ entrypoint: "@kyro-cms/admin/src/pages/[collection]/index.astro",
75
+ },
76
+ {
77
+ pattern: "/[collection]/[id]",
78
+ entrypoint: "@kyro-cms/admin/src/pages/[collection]/[id].astro",
79
+ },
80
+ ];