@adminforge/core 0.3.1

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 (86) hide show
  1. package/.turbo/turbo-build.log +56 -0
  2. package/CHANGELOG.md +32 -0
  3. package/LICENSE +21 -0
  4. package/bin/adminforge.js +317 -0
  5. package/dist/auth-client.cjs +45 -0
  6. package/dist/auth-client.cjs.map +1 -0
  7. package/dist/auth-client.d.cts +17 -0
  8. package/dist/auth-client.d.ts +17 -0
  9. package/dist/auth-client.js +20 -0
  10. package/dist/auth-client.js.map +1 -0
  11. package/dist/auth.cjs +65 -0
  12. package/dist/auth.cjs.map +1 -0
  13. package/dist/auth.d.cts +21 -0
  14. package/dist/auth.d.ts +21 -0
  15. package/dist/auth.js +36 -0
  16. package/dist/auth.js.map +1 -0
  17. package/dist/client-D0cjJVsn.d.ts +20 -0
  18. package/dist/client-sRnmZ-Y9.d.cts +20 -0
  19. package/dist/index-CyzxaE7n.d.cts +124 -0
  20. package/dist/index-CyzxaE7n.d.ts +124 -0
  21. package/dist/index.cjs +453 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +65 -0
  24. package/dist/index.d.ts +65 -0
  25. package/dist/index.js +410 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/next.cjs +839 -0
  28. package/dist/next.cjs.map +1 -0
  29. package/dist/next.d.cts +84 -0
  30. package/dist/next.d.ts +84 -0
  31. package/dist/next.js +800 -0
  32. package/dist/next.js.map +1 -0
  33. package/dist/styles.css +763 -0
  34. package/dist/styles.css.map +1 -0
  35. package/dist/styles.d.cts +2 -0
  36. package/dist/styles.d.ts +2 -0
  37. package/dist/ui.cjs +2500 -0
  38. package/dist/ui.cjs.map +1 -0
  39. package/dist/ui.d.cts +119 -0
  40. package/dist/ui.d.ts +119 -0
  41. package/dist/ui.js +2448 -0
  42. package/dist/ui.js.map +1 -0
  43. package/eslint.config.js +35 -0
  44. package/package.json +99 -0
  45. package/src/api/controller.ts +234 -0
  46. package/src/api/index.ts +4 -0
  47. package/src/api/next.ts +281 -0
  48. package/src/api/security/agent-auth.ts +134 -0
  49. package/src/auth/config.ts +20 -0
  50. package/src/auth/index.ts +3 -0
  51. package/src/auth/middleware.ts +15 -0
  52. package/src/auth/provider.tsx +28 -0
  53. package/src/core/fields/index.ts +119 -0
  54. package/src/core/hooks/index.ts +60 -0
  55. package/src/core/index.ts +43 -0
  56. package/src/core/registry/index.ts +22 -0
  57. package/src/core/schema/collection.ts +12 -0
  58. package/src/core/schema/config.ts +11 -0
  59. package/src/core/schema/normalize.ts +32 -0
  60. package/src/core/types/index.ts +114 -0
  61. package/src/db/client.ts +146 -0
  62. package/src/db/index.ts +3 -0
  63. package/src/db/schema-generator.ts +104 -0
  64. package/src/fields/index.ts +1 -0
  65. package/src/index.ts +4 -0
  66. package/src/next.ts +3 -0
  67. package/src/styles/adminforge.css +840 -0
  68. package/src/ui/AdminDashboard.tsx +176 -0
  69. package/src/ui/AdminForgeContext.tsx +64 -0
  70. package/src/ui/components/AdminLayout.tsx +107 -0
  71. package/src/ui/form-engine/FormEngine.tsx +250 -0
  72. package/src/ui/form-engine/ImageUpload.tsx +68 -0
  73. package/src/ui/form-engine/RelationInput.tsx +215 -0
  74. package/src/ui/form-engine/RichTextEditor.tsx +708 -0
  75. package/src/ui/index.ts +18 -0
  76. package/src/ui/screens/AdminPage.tsx +162 -0
  77. package/src/ui/screens/AgentTokenPage.tsx +232 -0
  78. package/src/ui/screens/CollectionFormPage.tsx +135 -0
  79. package/src/ui/screens/CollectionListPage.tsx +170 -0
  80. package/src/ui/screens/CollectionSchemaPage.tsx +180 -0
  81. package/src/ui/screens/RoleDetailPage.tsx +147 -0
  82. package/src/ui/screens/RolesListPage.tsx +57 -0
  83. package/src/ui/table-engine/TableEngine.tsx +157 -0
  84. package/src/ui.ts +3 -0
  85. package/tsconfig.json +10 -0
  86. package/tsup.config.ts +54 -0
