@kyro-cms/admin 0.3.1 → 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
@@ -0,0 +1,67 @@
1
+ ---
2
+ import AdminLayout from "../layouts/AdminLayout.astro";
3
+ import { adminPath } from "../lib/paths";
4
+
5
+ // Ensure this page looks premium
6
+ ---
7
+
8
+ <AdminLayout title="Access Denied">
9
+ <div class="flex-1 flex items-center justify-center p-8">
10
+ <div class="max-w-md w-full text-center space-y-8">
11
+ <div class="relative">
12
+ <div class="absolute inset-0 bg-red-500/20 blur-3xl rounded-full"></div>
13
+ <div
14
+ class="relative w-24 h-24 mx-auto bg-red-500/10 border border-red-500/20 rounded-3xl flex items-center justify-center text-red-500"
15
+ >
16
+ <svg
17
+ class="w-12 h-12"
18
+ fill="none"
19
+ stroke="currentColor"
20
+ viewBox="0 0 24 24"
21
+ >
22
+ <path
23
+ stroke-linecap="round"
24
+ stroke-linejoin="round"
25
+ stroke-width="2"
26
+ d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
27
+ ></path>
28
+ </svg>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="space-y-2">
33
+ <h1
34
+ class="text-4xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
35
+ >
36
+ Access Denied
37
+ </h1>
38
+ <p class="text-[var(--kyro-text-secondary)] font-medium">
39
+ You don't have the required permissions to access this page. Please
40
+ contact your administrator if you believe this is an error.
41
+ </p>
42
+ </div>
43
+
44
+ <div class="pt-4">
45
+ <a
46
+ href={adminPath}
47
+ class="inline-flex items-center gap-2 px-8 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"
48
+ >
49
+ <svg
50
+ class="w-4 h-4"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ viewBox="0 0 24 24"
54
+ >
55
+ <path
56
+ stroke-linecap="round"
57
+ stroke-linejoin="round"
58
+ stroke-width="2.5"
59
+ d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
60
+ ></path>
61
+ </svg>
62
+ Back to Dashboard
63
+ </a>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </AdminLayout>
@@ -1,51 +1,26 @@
1
1
  ---
2
2
  import AdminLayout from "../../layouts/AdminLayout.astro";
3
- import { collections } from "@/lib/config";
4
- import { AutoForm } from "@/components/AutoForm";
3
+ import { collections } from "../../lib/config";
4
+ import { AutoForm } from "../../components/AutoForm";
5
5
 
6
6
  const { collection, id } = Astro.params;
7
7
 
8
+ import { adminPath, apiPath } from "../../lib/paths";
9
+
8
10
  // Validate collection exists
9
11
  if (!collection || !collections[collection]) {
10
- return Astro.redirect("/");
12
+ return Astro.redirect(adminPath);
11
13
  }
12
14
 
13
15
  const config = collections[collection];
14
16
 
15
- // Handle legacy integer IDs (e.g., "team-1" -> find by slug instead)
16
- let lookupId = id;
17
- if (id && id.includes("-")) {
18
- const parts = id.split("-");
19
- const potentialNum = parts[parts.length - 1];
20
- if (/^\d+$/.test(potentialNum)) {
21
- // Legacy integer ID - try to find document by slug from the remaining parts
22
- const slugPart = parts.slice(0, -1).join("-");
23
- try {
24
- const slugResponse = await fetch(
25
- `${Astro.url.origin}/api/${collection}?limit=100`,
26
- {
27
- headers: { "Content-Type": "application/json" },
28
- },
29
- );
30
- if (slugResponse.ok) {
31
- const slugResult = await slugResponse.json();
32
- const found = (slugResult.docs || []).find(
33
- (d: any) => d.slug === slugPart,
34
- );
35
- if (found) {
36
- lookupId = found.id;
37
- }
38
- }
39
- } catch (e) {}
40
- }
41
- }
42
-
43
17
  // Fetch document if editing
44
18
  let doc: any = null;
