@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
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
+ "engines": {
5
+ "node": ">=22"
6
+ },
4
7
  "private": false,
5
8
  "publishConfig": {
6
9
  "access": "public"
@@ -41,7 +44,8 @@
41
44
  }
42
45
  },
43
46
  "files": [
44
- "src"
47
+ "src",
48
+ "dist"
45
49
  ],
46
50
  "scripts": {
47
51
  "dev": "astro dev",
@@ -49,11 +53,12 @@
49
53
  "preview": "astro preview",
50
54
  "check": "astro check",
51
55
  "test": "vitest",
52
- "test:run": "vitest run"
56
+ "test:run": "vitest run",
57
+ "prepare": "npm run build"
53
58
  },
54
59
  "dependencies": {
55
- "@astrojs/node": "^9.5.5",
56
- "@astrojs/react": "^4.2.0",
60
+ "@astrojs/node": "^10.1.0",
61
+ "@astrojs/react": "^5.0.4",
57
62
  "@codemirror/lang-cpp": "^6.0.3",
58
63
  "@codemirror/lang-css": "^6.3.1",
59
64
  "@codemirror/lang-html": "^6.4.11",
@@ -71,7 +76,7 @@
71
76
  "@dnd-kit/sortable": "^10.0.0",
72
77
  "@dnd-kit/utilities": "^3.2.2",
73
78
  "@graphiql/react": "^0.37.3",
74
- "@kyro-cms/core": "^0.3.2",
79
+ "@kyro-cms/core": "file:..",
75
80
  "@portabletext/editor": "^6.6.3",
76
81
  "@portabletext/react": "^6.0.3",
77
82
  "@portabletext/schema": "^2.1.1",
@@ -83,19 +88,13 @@
83
88
  "@uiw/codemirror-theme-dracula": "^4.25.9",
84
89
  "@uiw/codemirror-theme-github": "^4.25.9",
85
90
  "@uiw/react-codemirror": "^4.25.9",
86
- "astro": "^5.4.0",
87
- "better-sqlite3": "^11.10.0",
88
- "bcryptjs": "^2.4.3",
89
- "drizzle-orm": "^0.45.2",
91
+ "astro": "^6.3.1",
90
92
  "graphiql": "^5.2.2",
93
+ "idb-keyval": "^6.2.2",
91
94
  "lucide-react": "^0.475.0",
92
- "mongodb": "^7.1.1",
93
- "mysql2": "^3.21.0",
94
- "pg": "^8.20.0",
95
95
  "react": "^19.0.0",
96
96
  "react-dom": "^19.0.0",
97
97
  "react-image-crop": "^11.0.10",
98
- "sharp": "^0.34.5",
99
98
  "slate": "^0.124.1",
100
99
  "slate-history": "^0.113.1",
101
100
  "slate-react": "^0.124.0",
@@ -108,16 +107,17 @@
108
107
  "@types/react-dom": "^19.0.0",
109
108
  "dotenv": "^17.4.2",
110
109
  "dotenv-cli": "^11.0.0",
111
- "drizzle-kit": "^0.31.10",
110
+ "tsup": "^6.0.0",
112
111
  "typescript": "^6.0.3",
113
- "vitest": "^4.1.4",
114
- "tsup": "^6.0.0"
112
+ "vitest": "^4.1.4"
115
113
  },
116
114
  "peerDependencies": {
117
- "@kyro-cms/core": "^0.3.0"
115
+ "@kyro-cms/core": "^0.3.5",
116
+ "react": "^18.0.0",
117
+ "react-dom": "^18.0.0"
118
118
  },
119
119
  "repository": {
120
120
  "type": "git",
121
121
  "url": "https://github.com/danielDozie/kyro-cms"
122
122
  }
