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