45
- if (lookupId && lookupId !== "new") {
19
+ let fetchError: string | null = null;
20
+ if (id && id !== "new") {
46
21
  try {
47
22
  const response = await fetch(
48
- `${Astro.url.origin}/api/${collection}/${lookupId}`,
23
+ `${Astro.url.origin}${apiPath}/${collection}/${id}`,
49
24
  {
50
25
  headers: Astro.request.headers,
51
26
  credentials: "include",
@@ -54,17 +29,16 @@ if (lookupId && lookupId !== "new") {
54
29
  if (response.ok) {
55
30
  const result = await response.json();
56
31
  doc = result.data || null;
32
+ } else {
33
+ const errorData = await response.json().catch(() => ({}));
34
+ fetchError = errorData.error || `Failed to load document (${response.status})`;
57
35
  }
58
36
  } catch (error) {
37
+ fetchError = "Failed to fetch document. Check server connection.";
59
38
  console.error("Failed to fetch document:", error);
60
39
  }
61
40
  }
62
41
 
63
- // Redirect to UUID URL if using legacy ID
64
- if (id && lookupId && id !== lookupId && doc) {
65
- return Astro.redirect(`/${collection}/${lookupId}`, 301);
66
- }
67
-
68
42
  const isNew = !doc;
69
43
  const docStatus = doc?.status || "draft";
70
44
  const title = isNew
@@ -76,157 +50,17 @@ const description =
76
50
  ---
77
51
 
78
52
  <AdminLayout title={title}>
79
- <div class="flex-1 overflow-y-auto p-8 space-y-6">
53
+ <div class="flex-1 overflow-y-auto space-y-6">
80
54
  <form id="doc-form">
81
55
  <AutoForm
82
56
  client:only="react"
83
57
  config={config}
84
58
  data={doc || {}}
85
59
  collectionSlug={collection}
60
+ documentId={id || undefined}
86
61
  documentName={doc?.title || doc?.name || doc?.slug || "new-document"}
87
62
  />
88
63
  <input type="hidden" id="form-data" name="form-data" value="{}" />
89
64
  </form>
90
65
  </div>
91
-
92
- <script define:vars={{ collection, id, isNew }}>
93
- function showToast(message, isError = false) {
94
- const container = document.getElementById("toast-container");
95
- const msg = document.getElementById("toast-message");
96
- if (container && msg) {
97
- msg.textContent = message;
98
- container.classList.remove("hidden");
99
- if (!isError) {
100
- setTimeout(() => container.classList.add("hidden"), 3000);
101
- }
102
- }
103
- }
104
-
105
- // Wait for DOM to be ready
106
- function setupFormHandler() {
107
- const form = document.getElementById("doc-form");
108
- const btn = document.getElementById("btn-save");
109
-
110
- if (!form || !btn) {
111
- setTimeout(setupFormHandler, 100);
112
- return;
113
- }
114
-
115
- // Button click handler
116
- btn.addEventListener("click", async () => {
117
- // Check form validity
118
- if (!form.checkValidity()) {
119
- form.reportValidity();
120
- return;
121
- }
122
-
123
- const originalText = btn.textContent || "";
124
- btn.textContent = "Saving…";
125
- btn.setAttribute("disabled", "true");
126
-
127
- // Get data from hidden input (populated by React AutoForm onChange)
128
- const hiddenInput = document.getElementById("form-data");
129
- let data = {};
130
-
131
- if (hiddenInput && hiddenInput.value) {
132
- try {
133
- const val = hiddenInput.value;
134
- if (val) data = JSON.parse(val);
135
- } catch (err) {
136
- console.error("Failed to parse form data:", err);
137
- }
138
- }
139
-
140
- const url = isNew ? `/api/${collection}` : `/api/${collection}/${id}`;
141
- const method = isNew ? "POST" : "PATCH";
142
-
143
- try {
144
- const response = await fetch(url, {
145
- method,
146
- credentials: "include",
147
- headers: { "Content-Type": "application/json" },
148
- body: JSON.stringify(data),
149
- });
150
-
151
- if (response.ok) {
152
- showToast(
153
- isNew ? "Document created successfully" : "Changes saved",
154
- );
155
- setTimeout(() => {
156
- window.location.href = `/${collection}`;
157
- }, 800);
158
- } else {
159
- const error = await response.json();
160
- showToast(error.error || "An error occurred", true);
161
- }
162
- } catch (error) {
163
- showToast("Failed to save document", true);
164
- } finally {
165
- btn.textContent = originalText;
166
- btn.removeAttribute("disabled");
167
- }
168
- });
169
- }
170
-
171
- setupFormHandler();
172
-
173
- // Setup publish/unpublish handlers
174
- const btnPublish = document.getElementById("btn-publish");
175
- const btnUnpublish = document.getElementById("btn-unpublish");
176
-
177
- async function handlePublish() {
178
- if (!btnPublish) return;
179
- btnPublish.textContent = "Publishing...";
180
- btnPublish.setAttribute("disabled", "true");
181
-
182
- try {
183
- const response = await fetch(`/api/${collection}/${id}/publish`, {
184
- method: "POST",
185
- credentials: "include",
186
- });
187
-
188
- if (response.ok) {
189
- showToast("Published successfully");
190
- location.reload();
191
- } else {
192
- const error = await response.json();
193
- showToast(error.error || "Failed to publish", true);
194
- }
195
- } catch (err) {
196
- showToast("Failed to publish", true);
197
- } finally {
198
- btnPublish.textContent = "Publish";
199
- btnPublish.removeAttribute("disabled");
200
- }
201
- }
202
-
203
- async function handleUnpublish() {
204
- if (!btnUnpublish) return;
205
- btnUnpublish.textContent = "Unpublishing...";
206
- btnUnpublish.setAttribute("disabled", "true");
207
-
208
- try {
209
- const response = await fetch(`/api/${collection}/${id}/unpublish`, {
210
- method: "POST",
211
- credentials: "include",
212
- });
213
-
214
- if (response.ok) {
215
- showToast("Unpublished successfully");
216
- location.reload();
217
- } else {
218
- const error = await response.json();
219
- showToast(error.error || "Failed to unpublish", true);
220
- }
221
- } catch (err) {
222
- showToast("Failed to unpublish", true);
223
- } finally {
224
- btnUnpublish.textContent = "Unpublish";
225
- btnUnpublish.removeAttribute("disabled");
226
- }
227
- }
228
-
229
- if (btnPublish) btnPublish.addEventListener("click", handlePublish);
230
- if (btnUnpublish) btnUnpublish.addEventListener("click", handleUnpublish);
231
- </script>
232
66
  </AdminLayout>
@@ -1,14 +1,19 @@
1
1
  ---
2
2
  import AdminLayout from "../../layouts/AdminLayout.astro";
3
- import { collections } from "@/lib/config";
4
- import { EnhancedListView } from "@/components/EnhancedListView";
3
+ import { collections } from "../../lib/config";
4
+ import { ListView } from "../../components/ListView";
5
+ import { adminPath, apiPath } from "../../lib/paths";
5
6
 
6
7
  const { collection } = Astro.params;
7
8
 
8
9
  if (!collection || !collections[collection]) {
9
- return Astro.redirect("/");
10
+ return Astro.redirect(adminPath);
10
11
  }
11
12
 
13
+ // Redirect system collections to dedicated pages
14
+ if (collection === "users") return Astro.redirect(`${adminPath}/users`);
15
+ if (collection === "audit_logs") return Astro.redirect(`${adminPath}/audit`);
16
+
12
17
  const config = collections[collection];
13
18
 
14
19
  const page = parseInt(Astro.url.searchParams.get("page") || "1");
@@ -19,7 +24,7 @@ let totalDocs = 0;
19
24
 
20
25
  try {
21
26
  const response = await fetch(
22
- `${Astro.url.origin}/api/${collection}?page=${page}&limit=${limit}&t=${Date.now()}`,
27
+ `${Astro.url.origin}${apiPath}/${collection}?page=${page}&limit=${limit}&t=${Date.now()}`,
23
28
  {
24
29
  headers: Astro.request.headers,
25
30
  credentials: "include",
@@ -36,8 +41,8 @@ try {
36
41
  ---
37
42
 
38
43
  <AdminLayout title={config.label || collection || "Collection"}>
39
- <div class="flex-1 overflow-y-auto p-8">
40
- <EnhancedListView
44
+ <div class="flex-1 overflow-y-auto">
45
+ <ListView
41
46
  client:load
42
47
  collection={config}
43
48
  collectionSlug={collection}
@@ -0,0 +1,173 @@
1
+ ---
2
+ import AdminLayout from "../layouts/AdminLayout.astro";
3
+ import { ApiExplorer } from "../components/ApiExplorer";
4
+
5
+ import { adminPath, apiPath } from "../lib/paths";
6
+
7
+ const collectionsResponse = await fetch(
8
+ `${Astro.url.origin}${apiPath}/collections`,
9
+ );
10
+ const collectionsData = await collectionsResponse.json();
11
+ const collections = collectionsData.collections || [];
12
+ ---
13
+
14
+ <AdminLayout title="API Explorer">
15
+ <div class="flex-1 overflow-hidden">
16
+ <!-- Header -->
17
+ <div class="mb-6 surface-tile">
18
+ <div class="flex items-center justify-between mb-4">
19
+ <div>
20
+ <h1
21
+ class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
22
+ >
23
+ API Explorer
24
+ </h1>
25
+ <p
26
+ class="text-[var(--kyro-text-secondary)] font-bold mt-2 text-sm tracking-wider"
27
+ >
28
+ Test and explore REST API endpoints interactively
29
+ </p>
30
+ </div>
31
+ <div class="flex items-center gap-3">
32
+ <a
33
+ href={`${adminPath}/rest-playground`}
34
+ class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
35
+ >
36
+ <svg
37
+ class="w-4 h-4"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ viewBox="0 0 24 24"
41
+ >
42
+ <path
43
+ stroke-linecap="round"
44
+ stroke-linejoin="round"
45
+ stroke-width="2"
46
+ d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
47
+ ></path>
48
+ </svg>
49
+ Playground
50
+ </a>
51
+ <a
52
+ href={`${apiPath}/collections`}
53
+ target="_blank"
54
+ class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
55
+ >
56
+ <svg
57
+ class="w-4 h-4"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ viewBox="0 0 24 24"
61
+ >
62
+ <path
63
+ stroke-linecap="round"
64
+ stroke-linejoin="round"
65
+ stroke-width="2"
66
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
67
+ ></path>
68
+ </svg>
69
+ Collections JSON
70
+ </a>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- HTTP Methods Legend -->
75
+ <div class="flex items-center gap-4 text-xs">
76
+ <span class="text-[var(--kyro-text-muted)]">Methods:</span>
77
+ <span class="px-2 py-1 bg-green-500/10 text-green-600 rounded font-bold"
78
+ >GET</span
79
+ >
80
+ <span class="px-2 py-1 bg-blue-500/10 text-blue-600 rounded font-bold"
81
+ >POST</span
82
+ >
83
+ <span
84
+ class="px-2 py-1 bg-yellow-500/10 text-yellow-600 rounded font-bold"
85
+ >PATCH</span
86
+ >
87
+ <span class="px-2 py-1 bg-red-500/10 text-red-600 rounded font-bold"
88
+ >DELETE</span
89
+ >
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Explorer Component -->
94
+ <div class="surface-tile overflow-hidden p-6">
95
+ <ApiExplorer client:load collections={collections} />
96
+ </div>
97
+
98
+ <!-- Quick Reference -->
99
+ <div class="mt-6 surface-tile p-6">
100
+ <h2 class="text-xl font-bold text-[var(--kyro-text-primary)] mb-4">
101
+ Quick Reference
102
+ </h2>
103
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
104
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
105
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
106
+ List All
107
+ </h3>
108
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
109
+ >GET {apiPath}/:collection</code
110
+ >
111
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
112
+ Returns all documents with pagination
113
+ </p>
114
+ </div>
115
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
116
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
117
+ Get One
118
+ </h3>
119
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
120
+ >GET {apiPath}/:collection/:id</code
121
+ >
122
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
123
+ Returns a single document by ID
124
+ </p>
125
+ </div>
126
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
127
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
128
+ Create
129
+ </h3>
130
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
131
+ >POST {apiPath}/:collection</code
132
+ >
133
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
134
+ Creates a new document
135
+ </p>
136
+ </div>
137
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
138
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
139
+ Update
140
+ </h3>
141
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
142
+ >PATCH {apiPath}/:collection/:id</code
143
+ >
144
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
145
+ Updates an existing document
146
+ </p>
147
+ </div>
148
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
149
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
150
+ Delete
151
+ </h3>
152
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
153
+ >DELETE {apiPath}/:collection/:id</code
154
+ >
155
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
156
+ Deletes a document
157
+ </p>
158
+ </div>
159
+ <div class="bg-[var(--kyro-surface-accent)] rounded-lg p-4">
160
+ <h3 class="font-medium text-[var(--kyro-text-primary)] mb-2">
161
+ Pagination
162
+ </h3>
163
+ <code class="text-xs text-[var(--kyro-text-secondary)] block"
164
+ >?page=1&amp;limit=10</code
165
+ >
166
+ <p class="text-xs text-[var(--kyro-text-muted)] mt-2">
167
+ Add query params for pagination
168
+ </p>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </AdminLayout>
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import AdminLayout from '../../layouts/AdminLayout.astro';
3
3
  import { AuditLogsPage } from '../../components/AuditLogsPage';
4
+
5
+ import { adminPath, apiPath } from '../../lib/paths';
4
6
  ---
5
7
 
6
8
  <AdminLayout title="Audit Logs">
@@ -0,0 +1,122 @@
1
+ ---
2
+ import AuthLayout from "../../layouts/AuthLayout.astro";
3
+ import { adminPath, apiPath } from "../../lib/paths";
4
+ ---
5
+
6
+ <AuthLayout title="Sign In">
7
+ <div class="surface-tile p-8 w-full" style="max-width: 420px;">
8
+ <div class="text-center mb-8">
9
+ <h1
10
+ class="text-2xl font-bold tracking-tight text-[var(--kyro-text-primary)]"
11
+ >
12
+ Welcome back
13
+ </h1>
14
+ <p class="text-sm text-[var(--kyro-text-secondary)] mt-2">
15
+ Sign in to your account
16
+ </p>
17
+ </div>
18
+
19
+ <form id="login-form" class="space-y-5">
20
+ <div>
21
+ <label
22
+ for="email"
23
+ class="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2 text-left"
24
+ >Email</label
25
+ >
26
+ <input
27
+ type="email"
28
+ id="email"
29
+ name="email"
30
+ required
31
+ 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)]"
32
+ placeholder="admin@example.com"
33
+ />
34
+ </div>
35
+
36
+ <div>
37
+ <label
38
+ for="password"
39
+ class="block text-sm font-medium text-[var(--kyro-text-primary)] mb-2"
40
+ >Password</label
41
+ >
42
+ <input
43
+ type="password"
44
+ id="password"
45
+ name="password"
46
+ required
47
+ 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)]"
48
+ placeholder="••••••••"
49
+ />
50
+ </div>
51
+
52
+ <div id="form-message" class="hidden p-3 rounded-xl text-sm font-bold">
53
+ </div>
54
+
55
+ <button
56
+ type="submit"
57
+ class="w-full py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl text-sm font-bold hover:opacity-90 transition-colors shadow-lg"
58
+ >
59
+ Sign In
60
+ </button>
61
+ </form>
62
+
63
+ <p class="text-center text-sm text-[var(--kyro-text-secondary)] mt-6">
64
+ Don't have an account? <a
65
+ href={`${adminPath}/register`}
66
+ class="font-medium text-[var(--kyro-text-primary)] hover:underline"
67
+ >Register</a
68
+ >
69
+ </p>
70
+ </div>
71
+
72
+ <script define:vars={{ apiPath, adminPath }}>
73
+ document
74
+ .getElementById("login-form")
75
+ ?.addEventListener("submit", async (e) => {
76
+ e.preventDefault();
77
+ const form = e.target;
78
+ const message = document.getElementById("form-message");
79
+ const button = form.querySelector('button[type="submit"]');
80
+
81
+ const email = form.email.value;
82
+ const password = form.password.value;
83
+
84
+ button.disabled = true;
85
+ button.textContent = "Signing in...";
86
+
87
+ try {
88
+ const res = await fetch(apiPath + "/auth/login", {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({ email, password }),
92
+ });
93
+
94
+ const data = await res.json();
95
+
96
+ if (res.ok && data.success) {
97
+ // Cookies set server-side via Set-Cookie headers
98
+ // Store user in memory only (not localStorage)
99
+ window.__kyroAuth = { user: data.user, verified: true };
100
+ message.textContent = "Success! Redirecting...";
101
+ message.className =
102
+ "block p-3 rounded-xl text-sm font-regular bg-green-50 text-green-600";
103
+ setTimeout(() => {
104
+ window.location.href = adminPath;
105
+ }, 500);
106
+ } else {
107
+ message.textContent = data.error || "Login failed";
108
+ message.className =
109
+ "block p-3 rounded-xl text-sm font-regular bg-red-50 text-red-600";
110
+ button.disabled = false;
111
+ button.textContent = "Sign In";
112
+ }
113
+ } catch (err) {
114
+ message.textContent = "Connection error";
115
+ message.className =
116
+ "block p-3 rounded-xl text-sm font-regular bg-red-50 text-red-600";
117
+ button.disabled = false;
118
+ button.textContent = "Sign In";
119
+ }
120
+ });
121
+ </script>
122
+ </AuthLayout>