123
- }
123
+ }
@@ -34,28 +34,6 @@ export function ActionBar({
34
34
  publishedAt,
35
35
  updatedAt,
36
36
  }: ActionBarProps) {
37
- const styles = {
38
- btn: {
39
- padding: "0.5rem 1rem",
40
- borderRadius: "0.75rem",
41
- fontWeight: 600,
42
- fontSize: "0.875rem",
43
- transition: "all 0.2s",
44
- border: "none",
45
- cursor: "pointer",
46
- },
47
- idle: { backgroundColor: "#6b7280", color: "white" },
48
- saving: {
49
- backgroundColor: "#9ca3af",
50
- color: "white",
51
- opacity: 0.7,
52
- cursor: "not-allowed",
53
- },
54
- saved: { backgroundColor: "#22c55e", color: "white" },
55
- error: { backgroundColor: "#ef4444", color: "white" },
56
- changes: { backgroundColor: "#eab308", color: "black" },
57
- };
58
-
59
37
  const getSaveStatusText = () => {
60
38
  if (saveStatus === "saving") return "Saving...";
61
39
  if (saveStatus === "saved") return "Saved";
@@ -65,15 +43,12 @@ export function ActionBar({
65
43
  };
66
44
 
67
45
  const getSaveButtonClass = () => {
68
- const base = "kyro-btn kyro-btn-md";
69
- if (saveStatus === "saving") return `${base} opacity-50 cursor-wait`;
70
- if (saveStatus === "saved")
71
- return `${base} bg-green-500 hover:bg-green-600 text-white`;
72
- if (saveStatus === "error")
73
- return `${base} bg-red-500 hover:bg-red-600 text-white`;
74
- if (hasChanges)
75
- return `${base} bg-yellow-500 hover:bg-yellow-600 text-black`;
76
- return `${base} bg-gray-500 hover:bg-gray-600 text-white`;
46
+ const base = "kyro-btn kyro-btn-md text-[11px] font-bold tracking-widest transition-all duration-300";
47
+ if (saveStatus === "saving") return `${base} bg-[var(--kyro-gray-400)] text-white opacity-70 cursor-wait`;
48
+ if (saveStatus === "saved") return `${base} bg-[var(--kyro-success)] border-[var(--kyro-success)] text-white shadow-[0_0_15px_rgba(34,197,94,0.3)]`;
49
+ if (saveStatus === "error") return `${base} bg-[var(--kyro-error)] border-[var(--kyro-error)] text-white shadow-[0_0_15px_rgba(239,68,68,0.3)]`;
50
+ if (hasChanges) return `${base} bg-[var(--kyro-warning)] border-[var(--kyro-warning)] text-black shadow-[0_0_15px_rgba(255,174,0,0.3)] animate-pulse`;
51
+ return `${base} bg-[var(--kyro-gray-500)] border-[var(--kyro-gray-500)] text-white hover:bg-[var(--kyro-gray-600)]`;
77
52
  };
78
53
 
79
54
  const getStatusBadge = () => {
@@ -234,18 +209,7 @@ export function ActionBar({
234
209
  disabled={
235
210
  saveStatus === "saving" || (!hasChanges && saveStatus !== "error")
236
211
  }
237
- style={{
238
- ...styles.btn,
239
- ...(saveStatus === "saving"
240
- ? styles.saving
241
- : saveStatus === "saved"
242
- ? styles.saved
243
- : saveStatus === "error"
244
- ? styles.error
245
- : hasChanges
246
- ? styles.changes
247
- : styles.idle),
248
- }}
212
+ className={getSaveButtonClass()}
249
213
  >
250
214
  {saveStatus === "saving"
251
215
  ? "Saving..."
@@ -1,5 +1,6 @@
1
- import { useState, useEffect } from "react";
1
+ import { useState, useEffect, useMemo } from "react";
2
2
  import { apiPost } from "../lib/api";
3
+ import { useToastStore, toast, useAuthStore, type AuthUser } from "../lib/stores";
3
4
  import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core/client";
4
5
  import { ListView } from "./ListView";
5
6
  import { DetailView } from "./DetailView";
@@ -12,8 +13,10 @@ import { DeveloperCenter } from "./DeveloperCenter";
12
13
  import { WebhookManager } from "./WebhookManager";
13
14
  import { MediaGallery } from "./MediaGallery";
14
15
  import { CommandPalette } from "./ui/CommandPalette";
16
+ import { GlobalModal } from "./ui/GlobalModal";
15
17
  import { Toast, ToastProvider } from "./ui/Toast";
16
18
  import { ThemeProvider, type ThemeMode } from "./ThemeProvider";
19
+ import { toArray, toCollectionMap, toGlobalMap } from "../lib/config";
17
20
  import "../styles/main.css";
18
21
 
19
22
  type View =
@@ -42,332 +45,190 @@ interface AdminProps {
42
45
  onThemeChange?: (mode: ThemeMode) => void;
43
46
  }
44
47
 
45
- interface ToastMessage {
46
- id: string;
47
- type: "success" | "error" | "info" | "warning";
48
- message: string;
49
- }
50
-
51
- interface AuthUser {
52
- id: string;
53
- email: string;
54
- role: string;
55
- createdAt: string;
56
- updatedAt: string;
57
- }
58
-
59
- function normalizeCollections(
60
- input?: CollectionConfig[] | Record<string, CollectionConfig>,
61
- ): Record<string, CollectionConfig> {
62
- if (!input) return {};
63
-
64
- if (Array.isArray(input)) {
65
- return input.reduce(
66
- (acc, c) => {
67
- if (c.slug) acc[c.slug] = c;
68
- return acc;
69
- },
70
- {} as Record<string, CollectionConfig>,
71
- );
72
- }
73
-
74
- return input as Record<string, CollectionConfig>;
75
- }
76
-
77
- function normalizeGlobals(
78
- input?: GlobalConfig[] | Record<string, GlobalConfig>,
79
- ): Record<string, GlobalConfig> {
80
- if (!input) return {};
81
-
82
- if (Array.isArray(input)) {
83
- return input.reduce(
84
- (acc, g) => {
85
- if (g.slug) acc[g.slug] = g;
86
- return acc;
87
- },
88
- {} as Record<string, GlobalConfig>,
89
- );
90
- }
91
-
92
- return input as Record<string, GlobalConfig>;
93
- }
94
-
95
48
  export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
96
49
  const [authenticated, setAuthenticated] = useState(false);
97
50
  const [currentUser, setCurrentUser] = useState<AuthUser | null>(null);
51
+
52
+ const collections = useMemo(
53
+ () => toCollectionMap(toArray(config.collections)),
54
+ [config.collections],
55
+ );
56
+
57
+ const globals = useMemo(
58
+ () => toGlobalMap(toArray(config.globals)),
59
+ [config.globals],
60
+ );
61
+
98
62
  const [activeCollection, setActiveCollection] = useState<string | null>(null);
99
63
  const [activeGlobal, setActiveGlobal] = useState<string | null>(null);
100
64
  const [currentView, setCurrentView] = useState<View>("list");
101
- const [selectedId, setSelectedId] = useState<string | null>(null);
102
- const [toasts, setToasts] = useState<ToastMessage[]>([]);
103
- const [showCommandPalette, setShowCommandPalette] = useState(false);
65
+ const [activeDocumentId, setActiveDocumentId] = useState<string | null>(null);
66
+ const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
104
67
 
105
- const collections = normalizeCollections(config.collections);
106
- const globals = normalizeGlobals(config.globals);
107
-
108
- useEffect(() => {
109
- const handleKeyDown = (e: KeyboardEvent) => {
110
- if ((e.metaKey || e.ctrlKey) && e.key === "k") {
111
- e.preventDefault();
112
- setShowCommandPalette((prev) => !prev);
113
- }
114
- };
115
- window.addEventListener("keydown", handleKeyDown);
116
- return () => window.removeEventListener("keydown", handleKeyDown);
117
- }, []);
68
+ const toasts = useToastStore((state) => state.toasts);
69
+ const removeToast = useToastStore((state) => state.removeToast);
118
70
 
119
71
  useEffect(() => {
120
- const token = localStorage.getItem("kyro_token");
121
- const userStr = localStorage.getItem("kyro_user");
122
- if (token && userStr) {
72
+ // Basic session check
73
+ const checkAuth = async () => {
123
74
  try {
124
- const user = JSON.parse(userStr);
125
- setAuthenticated(true);
126
- setCurrentUser(user);
127
- } catch {
128
- localStorage.removeItem("kyro_token");
129
- localStorage.removeItem("kyro_user");
75
+ const response = await fetch("/api/users/me");
76
+ if (response.ok) {
77
+ const user = await response.json();
78
+ setCurrentUser(user);
79
+ setAuthenticated(true);
80
+ }
81
+ } catch (err) {
82
+ console.error("Auth check failed", err);
130
83
  }
131
- }
84
+ };
85
+ checkAuth();
132
86
  }, []);
133
87
 
134
88
  useEffect(() => {
135
- // No longer auto-selecting the first collection to allow Dashboard as home
136
- }, [authenticated]);
137
-
138
- const handleAuth = (token: string, user: AuthUser) => {
139
- setAuthenticated(true);
140
- setCurrentUser(user);
141
- };
142
-
143
- const handleLogout = async () => {
144
- try {
145
- await apiPost("/api/auth/logout");
146
- } catch {
147
- } finally {
148
- localStorage.removeItem("kyro_token");
149
- localStorage.removeItem("kyro_user");
150
- setAuthenticated(false);
151
- setCurrentUser(null);
152
- }
153
- };
154
-
155
- const addToast = (type: ToastMessage["type"], message: string) => {
156
- const id = Math.random().toString(36).substring(7);
157
- setToasts((prev) => [...prev, { id, type, message }]);
158
- setTimeout(() => {
159
- setToasts((prev) => prev.filter((t) => t.id !== id));
160
- }, 5000);
161
- };
162
-
163
- const removeToast = (id: string) => {
164
- setToasts((prev) => prev.filter((t) => t.id !== id));
165
- };
166
-
167
- const handleCollectionChange = (collectionName: string) => {
168
- if (!collectionName) {
169
- setActiveCollection(null);
170
- setActiveGlobal(null);
171
- setCurrentView("list");
172
- setSelectedId(null);
173
- return;
174
- }
175
-
176
- // Check if it's a special governance or developer view
177
- if (
178
- [
179
- "users",
180
- "roles",
181
- "audit",
182
- "media",
183
- "branding",
184
- "developer",
185
- "webhooks",
186
- ].includes(collectionName)
187
- ) {
188
- setActiveCollection(null);
189
- setActiveGlobal(null);
190
- setCurrentView(collectionName as any);
191
- return;
89
+ if (authenticated && !activeCollection) {
90
+ const firstCol = Object.keys(collections)[0];
91
+ if (firstCol) setActiveCollection(firstCol);
192
92
  }
93
+ }, [authenticated, collections, activeCollection]);
193
94
 
194
- setActiveCollection(collectionName);
195
- setActiveGlobal(null);
196
- setCurrentView("list");
197
- setSelectedId(null);
198
- };
199
-
200
- const handleGlobalChange = (globalName: string) => {
201
- setActiveGlobal(globalName);
202
- setActiveCollection(null);
203
- setCurrentView("settings");
204
- setSelectedId(null);
205
- };
206
-
207
- const handleNavigate = (view: View, id?: string) => {
95
+ const handleNavigate = (view: View, collection: string | null = null, id: string | null = null) => {
208
96
  setCurrentView(view);
209
- setSelectedId(id || null);
97
+ if (collection) setActiveCollection(collection);
98
+ if (id) setActiveDocumentId(id);
99
+ setIsCommandPaletteOpen(false);
210
100
  };
211
101
 
212
- const handleActionComplete = (action: string) => {
213
- const messages: Record<string, string> = {
214
- create: "Created successfully",
215
- update: "Updated successfully",
216
- delete: "Deleted successfully",
217
- publish: "Published successfully",
102
+ useEffect(() => {
103
+ const handleKeyDown = (e: KeyboardEvent) => {
104
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
105
+ e.preventDefault();
106
+ setIsCommandPaletteOpen((prev) => !prev);
107
+ }
218
108
  };
219
- addToast("success", messages[action] || "Action completed");
220
- if (action !== "delete") {
221
- setCurrentView("list");
109
+ window.addEventListener("keydown", handleKeyDown);
110
+ return () => window.removeEventListener("keydown", handleKeyDown);
111
+ }, []);
112
+
113
+ const handleLogin = async (data: Record<string, unknown>) => {
114
+ try {
115
+ const response = await apiPost<any>("/api/users/login", data);
116
+ if (response.user) {
117
+ setCurrentUser(response.user);
118
+ setAuthenticated(true);
119
+ toast.success("Welcome back!");
120
+ }
121
+ } catch (err: unknown) {
122
+ const message = err instanceof Error ? err.message : "Login failed";
123
+ toast.error(message || "Login failed");
222
124
  }
223
125
  };
224
126
 
225
- const handleError = (message: string) => {
226
- addToast("error", message);
227
- };
127
+ if (!authenticated) {
128
+ return (
129
+ <LoginPage
130
+ onAuth={(token, user) => {
131
+ setCurrentUser(user as any);
132
+ setAuthenticated(true);
133
+ }}
134
+ theme={theme as any}
135
+ />
136
+ );
137
+ }
228
138
 
229
139
  const renderContent = () => {
230
- if (currentView === "settings" && activeGlobal) {
231
- const global = globals[activeGlobal];
232
- if (!global) return null;
233
- return (
234
- <DetailView
235
- config={{} as any}
236
- global={global}
237
- onBack={() => setCurrentView("list")}
238
- onSave={() => addToast("success", "Configuration saved successfully")}
239
- onError={handleError}
240
- mode="global"
241
- />
242
- );
243
- }
244
-
245
- if (!activeCollection && !activeGlobal) {
246
- if (currentView === "users") return <UserManagement />;
247
- if (currentView === "roles")
248
- return (
249
- <div className="p-6">
250
- <h2 className="text-xl font-bold">Roles Management</h2>
251
- <p className="text-gray-500 mt-2">
252
- Configure user roles and permissions.
253
- </p>
254
- </div>
255
- );
256
- if (currentView === "audit")
257
- return (
258
- <div className="p-6">
259
- <h2 className="text-xl font-bold">Audit Logs</h2>
260
- <p className="text-gray-500 mt-2">
261
- View system activity and changes.
262
- </p>
263
- </div>
264
- );
265
- if (currentView === "media") return <MediaGallery />;
266
- if (currentView === "branding") return <BrandingHub />;
267
- if (currentView === "developer")
268
- return <DeveloperCenter collections={collections} />;
269
- if (currentView === "webhooks") return <WebhookManager />;
270
-
271
- return (
272
- <Dashboard
273
- collections={collections}
274
- onNavigate={(view, collection) => {
275
- if (collection) {
276
- setActiveCollection(collection);
277
- setActiveGlobal(null);
278
- }
279
- setCurrentView(view as any);
280
- }}
281
- user={currentUser}
282
- />
283
- );
284
- }
285
-
286
- if (!activeCollection) return null;
287
-
288
- const collection = collections[activeCollection];
289
- if (!collection) return null;
140
+ const collection = activeCollection ? collections[activeCollection] : null;
290
141
 
291
142
  switch (currentView) {
292
143
  case "create":
293
- return (
144
+ return collection ? (
294
145
  <CreateView
295
- config={{} as any}
146
+ config={config as any}
296
147
  collection={collection}
148
+ onSuccess={() => setCurrentView("list")}
297
149
  onCancel={() => setCurrentView("list")}
298
- onSuccess={() => handleActionComplete("create")}
299
- onError={handleError}
150
+ onError={(msg) => toast.error(msg)}
300
151
  />
301
- );
152
+ ) : null;
153
+
302
154
  case "detail":
303
- return (
155
+ return collection && activeDocumentId ? (
304
156
  <DetailView
305
- config={{} as any}
157
+ config={config as any}
306
158
  collection={collection}
307
- documentId={selectedId || undefined}
159
+ documentId={activeDocumentId}
308
160
  onBack={() => setCurrentView("list")}
309
- onSave={() => handleActionComplete("update")}
310
- onDelete={() => handleActionComplete("delete")}
311
- onError={handleError}
161
+ onSave={() => toast.success("Changes saved")}
162
+ onError={(msg) => toast.error(msg)}
312
163
  />
313
- );
164
+ ) : null;
165
+
166
+ case "users":
167
+ return <UserManagement />;
168
+
169
+ case "media":
170
+ return <MediaGallery />;
171
+
172
+ case "branding":
173
+ return <BrandingHub />;
174
+
175
+ case "developer":
176
+ return <DeveloperCenter collections={collections as any} />;
177
+
178
+ case "webhooks":
179
+ return <WebhookManager />;
180
+
181
+ case "list":
314
182
  default:
315
- return (
183
+ return collection ? (
316
184
  <ListView
317
- config={{} as any}
185
+ config={config as any}
318
186
  collection={collection}
319
187
  onCreate={() => setCurrentView("create")}
320
- onEdit={(id) => handleNavigate("detail", id)}
188
+ onEdit={(id: string) => handleNavigate("detail", activeCollection, id)}
189
+ />
190
+ ) : (
191
+ <Dashboard
192
+ onNavigate={handleNavigate as any}
193
+ collections={collections as any}
194
+ user={currentUser as any}
321
195
  />
322
196
  );
323
197
  }
324
198
  };
325
199
 
326
- if (!authenticated) {
327
- return <LoginPage onAuth={handleAuth} theme={theme} />;
328
- }
329
-
330
200
  return (
331
- <ThemeProvider defaultMode={theme}>
332
- <ToastProvider
333
- toasts={toasts}
334
- addToast={addToast}
335
- removeToast={removeToast}
336
- >
337
- <div className="kyro-admin">
338
- <div className="kyro-main">
339
- <div className="kyro-content">{renderContent()}</div>
201
+ <ThemeProvider {...({ mode: theme, onChange: onThemeChange } as any)}>
202
+ <ToastProvider>
203
+ <div className="kyro-admin min-h-screen bg-[var(--kyro-bg)] text-[var(--kyro-text-primary)]">
204
+ <div className="flex h-screen overflow-hidden">
205
+ <main className="flex-1 flex flex-col min-w-0 overflow-hidden">
206
+ <div className="flex-1 overflow-y-auto">
207
+ <CommandPalette
208
+ isOpen={isCommandPaletteOpen}
209
+ onClose={() => setIsCommandPaletteOpen(false)}
210
+ collections={collections as any}
211
+ globals={globals as any}
212
+ onNavigate={handleNavigate as any}
213
+ />
214
+ {renderContent()}
215
+ </div>
216
+ </main>
217
+ </div>
218
+ <GlobalModal />
219
+
220
+ <div className="kyro-toasts-container">
221
+ {toasts.map((t) => (
222
+ <Toast
223
+ key={t.id}
224
+ type={t.type}
225
+ message={t.message}
226
+ onClose={() => removeToast(t.id)}
227
+ />
228
+ ))}
340
229
  </div>
341
- <CommandPalette
342
- isOpen={showCommandPalette}
343
- onClose={() => setShowCommandPalette(false)}
344
- collections={collections}
345
- globals={globals}
346
- onNavigate={(view, collection, id) => {
347
- if (view === "list" && collection) {
348
- handleCollectionChange(collection);
349
- } else if (view === "settings" && collection) {
350
- handleGlobalChange(collection);
351
- } else if (view === "media") {
352
- setActiveCollection(null);
353
- setActiveGlobal(null);
354
- setCurrentView("list"); // This might need a separate 'media' view state if implemented
355
- } else if (view === "create" && collection) {
356
- setActiveCollection(collection);
357
- setCurrentView("create");
358
- }
359
- }}
360
- />
361
230
  </div>
362
231
  </ToastProvider>
363
- {toasts.map((toast) => (
364
- <Toast
365
- key={toast.id}
366
- type={toast.type}
367
- message={toast.message}
368
- onClose={() => removeToast(toast.id)}
369
- />
370
- ))}
371
232
  </ThemeProvider>
372
233
  );
373
234
  }