package/dist/ui.js ADDED
@@ -0,0 +1,2448 @@
1
+ "use client";
2
+
3
+ // src/ui/components/AdminLayout.tsx
4
+ import Link from "next/link";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function AdminLayout({ config, children, currentPath, role }) {
7
+ return /* @__PURE__ */ jsxs("div", { className: "adminforge-layout", children: [
8
+ /* @__PURE__ */ jsxs("nav", { className: "adminforge-sidebar", children: [
9
+ /* @__PURE__ */ jsxs("div", { className: "adminforge-sidebar-header", children: [
10
+ /* @__PURE__ */ jsx(Link, { href: "/admin", children: /* @__PURE__ */ jsx("h1", { children: "AdminForge" }) }),
11
+ /* @__PURE__ */ jsx("p", { className: "adminforge-sidebar-subtitle", children: "Collections Manager" })
12
+ ] }),
13
+ /* @__PURE__ */ jsxs("ul", { className: "adminforge-nav", children: [
14
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { href: "/admin", className: `adminforge-nav-link ${currentPath === "/admin" ? "active" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "adminforge-nav-item-content", children: [
15
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined adminforge-nav-icon", children: "dashboard" }),
16
+ /* @__PURE__ */ jsx("span", { children: "Overview" })
17
+ ] }) }) }),
18
+ config.collections.map((collection) => {
19
+ const a = collection.access;
20
+ if (a?.read && (!role || !a.read.includes(role))) return null;
21
+ const href = `/admin/${collection.name}`;
22
+ const icon = collection.icon || "database";
23
+ return /* @__PURE__ */ jsxs("li", { className: "adminforge-nav-item", children: [
24
+ /* @__PURE__ */ jsx(Link, { href, className: `adminforge-nav-link ${currentPath === href ? "active" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "adminforge-nav-item-content", children: [
25
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined adminforge-nav-icon", children: icon }),
26
+ /* @__PURE__ */ jsx("span", { children: collection.label })
27
+ ] }) }),
28
+ /* @__PURE__ */ jsx(Link, { href: `${href}/new`, className: "adminforge-nav-quick-create", title: `Create New ${collection.label}`, children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: "add" }) })
29
+ ] }, collection.name);
30
+ }),
31
+ /* @__PURE__ */ jsx("li", { className: "adminforge-nav-section-title", style: { marginTop: "24px", padding: "8px 16px", fontSize: "11px", fontWeight: 700, color: "#94a3b8", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Access Control" }),
32
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { href: "/admin/roles", className: `adminforge-nav-link ${currentPath?.startsWith("/admin/roles") ? "active" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "adminforge-nav-item-content", children: [
33
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined adminforge-nav-icon", children: "shield_person" }),
34
+ /* @__PURE__ */ jsx("span", { children: "Roles" })
35
+ ] }) }) }),
36
+ /* @__PURE__ */ jsx("li", { className: "adminforge-nav-section-title", style: { marginTop: "24px", padding: "8px 16px", fontSize: "11px", fontWeight: 700, color: "#94a3b8", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "AI & Agents" }),
37
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { href: "/admin/settings/agent-tokens", className: `adminforge-nav-link ${currentPath === "/admin/settings/agent-tokens" ? "active" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "adminforge-nav-item-content", children: [
38
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined adminforge-nav-icon", children: "smart_toy" }),
39
+ /* @__PURE__ */ jsx("span", { children: "Agent Tokens" })
40
+ ] }) }) })
41
+ ] })
42
+ ] }),
43
+ /* @__PURE__ */ jsxs("main", { className: "adminforge-content", children: [
44
+ /* @__PURE__ */ jsxs("header", { className: "adminforge-topbar", children: [
45
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.125rem", fontWeight: 600 }, children: currentPath === "/admin" ? "Dashboard" : "Management" }),
46
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
47
+ role && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
48
+ /* @__PURE__ */ jsx("div", { style: { width: "8px", height: "8px", borderRadius: "50%", background: "#10b981" } }),
49
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", fontWeight: 500, color: "var(--color-text-secondary)" }, children: role })
50
+ ] }),
51
+ config.auth?.enabled && /* @__PURE__ */ jsx("form", { action: "/api/logout", method: "POST", children: /* @__PURE__ */ jsx("button", { type: "submit", className: "adminforge-btn adminforge-btn-secondary", style: { padding: "6px 12px", fontSize: "13px" }, children: "Log Out" }) })
52
+ ] })
53
+ ] }),
54
+ /* @__PURE__ */ jsx("div", { className: "adminforge-main-canvas", children })
55
+ ] })
56
+ ] });
57
+ }
58
+
59
+ // src/ui/AdminDashboard.tsx
60
+ import React3 from "react";
61
+
62
+ // src/ui/screens/AdminPage.tsx
63
+ import Link2 from "next/link";
64
+
65
+ // src/auth/provider.tsx
66
+ import { createContext, useContext } from "react";
67
+ import { jsx as jsx2 } from "react/jsx-runtime";
68
+ var AdminSessionContext = createContext(null);
69
+ function AuthProvider({
70
+ children,
71
+ session
72
+ }) {
73
+ return /* @__PURE__ */ jsx2(AdminSessionContext.Provider, { value: session, children });
74
+ }
75
+ function useAdminSession() {
76
+ return useContext(AdminSessionContext);
77
+ }
78
+
79
+ // src/ui/screens/AdminPage.tsx
80
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
81
+ function formatRelativeTime(date) {
82
+ const diff = Date.now() - date.getTime();
83
+ const minutes = Math.floor(diff / 6e4);
84
+ const hours = Math.floor(minutes / 60);
85
+ const days = Math.floor(hours / 24);
86
+ if (isNaN(date.getTime())) return "Invalid date";
87
+ if (minutes < 1) return "Just now";
88
+ if (minutes < 60) return `${minutes}m ago`;
89
+ if (hours < 24) return `${hours}h ago`;
90
+ return `${days}d ago`;
91
+ }
92
+ function AdminPage({ config, role: propRole }) {
93
+ const session = useAdminSession();
94
+ const role = propRole || session?.role || session?.user?.role;
95
+ const schemaActivity = config.collections?.[0] && config.collections[0]?.schemaActivity;
96
+ return /* @__PURE__ */ jsx3(AdminLayout, { config, currentPath: "/admin", role, children: /* @__PURE__ */ jsxs2("div", { className: "adminforge-dashboard", children: [
97
+ /* @__PURE__ */ jsxs2("div", { className: "mb-10", children: [
98
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", gap: "16px", marginBottom: "8px" }, children: /* @__PURE__ */ jsx3("h2", { className: "adminforge-display-title", style: { marginBottom: 0 }, children: "Collection Registry" }) }),
99
+ /* @__PURE__ */ jsx3("p", { className: "adminforge-display-subtitle", style: { marginBottom: "32px" }, children: "Index of all data models defined in your system." })
100
+ ] }),
101
+ schemaActivity && /* @__PURE__ */ jsxs2("div", { style: {
102
+ display: "flex",
103
+ alignItems: "center",
104
+ gap: "12px",
105
+ padding: "12px 20px",
106
+ background: "#f0f9ff",
107
+ border: "1px solid #bae6fd",
108
+ borderRadius: "12px",
109
+ marginBottom: "24px",
110
+ boxShadow: "0 1px 2px rgba(0,0,0,0.05)"
111
+ }, children: [
112
+ /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { color: "#0284c7", fontSize: "20px" }, children: "sync" }),
113
+ /* @__PURE__ */ jsxs2("div", { style: { fontSize: "14px", color: "#0369a1" }, children: [
114
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 600 }, children: "Schema Synced:" }),
115
+ " Your definitions from ",
116
+ /* @__PURE__ */ jsx3("code", { style: { background: "#e0f2fe", padding: "2px 4px", borderRadius: "4px" }, children: "adminforge.ts" }),
117
+ " were last updated ",
118
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500 }, children: formatRelativeTime(new Date(schemaActivity.updatedAt)) })
119
+ ] })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs2("div", { className: "adminforge-table-wrapper", children: [
122
+ /* @__PURE__ */ jsx3("div", { style: { padding: "24px", borderBottom: "1px solid var(--color-border)", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: /* @__PURE__ */ jsx3("h3", { style: { fontSize: "18px", fontWeight: 600 }, children: "Registered Collections" }) }),
123
+ /* @__PURE__ */ jsxs2("table", { className: "adminforge-table", children: [
124
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
125
+ /* @__PURE__ */ jsx3("th", { style: { width: "auto" }, children: "Collection Name" }),
126
+ /* @__PURE__ */ jsx3("th", { style: { width: "180px" }, children: "Field Definitions" }),
127
+ /* @__PURE__ */ jsx3("th", { style: { width: "120px", textAlign: "right" }, children: "Actions" })
128
+ ] }) }),
129
+ /* @__PURE__ */ jsx3("tbody", { children: config.collections.filter((collection) => {
130
+ const a = collection.access;
131
+ if (!a?.read) return true;
132
+ if (!role) return false;
133
+ return a.read.includes(role);
134
+ }).map((collection) => {
135
+ return /* @__PURE__ */ jsxs2("tr", { children: [
136
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
137
+ /* @__PURE__ */ jsx3("div", { style: { width: "36px", height: "36px", borderRadius: "8px", background: "#f1f5f9", display: "flex", alignItems: "center", justifyContent: "center", color: "#64748b" }, children: /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { fontSize: "20px" }, children: collection.icon || "database" }) }),
138
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 600, color: "var(--color-text)", fontSize: "15px" }, children: collection.label })
139
+ ] }) }),
140
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsxs2("span", { className: "adminforge-badge adminforge-badge-secondary", style: { padding: "4px 10px" }, children: [
141
+ Object.keys(collection.fields).length,
142
+ " mapped fields"
143
+ ] }) }),
144
+ /* @__PURE__ */ jsx3("td", { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px", justifyContent: "flex-end" }, children: [
145
+ /* @__PURE__ */ jsx3(Link2, { href: `/admin/${collection.name}`, className: "adminforge-btn-icon", title: "View Data", children: /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { fontSize: "20px" }, children: "database" }) }),
146
+ /* @__PURE__ */ jsx3(Link2, { href: `/admin/${collection.name}/schema`, className: "adminforge-btn-icon", title: "View Schema", children: /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { fontSize: "20px" }, children: "visibility" }) })
147
+ ] }) })
148
+ ] }, collection.name);
149
+ }) })
150
+ ] }),
151
+ /* @__PURE__ */ jsx3("div", { style: { padding: "20px 24px", background: "#fcfcfd", borderTop: "1px solid var(--color-border)", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: /* @__PURE__ */ jsxs2("p", { style: { fontSize: "13px", color: "var(--color-text-secondary)" }, children: [
152
+ "Showing ",
153
+ config.collections.length,
154
+ " models from your current configuration."
155
+ ] }) })
156
+ ] })
157
+ ] }) });
158
+ }
159
+
160
+ // src/ui/table-engine/TableEngine.tsx
161
+ import Link3 from "next/link";
162
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
163
+ function hasAccess(access, operation, role) {
164
+ if (!access) return true;
165
+ const allowed = access[operation];
166
+ if (!allowed || !Array.isArray(allowed)) return true;
167
+ if (!role) return false;
168
+ return allowed.includes(role);
169
+ }
170
+ function formatRelativeTime2(date) {
171
+ const diff = Date.now() - date.getTime();
172
+ const minutes = Math.floor(diff / 6e4);
173
+ const hours = Math.floor(minutes / 60);
174
+ const days = Math.floor(hours / 24);
175
+ if (isNaN(date.getTime())) return "Invalid date";
176
+ if (minutes < 1) return "Just now";
177
+ if (minutes < 60) return `${minutes}m ago`;
178
+ if (hours < 24) return `${hours}h ago`;
179
+ return `${days}d ago`;
180
+ }
181
+ function ActivityCell({ record }) {
182
+ const createdAtStr = record.createdAt || record.created_at;
183
+ const updatedAtStr = record.updatedAt || record.updated_at;
184
+ const createdAt = createdAtStr ? new Date(createdAtStr) : null;
185
+ const updatedAt = updatedAtStr ? new Date(updatedAtStr) : null;
186
+ const isUpdated = updatedAt && createdAt && updatedAt.getTime() > createdAt.getTime() + 1e3;
187
+ const date = isUpdated ? updatedAt : createdAt;
188
+ const label = isUpdated ? "Updated" : "Created";
189
+ if (!date) return /* @__PURE__ */ jsx4("span", { style: { color: "#94a3b8", fontSize: "13px" }, children: "No activity" });
190
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: "120px" }, children: [
191
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "6px", fontSize: "13px" }, children: [
192
+ /* @__PURE__ */ jsx4(
193
+ "span",
194
+ {
195
+ style: {
196
+ fontSize: "10px",
197
+ padding: "1px 6px",
198
+ borderRadius: "10px",
199
+ background: isUpdated ? "#fef3c7" : "#dcfce7",
200
+ color: isUpdated ? "#92400e" : "#166534",
201
+ fontWeight: 700,
202
+ textTransform: "uppercase"
203
+ },
204
+ children: label
205
+ }
206
+ ),
207
+ /* @__PURE__ */ jsx4("span", { style: { fontWeight: 500, color: "var(--color-text)" }, children: formatRelativeTime2(date) })
208
+ ] }),
209
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "11px", color: "#64748b" }, children: [
210
+ "by ",
211
+ /* @__PURE__ */ jsx4("span", { style: { color: "#0f172a", fontWeight: 600 }, children: "Admin" })
212
+ ] })
213
+ ] });
214
+ }
215
+ function TableEngine({ collection, data, basePath, role }) {
216
+ const records = data;
217
+ const fieldKeys = Object.keys(collection.fields);
218
+ const displayKeys = ["id", ...fieldKeys.slice(0, 4)];
219
+ const canDelete = hasAccess(collection.access, "delete", role);
220
+ const canUpdate = hasAccess(collection.access, "update", role);
221
+ return /* @__PURE__ */ jsx4("div", { className: "adminforge-table-wrapper", children: /* @__PURE__ */ jsxs3("table", { className: "adminforge-table", children: [
222
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
223
+ displayKeys.map((key) => /* @__PURE__ */ jsx4("th", { children: key }, key)),
224
+ /* @__PURE__ */ jsx4("th", { children: "Activity" }),
225
+ /* @__PURE__ */ jsx4("th", { style: { textAlign: "right", paddingRight: "20px" }, children: "Actions" })
226
+ ] }) }),
227
+ /* @__PURE__ */ jsxs3("tbody", { children: [
228
+ records.map((record) => /* @__PURE__ */ jsxs3("tr", { children: [
229
+ displayKeys.map((key) => /* @__PURE__ */ jsx4("td", { children: key === "id" ? /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
230
+ /* @__PURE__ */ jsxs3("span", { className: "adminforge-id-badge", title: String(record[key]), children: [
231
+ String(record[key]).substring(0, 8),
232
+ "..."
233
+ ] }),
234
+ /* @__PURE__ */ jsx4(
235
+ "button",
236
+ {
237
+ type: "button",
238
+ className: "adminforge-btn-icon",
239
+ style: { width: "24px", height: "24px", minWidth: "24px" },
240
+ title: "Copy ID",
241
+ onClick: (e) => {
242
+ e.stopPropagation();
243
+ navigator.clipboard.writeText(String(record[key]));
244
+ },
245
+ children: /* @__PURE__ */ jsx4("span", { className: "material-symbols-outlined", style: { fontSize: "14px" }, children: "content_copy" })
246
+ }
247
+ )
248
+ ] }) : /* @__PURE__ */ jsx4("div", { style: { maxWidth: "140px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: String(record[key] ?? "") }) }, key)),
249
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4(ActivityCell, { record }) }),
250
+ /* @__PURE__ */ jsx4("td", { style: { textAlign: "right", paddingRight: "20px" }, children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "4px", justifyContent: "flex-end" }, children: [
251
+ canUpdate && /* @__PURE__ */ jsx4(Link3, { href: `${basePath}/${record.id}`, className: "adminforge-btn-icon", title: "Edit", children: /* @__PURE__ */ jsx4("span", { className: "material-symbols-outlined", style: { fontSize: "20px" }, children: "edit" }) }),
252
+ canDelete && /* @__PURE__ */ jsx4(
253
+ "button",
254
+ {
255
+ className: "adminforge-btn-icon adminforge-btn-icon-danger",
256
+ title: "Delete",
257
+ onClick: async () => {
258
+ if (confirm("Are you sure you want to delete this item?")) {
259
+ const res = await fetch(`/api/${collection.name}/${record.id}`, { method: "DELETE" });
260
+ if (res.ok) {
261
+ window.location.reload();
262
+ } else {
263
+ const err = await res.json();
264
+ alert(err.error || "Failed to delete item");
265
+ }
266
+ }
267
+ },
268
+ children: /* @__PURE__ */ jsx4("span", { className: "material-symbols-outlined", style: { fontSize: "20px" }, children: "delete" })
269
+ }
270
+ )
271
+ ] }) })
272
+ ] }, record.id)),
273
+ records.length === 0 && /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: displayKeys.length + 1, children: "No records found" }) })
274
+ ] })
275
+ ] }) });
276
+ }
277
+
278
+ // src/ui/screens/CollectionListPage.tsx
279
+ import Link4 from "next/link";
280
+ import { useState, useCallback } from "react";
281
+ import { useRouter } from "next/navigation";
282
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
283
+ function hasAccess2(access, operation, role) {
284
+ if (!access) return true;
285
+ const allowed = access[operation];
286
+ if (!allowed || !Array.isArray(allowed)) return true;
287
+ if (!role) return false;
288
+ return allowed.includes(role);
289
+ }
290
+ function CollectionListPage({ config, collection, data, total, page, pageSize, role: propRole }) {
291
+ const router = useRouter();
292
+ const session = useAdminSession();
293
+ const role = propRole || session?.role || session?.user?.role;
294
+ const [searchQuery, setSearchQuery] = useState("");
295
+ const canCreate = hasAccess2(collection.access, "create", role);
296
+ const updateParams = useCallback((newParams) => {
297
+ const params = new URLSearchParams(window.location.search);
298
+ Object.entries(newParams).forEach(([key, value]) => {
299
+ if (value) params.set(key, String(value));
300
+ else params.delete(key);
301
+ });
302
+ router.push(`/admin/${collection.name}?${params.toString()}`);
303
+ }, [collection.name, router]);
304
+ const handleSearch = (e) => {
305
+ e.preventDefault();
306
+ updateParams({ search: searchQuery, page: 1 });
307
+ };
308
+ const totalPages = Math.ceil(total / pageSize);
309
+ const startEntry = (page - 1) * pageSize + 1;
310
+ const endEntry = Math.min(page * pageSize, total);
311
+ return /* @__PURE__ */ jsx5(AdminLayout, { config, currentPath: `/admin/${collection.name}`, role, children: /* @__PURE__ */ jsxs4("div", { className: "adminforge-collection-page", children: [
312
+ /* @__PURE__ */ jsxs4("div", { className: "adminforge-page-header", children: [
313
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
314
+ /* @__PURE__ */ jsx5("h2", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: collection.label }),
315
+ /* @__PURE__ */ jsxs4("span", { className: "adminforge-badge", style: { background: "#f8fafc", color: "#64748b", borderColor: "#e2e8f0", fontSize: "11px", fontWeight: 600 }, children: [
316
+ total,
317
+ " total records"
318
+ ] })
319
+ ] }),
320
+ canCreate && /* @__PURE__ */ jsxs4(Link4, { href: `/admin/${collection.name}/new`, className: "adminforge-btn adminforge-btn-primary", children: [
321
+ /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined", style: { fontSize: "20px", marginRight: "8px" }, children: "add" }),
322
+ "Create New"
323
+ ] })
324
+ ] }),
325
+ /* @__PURE__ */ jsxs4("div", { style: { background: "var(--color-surface)", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", overflow: "hidden", boxShadow: "var(--shadow-sm)" }, children: [
326
+ /* @__PURE__ */ jsx5("div", { style: { padding: "20px", borderBottom: "1px solid var(--color-border)", background: "#fcfcfd" }, children: /* @__PURE__ */ jsxs4("form", { onSubmit: handleSearch, style: { maxWidth: "400px", position: "relative" }, children: [
327
+ /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined", style: { position: "absolute", left: "12px", top: "50%", transform: "translateY(-50%)", color: "#94a3b8", fontSize: "20px" }, children: "search" }),
328
+ /* @__PURE__ */ jsx5(
329
+ "input",
330
+ {
331
+ type: "text",
332
+ className: "adminforge-search-input",
333
+ placeholder: `Search ${(collection.label || collection.name).toLowerCase()}...`,
334
+ value: searchQuery,
335
+ onChange: (e) => setSearchQuery(e.target.value),
336
+ style: { paddingLeft: "40px", width: "100%" }
337
+ }
338
+ )
339
+ ] }) }),
340
+ /* @__PURE__ */ jsx5(TableEngine, { collection, data, basePath: `/admin/${collection.name}`, role }),
341
+ /* @__PURE__ */ jsxs4("div", { style: { padding: "16px 24px", borderTop: "1px solid var(--color-border)", display: "flex", justifyContent: "space-between", alignItems: "center", background: "#fcfcfd" }, children: [
342
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
343
+ /* @__PURE__ */ jsxs4("div", { style: { fontSize: "14px", color: "var(--color-text-secondary)" }, children: [
344
+ "Showing ",
345
+ /* @__PURE__ */ jsx5("span", { style: { fontWeight: 600, color: "var(--color-text)" }, children: startEntry }),
346
+ " to ",
347
+ /* @__PURE__ */ jsx5("span", { style: { fontWeight: 600, color: "var(--color-text)" }, children: endEntry }),
348
+ " of ",
349
+ /* @__PURE__ */ jsx5("span", { style: { fontWeight: 600, color: "var(--color-text)" }, children: total }),
350
+ " results"
351
+ ] }),
352
+ /* @__PURE__ */ jsx5(
353
+ "select",
354
+ {
355
+ value: pageSize,
356
+ onChange: (e) => updateParams({ pageSize: e.target.value, page: 1 }),
357
+ style: {
358
+ padding: "4px 8px",
359
+ borderRadius: "6px",
360
+ border: "1px solid var(--color-border)",
361
+ fontSize: "12px",
362
+ background: "white",
363
+ color: "var(--color-text)",
364
+ cursor: "pointer",
365
+ outline: "none"
366
+ },
367
+ children: [10, 25, 50, 100].map((size) => /* @__PURE__ */ jsxs4("option", { value: size, children: [
368
+ size,
369
+ " / page"
370
+ ] }, size))
371
+ }
372
+ )
373
+ ] }),
374
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "8px" }, children: [
375
+ /* @__PURE__ */ jsx5(
376
+ "button",
377
+ {
378
+ onClick: () => updateParams({ page: page - 1 }),
379
+ disabled: page <= 1,
380
+ className: "adminforge-btn-icon",
381
+ style: { border: "1px solid var(--color-border)", background: "white" },
382
+ children: /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined", children: "chevron_left" })
383
+ }
384
+ ),
385
+ [...Array(totalPages)].map((_, i) => {
386
+ const p = i + 1;
387
+ if (totalPages > 7 && p > 1 && p < totalPages && Math.abs(p - page) > 1) {
388
+ if (p === 2 || p === totalPages - 1) return /* @__PURE__ */ jsx5("span", { style: { padding: "0 4px" }, children: "..." }, p);
389
+ return null;
390
+ }
391
+ return /* @__PURE__ */ jsx5(
392
+ "button",
393
+ {
394
+ onClick: () => updateParams({ page: p }),
395
+ style: {
396
+ width: "36px",
397
+ height: "36px",
398
+ display: "flex",
399
+ alignItems: "center",
400
+ justifyContent: "center",
401
+ borderRadius: "6px",
402
+ border: "1px solid",
403
+ borderColor: p === page ? "var(--color-primary)" : "var(--color-border)",
404
+ background: p === page ? "var(--color-primary)" : "white",
405
+ color: p === page ? "white" : "var(--color-text)",
406
+ fontWeight: p === page ? 600 : 400,
407
+ cursor: "pointer"
408
+ },
409
+ children: p
410
+ },
411
+ p
412
+ );
413
+ }),
414
+ /* @__PURE__ */ jsx5(
415
+ "button",
416
+ {
417
+ onClick: () => updateParams({ page: page + 1 }),
418
+ disabled: page >= totalPages,
419
+ className: "adminforge-btn-icon",
420
+ style: { border: "1px solid var(--color-border)", background: "white" },
421
+ children: /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined", children: "chevron_right" })
422
+ }
423
+ )
424
+ ] })
425
+ ] })
426
+ ] })
427
+ ] }) });
428
+ }
429
+
430
+ // src/ui/form-engine/FormEngine.tsx
431
+ import { useCallback as useCallback3, useRef as useRef3, useState as useState5, useEffect as useEffect3 } from "react";
432
+
433
+ // src/ui/form-engine/RichTextEditor.tsx
434
+ import { useEditor, EditorContent } from "@tiptap/react";
435
+ import { BubbleMenu } from "@tiptap/react/menus";
436
+ import StarterKit from "@tiptap/starter-kit";
437
+ import Link5 from "@tiptap/extension-link";
438
+ import Underline from "@tiptap/extension-underline";
439
+ import TextAlign from "@tiptap/extension-text-align";
440
+ import Placeholder from "@tiptap/extension-placeholder";
441
+ import Highlight from "@tiptap/extension-highlight";
442
+ import Subscript from "@tiptap/extension-subscript";
443
+ import Superscript from "@tiptap/extension-superscript";
444
+ import TaskList from "@tiptap/extension-task-list";
445
+ import TaskItem from "@tiptap/extension-task-item";
446
+ import HorizontalRule from "@tiptap/extension-horizontal-rule";
447
+ import Typography from "@tiptap/extension-typography";
448
+ import BubbleMenuExtension from "@tiptap/extension-bubble-menu";
449
+ import Image from "@tiptap/extension-image";
450
+ import { useEffect, useCallback as useCallback2 } from "react";
451
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
452
+ function MenuButton({
453
+ onClick,
454
+ isActive = false,
455
+ disabled = false,
456
+ icon,
457
+ title
458
+ }) {
459
+ return /* @__PURE__ */ jsx6(
460
+ "button",
461
+ {
462
+ type: "button",
463
+ onClick,
464
+ disabled,
465
+ className: `adminforge-editor-btn ${isActive ? "active" : ""}`,
466
+ title,
467
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: icon })
468
+ }
469
+ );
470
+ }
471
+ function Toolbar({ editor }) {
472
+ const setLink = useCallback2(() => {
473
+ if (!editor) return;
474
+ const previousUrl = editor.getAttributes("link").href;
475
+ const url = window.prompt("URL", previousUrl);
476
+ if (url === null) return;
477
+ if (url === "") {
478
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
479
+ return;
480
+ }
481
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
482
+ }, [editor]);
483
+ const addImage = useCallback2(() => {
484
+ if (!editor) return;
485
+ const input = document.createElement("input");
486
+ input.type = "file";
487
+ input.accept = "image/*";
488
+ input.onchange = async () => {
489
+ const file = input.files?.[0];
490
+ if (!file) return;
491
+ const formData = new FormData();
492
+ formData.append("file", file);
493
+ try {
494
+ const res = await fetch("/api/upload", {
495
+ method: "POST",
496
+ body: formData
497
+ });
498
+ if (!res.ok) throw new Error("Upload failed");
499
+ const data = await res.json();
500
+ editor.chain().focus().setImage({ src: data.url }).run();
501
+ } catch (err) {
502
+ alert("Failed to upload image");
503
+ }
504
+ };
505
+ input.click();
506
+ }, [editor]);
507
+ if (!editor) return null;
508
+ return /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar", children: [
509
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
510
+ /* @__PURE__ */ jsx6(
511
+ MenuButton,
512
+ {
513
+ onClick: () => editor.chain().focus().undo().run(),
514
+ disabled: !editor.can().undo(),
515
+ icon: "undo",
516
+ title: "Undo"
517
+ }
518
+ ),
519
+ /* @__PURE__ */ jsx6(
520
+ MenuButton,
521
+ {
522
+ onClick: () => editor.chain().focus().redo().run(),
523
+ disabled: !editor.can().redo(),
524
+ icon: "redo",
525
+ title: "Redo"
526
+ }
527
+ )
528
+ ] }),
529
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
530
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
531
+ /* @__PURE__ */ jsx6(
532
+ MenuButton,
533
+ {
534
+ onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
535
+ isActive: editor.isActive("heading", { level: 1 }),
536
+ icon: "format_h1",
537
+ title: "Heading 1"
538
+ }
539
+ ),
540
+ /* @__PURE__ */ jsx6(
541
+ MenuButton,
542
+ {
543
+ onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
544
+ isActive: editor.isActive("heading", { level: 2 }),
545
+ icon: "format_h2",
546
+ title: "Heading 2"
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsx6(
550
+ MenuButton,
551
+ {
552
+ onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
553
+ isActive: editor.isActive("heading", { level: 3 }),
554
+ icon: "format_h3",
555
+ title: "Heading 3"
556
+ }
557
+ )
558
+ ] }),
559
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
560
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
561
+ /* @__PURE__ */ jsx6(
562
+ MenuButton,
563
+ {
564
+ onClick: () => editor.chain().focus().toggleBold().run(),
565
+ isActive: editor.isActive("bold"),
566
+ icon: "format_bold",
567
+ title: "Bold"
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsx6(
571
+ MenuButton,
572
+ {
573
+ onClick: () => editor.chain().focus().toggleItalic().run(),
574
+ isActive: editor.isActive("italic"),
575
+ icon: "format_italic",
576
+ title: "Italic"
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx6(
580
+ MenuButton,
581
+ {
582
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
583
+ isActive: editor.isActive("underline"),
584
+ icon: "format_underlined",
585
+ title: "Underline"
586
+ }
587
+ ),
588
+ /* @__PURE__ */ jsx6(
589
+ MenuButton,
590
+ {
591
+ onClick: () => editor.chain().focus().toggleStrike().run(),
592
+ isActive: editor.isActive("strike"),
593
+ icon: "format_strikethrough",
594
+ title: "Strikethrough"
595
+ }
596
+ ),
597
+ /* @__PURE__ */ jsx6(
598
+ MenuButton,
599
+ {
600
+ onClick: () => editor.chain().focus().toggleCode().run(),
601
+ isActive: editor.isActive("code"),
602
+ icon: "code",
603
+ title: "Code"
604
+ }
605
+ ),
606
+ /* @__PURE__ */ jsx6(
607
+ MenuButton,
608
+ {
609
+ onClick: () => editor.chain().focus().toggleHighlight().run(),
610
+ isActive: editor.isActive("highlight"),
611
+ icon: "format_ink_highlighter",
612
+ title: "Highlight"
613
+ }
614
+ )
615
+ ] }),
616
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
617
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
618
+ /* @__PURE__ */ jsx6(
619
+ MenuButton,
620
+ {
621
+ onClick: setLink,
622
+ isActive: editor.isActive("link"),
623
+ icon: "link",
624
+ title: "Link"
625
+ }
626
+ ),
627
+ /* @__PURE__ */ jsx6(
628
+ MenuButton,
629
+ {
630
+ onClick: addImage,
631
+ icon: "add_photo_alternate",
632
+ title: "Add Image"
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsx6(
636
+ MenuButton,
637
+ {
638
+ onClick: () => editor.chain().focus().toggleSubscript().run(),
639
+ isActive: editor.isActive("subscript"),
640
+ icon: "subscript",
641
+ title: "Subscript"
642
+ }
643
+ ),
644
+ /* @__PURE__ */ jsx6(
645
+ MenuButton,
646
+ {
647
+ onClick: () => editor.chain().focus().toggleSuperscript().run(),
648
+ isActive: editor.isActive("superscript"),
649
+ icon: "superscript",
650
+ title: "Superscript"
651
+ }
652
+ )
653
+ ] }),
654
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
655
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
656
+ /* @__PURE__ */ jsx6(
657
+ MenuButton,
658
+ {
659
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
660
+ isActive: editor.isActive({ textAlign: "left" }),
661
+ icon: "format_align_left",
662
+ title: "Align Left"
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsx6(
666
+ MenuButton,
667
+ {
668
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
669
+ isActive: editor.isActive({ textAlign: "center" }),
670
+ icon: "format_align_center",
671
+ title: "Align Center"
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx6(
675
+ MenuButton,
676
+ {
677
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
678
+ isActive: editor.isActive({ textAlign: "right" }),
679
+ icon: "format_align_right",
680
+ title: "Align Right"
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsx6(
684
+ MenuButton,
685
+ {
686
+ onClick: () => editor.chain().focus().setTextAlign("justify").run(),
687
+ isActive: editor.isActive({ textAlign: "justify" }),
688
+ icon: "format_align_justify",
689
+ title: "Justify"
690
+ }
691
+ )
692
+ ] }),
693
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
694
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
695
+ /* @__PURE__ */ jsx6(
696
+ MenuButton,
697
+ {
698
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
699
+ isActive: editor.isActive("bulletList"),
700
+ icon: "format_list_bulleted",
701
+ title: "Bullet List"
702
+ }
703
+ ),
704
+ /* @__PURE__ */ jsx6(
705
+ MenuButton,
706
+ {
707
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
708
+ isActive: editor.isActive("orderedList"),
709
+ icon: "format_list_numbered",
710
+ title: "Ordered List"
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsx6(
714
+ MenuButton,
715
+ {
716
+ onClick: () => editor.chain().focus().toggleTaskList().run(),
717
+ isActive: editor.isActive("taskList"),
718
+ icon: "checklist",
719
+ title: "Task List"
720
+ }
721
+ )
722
+ ] }),
723
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-toolbar-separator" }),
724
+ /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-toolbar-group", children: [
725
+ /* @__PURE__ */ jsx6(
726
+ MenuButton,
727
+ {
728
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
729
+ isActive: editor.isActive("blockquote"),
730
+ icon: "format_quote",
731
+ title: "Blockquote"
732
+ }
733
+ ),
734
+ /* @__PURE__ */ jsx6(
735
+ MenuButton,
736
+ {
737
+ onClick: () => editor.chain().focus().toggleCodeBlock().run(),
738
+ isActive: editor.isActive("codeBlock"),
739
+ icon: "terminal",
740
+ title: "Code Block"
741
+ }
742
+ ),
743
+ /* @__PURE__ */ jsx6(
744
+ MenuButton,
745
+ {
746
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
747
+ icon: "horizontal_rule",
748
+ title: "Horizontal Rule"
749
+ }
750
+ )
751
+ ] })
752
+ ] });
753
+ }
754
+ function RichTextEditor({ name, value = "", onChange }) {
755
+ const editor = useEditor({
756
+ extensions: [
757
+ StarterKit.configure({
758
+ horizontalRule: false
759
+ }),
760
+ Link5.configure({
761
+ openOnClick: false,
762
+ HTMLAttributes: {
763
+ class: "adminforge-editor-link"
764
+ }
765
+ }),
766
+ Underline,
767
+ TextAlign.configure({
768
+ types: ["heading", "paragraph", "image"]
769
+ }),
770
+ Placeholder.configure({
771
+ placeholder: "Write something amazing..."
772
+ }),
773
+ Highlight,
774
+ Subscript,
775
+ Superscript,
776
+ TaskList,
777
+ TaskItem.configure({
778
+ nested: true
779
+ }),
780
+ HorizontalRule,
781
+ Typography,
782
+ BubbleMenuExtension,
783
+ Image.extend({
784
+ addAttributes() {
785
+ return {
786
+ ...this.parent?.(),
787
+ width: {
788
+ default: "100%",
789
+ renderHTML: (attributes) => {
790
+ return {
791
+ style: `width: ${attributes.width}; height: auto;`
792
+ };
793
+ }
794
+ },
795
+ textAlign: {
796
+ default: "left",
797
+ parseHTML: (element) => element.style.textAlign || element.getAttribute("data-text-align"),
798
+ renderHTML: (attributes) => {
799
+ return {
800
+ "data-align": attributes.textAlign
801
+ };
802
+ }
803
+ }
804
+ };
805
+ }
806
+ }).configure({
807
+ allowBase64: true,
808
+ HTMLAttributes: {
809
+ class: "adminforge-editor-image"
810
+ }
811
+ })
812
+ ],
813
+ content: value,
814
+ immediatelyRender: false,
815
+ editorProps: {
816
+ handleDrop: (view, event, slice, moved) => {
817
+ if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
818
+ const file = event.dataTransfer.files[0];
819
+ if (file.type.startsWith("image/")) {
820
+ const formData = new FormData();
821
+ formData.append("file", file);
822
+ fetch("/api/upload", { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
823
+ if (data.url) {
824
+ const { schema } = view.state;
825
+ const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
826
+ const node = schema.nodes.image.create({ src: data.url });
827
+ const transaction = view.state.tr.insert(coordinates?.pos ?? 0, node);
828
+ view.dispatch(transaction);
829
+ }
830
+ }).catch(() => alert("Upload failed"));
831
+ return true;
832
+ }
833
+ }
834
+ return false;
835
+ },
836
+ handlePaste: (view, event) => {
837
+ if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
838
+ const file = event.clipboardData.files[0];
839
+ if (file.type.startsWith("image/")) {
840
+ const formData = new FormData();
841
+ formData.append("file", file);
842
+ fetch("/api/upload", { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
843
+ if (data.url) {
844
+ const { schema } = view.state;
845
+ const node = schema.nodes.image.create({ src: data.url });
846
+ const transaction = view.state.tr.replaceSelectionWith(node);
847
+ view.dispatch(transaction);
848
+ }
849
+ }).catch(() => alert("Upload failed"));
850
+ return true;
851
+ }
852
+ }
853
+ return false;
854
+ }
855
+ },
856
+ onUpdate: ({ editor: editor2 }) => {
857
+ onChange(editor2.getHTML());
858
+ }
859
+ });
860
+ useEffect(() => {
861
+ if (editor && value !== editor.getHTML()) {
862
+ editor.commands.setContent(value);
863
+ }
864
+ }, [value, editor]);
865
+ if (!editor) {
866
+ return null;
867
+ }
868
+ return /* @__PURE__ */ jsxs5("div", { className: "adminforge-editor-container", children: [
869
+ /* @__PURE__ */ jsx6(Toolbar, { editor }),
870
+ editor && /* @__PURE__ */ jsx6(Fragment, { children: editor && /* @__PURE__ */ jsx6(
871
+ BubbleMenu,
872
+ {
873
+ editor,
874
+ shouldShow: ({ editor: editor2, state }) => {
875
+ const isImage = editor2.isActive("image") || state.selection.content().content.firstChild?.type.name === "image";
876
+ return isImage || !state.selection.empty;
877
+ },
878
+ options: { duration: 100, zIndex: 9999 },
879
+ children: /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-bubble-menu", style: { zIndex: 1e4 }, children: editor.isActive("image") ? /* @__PURE__ */ jsxs5(Fragment, { children: [
880
+ /* @__PURE__ */ jsx6(
881
+ "button",
882
+ {
883
+ type: "button",
884
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
885
+ className: editor.isActive({ textAlign: "left" }) ? "active" : "",
886
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_align_left" })
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsx6(
890
+ "button",
891
+ {
892
+ type: "button",
893
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
894
+ className: editor.isActive({ textAlign: "center" }) ? "active" : "",
895
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_align_center" })
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsx6(
899
+ "button",
900
+ {
901
+ type: "button",
902
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
903
+ className: editor.isActive({ textAlign: "right" }) ? "active" : "",
904
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_align_right" })
905
+ }
906
+ ),
907
+ /* @__PURE__ */ jsx6("div", { style: { width: "1px", background: "rgba(255,255,255,0.2)", margin: "4px 2px" } }),
908
+ /* @__PURE__ */ jsx6(
909
+ "button",
910
+ {
911
+ type: "button",
912
+ onClick: () => editor.chain().focus().updateAttributes("image", { width: "calc(25% - 1rem)" }).run(),
913
+ style: { fontSize: "11px", fontWeight: "bold" },
914
+ children: "25%"
915
+ }
916
+ ),
917
+ /* @__PURE__ */ jsx6(
918
+ "button",
919
+ {
920
+ type: "button",
921
+ onClick: () => editor.chain().focus().updateAttributes("image", { width: "calc(50% - 1rem)" }).run(),
922
+ style: { fontSize: "11px", fontWeight: "bold" },
923
+ children: "50%"
924
+ }
925
+ ),
926
+ /* @__PURE__ */ jsx6(
927
+ "button",
928
+ {
929
+ type: "button",
930
+ onClick: () => editor.chain().focus().updateAttributes("image", { width: "100%" }).run(),
931
+ style: { fontSize: "11px", fontWeight: "bold" },
932
+ children: "100%"
933
+ }
934
+ ),
935
+ /* @__PURE__ */ jsx6("div", { style: { width: "1px", background: "rgba(255,255,255,0.2)", margin: "4px 2px" } }),
936
+ /* @__PURE__ */ jsx6(
937
+ "button",
938
+ {
939
+ type: "button",
940
+ onClick: () => editor.chain().focus().deleteSelection().run(),
941
+ style: { color: "#f87171" },
942
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "delete" })
943
+ }
944
+ )
945
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
946
+ /* @__PURE__ */ jsx6(
947
+ "button",
948
+ {
949
+ type: "button",
950
+ onClick: () => editor.chain().focus().toggleBold().run(),
951
+ className: editor.isActive("bold") ? "active" : "",
952
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_bold" })
953
+ }
954
+ ),
955
+ /* @__PURE__ */ jsx6(
956
+ "button",
957
+ {
958
+ type: "button",
959
+ onClick: () => editor.chain().focus().toggleItalic().run(),
960
+ className: editor.isActive("italic") ? "active" : "",
961
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_italic" })
962
+ }
963
+ ),
964
+ /* @__PURE__ */ jsx6(
965
+ "button",
966
+ {
967
+ type: "button",
968
+ onClick: () => editor.chain().focus().toggleStrike().run(),
969
+ className: editor.isActive("strike") ? "active" : "",
970
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_strikethrough" })
971
+ }
972
+ ),
973
+ /* @__PURE__ */ jsx6(
974
+ "button",
975
+ {
976
+ type: "button",
977
+ onClick: () => editor.chain().focus().toggleHighlight().run(),
978
+ className: editor.isActive("highlight") ? "active" : "",
979
+ children: /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined", children: "format_ink_highlighter" })
980
+ }
981
+ )
982
+ ] }) })
983
+ }
984
+ ) }),
985
+ /* @__PURE__ */ jsx6("div", { className: "adminforge-editor-content", children: /* @__PURE__ */ jsx6(EditorContent, { editor }) }),
986
+ /* @__PURE__ */ jsx6("style", { dangerouslySetInnerHTML: { __html: `
987
+ .adminforge-editor-container {
988
+ border: 1px solid #e5e7eb;
989
+ border-radius: 8px;
990
+ overflow: hidden;
991
+ background: white;
992
+ display: flex;
993
+ flex-direction: column;
994
+ min-height: 400px;
995
+ }
996
+
997
+ .adminforge-editor-toolbar {
998
+ display: flex;
999
+ flex-wrap: wrap;
1000
+ padding: 8px;
1001
+ background: #f9fafb;
1002
+ border-bottom: 1px solid #e5e7eb;
1003
+ gap: 4px;
1004
+ }
1005
+
1006
+ .adminforge-editor-toolbar-group {
1007
+ display: flex;
1008
+ gap: 2px;
1009
+ }
1010
+
1011
+ .adminforge-editor-toolbar-separator {
1012
+ width: 1px;
1013
+ background: #e5e7eb;
1014
+ margin: 4px 2px;
1015
+ }
1016
+
1017
+ .adminforge-editor-btn {
1018
+ width: 32px;
1019
+ height: 32px;
1020
+ display: flex;
1021
+ align-items: center;
1022
+ justify-content: center;
1023
+ border-radius: 4px;
1024
+ border: none;
1025
+ background: transparent;
1026
+ color: #4b5563;
1027
+ cursor: pointer;
1028
+ transition: all 0.2s;
1029
+ }
1030
+
1031
+ .adminforge-editor-btn:hover {
1032
+ background: #f3f4f6;
1033
+ color: #111827;
1034
+ }
1035
+
1036
+ .adminforge-editor-btn:disabled {
1037
+ opacity: 0.5;
1038
+ cursor: not-allowed;
1039
+ }
1040
+
1041
+ .adminforge-editor-btn.active {
1042
+ background: #e5e7eb;
1043
+ color: #2563eb;
1044
+ }
1045
+
1046
+ .adminforge-editor-content {
1047
+ padding: 16px;
1048
+ flex: 1;
1049
+ overflow-y: auto;
1050
+ }
1051
+
1052
+ .ProseMirror {
1053
+ min-height: 300px;
1054
+ outline: none;
1055
+ }
1056
+
1057
+ .ProseMirror p.is-editor-empty:first-child::before {
1058
+ content: attr(data-placeholder);
1059
+ float: left;
1060
+ color: #9ca3af;
1061
+ pointer-events: none;
1062
+ height: 0;
1063
+ }
1064
+
1065
+ .adminforge-editor-link {
1066
+ color: #2563eb;
1067
+ text-decoration: underline;
1068
+ cursor: pointer;
1069
+ }
1070
+
1071
+ .adminforge-editor-bubble-menu {
1072
+ display: flex;
1073
+ background: #1f2937;
1074
+ padding: 4px;
1075
+ border-radius: 6px;
1076
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
1077
+ gap: 2px;
1078
+ }
1079
+
1080
+ .adminforge-editor-bubble-menu button {
1081
+ width: 28px;
1082
+ height: 28px;
1083
+ display: flex;
1084
+ align-items: center;
1085
+ justify-content: center;
1086
+ border: none;
1087
+ background: transparent;
1088
+ color: white;
1089
+ border-radius: 4px;
1090
+ cursor: pointer;
1091
+ }
1092
+
1093
+ .adminforge-editor-bubble-menu button:hover {
1094
+ background: #374151;
1095
+ }
1096
+
1097
+ .adminforge-editor-bubble-menu button.active {
1098
+ color: #60a5fa;
1099
+ background: #374151;
1100
+ }
1101
+
1102
+ .adminforge-editor-bubble-menu .material-symbols-outlined {
1103
+ font-size: 16px;
1104
+ }
1105
+
1106
+ /* Task list styles */
1107
+ ul[data-type="taskList"] {
1108
+ list-style: none;
1109
+ padding: 0;
1110
+ }
1111
+
1112
+ ul[data-type="taskList"] li {
1113
+ display: flex;
1114
+ align-items: flex-start;
1115
+ margin-bottom: 0.5rem;
1116
+ }
1117
+
1118
+ ul[data-type="taskList"] li > label {
1119
+ margin-right: 0.5rem;
1120
+ user-select: none;
1121
+ }
1122
+
1123
+ ul[data-type="taskList"] li > div {
1124
+ flex: 1;
1125
+ }
1126
+
1127
+ .adminforge-editor-image {
1128
+ max-width: 100% !important;
1129
+ height: auto !important;
1130
+ border-radius: 8px;
1131
+ margin: 0.5rem;
1132
+ display: inline-block;
1133
+ vertical-align: middle;
1134
+ transition: all 0.2s;
1135
+ }
1136
+
1137
+ /* Float logic for text wrapping and alignment */
1138
+ .adminforge-editor-image[data-align="left"] {
1139
+ float: left;
1140
+ margin-left: 0;
1141
+ margin-right: 1.5rem;
1142
+ display: block; /* block + float is standard */
1143
+ }
1144
+
1145
+ .adminforge-editor-image[data-align="right"] {
1146
+ float: right;
1147
+ margin-right: 0;
1148
+ margin-left: 1.5rem;
1149
+ display: block;
1150
+ }
1151
+
1152
+ .adminforge-editor-image[data-align="center"] {
1153
+ display: block;
1154
+ margin-left: auto;
1155
+ margin-right: auto;
1156
+ float: none;
1157
+ }
1158
+
1159
+ /* Clearfix for the parent paragraph to contain floats */
1160
+ .ProseMirror p::after {
1161
+ content: "";
1162
+ display: table;
1163
+ clear: both;
1164
+ }
1165
+
1166
+ .adminforge-editor-image.ProseMirror-selectednode {
1167
+ outline: 3px solid #2563eb;
1168
+ }
1169
+ ` } })
1170
+ ] });
1171
+ }
1172
+
1173
+ // src/ui/form-engine/ImageUpload.tsx
1174
+ import { useState as useState3, useRef } from "react";
1175
+
1176
+ // src/ui/AdminForgeContext.tsx
1177
+ import React, { createContext as createContext2, useContext as useContext2 } from "react";
1178
+ import { jsx as jsx7 } from "react/jsx-runtime";
1179
+ var AdminForgeContext = createContext2({
1180
+ config: void 0,
1181
+ apiBase: "/api/adminforge",
1182
+ unauthorized: false
1183
+ });
1184
+ function AdminForgeProvider({
1185
+ children,
1186
+ config: initialConfig,
1187
+ apiBase = "/api"
1188
+ }) {
1189
+ const [config, setConfig] = React.useState(initialConfig);
1190
+ const [unauthorized, setUnauthorized] = React.useState(false);
1191
+ React.useEffect(() => {
1192
+ if (typeof document !== "undefined" && !document.getElementById("adminforge-fonts")) {
1193
+ const link = document.createElement("link");
1194
+ link.id = "adminforge-fonts";
1195
+ link.rel = "stylesheet";
1196
+ link.href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200";
1197
+ document.head.appendChild(link);
1198
+ }
1199
+ if (!config) {
1200
+ fetch(`${apiBase}/_config`).then((res) => {
1201
+ if (res.status === 401) {
1202
+ setUnauthorized(true);
1203
+ return null;
1204
+ }
1205
+ return res.ok ? res.json() : null;
1206
+ }).then((cfg) => cfg?.collections ? setConfig(cfg) : null).catch((e) => console.error("[AdminForge] Failed to fetch config:", e));
1207
+ }
1208
+ }, [config, apiBase]);
1209
+ return /* @__PURE__ */ jsx7(AdminForgeContext.Provider, { value: { config, apiBase, unauthorized }, children });
1210
+ }
1211
+ function useAdminForge() {
1212
+ return useContext2(AdminForgeContext);
1213
+ }
1214
+
1215
+ // src/ui/form-engine/ImageUpload.tsx
1216
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1217
+ function ImageUpload({ name, value, onChange }) {
1218
+ const { apiBase } = useAdminForge();
1219
+ const [uploading, setUploading] = useState3(false);
1220
+ const [preview, setPreview] = useState3(value ?? "");
1221
+ const inputRef = useRef(null);
1222
+ async function handleFile(e) {
1223
+ const file = e.target.files?.[0];
1224
+ if (!file) return;
1225
+ setUploading(true);
1226
+ const formData = new FormData();
1227
+ formData.append("file", file);
1228
+ try {
1229
+ const res = await fetch(`${apiBase}/_media`, {
1230
+ method: "POST",
1231
+ body: formData
1232
+ });
1233
+ if (!res.ok) throw new Error("Upload failed");
1234
+ const data = await res.json();
1235
+ onChange(data.url);
1236
+ setPreview(data.url);
1237
+ } catch {
1238
+ alert("Upload failed");
1239
+ } finally {
1240
+ setUploading(false);
1241
+ }
1242
+ }
1243
+ return /* @__PURE__ */ jsxs6("div", { className: "adminforge-field", children: [
1244
+ /* @__PURE__ */ jsxs6("label", { children: [
1245
+ name.charAt(0).toUpperCase() + name.slice(1),
1246
+ " Image"
1247
+ ] }),
1248
+ /* @__PURE__ */ jsx8(
1249
+ "input",
1250
+ {
1251
+ ref: inputRef,
1252
+ type: "file",
1253
+ accept: "image/*",
1254
+ onChange: handleFile,
1255
+ style: { display: "none" }
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsxs6("div", { className: "adminforge-image-upload", children: [
1259
+ preview && /* @__PURE__ */ jsx8("img", { src: preview, alt: "Preview", className: "adminforge-image-preview" }),
1260
+ /* @__PURE__ */ jsx8(
1261
+ "button",
1262
+ {
1263
+ type: "button",
1264
+ className: "adminforge-btn adminforge-btn-secondary",
1265
+ onClick: () => inputRef.current?.click(),
1266
+ disabled: uploading,
1267
+ children: uploading ? "Uploading..." : preview ? "Replace Image" : "Choose Image"
1268
+ }
1269
+ )
1270
+ ] }),
1271
+ /* @__PURE__ */ jsx8("input", { type: "hidden", name, value: preview })
1272
+ ] });
1273
+ }
1274
+
1275
+ // src/ui/form-engine/RelationInput.tsx
1276
+ import { useEffect as useEffect2, useState as useState4, useRef as useRef2 } from "react";
1277
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1278
+ function RelationInput({ name, to, relationType, value, onChange, error, disabled }) {
1279
+ const [options, setOptions] = useState4([]);
1280
+ const [loading, setLoading] = useState4(true);
1281
+ const [search, setSearch] = useState4("");
1282
+ const [open, setOpen] = useState4(false);
1283
+ const containerRef = useRef2(null);
1284
+ useEffect2(() => {
1285
+ function handleClickOutside(event) {
1286
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
1287
+ setOpen(false);
1288
+ }
1289
+ }
1290
+ document.addEventListener("mousedown", handleClickOutside);
1291
+ return () => {
1292
+ document.removeEventListener("mousedown", handleClickOutside);
1293
+ };
1294
+ }, []);
1295
+ const isMulti = relationType === "many-to-many" || relationType === "one-to-many";
1296
+ const rawValueArray = Array.isArray(value) ? value : value ? [value] : [];
1297
+ const selectedIds = rawValueArray.map((v) => typeof v === "object" && v !== null ? v.id : v).filter(Boolean);
1298
+ useEffect2(() => {
1299
+ let mounted = true;
1300
+ async function fetchOptions() {
1301
+ try {
1302
+ setLoading(true);
1303
+ const res = await fetch(`/api/${to}?pageSize=100`);
1304
+ const json = await res.json();
1305
+ if (mounted && json.data) {
1306
+ const uniqueOptions = [];
1307
+ const seen = /* @__PURE__ */ new Set();
1308
+ const getLbl = (opt) => opt.name || opt.title || opt.label || opt.id;
1309
+ for (const opt of json.data) {
1310
+ const lbl = getLbl(opt);
1311
+ if (!seen.has(lbl)) {
1312
+ seen.add(lbl);
1313
+ uniqueOptions.push(opt);
1314
+ }
1315
+ }
1316
+ setOptions(uniqueOptions);
1317
+ }
1318
+ } catch (err) {
1319
+ console.error("Failed to fetch relations", err);
1320
+ } finally {
1321
+ if (mounted) setLoading(false);
1322
+ }
1323
+ }
1324
+ fetchOptions();
1325
+ return () => {
1326
+ mounted = false;
1327
+ };
1328
+ }, [to]);
1329
+ const getLabel = (opt) => opt.name || opt.title || opt.label || opt.id;
1330
+ const filteredOptions = options.filter(
1331
+ (o) => getLabel(o).toLowerCase().includes(search.toLowerCase())
1332
+ );
1333
+ const toggleOption = (id) => {
1334
+ if (isMulti) {
1335
+ const newIds = selectedIds.includes(id) ? selectedIds.filter((v) => v !== id) : [...selectedIds, id];
1336
+ onChange?.(newIds);
1337
+ } else {
1338
+ onChange?.(id);
1339
+ setOpen(false);
1340
+ setSearch("");
1341
+ }
1342
+ };
1343
+ const removeOption = (e, id) => {
1344
+ e.stopPropagation();
1345
+ if (disabled) return;
1346
+ onChange?.(selectedIds.filter((v) => v !== id));
1347
+ };
1348
+ return /* @__PURE__ */ jsxs7("div", { className: "adminforge-relation", ref: containerRef, style: { position: "relative" }, children: [
1349
+ selectedIds.length === 0 && /* @__PURE__ */ jsx9("input", { type: "hidden", name, value: "" }),
1350
+ selectedIds.map((id) => /* @__PURE__ */ jsx9("input", { type: "hidden", name, value: id }, id)),
1351
+ /* @__PURE__ */ jsx9(
1352
+ "div",
1353
+ {
1354
+ className: `adminforge-input ${error ? "adminforge-input-error" : ""}`,
1355
+ style: {
1356
+ position: "relative",
1357
+ cursor: disabled ? "not-allowed" : "pointer",
1358
+ minHeight: "38px",
1359
+ display: "flex",
1360
+ alignItems: "center",
1361
+ flexWrap: "wrap",
1362
+ gap: "4px",
1363
+ padding: "4px 8px",
1364
+ opacity: disabled ? 0.6 : 1
1365
+ },
1366
+ onClick: () => !disabled && setOpen(!open),
1367
+ children: selectedIds.length > 0 ? selectedIds.map((id) => {
1368
+ const opt = options.find((o) => o.id === id);
1369
+ const label = opt ? getLabel(opt) : id;
1370
+ if (isMulti) {
1371
+ return /* @__PURE__ */ jsxs7("span", { style: {
1372
+ background: "#e0e0e0",
1373
+ padding: "2px 6px",
1374
+ borderRadius: "4px",
1375
+ fontSize: "14px",
1376
+ display: "flex",
1377
+ alignItems: "center",
1378
+ gap: "4px"
1379
+ }, children: [
1380
+ label,
1381
+ /* @__PURE__ */ jsx9(
1382
+ "button",
1383
+ {
1384
+ type: "button",
1385
+ onClick: (e) => removeOption(e, id),
1386
+ style: { background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontWeight: "bold" },
1387
+ children: "\xD7"
1388
+ }
1389
+ )
1390
+ ] }, id);
1391
+ }
1392
+ return /* @__PURE__ */ jsx9("span", { children: label }, id);
1393
+ }) : /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "Select..." })
1394
+ }
1395
+ ),
1396
+ open && /* @__PURE__ */ jsxs7(
1397
+ "div",
1398
+ {
1399
+ style: {
1400
+ position: "absolute",
1401
+ zIndex: 10,
1402
+ background: "white",
1403
+ border: "1px solid #ccc",
1404
+ borderRadius: "4px",
1405
+ marginTop: "4px",
1406
+ width: "100%",
1407
+ maxHeight: "250px",
1408
+ overflowY: "auto",
1409
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)"
1410
+ },
1411
+ children: [
1412
+ /* @__PURE__ */ jsx9("div", { style: { padding: "8px", borderBottom: "1px solid #eee" }, children: /* @__PURE__ */ jsx9(
1413
+ "input",
1414
+ {
1415
+ type: "text",
1416
+ placeholder: "Search...",
1417
+ value: search,
1418
+ onChange: (e) => setSearch(e.target.value),
1419
+ onClick: (e) => e.stopPropagation(),
1420
+ style: {
1421
+ width: "100%",
1422
+ padding: "4px 8px",
1423
+ border: "1px solid #ccc",
1424
+ borderRadius: "4px"
1425
+ }
1426
+ }
1427
+ ) }),
1428
+ loading ? /* @__PURE__ */ jsx9("div", { style: { padding: "8px", color: "#888" }, children: "Loading..." }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsx9("div", { style: { padding: "8px", color: "#888" }, children: "No options found" }) : /* @__PURE__ */ jsx9("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: filteredOptions.map((opt) => {
1429
+ const isSelected = selectedIds.includes(opt.id);
1430
+ return /* @__PURE__ */ jsxs7(
1431
+ "li",
1432
+ {
1433
+ style: {
1434
+ padding: "8px 12px",
1435
+ cursor: "pointer",
1436
+ background: isSelected ? "#f0f0f0" : "transparent",
1437
+ display: "flex",
1438
+ justifyContent: "space-between"
1439
+ },
1440
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f9f9f9",
1441
+ onMouseLeave: (e) => e.currentTarget.style.background = isSelected ? "#f0f0f0" : "transparent",
1442
+ onClick: (e) => {
1443
+ e.stopPropagation();
1444
+ toggleOption(opt.id);
1445
+ },
1446
+ children: [
1447
+ getLabel(opt),
1448
+ isSelected && isMulti && /* @__PURE__ */ jsx9("span", { style: { color: "#417690" }, children: "\u2713" })
1449
+ ]
1450
+ },
1451
+ opt.id
1452
+ );
1453
+ }) })
1454
+ ]
1455
+ }
1456
+ )
1457
+ ] });
1458
+ }
1459
+
1460
+ // src/ui/form-engine/FormEngine.tsx
1461
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1462
+ function hasAccess3(access, operation, role) {
1463
+ if (!access) return true;
1464
+ const allowed = access[operation];
1465
+ if (!allowed || !Array.isArray(allowed)) return true;
1466
+ if (!role) return false;
1467
+ return allowed.includes(role);
1468
+ }
1469
+ function FieldRenderer({
1470
+ name,
1471
+ field,
1472
+ value,
1473
+ onChange,
1474
+ onRelationChange,
1475
+ error,
1476
+ role,
1477
+ checked,
1478
+ onCheckedChange
1479
+ }) {
1480
+ const { component, props } = field.ui;
1481
+ if (props?.hidden) return null;
1482
+ if (!hasAccess3(field.access, "read", role)) return null;
1483
+ const errorClass = error ? "adminforge-input-error" : "";
1484
+ const resolvedValue = value !== void 0 ? value : field.db?.default;
1485
+ const isReadOnly = Boolean(props?.readOnly) || !hasAccess3(field.access, "update", role);
1486
+ switch (component) {
1487
+ case "text":
1488
+ case "slug":
1489
+ case "date":
1490
+ return /* @__PURE__ */ jsxs8("div", { className: "adminforge-field", children: [
1491
+ /* @__PURE__ */ jsx10("label", { htmlFor: name, children: props?.label ?? name }),
1492
+ /* @__PURE__ */ jsx10(
1493
+ "input",
1494
+ {
1495
+ id: name,
1496
+ name,
1497
+ type: component === "date" ? "datetime-local" : "text",
1498
+ className: `adminforge-input ${errorClass}`,
1499
+ required: !field.db?.nullable,
1500
+ defaultValue: typeof resolvedValue === "string" ? resolvedValue : "",
1501
+ readOnly: isReadOnly
1502
+ }
1503
+ ),
1504
+ error && /* @__PURE__ */ jsx10("span", { className: "adminforge-field-err", children: error })
1505
+ ] });
1506
+ case "boolean":
1507
+ return /* @__PURE__ */ jsxs8("div", { className: "adminforge-field adminforge-field-checkbox", children: [
1508
+ /* @__PURE__ */ jsxs8("label", { htmlFor: name, children: [
1509
+ /* @__PURE__ */ jsx10(
1510
+ "input",
1511
+ {
1512
+ id: name,
1513
+ name,
1514
+ type: "checkbox",
1515
+ checked: !!checked,
1516
+ onChange: (e) => onCheckedChange?.(e.target.checked),
1517
+ disabled: isReadOnly
1518
+ }
1519
+ ),
1520
+ props?.label ?? name
1521
+ ] }),
1522
+ error && /* @__PURE__ */ jsx10("span", { className: "adminforge-field-err", children: error })
1523
+ ] });
1524
+ case "relation":
1525
+ return /* @__PURE__ */ jsxs8("div", { className: "adminforge-field", children: [
1526
+ /* @__PURE__ */ jsx10("label", { children: props?.label ?? name }),
1527
+ /* @__PURE__ */ jsx10(
1528
+ RelationInput,
1529
+ {
1530
+ name,
1531
+ to: props?.to,
1532
+ relationType: props?.relationType,
1533
+ value: resolvedValue,
1534
+ onChange: onRelationChange,
1535
+ error,
1536
+ disabled: isReadOnly
1537
+ }
1538
+ ),
1539
+ error && /* @__PURE__ */ jsx10("span", { className: "adminforge-field-err", children: error })
1540
+ ] });
1541
+ case "richText":
1542
+ return /* @__PURE__ */ jsxs8("div", { className: "adminforge-field", children: [
1543
+ /* @__PURE__ */ jsx10("label", { children: props?.label ?? name }),
1544
+ /* @__PURE__ */ jsx10(
1545
+ RichTextEditor,
1546
+ {
1547
+ name,
1548
+ value: typeof resolvedValue === "string" ? resolvedValue : "",
1549
+ onChange: (val) => !isReadOnly && onChange?.(val)
1550
+ }
1551
+ ),
1552
+ /* @__PURE__ */ jsx10("input", { type: "hidden", name, id: `${name}-hidden`, defaultValue: typeof resolvedValue === "string" ? resolvedValue : "" }),
1553
+ error && /* @__PURE__ */ jsx10("span", { className: "adminforge-field-err", children: error })
1554
+ ] });
1555
+ case "image":
1556
+ return /* @__PURE__ */ jsx10(
1557
+ ImageUpload,
1558
+ {
1559
+ name,
1560
+ value: typeof resolvedValue === "string" ? resolvedValue : "",
1561
+ onChange: (val) => !isReadOnly && onChange?.(val)
1562
+ }
1563
+ );
1564
+ default:
1565
+ return /* @__PURE__ */ jsxs8("div", { className: "adminforge-field", children: [
1566
+ /* @__PURE__ */ jsx10("label", { htmlFor: name, children: props?.label ?? name }),
1567
+ /* @__PURE__ */ jsx10(
1568
+ "input",
1569
+ {
1570
+ id: name,
1571
+ name,
1572
+ type: "text",
1573
+ className: `adminforge-input ${errorClass}`,
1574
+ defaultValue: typeof resolvedValue === "string" ? resolvedValue : "",
1575
+ readOnly: isReadOnly
1576
+ }
1577
+ ),
1578
+ error && /* @__PURE__ */ jsx10("span", { className: "adminforge-field-err", children: error })
1579
+ ] });
1580
+ }
1581
+ }
1582
+ function FormEngine({ collection, record, isNew, role }) {
1583
+ const formRef = useRef3(null);
1584
+ const [richTextValues, setRichTextValues] = useState5({});
1585
+ const [relationValues, setRelationValues] = useState5({});
1586
+ const [booleanValues, setBooleanValues] = useState5({});
1587
+ const [fieldErrors, setFieldErrors] = useState5({});
1588
+ const [submitError, setSubmitError] = useState5(null);
1589
+ const canSave = isNew ? hasAccess3(collection.access, "create", role) : hasAccess3(collection.access, "update", role);
1590
+ useEffect3(() => {
1591
+ if (record && !isNew) {
1592
+ const rt = {};
1593
+ const rv = {};
1594
+ const bv = {};
1595
+ for (const [key, value] of Object.entries(record)) {
1596
+ const field = collection.fields[key];
1597
+ if (field?.type === "richText" && typeof value === "string") rt[key] = value;
1598
+ if (field?.type === "relation") {
1599
+ if (Array.isArray(value)) rv[key] = value.map((v) => typeof v === "string" ? v : v.id);
1600
+ else if (typeof value === "string") rv[key] = value;
1601
+ else if (value && typeof value === "object" && value.id) rv[key] = value.id;
1602
+ }
1603
+ if (field?.type === "boolean") bv[key] = !!value;
1604
+ }
1605
+ setRichTextValues(rt);
1606
+ setRelationValues(rv);
1607
+ setBooleanValues(bv);
1608
+ }
1609
+ }, [record, isNew, collection.fields]);
1610
+ useEffect3(() => {
1611
+ if (!isNew) return;
1612
+ const form = formRef.current;
1613
+ if (!form) return;
1614
+ const listeners = [];
1615
+ Object.entries(collection.fields).forEach(([name, field]) => {
1616
+ if (field.type === "slug" && field.ui.props?.from) {
1617
+ const src = form.querySelector(`[name="${field.ui.props.from}"]`);
1618
+ const dst = form.querySelector(`[name="${name}"]`);
1619
+ if (src && dst) {
1620
+ const fn = (e) => {
1621
+ const val = e.target.value;
1622
+ dst.value = val.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1623
+ };
1624
+ src.addEventListener("input", fn);
1625
+ listeners.push({ input: src, fn });
1626
+ }
1627
+ }
1628
+ });
1629
+ return () => listeners.forEach((l) => l.input.removeEventListener("input", l.fn));
1630
+ }, [isNew, collection.fields]);
1631
+ const handleSubmit = useCallback3(async (e) => {
1632
+ e.preventDefault();
1633
+ if (!canSave) return;
1634
+ setFieldErrors({});
1635
+ setSubmitError(null);
1636
+ const form = e.currentTarget;
1637
+ const formData = new FormData(form);
1638
+ const data = {};
1639
+ for (const [key, value] of formData.entries()) {
1640
+ if (value instanceof File) continue;
1641
+ const field = collection.fields[key];
1642
+ if (!hasAccess3(field?.access, isNew ? "create" : "update", role)) continue;
1643
+ if (richTextValues[key]) {
1644
+ data[key] = richTextValues[key];
1645
+ } else if (field?.type === "boolean") {
1646
+ continue;
1647
+ } else if (field?.type === "relation") {
1648
+ continue;
1649
+ } else {
1650
+ data[key] = value.toString();
1651
+ }
1652
+ }
1653
+ for (const [name, field] of Object.entries(collection.fields)) {
1654
+ if (field.type === "boolean" && hasAccess3(field.access, isNew ? "create" : "update", role)) {
1655
+ data[name] = booleanValues[name] ?? false;
1656
+ }
1657
+ }
1658
+ for (const [key, val] of Object.entries(relationValues)) {
1659
+ if (collection.fields[key] && hasAccess3(collection.fields[key]?.access, isNew ? "create" : "update", role)) {
1660
+ data[key] = val;
1661
+ }
1662
+ }
1663
+ const url = isNew ? `/api/${collection.name}` : `/api/${collection.name}/${record?.id}`;
1664
+ const method = isNew ? "POST" : "PATCH";
1665
+ const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) });
1666
+ if (res.ok) {
1667
+ window.location.href = `/admin/${collection.name}`;
1668
+ } else {
1669
+ const err = await res.json().catch(() => ({ error: "Request failed" }));
1670
+ if (err.fields) {
1671
+ const errors = {};
1672
+ for (const f of err.fields) errors[f.path] = f.message;
1673
+ setFieldErrors(errors);
1674
+ }
1675
+ setSubmitError(err.error ?? "An error occurred");
1676
+ }
1677
+ }, [collection, isNew, record?.id, richTextValues, relationValues, booleanValues, role, canSave]);
1678
+ const handleRichTextChange = useCallback3((name, value) => {
1679
+ setRichTextValues((prev) => {
1680
+ const next = { ...prev, [name]: value };
1681
+ const hidden = formRef.current?.querySelector(`#${name}-hidden`);
1682
+ if (hidden) hidden.value = value;
1683
+ return next;
1684
+ });
1685
+ }, []);
1686
+ return /* @__PURE__ */ jsxs8("form", { ref: formRef, onSubmit: handleSubmit, className: "adminforge-form", children: [
1687
+ submitError && /* @__PURE__ */ jsx10("div", { className: "adminforge-form-error", children: submitError }),
1688
+ !isNew && !!record?.id && /* @__PURE__ */ jsxs8("div", { className: "adminforge-field", children: [
1689
+ /* @__PURE__ */ jsx10("label", { children: "Internal ID" }),
1690
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
1691
+ /* @__PURE__ */ jsx10(
1692
+ "input",
1693
+ {
1694
+ type: "text",
1695
+ className: "adminforge-input",
1696
+ value: String(record.id),
1697
+ readOnly: true,
1698
+ style: { background: "#f8fafc", color: "#64748b", fontFamily: "monospace" }
1699
+ }
1700
+ ),
1701
+ /* @__PURE__ */ jsx10(
1702
+ "button",
1703
+ {
1704
+ type: "button",
1705
+ className: "adminforge-btn-icon",
1706
+ title: "Copy ID",
1707
+ onClick: () => navigator.clipboard.writeText(String(record.id)),
1708
+ children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined", children: "content_copy" })
1709
+ }
1710
+ )
1711
+ ] })
1712
+ ] }),
1713
+ Object.entries(collection.fields).map(([name, field]) => {
1714
+ const fv = field.type === "relation" && relationValues[name] !== void 0 ? relationValues[name] : record?.[name];
1715
+ return /* @__PURE__ */ jsx10(
1716
+ FieldRenderer,
1717
+ {
1718
+ name,
1719
+ field,
1720
+ value: fv,
1721
+ onChange: (val) => handleRichTextChange(name, val),
1722
+ onRelationChange: (val) => setRelationValues((prev) => ({ ...prev, [name]: val })),
1723
+ checked: field.type === "boolean" ? booleanValues[name] ?? false : void 0,
1724
+ onCheckedChange: field.type === "boolean" ? (checked) => setBooleanValues((prev) => ({ ...prev, [name]: checked })) : void 0,
1725
+ error: fieldErrors[name],
1726
+ role
1727
+ },
1728
+ name
1729
+ );
1730
+ }),
1731
+ canSave && /* @__PURE__ */ jsx10("div", { className: "adminforge-form-actions", children: /* @__PURE__ */ jsx10("button", { type: "submit", className: "adminforge-btn adminforge-btn-primary", children: isNew ? "Create" : "Save" }) })
1732
+ ] });
1733
+ }
1734
+
1735
+ // src/ui/screens/CollectionFormPage.tsx
1736
+ import Link6 from "next/link";
1737
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1738
+ function formatDate(date) {
1739
+ if (isNaN(date.getTime())) return "Invalid date";
1740
+ return date.toLocaleString("en-US", {
1741
+ month: "short",
1742
+ day: "numeric",
1743
+ year: "numeric",
1744
+ hour: "numeric",
1745
+ minute: "2-digit",
1746
+ hour12: true
1747
+ });
1748
+ }
1749
+ function CollectionFormPage({ config, collection, record, isNew, role: propRole }) {
1750
+ const session = useAdminSession();
1751
+ const role = propRole || session?.role || session?.user?.role;
1752
+ return /* @__PURE__ */ jsx11(AdminLayout, { config, currentPath: `/admin/${collection.name}`, role, children: /* @__PURE__ */ jsxs9("div", { className: "adminforge-collection-page", children: [
1753
+ /* @__PURE__ */ jsxs9("div", { className: "adminforge-page-header", children: [
1754
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
1755
+ /* @__PURE__ */ jsx11("h2", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: isNew ? `Create ${collection.label}` : `Edit ${collection.label}` }),
1756
+ !isNew && /* @__PURE__ */ jsxs9("span", { className: "adminforge-badge", style: { background: "#f8fafc", color: "#64748b", borderColor: "#e2e8f0", fontSize: "11px" }, children: [
1757
+ "ID: ",
1758
+ String(record?.id).substring(0, 8),
1759
+ "..."
1760
+ ] })
1761
+ ] }),
1762
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: "12px" }, children: [
1763
+ !isNew && /* @__PURE__ */ jsxs9(
1764
+ "button",
1765
+ {
1766
+ className: "adminforge-btn adminforge-btn-danger",
1767
+ onClick: async () => {
1768
+ if (confirm("Are you sure you want to delete this item?")) {
1769
+ const res = await fetch(`/api/${collection.name}/${record?.id}`, { method: "DELETE" });
1770
+ if (res.ok) {
1771
+ window.location.href = `/admin/${collection.name}`;
1772
+ } else {
1773
+ const err = await res.json();
1774
+ alert(err.error || "Failed to delete item");
1775
+ }
1776
+ }
1777
+ },
1778
+ children: [
1779
+ /* @__PURE__ */ jsx11("span", { className: "material-symbols-outlined", style: { fontSize: "20px", marginRight: "8px" }, children: "delete" }),
1780
+ "Delete"
1781
+ ]
1782
+ }
1783
+ ),
1784
+ /* @__PURE__ */ jsxs9(Link6, { href: `/admin/${collection.name}`, className: "adminforge-btn adminforge-btn-secondary", children: [
1785
+ /* @__PURE__ */ jsx11("span", { className: "material-symbols-outlined", style: { fontSize: "20px", marginRight: "8px" }, children: "arrow_back" }),
1786
+ "Back"
1787
+ ] })
1788
+ ] })
1789
+ ] }),
1790
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [
1791
+ /* @__PURE__ */ jsx11(FormEngine, { collection, record, isNew, role }),
1792
+ !isNew && record && /* @__PURE__ */ jsxs9("div", { style: {
1793
+ padding: "24px",
1794
+ background: "var(--color-surface)",
1795
+ border: "1px solid var(--color-border)",
1796
+ borderRadius: "var(--radius-lg)",
1797
+ boxShadow: "var(--shadow-sm)"
1798
+ }, children: [
1799
+ /* @__PURE__ */ jsxs9("h3", { style: {
1800
+ fontSize: "14px",
1801
+ fontWeight: 600,
1802
+ color: "#64748b",
1803
+ marginBottom: "20px",
1804
+ display: "flex",
1805
+ alignItems: "center",
1806
+ gap: "10px",
1807
+ textTransform: "uppercase",
1808
+ letterSpacing: "0.025em"
1809
+ }, children: [
1810
+ /* @__PURE__ */ jsx11("span", { className: "material-symbols-outlined", style: { fontSize: "20px", color: "var(--color-primary)" }, children: "info" }),
1811
+ "System Information"
1812
+ ] }),
1813
+ /* @__PURE__ */ jsxs9("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "32px" }, children: [
1814
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
1815
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "11px", fontWeight: 700, color: "#94a3b8", textTransform: "uppercase" }, children: "Created" }),
1816
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
1817
+ /* @__PURE__ */ jsx11("div", { style: { width: "32px", height: "32px", borderRadius: "50%", background: "#f1f5f9", display: "flex", alignItems: "center", justifyContent: "center", color: "#64748b" }, children: /* @__PURE__ */ jsx11("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: "person" }) }),
1818
+ /* @__PURE__ */ jsxs9("div", { children: [
1819
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "14px", color: "var(--color-text)", fontWeight: 500 }, children: "Admin" }),
1820
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "12px", color: "#64748b" }, children: record.createdAt ? formatDate(new Date(record.createdAt)) : "Unknown date" })
1821
+ ] })
1822
+ ] })
1823
+ ] }),
1824
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
1825
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "11px", fontWeight: 700, color: "#94a3b8", textTransform: "uppercase" }, children: "Last Updated" }),
1826
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
1827
+ /* @__PURE__ */ jsx11("div", { style: { width: "32px", height: "32px", borderRadius: "50%", background: "#f0f9ff", display: "flex", alignItems: "center", justifyContent: "center", color: "#0284c7" }, children: /* @__PURE__ */ jsx11("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: "edit_note" }) }),
1828
+ /* @__PURE__ */ jsxs9("div", { children: [
1829
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "14px", color: "var(--color-text)", fontWeight: 500 }, children: "Admin" }),
1830
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "12px", color: "#64748b" }, children: record.updatedAt || record.createdAt ? formatDate(new Date(record.updatedAt || record.createdAt)) : "Unknown date" })
1831
+ ] })
1832
+ ] })
1833
+ ] })
1834
+ ] })
1835
+ ] })
1836
+ ] })
1837
+ ] }) });
1838
+ }
1839
+
1840
+ // src/ui/screens/CollectionSchemaPage.tsx
1841
+ import Link7 from "next/link";
1842
+ import { useEffect as useEffect4, useState as useState6 } from "react";
1843
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1844
+ function formatFullTimestamp(date) {
1845
+ if (isNaN(date.getTime())) return "Unknown";
1846
+ return new Intl.DateTimeFormat("en-US", {
1847
+ month: "short",
1848
+ day: "numeric",
1849
+ year: "numeric",
1850
+ hour: "numeric",
1851
+ minute: "2-digit",
1852
+ hour12: true
1853
+ }).format(date);
1854
+ }
1855
+ function CollectionSchemaPage({ config, collection, role }) {
1856
+ const [lastActivity, setLastActivity] = useState6("Loading...");
1857
+ useEffect4(() => {
1858
+ fetch(`/api/${collection.name}?pageSize=1`).then((res) => res.json()).then((result) => {
1859
+ const item = result.data?.[0];
1860
+ if (item) {
1861
+ const dateStr = item.updatedAt || item.updated_at || item.createdAt || item.created_at;
1862
+ if (dateStr) {
1863
+ setLastActivity(formatFullTimestamp(new Date(dateStr)));
1864
+ } else {
1865
+ setLastActivity("No date info");
1866
+ }
1867
+ } else {
1868
+ setLastActivity("No activity");
1869
+ }
1870
+ }).catch(() => setLastActivity("Unknown"));
1871
+ }, [collection.name]);
1872
+ const getFieldIcon = (type) => {
1873
+ switch (type) {
1874
+ case "text":
1875
+ return "text_fields";
1876
+ case "slug":
1877
+ return "link";
1878
+ case "richText":
1879
+ return "description";
1880
+ case "boolean":
1881
+ return "toggle_on";
1882
+ case "image":
1883
+ return "image";
1884
+ case "relation":
1885
+ return "account_tree";
1886
+ case "date":
1887
+ return "calendar_today";
1888
+ default:
1889
+ return "label";
1890
+ }
1891
+ };
1892
+ return /* @__PURE__ */ jsx12(AdminLayout, { config, currentPath: `/admin/${collection.name}`, role, children: /* @__PURE__ */ jsxs10("div", { className: "adminforge-schema-page", children: [
1893
+ /* @__PURE__ */ jsx12("div", { style: { marginBottom: "32px", display: "flex", justifyContent: "space-between", alignItems: "flex-end" }, children: /* @__PURE__ */ jsxs10("div", { children: [
1894
+ /* @__PURE__ */ jsxs10(Link7, { href: "/admin", style: { display: "flex", alignItems: "center", gap: "4px", fontSize: "14px", color: "var(--color-text-secondary)", marginBottom: "16px", textDecoration: "none" }, children: [
1895
+ /* @__PURE__ */ jsx12("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: "arrow_back" }),
1896
+ "Back to Collection Registry"
1897
+ ] }),
1898
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
1899
+ /* @__PURE__ */ jsxs10("h1", { style: { fontSize: "32px", fontWeight: 700 }, children: [
1900
+ collection.label,
1901
+ " Schema"
1902
+ ] }),
1903
+ /* @__PURE__ */ jsx12("span", { className: "adminforge-badge", style: { background: "#f8fafc", color: "#64748b", borderColor: "#e2e8f0", fontSize: "11px", fontWeight: 600 }, children: "defined in adminforge.ts" })
1904
+ ] })
1905
+ ] }) }),
1906
+ /* @__PURE__ */ jsxs10("div", { style: { display: "grid", gridTemplateColumns: "1fr 340px", gap: "24px", alignItems: "start" }, children: [
1907
+ /* @__PURE__ */ jsxs10("div", { className: "adminforge-card", style: { border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", overflow: "hidden" }, children: [
1908
+ /* @__PURE__ */ jsx12("div", { style: { padding: "24px", borderBottom: "1px solid var(--color-border)" }, children: /* @__PURE__ */ jsx12("h3", { style: { fontSize: "18px", fontWeight: 600 }, children: "Field Definitions" }) }),
1909
+ /* @__PURE__ */ jsxs10("table", { className: "adminforge-table", children: [
1910
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs10("tr", { children: [
1911
+ /* @__PURE__ */ jsx12("th", { style: { fontSize: "12px", color: "var(--color-text-secondary)" }, children: "FIELD NAME" }),
1912
+ /* @__PURE__ */ jsx12("th", { style: { fontSize: "12px", color: "var(--color-text-secondary)" }, children: "TYPE" }),
1913
+ /* @__PURE__ */ jsx12("th", { style: { fontSize: "12px", color: "var(--color-text-secondary)" }, children: "PROPERTIES" })
1914
+ ] }) }),
1915
+ /* @__PURE__ */ jsx12("tbody", { children: Object.entries(collection.fields).map(([name, field]) => /* @__PURE__ */ jsxs10("tr", { children: [
1916
+ /* @__PURE__ */ jsx12("td", { style: { fontWeight: 600 }, children: name }),
1917
+ /* @__PURE__ */ jsx12("td", { children: /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1918
+ /* @__PURE__ */ jsx12("span", { className: "material-symbols-outlined", style: { fontSize: "20px", color: "var(--color-text-secondary)" }, children: getFieldIcon(field.type) }),
1919
+ /* @__PURE__ */ jsx12("span", { children: field.type })
1920
+ ] }) }),
1921
+ /* @__PURE__ */ jsx12("td", { children: /* @__PURE__ */ jsxs10("div", { style: { display: "flex", flexWrap: "wrap", gap: "4px" }, children: [
1922
+ field.meta?.required && /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-danger", style: { fontSize: "11px" }, children: "required" }),
1923
+ field.meta?.unique && /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-primary", style: { fontSize: "11px" }, children: "unique" }),
1924
+ !!field.ui.props?.from && /* @__PURE__ */ jsxs10("span", { className: "adminforge-badge adminforge-badge-secondary", style: { fontSize: "11px" }, children: [
1925
+ "from: ",
1926
+ String(field.ui.props.from)
1927
+ ] }),
1928
+ !!field.ui.props?.to && /* @__PURE__ */ jsxs10("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [
1929
+ /* @__PURE__ */ jsxs10("span", { style: { fontSize: "12px" }, children: [
1930
+ "to: ",
1931
+ /* @__PURE__ */ jsx12("strong", { children: String(field.ui.props.to) })
1932
+ ] }),
1933
+ /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-secondary", style: { fontSize: "10px", alignSelf: "flex-start" }, children: String(field.ui.props.relationType) })
1934
+ ] }),
1935
+ field.meta?.default !== void 0 && /* @__PURE__ */ jsxs10("span", { style: { fontSize: "12px", color: "var(--color-text-secondary)" }, children: [
1936
+ "default: ",
1937
+ /* @__PURE__ */ jsx12("code", { children: JSON.stringify(field.meta.default) })
1938
+ ] }),
1939
+ !field.meta?.required && !field.meta?.unique && !field.ui.props?.from && !field.ui.props?.to && /* @__PURE__ */ jsx12("span", { style: { color: "var(--color-text-secondary)" }, children: "-" })
1940
+ ] }) })
1941
+ ] }, name)) })
1942
+ ] })
1943
+ ] }),
1944
+ /* @__PURE__ */ jsxs10("div", { className: "adminforge-card", style: { padding: "24px", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)" }, children: [
1945
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: "12px", marginBottom: "24px" }, children: [
1946
+ /* @__PURE__ */ jsx12("span", { className: "material-symbols-outlined", style: { color: "var(--color-text-secondary)" }, children: "info" }),
1947
+ /* @__PURE__ */ jsx12("h3", { style: { fontSize: "18px", fontWeight: 600 }, children: "Collection Meta" })
1948
+ ] }),
1949
+ /* @__PURE__ */ jsxs10("div", { style: { marginBottom: "20px" }, children: [
1950
+ /* @__PURE__ */ jsx12("label", { style: { fontSize: "12px", fontWeight: 700, color: "var(--color-text-secondary)", textTransform: "uppercase", marginBottom: "8px", display: "block" }, children: "Collection Name" }),
1951
+ /* @__PURE__ */ jsx12("code", { style: { background: "#f1f5f9", padding: "4px 8px", borderRadius: "4px", fontSize: "14px" }, children: collection.name })
1952
+ ] }),
1953
+ /* @__PURE__ */ jsxs10("div", { style: { marginBottom: "20px" }, children: [
1954
+ /* @__PURE__ */ jsx12("label", { style: { fontSize: "12px", fontWeight: 700, color: "var(--color-text-secondary)", textTransform: "uppercase", marginBottom: "8px", display: "block" }, children: "UI Label" }),
1955
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "16px", fontWeight: 500 }, children: collection.label })
1956
+ ] }),
1957
+ /* @__PURE__ */ jsxs10("div", { style: { marginBottom: "24px", paddingBottom: "24px", borderBottom: "1px solid var(--color-border)" }, children: [
1958
+ /* @__PURE__ */ jsx12("label", { style: { fontSize: "12px", fontWeight: 700, color: "var(--color-text-secondary)", textTransform: "uppercase", marginBottom: "8px", display: "block" }, children: "Last Activity" }),
1959
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "14px", color: "var(--color-text-secondary)" }, children: lastActivity })
1960
+ ] }),
1961
+ /* @__PURE__ */ jsxs10("div", { children: [
1962
+ /* @__PURE__ */ jsx12("label", { style: { fontSize: "12px", fontWeight: 700, color: "var(--color-text-secondary)", textTransform: "uppercase", marginBottom: "16px", display: "block" }, children: "Access Rules" }),
1963
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
1964
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
1965
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "14px" }, children: "Create" }),
1966
+ /* @__PURE__ */ jsx12("div", { style: { display: "flex", gap: "4px" }, children: (collection.access?.create || ["admin"]).map((role2) => /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-secondary", style: { fontSize: "11px" }, children: role2 }, role2)) })
1967
+ ] }),
1968
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
1969
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "14px" }, children: "Update" }),
1970
+ /* @__PURE__ */ jsx12("div", { style: { display: "flex", gap: "4px" }, children: (collection.access?.update || ["admin"]).map((role2) => /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-secondary", style: { fontSize: "11px" }, children: role2 }, role2)) })
1971
+ ] }),
1972
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
1973
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "14px" }, children: "Delete" }),
1974
+ /* @__PURE__ */ jsx12("div", { style: { display: "flex", gap: "4px" }, children: (collection.access?.delete || ["admin"]).map((role2) => /* @__PURE__ */ jsx12("span", { className: "adminforge-badge adminforge-badge-danger", style: { fontSize: "11px", background: "#fee2e2", color: "#b91c1c" }, children: role2 }, role2)) })
1975
+ ] })
1976
+ ] })
1977
+ ] })
1978
+ ] })
1979
+ ] })
1980
+ ] }) });
1981
+ }
1982
+
1983
+ // src/ui/screens/RolesListPage.tsx
1984
+ import Link8 from "next/link";
1985
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1986
+ function RolesListPage({ config, role }) {
1987
+ const roles = Object.entries(config.auth?.roles || {});
1988
+ return /* @__PURE__ */ jsx13(AdminLayout, { config, currentPath: "/admin/roles", role, children: /* @__PURE__ */ jsxs11("div", { className: "adminforge-collection-page", children: [
1989
+ /* @__PURE__ */ jsx13("div", { className: "adminforge-page-header", children: /* @__PURE__ */ jsx13("h2", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: "Roles" }) }),
1990
+ /* @__PURE__ */ jsx13("div", { style: { background: "var(--color-surface)", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", overflow: "hidden", boxShadow: "var(--shadow-sm)" }, children: /* @__PURE__ */ jsxs11("table", { className: "adminforge-table", style: { width: "100%", borderCollapse: "collapse" }, children: [
1991
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsxs11("tr", { style: { background: "#fcfcfd", borderBottom: "1px solid var(--color-border)" }, children: [
1992
+ /* @__PURE__ */ jsx13("th", { style: { textAlign: "left", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Role Name" }),
1993
+ /* @__PURE__ */ jsx13("th", { style: { textAlign: "left", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Label" }),
1994
+ /* @__PURE__ */ jsx13("th", { style: { textAlign: "right", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Actions" })
1995
+ ] }) }),
1996
+ /* @__PURE__ */ jsx13("tbody", { children: roles.map(([key, roleDef]) => /* @__PURE__ */ jsxs11("tr", { style: { borderBottom: "1px solid var(--color-border)" }, children: [
1997
+ /* @__PURE__ */ jsx13("td", { style: { padding: "16px 24px", fontSize: "14px", color: "var(--color-text)", fontWeight: 500 }, children: /* @__PURE__ */ jsx13("code", { children: key }) }),
1998
+ /* @__PURE__ */ jsx13("td", { style: { padding: "16px 24px", fontSize: "14px", color: "var(--color-text-secondary)" }, children: roleDef.label }),
1999
+ /* @__PURE__ */ jsx13("td", { style: { padding: "16px 24px", textAlign: "right" }, children: /* @__PURE__ */ jsx13(
2000
+ Link8,
2001
+ {
2002
+ href: `/admin/roles/${key}`,
2003
+ className: "adminforge-btn adminforge-btn-secondary",
2004
+ style: { padding: "6px 12px", fontSize: "13px" },
2005
+ children: "View Permissions"
2006
+ }
2007
+ ) })
2008
+ ] }, key)) })
2009
+ ] }) })
2010
+ ] }) });
2011
+ }
2012
+
2013
+ // src/ui/screens/RoleDetailPage.tsx
2014
+ import Link9 from "next/link";
2015
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2016
+ function hasAccess4(access, operation, roleName) {
2017
+ if (!access) return true;
2018
+ const allowed = access[operation];
2019
+ if (!allowed || !Array.isArray(allowed)) return true;
2020
+ return allowed.includes(roleName);
2021
+ }
2022
+ function RoleDetailPage({ config, roleId, role }) {
2023
+ const roleDef = config.auth?.roles?.[roleId];
2024
+ if (!roleDef) {
2025
+ return /* @__PURE__ */ jsx14(AdminLayout, { config, currentPath: "/admin/roles", role, children: /* @__PURE__ */ jsxs12("div", { className: "adminforge-collection-page", children: [
2026
+ /* @__PURE__ */ jsx14("div", { className: "adminforge-page-header", children: /* @__PURE__ */ jsx14("h2", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: "Role not found" }) }),
2027
+ /* @__PURE__ */ jsx14(Link9, { href: "/admin/roles", className: "adminforge-btn adminforge-btn-secondary", children: "Back to Roles" })
2028
+ ] }) });
2029
+ }
2030
+ return /* @__PURE__ */ jsx14(AdminLayout, { config, currentPath: "/admin/roles", role, children: /* @__PURE__ */ jsxs12("div", { className: "adminforge-collection-page", children: [
2031
+ /* @__PURE__ */ jsxs12("div", { className: "adminforge-page-header", children: [
2032
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
2033
+ /* @__PURE__ */ jsx14(Link9, { href: "/admin/roles", className: "adminforge-btn-icon", style: { border: "1px solid var(--color-border)", background: "white" }, children: /* @__PURE__ */ jsx14("span", { className: "material-symbols-outlined", children: "arrow_back" }) }),
2034
+ /* @__PURE__ */ jsxs12("h2", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: [
2035
+ "Role: ",
2036
+ roleDef.label || roleId
2037
+ ] })
2038
+ ] }),
2039
+ /* @__PURE__ */ jsx14("div", { className: "adminforge-badge", style: { background: "#f8fafc", color: "#64748b", borderColor: "#e2e8f0", fontSize: "11px", fontWeight: 600 }, children: "Read-only Config" })
2040
+ ] }),
2041
+ /* @__PURE__ */ jsxs12("div", { style: { marginBottom: "32px" }, children: [
2042
+ /* @__PURE__ */ jsx14("h3", { style: { fontSize: "16px", fontWeight: 600, marginBottom: "16px", color: "var(--color-text)" }, children: "Permissions Matrix" }),
2043
+ /* @__PURE__ */ jsx14("div", { style: { background: "var(--color-surface)", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", overflow: "hidden", boxShadow: "var(--shadow-sm)" }, children: /* @__PURE__ */ jsxs12("table", { className: "adminforge-table", style: { width: "100%", borderCollapse: "collapse" }, children: [
2044
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsxs12("tr", { style: { background: "#fcfcfd", borderBottom: "1px solid var(--color-border)" }, children: [
2045
+ /* @__PURE__ */ jsx14("th", { style: { textAlign: "left", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Collection" }),
2046
+ /* @__PURE__ */ jsx14("th", { style: { textAlign: "center", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Read" }),
2047
+ /* @__PURE__ */ jsx14("th", { style: { textAlign: "center", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Create" }),
2048
+ /* @__PURE__ */ jsx14("th", { style: { textAlign: "center", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Update" }),
2049
+ /* @__PURE__ */ jsx14("th", { style: { textAlign: "center", padding: "16px 24px", fontSize: "13px", fontWeight: 600, color: "#64748b", textTransform: "uppercase", letterSpacing: "0.05em" }, children: "Delete" })
2050
+ ] }) }),
2051
+ /* @__PURE__ */ jsx14("tbody", { children: config.collections.map((collection) => {
2052
+ const canRead = hasAccess4(collection.access, "read", roleId);
2053
+ const canCreate = hasAccess4(collection.access, "create", roleId);
2054
+ const canUpdate = hasAccess4(collection.access, "update", roleId);
2055
+ const canDelete = hasAccess4(collection.access, "delete", roleId);
2056
+ return /* @__PURE__ */ jsxs12("tr", { style: { borderBottom: "1px solid var(--color-border)" }, children: [
2057
+ /* @__PURE__ */ jsx14("td", { style: { padding: "16px 24px", fontSize: "14px", fontWeight: 600, color: "var(--color-text)" }, children: collection.label || collection.name }),
2058
+ /* @__PURE__ */ jsx14("td", { style: { padding: "16px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx14(PermissionStatus, { allowed: canRead }) }),
2059
+ /* @__PURE__ */ jsx14("td", { style: { padding: "16px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx14(PermissionStatus, { allowed: canCreate }) }),
2060
+ /* @__PURE__ */ jsx14("td", { style: { padding: "16px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx14(PermissionStatus, { allowed: canUpdate }) }),
2061
+ /* @__PURE__ */ jsx14("td", { style: { padding: "16px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx14(PermissionStatus, { allowed: canDelete }) })
2062
+ ] }, collection.name);
2063
+ }) })
2064
+ ] }) })
2065
+ ] }),
2066
+ /* @__PURE__ */ jsxs12("div", { children: [
2067
+ /* @__PURE__ */ jsx14("h3", { style: { fontSize: "16px", fontWeight: 600, marginBottom: "16px", color: "var(--color-text)" }, children: "Field-Level Overrides" }),
2068
+ config.collections.map((collection) => {
2069
+ const fieldsWithAccess = Object.entries(collection.fields).filter(([_, field]) => field.access);
2070
+ if (fieldsWithAccess.length === 0) return null;
2071
+ return /* @__PURE__ */ jsxs12("div", { style: { marginBottom: "24px", padding: "20px", border: "1px solid var(--color-border)", borderRadius: "var(--radius-lg)", background: "white" }, children: [
2072
+ /* @__PURE__ */ jsx14("h4", { style: { fontSize: "14px", fontWeight: 600, marginBottom: "12px", color: "var(--color-text-secondary)" }, children: collection.label || collection.name }),
2073
+ /* @__PURE__ */ jsx14("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", gap: "12px" }, children: fieldsWithAccess.map(([name, field]) => {
2074
+ const canRead = hasAccess4(field.access, "read", roleId);
2075
+ const canCreate = hasAccess4(field.access, "create", roleId);
2076
+ const canUpdate = hasAccess4(field.access, "update", roleId);
2077
+ return /* @__PURE__ */ jsxs12("div", { style: { padding: "12px", border: "1px solid #f1f5f9", borderRadius: "8px", background: "#f8fafc" }, children: [
2078
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" }, children: /* @__PURE__ */ jsx14("code", { children: name }) }),
2079
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: "8px", fontSize: "11px" }, children: [
2080
+ /* @__PURE__ */ jsxs12("span", { style: { display: "flex", alignItems: "center", gap: "4px", color: canRead ? "#10b981" : "#ef4444" }, children: [
2081
+ /* @__PURE__ */ jsx14("span", { className: "material-symbols-outlined", style: { fontSize: "14px" }, children: canRead ? "check_circle" : "cancel" }),
2082
+ " read"
2083
+ ] }),
2084
+ /* @__PURE__ */ jsxs12("span", { style: { display: "flex", alignItems: "center", gap: "4px", color: canCreate ? "#10b981" : "#ef4444" }, children: [
2085
+ /* @__PURE__ */ jsx14("span", { className: "material-symbols-outlined", style: { fontSize: "14px" }, children: canCreate ? "check_circle" : "cancel" }),
2086
+ " create"
2087
+ ] }),
2088
+ /* @__PURE__ */ jsxs12("span", { style: { display: "flex", alignItems: "center", gap: "4px", color: canUpdate ? "#10b981" : "#ef4444" }, children: [
2089
+ /* @__PURE__ */ jsx14("span", { className: "material-symbols-outlined", style: { fontSize: "14px" }, children: canUpdate ? "check_circle" : "cancel" }),
2090
+ " update"
2091
+ ] })
2092
+ ] })
2093
+ ] }, name);
2094
+ }) })
2095
+ ] }, collection.name);
2096
+ })
2097
+ ] })
2098
+ ] }) });
2099
+ }
2100
+ function PermissionStatus({ allowed }) {
2101
+ return /* @__PURE__ */ jsx14("span", { className: "material-symbols-outlined", style: {
2102
+ color: allowed ? "#10b981" : "#ef4444",
2103
+ fontSize: "20px",
2104
+ fontWeight: "bold"
2105
+ }, children: allowed ? "check" : "close" });
2106
+ }
2107
+
2108
+ // src/ui/screens/AgentTokenPage.tsx
2109
+ import { useState as useState7 } from "react";
2110
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2111
+ function AgentTokenPage({ config }) {
2112
+ const { apiBase } = useAdminForge();
2113
+ const session = useAdminSession();
2114
+ const [selectedScopes, setSelectedScopes] = useState7([]);
2115
+ const [expiresIn, setExpiresIn] = useState7(600);
2116
+ const [token, setToken] = useState7(null);
2117
+ const [loading, setLoading] = useState7(false);
2118
+ const toggleScope = (scope) => {
2119
+ setSelectedScopes(
2120
+ (prev) => prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope]
2121
+ );
2122
+ };
2123
+ const handleGenerate = async () => {
2124
+ setLoading(true);
2125
+ try {
2126
+ const res = await fetch(`${apiBase}/_tokens`, {
2127
+ method: "POST",
2128
+ headers: { "Content-Type": "application/json" },
2129
+ body: JSON.stringify({ scope: selectedScopes, expiresIn })
2130
+ });
2131
+ const data = await res.json();
2132
+ if (data.token) {
2133
+ setToken(data.token);
2134
+ } else {
2135
+ alert(data.error || "Failed to generate token");
2136
+ }
2137
+ } catch (e) {
2138
+ alert("Error connecting to API: " + e.message);
2139
+ } finally {
2140
+ setLoading(false);
2141
+ }
2142
+ };
2143
+ const role = session?.role || session?.user?.role;
2144
+ return /* @__PURE__ */ jsx15(AdminLayout, { config, currentPath: "/admin/settings/agent-tokens", role, children: /* @__PURE__ */ jsxs13("div", { className: "adminforge-collection-page", children: [
2145
+ /* @__PURE__ */ jsx15("div", { className: "adminforge-page-header", style: { marginBottom: "40px" }, children: /* @__PURE__ */ jsxs13("div", { children: [
2146
+ /* @__PURE__ */ jsx15("h1", { style: { fontSize: "24px", fontWeight: 700, margin: 0 }, children: "Agent Token Generator" }),
2147
+ /* @__PURE__ */ jsx15("p", { style: { color: "#64748b", fontSize: "14px", marginTop: "4px" }, children: "Issue secure, scoped passes for your AI agents." })
2148
+ ] }) }),
2149
+ token ? /* @__PURE__ */ jsxs13("div", { style: {
2150
+ background: "#f0fdf4",
2151
+ border: "1px solid #10b981",
2152
+ borderRadius: "12px",
2153
+ padding: "32px",
2154
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)"
2155
+ }, children: [
2156
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "12px", marginBottom: "20px" }, children: [
2157
+ /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined", style: { color: "#059669", fontSize: "32px" }, children: "verified_user" }),
2158
+ /* @__PURE__ */ jsx15("h2", { style: { fontSize: "20px", fontWeight: 700, color: "#064e3b", margin: 0 }, children: "Token Generated Successfully" })
2159
+ ] }),
2160
+ /* @__PURE__ */ jsx15("div", { style: { background: "#dcfce7", padding: "16px", borderRadius: "8px", marginBottom: "24px", border: "1px solid #10b98140" }, children: /* @__PURE__ */ jsxs13("p", { style: { fontSize: "14px", color: "#166534", lineHeight: 1.6, margin: 0 }, children: [
2161
+ /* @__PURE__ */ jsx15("strong", { children: "\u26A0\uFE0F Security Alert:" }),
2162
+ " Copy this token now. It is never stored and will only be shown once. It will expire in ",
2163
+ /* @__PURE__ */ jsxs13("strong", { children: [
2164
+ expiresIn / 60,
2165
+ " minutes"
2166
+ ] }),
2167
+ "."
2168
+ ] }) }),
2169
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: "12px" }, children: [
2170
+ /* @__PURE__ */ jsx15(
2171
+ "input",
2172
+ {
2173
+ readOnly: true,
2174
+ value: token,
2175
+ className: "adminforge-input",
2176
+ style: { fontFamily: "monospace", fontSize: "12px", flex: 1, height: "48px", background: "white" }
2177
+ }
2178
+ ),
2179
+ /* @__PURE__ */ jsxs13(
2180
+ "button",
2181
+ {
2182
+ onClick: () => {
2183
+ navigator.clipboard.writeText(token);
2184
+ alert("Copied to clipboard!");
2185
+ },
2186
+ className: "adminforge-btn adminforge-btn-primary",
2187
+ style: { height: "48px", padding: "0 24px" },
2188
+ children: [
2189
+ /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined", children: "content_copy" }),
2190
+ "Copy"
2191
+ ]
2192
+ }
2193
+ )
2194
+ ] }),
2195
+ /* @__PURE__ */ jsxs13(
2196
+ "button",
2197
+ {
2198
+ onClick: () => setToken(null),
2199
+ className: "adminforge-btn-text",
2200
+ style: { marginTop: "24px", display: "flex", alignItems: "center", gap: "8px", color: "#059669", background: "none", border: "none", cursor: "pointer", fontWeight: 600 },
2201
+ children: [
2202
+ /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined", style: { fontSize: "18px" }, children: "refresh" }),
2203
+ "Generate another token"
2204
+ ]
2205
+ }
2206
+ )
2207
+ ] }) : /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: "32px" }, children: [
2208
+ /* @__PURE__ */ jsx15("div", { className: "adminforge-table-wrapper", children: /* @__PURE__ */ jsxs13("table", { className: "adminforge-table", children: [
2209
+ /* @__PURE__ */ jsx15("thead", { children: /* @__PURE__ */ jsxs13("tr", { children: [
2210
+ /* @__PURE__ */ jsx15("th", { children: "Collection" }),
2211
+ /* @__PURE__ */ jsx15("th", { style: { textAlign: "center" }, children: "Create" }),
2212
+ /* @__PURE__ */ jsx15("th", { style: { textAlign: "center" }, children: "Read" }),
2213
+ /* @__PURE__ */ jsx15("th", { style: { textAlign: "center" }, children: "Update" }),
2214
+ /* @__PURE__ */ jsx15("th", { style: { textAlign: "center" }, children: "Delete" })
2215
+ ] }) }),
2216
+ /* @__PURE__ */ jsx15("tbody", { children: config.collections.map((c) => /* @__PURE__ */ jsxs13("tr", { children: [
2217
+ /* @__PURE__ */ jsx15("td", { style: { fontWeight: 600 }, children: /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [
2218
+ /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined", style: { fontSize: "20px", color: "#94a3b8" }, children: c.icon || "database" }),
2219
+ c.label
2220
+ ] }) }),
2221
+ ["create", "read", "update", "delete"].map((action) => {
2222
+ const scope = `${c.name}:${action}`;
2223
+ return /* @__PURE__ */ jsx15("td", { style: { textAlign: "center" }, children: /* @__PURE__ */ jsx15("div", { style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx15(
2224
+ "input",
2225
+ {
2226
+ type: "checkbox",
2227
+ checked: selectedScopes.includes(scope),
2228
+ onChange: () => toggleScope(scope),
2229
+ style: { width: "18px", height: "18px", cursor: "pointer" }
2230
+ }
2231
+ ) }) }, action);
2232
+ })
2233
+ ] }, c.name)) })
2234
+ ] }) }),
2235
+ /* @__PURE__ */ jsxs13("div", { style: {
2236
+ display: "flex",
2237
+ justifyContent: "space-between",
2238
+ alignItems: "center",
2239
+ background: "#fff",
2240
+ padding: "24px",
2241
+ borderRadius: "12px",
2242
+ border: "1px solid var(--color-border)",
2243
+ boxShadow: "var(--shadow-sm)"
2244
+ }, children: [
2245
+ /* @__PURE__ */ jsxs13("div", { children: [
2246
+ /* @__PURE__ */ jsxs13("div", { style: { marginBottom: "16px" }, children: [
2247
+ /* @__PURE__ */ jsxs13("p", { style: { fontWeight: 700, fontSize: "16px", color: "var(--color-text)", marginBottom: "8px", margin: 0 }, children: [
2248
+ selectedScopes.length,
2249
+ " scopes selected"
2250
+ ] }),
2251
+ /* @__PURE__ */ jsx15("div", { style: { display: "flex", gap: "8px", marginTop: "12px" }, children: [
2252
+ { label: "10m", val: 600 },
2253
+ { label: "30m", val: 1800 },
2254
+ { label: "1h", val: 3600 }
2255
+ ].map((opt) => /* @__PURE__ */ jsx15(
2256
+ "button",
2257
+ {
2258
+ onClick: () => setExpiresIn(opt.val),
2259
+ style: {
2260
+ padding: "6px 12px",
2261
+ fontSize: "12px",
2262
+ fontWeight: 600,
2263
+ borderRadius: "6px",
2264
+ border: "1px solid",
2265
+ background: expiresIn === opt.val ? "var(--color-primary)" : "#fff",
2266
+ color: expiresIn === opt.val ? "#fff" : "var(--color-text)",
2267
+ borderColor: expiresIn === opt.val ? "var(--color-primary)" : "var(--color-border)",
2268
+ cursor: "pointer",
2269
+ transition: "all 0.2s"
2270
+ },
2271
+ children: opt.label
2272
+ },
2273
+ opt.val
2274
+ )) })
2275
+ ] }),
2276
+ /* @__PURE__ */ jsxs13("p", { style: { fontSize: "13px", color: "#64748b", margin: 0 }, children: [
2277
+ "Token will expire in ",
2278
+ expiresIn / 60,
2279
+ " minutes."
2280
+ ] })
2281
+ ] }),
2282
+ /* @__PURE__ */ jsx15(
2283
+ "button",
2284
+ {
2285
+ onClick: handleGenerate,
2286
+ disabled: loading || selectedScopes.length === 0,
2287
+ className: "adminforge-btn adminforge-btn-primary",
2288
+ style: { padding: "12px 32px", fontSize: "15px" },
2289
+ children: loading ? "Generating..." : "Generate Agent Token"
2290
+ }
2291
+ )
2292
+ ] }),
2293
+ /* @__PURE__ */ jsxs13("div", { style: { marginTop: "24px" }, children: [
2294
+ /* @__PURE__ */ jsx15("h3", { style: { fontSize: "11px", fontWeight: 700, color: "#94a3b8", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "16px", margin: 0 }, children: "Security Protocol" }),
2295
+ /* @__PURE__ */ jsxs13("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "24px", marginTop: "16px" }, children: [
2296
+ /* @__PURE__ */ jsxs13("div", { style: { background: "#fff", padding: "20px", borderRadius: "8px", border: "1px solid var(--color-border)" }, children: [
2297
+ /* @__PURE__ */ jsx15("p", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", margin: 0 }, children: "Short-Lived Keys" }),
2298
+ /* @__PURE__ */ jsx15("p", { style: { fontSize: "13px", color: "#64748b", lineHeight: 1.6, margin: 0 }, children: "Tokens expire after 10 minutes. This reduces the risk of long-term credential leakage." })
2299
+ ] }),
2300
+ /* @__PURE__ */ jsxs13("div", { style: { background: "#fff", padding: "20px", borderRadius: "8px", border: "1px solid var(--color-border)" }, children: [
2301
+ /* @__PURE__ */ jsx15("p", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", margin: 0 }, children: "Scoped Authority" }),
2302
+ /* @__PURE__ */ jsx15("p", { style: { fontSize: "13px", color: "#64748b", lineHeight: 1.6, margin: 0 }, children: "Agents are strictly limited to the checkboxes above. They cannot bypass RBAC rules." })
2303
+ ] })
2304
+ ] })
2305
+ ] })
2306
+ ] })
2307
+ ] }) });
2308
+ }
2309
+
2310
+ // src/ui/AdminDashboard.tsx
2311
+ import { useSearchParams } from "next/navigation.js";
2312
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2313
+ function AdminDashboard({ config: initialConfig, params: initialParams, apiBase: initialApiBase }) {
2314
+ const ctx = useAdminForge();
2315
+ const config = initialConfig ?? ctx.config;
2316
+ const apiBase = initialApiBase ?? ctx.apiBase;
2317
+ const searchParams = useSearchParams();
2318
+ const queryStr = searchParams.toString();
2319
+ const [adminParams, setAdminParams] = React3.useState([]);
2320
+ const [data, setData] = React3.useState(null);
2321
+ const [record, setRecord] = React3.useState(null);
2322
+ const [unauthorized, setUnauthorized] = React3.useState(false);
2323
+ React3.useEffect(() => {
2324
+ async function resolveParams() {
2325
+ const resolved = initialParams instanceof Promise ? await initialParams : initialParams;
2326
+ if (resolved?.admin) {
2327
+ setAdminParams(resolved.admin);
2328
+ } else if (typeof window !== "undefined") {
2329
+ const path = window.location.pathname;
2330
+ const segments = path.split("/admin/").pop()?.split("/") || [];
2331
+ setAdminParams(segments.filter(Boolean));
2332
+ }
2333
+ }
2334
+ resolveParams();
2335
+ }, [initialParams]);
2336
+ const [segment, actionOrId] = adminParams;
2337
+ React3.useEffect(() => {
2338
+ if (!config || !segment) return;
2339
+ const isCollection = config.collections.some((c) => c.name === segment);
2340
+ if (!isCollection) return;
2341
+ const query = queryStr ? `?${queryStr}` : "";
2342
+ if (!actionOrId) {
2343
+ fetch(`${apiBase}/${segment}${query}`).then(async (res) => {
2344
+ if (res.status === 401) {
2345
+ setUnauthorized(true);
2346
+ return null;
2347
+ }
2348
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
2349
+ const text = await res.text();
2350
+ return text ? JSON.parse(text) : {};
2351
+ }).then((res) => setData(res)).catch((e) => console.error(`[AdminForge] Failed to fetch ${apiBase}/${segment}:`, e));
2352
+ } else if (actionOrId !== "new" && actionOrId !== "schema") {
2353
+ fetch(`${apiBase}/${segment}/${actionOrId}${query}`).then(async (res) => {
2354
+ if (res.status === 401) {
2355
+ setUnauthorized(true);
2356
+ return null;
2357
+ }
2358
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
2359
+ const text = await res.text();
2360
+ return text ? JSON.parse(text) : {};
2361
+ }).then((res) => setRecord(res)).catch((e) => console.error(`[AdminForge] Failed to fetch ${apiBase}/${segment}/${actionOrId}:`, e));
2362
+ }
2363
+ }, [segment, actionOrId, apiBase, config, queryStr]);
2364
+ return /* @__PURE__ */ jsx16(AdminForgeProvider, { config, apiBase, children: /* @__PURE__ */ jsx16(
2365
+ AdminDashboardContent,
2366
+ {
2367
+ config,
2368
+ adminParams,
2369
+ unauthorized,
2370
+ data,
2371
+ record
2372
+ }
2373
+ ) });
2374
+ }
2375
+ function AdminDashboardContent({ config: _config, adminParams, unauthorized: localUnauthorized, data, record }) {
2376
+ const { config: ctxConfig, unauthorized: ctxUnauthorized } = useAdminForge();
2377
+ const config = _config ?? ctxConfig;
2378
+ const session = useAdminSession();
2379
+ const noSession = config?.auth?.enabled && !session?.user;
2380
+ const unauthorized = localUnauthorized || ctxUnauthorized || noSession;
2381
+ if (unauthorized) {
2382
+ return /* @__PURE__ */ jsx16("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh", background: "#f8fafc", padding: "20px" }, children: /* @__PURE__ */ jsxs14("div", { style: { background: "#fff", padding: "40px", borderRadius: "16px", boxShadow: "0 20px 25px -5px rgba(0,0,0,0.1)", maxWidth: "400px", width: "100%", textAlign: "center" }, children: [
2383
+ /* @__PURE__ */ jsx16("div", { style: { width: "64px", height: "64px", background: "#eff6ff", borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 24px" }, children: /* @__PURE__ */ jsx16("span", { className: "material-symbols-outlined", style: { color: "#3b82f6", fontSize: "32px" }, children: "lock" }) }),
2384
+ /* @__PURE__ */ jsx16("h2", { style: { fontSize: "24px", fontWeight: 700, color: "#0f172a", marginBottom: "8px" }, children: "Login Required" }),
2385
+ /* @__PURE__ */ jsx16("p", { style: { color: "#64748b", fontSize: "15px", marginBottom: "32px", lineHeight: 1.6 }, children: "Please sign in to access the AdminForge dashboard." }),
2386
+ /* @__PURE__ */ jsx16("form", { action: "/api/auth/signin", method: "GET", children: /* @__PURE__ */ jsx16("button", { className: "adminforge-btn adminforge-btn-primary", style: { width: "100%", height: "48px", fontSize: "16px" }, children: "Sign In to Admin" }) })
2387
+ ] }) });
2388
+ }
2389
+ if (config) {
2390
+ const [segment, actionOrId] = adminParams;
2391
+ if (adminParams.length === 0) return /* @__PURE__ */ jsx16(AdminPage, { config });
2392
+ if (segment === "roles") {
2393
+ if (actionOrId) return /* @__PURE__ */ jsx16(RoleDetailPage, { config, roleId: actionOrId });
2394
+ return /* @__PURE__ */ jsx16(RolesListPage, { config });
2395
+ }
2396
+ if (segment === "settings" && actionOrId === "agent-tokens") {
2397
+ return /* @__PURE__ */ jsx16(AgentTokenPage, { config });
2398
+ }
2399
+ const collection = config.collections.find((c) => c.name === segment);
2400
+ if (collection) {
2401
+ if (actionOrId === "new") return /* @__PURE__ */ jsx16(CollectionFormPage, { config, collection, isNew: true });
2402
+ if (actionOrId === "schema") return /* @__PURE__ */ jsx16(CollectionSchemaPage, { config, collection });
2403
+ if (actionOrId) return /* @__PURE__ */ jsx16(CollectionFormPage, { config, collection, isNew: false, record });
2404
+ return /* @__PURE__ */ jsx16(
2405
+ CollectionListPage,
2406
+ {
2407
+ config,
2408
+ collection,
2409
+ data: data?.data || [],
2410
+ total: data?.total || 0,
2411
+ page: data?.page || 1,
2412
+ pageSize: data?.pageSize || 10
2413
+ }
2414
+ );
2415
+ }
2416
+ return /* @__PURE__ */ jsx16(AdminPage, { config });
2417
+ }
2418
+ return /* @__PURE__ */ jsxs14("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh", background: "#f8fafc" }, children: [
2419
+ /* @__PURE__ */ jsxs14("div", { style: { textAlign: "center" }, children: [
2420
+ /* @__PURE__ */ jsx16("div", { className: "adminforge-spinner", style: { width: "40px", height: "40px", border: "3px solid #e2e8f0", borderTopColor: "#3b82f6", borderRadius: "50%", margin: "0 auto 16px" } }),
2421
+ /* @__PURE__ */ jsx16("p", { style: { color: "#64748b", fontSize: "14px", fontWeight: 500 }, children: "Loading AdminForge..." })
2422
+ ] }),
2423
+ /* @__PURE__ */ jsx16("style", { children: `
2424
+ .adminforge-spinner { animation: spin 0.8s linear infinite; }
2425
+ @keyframes spin { to { transform: rotate(360deg); } }
2426
+ ` })
2427
+ ] });
2428
+ }
2429
+ export {
2430
+ AdminDashboard,
2431
+ AdminForgeProvider,
2432
+ AdminLayout,
2433
+ AdminPage,
2434
+ AgentTokenPage,
2435
+ AuthProvider,
2436
+ CollectionFormPage,
2437
+ CollectionListPage,
2438
+ CollectionSchemaPage,
2439
+ FormEngine,
2440
+ ImageUpload,
2441
+ RichTextEditor,
2442
+ RoleDetailPage,
2443
+ RolesListPage,
2444
+ TableEngine,
2445
+ useAdminForge,
2446
+ useAdminSession
2447
+ };
2448
+ //# sourceMappingURL=ui.js.map