@kyro-cms/admin 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect, useRef, useCallback } from "react";
2
- import { apiGet } from "../../lib/api";
2
+ import { apiGet, resolveApi } from "../../lib/api";
3
3
  import {
4
4
  Search,
5
5
  FileText,
@@ -16,24 +16,25 @@ import {
16
16
  Shield,
17
17
  Code,
18
18
  Database,
19
- Network,
19
+ Network as NetworkIcon,
20
20
  Hexagon,
21
- } from "lucide-react";
21
+ } from "./icons";
22
+ import { useAuthStore } from "../../lib/stores";
22
23
 
23
24
  interface SearchResult {
24
25
  collection: string;
25
26
  label: string;
26
27
  id: string;
27
28
  title: string;
28
- doc?: any;
29
+ doc?: Record<string, unknown>;
29
30
  }
30
31
 
31
32
  interface CommandPaletteProps {
32
33
  isOpen: boolean;
33
34
  onClose: () => void;
34
- collections: any;
35
- globals: any;
36
- onNavigate: (view: any, collection?: string, id?: string) => void;
35
+ collections: Record<string, unknown>;
36
+ globals: Record<string, unknown>;
37
+ onNavigate: (view: string, collection?: string, id?: string) => void;
37
38
  }
38
39
 
39
40
  export function CommandPalette({
@@ -43,6 +44,7 @@ export function CommandPalette({
43
44
  globals,
44
45
  onNavigate,
45
46
  }: CommandPaletteProps) {
47
+ const { user, permissions } = useAuthStore();
46
48
  const [query, setQuery] = useState("");
47
49
  const [selectedIndex, setSelectedIndex] = useState(0);
48
50
  const [loading, setLoading] = useState(false);
@@ -69,7 +71,7 @@ export function CommandPalette({
69
71
  setLoading(true);
70
72
  try {
71
73
  const response = await fetch(
72
- `/api/search?q=${encodeURIComponent(searchQuery)}&limit=15`,
74
+ resolveApi(`/api/search?q=${encodeURIComponent(searchQuery)}&limit=15`),
73
75
  );
74
76
  const data = await response.json();
75
77
  if (data.results) {
@@ -103,30 +105,32 @@ export function CommandPalette({
103
105
 
104
106
  if (!isOpen) return null;
105
107
 
106
- const collectionItems = Object.entries(collections).map(
107
- ([slug, config]: [string, any]) => ({
108
+ const collectionItems = Object.entries(collections)
109
+ .filter(([slug]) => permissions?.collections?.[slug]?.read !== false)
110
+ .map(([slug, config]: [string, Record<string, unknown>]) => ({
108
111
  id: `col-${slug}`,
109
112
  label: config.label || slug,
110
113
  type: "collection",
111
114
  slug,
112
115
  icon: FileText,
113
- }),
114
- );
116
+ }));
115
117
 
116
- const globalItems = Object.entries(globals).map(
117
- ([slug, config]: [string, any]) => ({
118
+ const globalItems = Object.entries(globals)
119
+ .filter(([slug]) => permissions?.globals?.[slug]?.read !== false)
120
+ .map(([slug, config]: [string, Record<string, unknown>]) => ({
118
121
  id: `global-${slug}`,
119
122
  label: config.label || slug,
120
123
  type: "global",
121
124
  slug,
122
125
  icon: Settings,
123
- }),
124
- );
126
+ }));
125
127
 
126
128
  const isDark =
127
129
  typeof document !== "undefined" &&
128
130
  document.documentElement.classList.contains("dark");
129
131
 
132
+ const isAdmin = user?.role === "admin";
133
+
130
134
  const actionItems = [
131
135
  {
132
136
  id: "action-media",
@@ -134,6 +138,7 @@ export function CommandPalette({
134
138
  type: "action",
135
139
  view: "media",
136
140
  icon: ImageIcon,
141
+ visible: permissions?.collections?.media?.read !== false,
137
142
  },
138
143
  {
139
144
  id: "action-users",
@@ -141,6 +146,7 @@ export function CommandPalette({
141
146
  type: "action",
142
147
  view: "users",
143
148
  icon: Clock,
149
+ visible: isAdmin,
144
150
  },
145
151
  {
146
152
  id: "action-audit",
@@ -148,6 +154,7 @@ export function CommandPalette({
148
154
  type: "action",
149
155
  view: "audit",
150
156
  icon: File,
157
+ visible: isAdmin,
151
158
  },
152
159
  {
153
160
  id: "action-roles",
@@ -155,6 +162,7 @@ export function CommandPalette({
155
162
  type: "action",
156
163
  view: "roles",
157
164
  icon: Shield,
165
+ visible: isAdmin,
158
166
  },
159
167
  {
160
168
  id: "action-api",
@@ -162,6 +170,7 @@ export function CommandPalette({
162
170
  type: "action",
163
171
  view: "api-explorer",
164
172
  icon: Database,
173
+ visible: isAdmin,
165
174
  },
166
175
  {
167
176
  id: "action-graphql",
@@ -169,13 +178,15 @@ export function CommandPalette({
169
178
  type: "action",
170
179
  view: "graphql",
171
180
  icon: Hexagon,
181
+ visible: isAdmin,
172
182
  },
173
183
  {
174
184
  id: "action-rest",
175
185
  label: "REST Playground",
176
186
  type: "action",
177
187
  view: "rest",
178
- icon: Network,
188
+ icon: NetworkIcon,
189
+ visible: isAdmin,
179
190
  },
180
191
  {
181
192
  id: "action-theme",
@@ -183,6 +194,7 @@ export function CommandPalette({
183
194
  type: "action",
184
195
  view: "theme",
185
196
  icon: isDark ? Sun : Moon,
197
+ visible: true,
186
198
  },
187
199
  {
188
200
  id: "action-logout",
@@ -190,10 +202,11 @@ export function CommandPalette({
190
202
  type: "action",
191
203
  view: "logout",
192
204
  icon: LogOut,
205
+ visible: true,
193
206
  },
194
- ];
207
+ ].filter((a) => a.visible);
195
208
 
196
- const docResultItems: any[] = searchResults.map((result, idx) => ({
209
+ const docResultItems: { id: string; label: string; type: string; collection: string; label2?: string; docId: string; icon: typeof File; doc?: Record<string, unknown> }[] = searchResults.map((result, idx) => ({
197
210
  id: `doc-${result.collection}-${result.id}`,
198
211
  label: result.title,
199
212
  type: "document",
@@ -213,8 +226,8 @@ export function CommandPalette({
213
226
  query === ""
214
227
  ? allItems
215
228
  : allItems.filter((item) =>
216
- item.label.toLowerCase().includes(query.toLowerCase()),
217
- );
229
+ item.label.toLowerCase().includes(query.toLowerCase()),
230
+ );
218
231
 
219
232
  const handleKeyDown = (e: React.KeyboardEvent) => {
220
233
  if (e.key === "ArrowDown") {
@@ -233,18 +246,28 @@ export function CommandPalette({
233
246
  }
234
247
  };
235
248
 
236
- const handleSelect = (item: any) => {
249
+ const handleSelect = (item: { type: string; slug?: string; view?: string; collection?: string; docId?: string }) => {
237
250
  if (item.type === "collection") {
238
- onNavigate("list", item.slug);
251
+ if (item.slug === "users") {
252
+ onNavigate(item.slug, item.slug);
253
+ } else {
254
+ onNavigate("list", item.slug);
255
+ }
239
256
  } else if (item.type === "global") {
240
257
  onNavigate("settings", item.slug);
241
258
  } else if (item.type === "document") {
242
- onNavigate("edit", item.collection, item.docId);
259
+ if (item.collection === "users") {
260
+ onNavigate("users", "users", item.docId);
261
+ } else {
262
+ onNavigate("edit", item.collection, item.docId);
263
+ }
243
264
  } else if (item.type === "action") {
244
265
  if (item.view === "users") {
245
- onNavigate(item.view, item.view);
266
+ onNavigate("users", "users");
246
267
  } else if (item.view === "media") {
247
- onNavigate("media", item.view);
268
+ onNavigate("media", "media");
269
+ } else {
270
+ onNavigate(item.view, item.view);
248
271
  }
249
272
  }
250
273
  onClose();
@@ -282,7 +305,7 @@ export function CommandPalette({
282
305
  onKeyDown={handleKeyDown}
283
306
  />
284
307
  <div className="flex items-center gap-2 px-2 py-1 bg-[var(--kyro-bg-secondary)] rounded-lg border border-[var(--kyro-border)]">
285
- <span className="text-[10px] font-black opacity-40 uppercase tracking-widest">
308
+ <span className="text-[10px] font-bold opacity-40 tracking-widest">
286
309
  ESC
287
310
  </span>
288
311
  </div>
@@ -291,7 +314,7 @@ export function CommandPalette({
291
314
  <div className="max-h-[400px] overflow-y-auto py-4">
292
315
  {filteredItems.length > 0 ? (
293
316
  <div className="space-y-1 px-4">
294
- <p className="px-4 text-[10px] font-black uppercase tracking-[0.2em] opacity-40 mb-4">
317
+ <p className="px-4 text-[10px] font-bold tracking-[0.2em] opacity-40 mb-4">
295
318
  {getSectionLabel()}
296
319
  </p>
297
320
  {filteredItems.map((item, index) => (
@@ -299,11 +322,10 @@ export function CommandPalette({
299
322
  key={item.id}
300
323
  onClick={() => handleSelect(item)}
301
324
  onMouseEnter={() => setSelectedIndex(index)}
302
- className={`flex items-center justify-between px-4 py-4 rounded-2xl cursor-pointer transition-all ${
303
- index === selectedIndex
304
- ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] shadow-xl shadow-[var(--kyro-primary)]"
305
- : "hover:bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)]"
306
- }`}
325
+ className={`flex items-center justify-between px-4 py-4 rounded-2xl cursor-pointer ${index === selectedIndex
326
+ ? "bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] shadow-xl shadow-[var(--kyro-primary)]"
327
+ : "hover:bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)]"
328
+ }`}
307
329
  >
308
330
  <div className="flex items-center gap-4">
309
331
  <div
@@ -315,7 +337,7 @@ export function CommandPalette({
315
337
  <span className="font-bold text-sm">{item.label}</span>
316
338
  {item.type === "document" && item.label2 && (
317
339
  <span
318
- className={`text-[10px] font-black uppercase tracking-widest ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)]/60" : "opacity-40"}`}
340
+ className={`text-[10px] font-bold tracking-widest ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)]/60" : "opacity-40"}`}
319
341
  >
320
342
  {item.label2}
321
343
  </span>
@@ -324,7 +346,7 @@ export function CommandPalette({
324
346
  </div>
325
347
  <div className="flex items-center gap-2">
326
348
  <span
327
- className={`text-[10px] font-black uppercase tracking-widest opacity-40 ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)] p-1" : ""}`}
349
+ className={`text-[10px] font-bold tracking-widest opacity-40 ${index === selectedIndex ? "text-[var(--kyro-sidebar-text-active)] p-1" : ""}`}
328
350
  >
329
351
  {item.type}
330
352
  </span>
@@ -346,7 +368,7 @@ export function CommandPalette({
346
368
  )}
347
369
  </div>
348
370
 
349
- <div className="px-8 py-4 bg-[var(--kyro-bg-secondary)] border-t border-[var(--kyro-border)] flex items-center justify-between text-[10px] font-black uppercase tracking-widest text-[var(--kyro-text-secondary)] opacity-60">
371
+ <div className="px-8 py-4 bg-[var(--kyro-bg-secondary)] border-t border-[var(--kyro-border)] flex items-center justify-between text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] opacity-60">
350
372
  <div className="flex gap-6">
351
373
  <span className="flex items-center gap-2 underline underline-offset-4 decoration-2 decoration-[var(--kyro-primary)]">
352
374
  ↑↓ Navigate
@@ -1,10 +1,11 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import { CommandPalette } from "./CommandPalette";
3
3
  import { ConfirmModal } from "./Modal";
4
+ import { adminPath } from "../../lib/paths";
4
5
 
5
6
  interface Props {
6
- collections: any;
7
- globals: any;
7
+ collections: Record<string, unknown>;
8
+ globals: Record<string, unknown>;
8
9
  }
9
10
 
10
11
  export function CommandPaletteWrapper({ collections, globals }: Props) {
@@ -19,47 +20,47 @@ export function CommandPaletteWrapper({ collections, globals }: Props) {
19
20
  }
20
21
  };
21
22
 
22
- (window as any).openCommandPalette = () => setIsOpen(true);
23
+ (window as { openCommandPalette?: () => void }).openCommandPalette = () => setIsOpen(true);
23
24
 
24
25
  window.addEventListener("keydown", handleKeyDown);
25
26
 
26
27
  return () => {
27
28
  window.removeEventListener("keydown", handleKeyDown);
28
- delete (window as any).openCommandPalette;
29
+ delete (window as { openCommandPalette?: () => void }).openCommandPalette;
29
30
  };
30
31
  }, []);
31
32
 
32
33
  const handleClose = () => setIsOpen(false);
33
34
 
34
35
  const handleLogoutConfirm = () => {
35
- localStorage.removeItem("kyro_token");
36
- localStorage.removeItem("kyro_user");
36
+ // Clear in-memory auth (not localStorage)
37
+ (window as { __kyroAuth?: { user: unknown; verified: boolean } }).__kyroAuth = { user: null, verified: false };
37
38
  window.location.href = "/login";
38
39
  };
39
40
 
40
41
  const handleNavigate = (view: string, collection?: string, id?: string) => {
41
42
  if (view === "list" && collection) {
42
- window.location.href = `/${collection}`;
43
+ window.location.href = `${adminPath}/${collection}`;
43
44
  } else if (view === "edit" && collection && id) {
44
- window.location.href = `/${collection}/${id}`;
45
+ window.location.href = `${adminPath}/${collection}/${id}`;
45
46
  } else if (view === "create" && collection) {
46
- window.location.href = `/${collection}/new`;
47
+ window.location.href = `${adminPath}/${collection}/new`;
47
48
  } else if (view === "settings" && collection) {
48
- window.location.href = `/settings/${collection}`;
49
+ window.location.href = `${adminPath}/settings/${collection}`;
49
50
  } else if (view === "media") {
50
- window.location.href = `/media`;
51
+ window.location.href = `${adminPath}/media`;
51
52
  } else if (view === "users") {
52
- window.location.href = `/users`;
53
+ window.location.href = `${adminPath}/users`;
53
54
  } else if (view === "audit") {
54
- window.location.href = `/audit`;
55
+ window.location.href = `${adminPath}/audit`;
55
56
  } else if (view === "roles") {
56
- window.location.href = `/roles`;
57
+ window.location.href = `${adminPath}/roles`;
57
58
  } else if (view === "api-explorer") {
58
- window.location.href = `/admin/api-explorer`;
59
+ window.location.href = `${adminPath}/api-explorer`;
59
60
  } else if (view === "graphql") {
60
- window.location.href = `/admin/graphql`;
61
+ window.location.href = `${adminPath}/graphql`;
61
62
  } else if (view === "rest") {
62
- window.location.href = `/admin/rest-playground`;
63
+ window.location.href = `${adminPath}/rest-playground`;
63
64
  } else if (view === "theme") {
64
65
  const isDark = document.documentElement.classList.contains("dark");
65
66
  if (isDark) {
@@ -34,9 +34,8 @@ export function Dropdown({
34
34
  </div>
35
35
  {open && (
36
36
  <div
37
- className={`absolute z-50 mt-2 min-w-[180px] py-1 bg-white rounded-lg shadow-lg border border-gray-200 ${
38
- align === "right" ? "right-0" : "left-0"
39
- }`}
37
+ className={`absolute z-[100] mt-2 min-w-[200px] py-2 bg-[var(--kyro-surface)] rounded-2xl shadow-2xl border border-[var(--kyro-border)] animate-in fade-in zoom-in-95 duration-100 ${align === "right" ? "right-0 bottom-full mb-2" : "left-0 bottom-full mb-2"
38
+ }`}
40
39
  onClick={() => setOpen(false)}
41
40
  >
42
41
  {children}
@@ -52,6 +51,7 @@ interface DropdownItemProps {
52
51
  icon?: ReactNode;
53
52
  danger?: boolean;
54
53
  disabled?: boolean;
54
+ className?: string;
55
55
  }
56
56
 
57
57
  export function DropdownItem({
@@ -60,23 +60,25 @@ export function DropdownItem({
60
60
  icon,
61
61
  danger,
62
62
  disabled,
63
+ className = "",
63
64
  }: DropdownItemProps) {
64
65
  return (
65
- <button type="button"
66
- onClick={onClick}
67
- disabled={disabled}
68
- className={`w-full flex items-center gap-3 px-3 py-2 text-sm text-left transition-colors ${
69
- danger
70
- ? "text-red-600 hover:bg-red-50"
71
- : "text-gray-700 hover:bg-gray-50"
72
- } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
73
- >
74
- {icon && <span className="w-4 h-4">{icon}</span>}
75
- {children}
76
- </button>
66
+ <div className="px-1.5">
67
+ <button type="button"
68
+ onClick={onClick}
69
+ disabled={disabled}
70
+ className={`w-full flex items-center gap-3 px-3 py-2.5 text-[11px] font-medium tracking-wide text-left transition-all rounded-xl ${danger
71
+ ? "text-red-500 hover:bg-red-500/10"
72
+ : "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
73
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`}
74
+ >
75
+ {icon && <span className="w-4 h-4 opacity-70">{icon}</span>}
76
+ <span className="flex-1">{children}</span>
77
+ </button>
78
+ </div>
77
79
  );
78
80
  }
79
81
 
80
82
  export function DropdownSeparator() {
81
- return <div className="my-1 border-t border-gray-100" />;
83
+ return <div className="my-1 border-t border-[var(--kyro-border)] opacity-50" />;
82
84
  }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ interface EmptyStateProps {
4
+ icon?: ReactNode;
5
+ title: string;
6
+ description?: string;
7
+ action?: ReactNode;
8
+ }
9
+
10
+ export function EmptyState({ icon, title, description, action }: EmptyStateProps) {
11
+ return (
12
+ <div className="flex flex-col items-center gap-3 justify-center py-16 px-8">
13
+ {icon && (
14
+ <div className="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center mb-4">
15
+ {icon}
16
+ </div>
17
+ )}
18
+ <p className="font-medium text-[var(--kyro-text-primary)] text-base">{title}</p>
19
+ {description && (
20
+ <p className="text-sm text-[var(--kyro-text-secondary)] mt-1">{description}</p>
21
+ )}
22
+ {action}
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,49 @@
1
+ import React, { useState } from "react";
2
+ import { useUIStore } from "../../lib/stores";
3
+ import { ConfirmModal } from "./Modal";
4
+
5
+ export function GlobalModal() {
6
+ const { modal, closeModal } = useUIStore();
7
+ const [loading, setLoading] = useState(false);
8
+
9
+ if (!modal.open || !modal.config) return null;
10
+
11
+ const { config } = modal;
12
+
13
+ const handleConfirm = async () => {
14
+ if (config.onConfirm) {
15
+ try {
16
+ setLoading(true);
17
+ await config.onConfirm();
18
+ } catch (error) {
19
+ console.error("Modal confirm action failed:", error);
20
+ } finally {
21
+ setLoading(false);
22
+ closeModal();
23
+ }
24
+ } else {
25
+ closeModal();
26
+ }
27
+ };
28
+
29
+ const handleClose = () => {
30
+ if (config.onCancel) {
31
+ config.onCancel();
32
+ }
33
+ closeModal();
34
+ };
35
+
36
+ return (
37
+ <ConfirmModal
38
+ open={modal.open}
39
+ onClose={handleClose}
40
+ onConfirm={handleConfirm}
41
+ title={config.title}
42
+ message={config.message}
43
+ confirmLabel={config.confirmLabel}
44
+ cancelLabel={config.cancelLabel}
45
+ variant={config.variant === "danger" ? "danger" : "default"}
46
+ loading={loading}
47
+ />
48
+ );
49
+ }
@@ -0,0 +1,44 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import type { ButtonVariant, ButtonSize } from './Button';
3
+
4
+ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
5
+ icon: ReactNode;
6
+ size?: ButtonSize;
7
+ variant?: ButtonVariant;
8
+ label: string;
9
+ active?: boolean;
10
+ }
11
+
12
+ export function IconButton({
13
+ icon,
14
+ size = 'md',
15
+ variant = 'ghost',
16
+ label,
17
+ active = false,
18
+ className = '',
19
+ disabled,
20
+ ...props
21
+ }: IconButtonProps) {
22
+ const classes = [
23
+ 'kyro-btn',
24
+ `kyro-btn-${variant}`,
25
+ `kyro-btn-${size}`,
26
+ 'kyro-btn-icon',
27
+ active ? 'kyro-btn-active' : '',
28
+ className,
29
+ ]
30
+ .filter(Boolean)
31
+ .join(' ');
32
+
33
+ return (
34
+ <button type="button"
35
+ className={classes}
36
+ aria-label={label}
37
+ title={label}
38
+ disabled={disabled}
39
+ {...props}
40
+ >
41
+ {icon}
42
+ </button>
43
+ );
44
+ }
@@ -51,30 +51,29 @@ export function Modal({
51
51
  const sizeClasses = {
52
52
  sm: "max-w-sm",
53
53
  md: "max-w-md",
54
- lg: "max-w-2xl",
54
+ lg: "max-w-lg",
55
55
  };
56
56
 
57
57
  return createPortal(
58
- <div className="fixed inset-0 z-[9999] flex items-center justify-center">
58
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
59
59
  <div
60
- className="absolute inset-0 bg-black/50 backdrop-blur-sm"
60
+ className="absolute inset-0 bg-[var(--kyro-black)]/40 backdrop-blur-md transition-all duration-500"
61
61
  onClick={onClose}
62
62
  />
63
63
  <div
64
- className={`relative w-full ${sizeClasses[size]} mx-4 bg-[var(--kyro-surface)] rounded-lg shadow-2xl animate-in fade-in zoom-in-95 duration-200 border ${
65
- variant === "danger"
66
- ? "border-red-500/20"
67
- : "border-[var(--kyro-border)]"
68
- }`}
64
+ className={`relative w-full ${sizeClasses[size]} bg-[var(--kyro-surface)] rounded-[var(--kyro-radius-lg)] shadow-2xl animate-in fade-in zoom-in-95 duration-300 border ${variant === "danger"
65
+ ? "border-red-500/30"
66
+ : "border-[var(--kyro-border)]"
67
+ } overflow-hidden`}
69
68
  >
70
- <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
71
- <h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
69
+ <div className="flex items-center justify-between px-8 py-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50 backdrop-blur-md">
70
+ <h2 className="text-xl font-bold text-[var(--kyro-text-primary)]">
72
71
  {title}
73
72
  </h2>
74
73
  <button
75
74
  type="button"
76
75
  onClick={onClose}
77
- className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
76
+ className="p-2 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all duration-200"
78
77
  >
79
78
  <svg
80
79
  width="20"
@@ -82,15 +81,15 @@ export function Modal({
82
81
  viewBox="0 0 24 24"
83
82
  fill="none"
84
83
  stroke="currentColor"
85
- strokeWidth="2"
84
+ strokeWidth="2.5"
86
85
  >
87
86
  <path d="M18 6L6 18M6 6l12 12" />
88
87
  </svg>
89
88
  </button>
90
89
  </div>
91
- <div className="px-6 py-4">{children}</div>
90
+ <div className="px-8 py-8">{children}</div>
92
91
  {footer && (
93
- <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-b-lg">
92
+ <div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50">
94
93
  {footer}
95
94
  </div>
96
95
  )}
@@ -98,6 +97,7 @@ export function Modal({
98
97
  </div>,
99
98
  document.body,
100
99
  );
100
+
101
101
  }
102
102
 
103
103
  interface ConfirmModalProps {
@@ -135,7 +135,7 @@ export function ConfirmModal({
135
135
  type="button"
136
136
  onClick={onClose}
137
137
  disabled={loading}
138
- className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
138
+ className="kyro-btn kyro-btn-md kyro-btn-secondary"
139
139
  >
140
140
  {cancelLabel}
141
141
  </button>
@@ -143,11 +143,10 @@ export function ConfirmModal({
143
143
  type="button"
144
144
  onClick={onConfirm}
145
145
  disabled={loading}
146
- className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${
147
- variant === "danger"
148
- ? "bg-red-500 text-white hover:bg-red-600"
149
- : "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
150
- }`}
146
+ className={`kyro-btn kyro-btn-md ${variant === "danger"
147
+ ? "kyro-btn-danger"
148
+ : "kyro-btn-primary"
149
+ }`}
151
150
  >
152
151
  {loading ? "Loading..." : confirmLabel}
153
152
  </button>