@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
@@ -0,0 +1,215 @@
1
+ import { useEffect, useState, useRef } from "react";
2
+
3
+ interface RelationInputProps {
4
+ name: string;
5
+ to: string;
6
+ relationType?: string;
7
+ value?: string | string[];
8
+ onChange?: (val: string | string[]) => void;
9
+ error?: string;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ export function RelationInput({ name, to, relationType, value, onChange, error, disabled }: RelationInputProps) {
14
+ const [options, setOptions] = useState<any[]>([]);
15
+ const [loading, setLoading] = useState(true);
16
+ const [search, setSearch] = useState("");
17
+ const [open, setOpen] = useState(false);
18
+ const containerRef = useRef<HTMLDivElement>(null);
19
+
20
+ useEffect(() => {
21
+ function handleClickOutside(event: MouseEvent) {
22
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
23
+ setOpen(false);
24
+ }
25
+ }
26
+ document.addEventListener("mousedown", handleClickOutside);
27
+ return () => {
28
+ document.removeEventListener("mousedown", handleClickOutside);
29
+ };
30
+ }, []);
31
+
32
+ const isMulti = relationType === "many-to-many" || relationType === "one-to-many";
33
+
34
+ // Normalize value to array of strings for easier handling
35
+ const rawValueArray = Array.isArray(value) ? value : value ? [value] : [];
36
+ const selectedIds = rawValueArray.map((v: any) => typeof v === "object" && v !== null ? v.id : v).filter(Boolean);
37
+
38
+ useEffect(() => {
39
+ let mounted = true;
40
+ async function fetchOptions() {
41
+ try {
42
+ setLoading(true);
43
+ const res = await fetch(`/api/${to}?pageSize=100`);
44
+ const json = await res.json();
45
+ if (mounted && json.data) {
46
+ const uniqueOptions = [];
47
+ const seen = new Set();
48
+ const getLbl = (opt: any) => opt.name || opt.title || opt.label || opt.id;
49
+ for (const opt of json.data) {
50
+ const lbl = getLbl(opt);
51
+ if (!seen.has(lbl)) {
52
+ seen.add(lbl);
53
+ uniqueOptions.push(opt);
54
+ }
55
+ }
56
+ setOptions(uniqueOptions);
57
+ }
58
+ } catch (err) {
59
+ console.error("Failed to fetch relations", err);
60
+ } finally {
61
+ if (mounted) setLoading(false);
62
+ }
63
+ }
64
+ fetchOptions();
65
+ return () => {
66
+ mounted = false;
67
+ };
68
+ }, [to]);
69
+
70
+ const getLabel = (opt: any) => opt.name || opt.title || opt.label || opt.id;
71
+
72
+ const filteredOptions = options.filter((o) =>
73
+ getLabel(o).toLowerCase().includes(search.toLowerCase())
74
+ );
75
+
76
+ const toggleOption = (id: string) => {
77
+ if (isMulti) {
78
+ const newIds = selectedIds.includes(id)
79
+ ? selectedIds.filter(v => v !== id)
80
+ : [...selectedIds, id];
81
+ onChange?.(newIds);
82
+ } else {
83
+ onChange?.(id);
84
+ setOpen(false);
85
+ setSearch("");
86
+ }
87
+ };
88
+
89
+ const removeOption = (e: React.MouseEvent, id: string) => {
90
+ e.stopPropagation();
91
+ if (disabled) return;
92
+ onChange?.(selectedIds.filter(v => v !== id));
93
+ };
94
+
95
+ return (
96
+ <div className="adminforge-relation" ref={containerRef} style={{ position: "relative" }}>
97
+ {/* Hidden inputs for native form submission */}
98
+ {selectedIds.length === 0 && <input type="hidden" name={name} value="" />}
99
+ {selectedIds.map(id => (
100
+ <input key={id} type="hidden" name={name} value={id} />
101
+ ))}
102
+
103
+ <div
104
+ className={`adminforge-input ${error ? "adminforge-input-error" : ""}`}
105
+ style={{
106
+ position: "relative",
107
+ cursor: disabled ? "not-allowed" : "pointer",
108
+ minHeight: "38px",
109
+ display: "flex",
110
+ alignItems: "center",
111
+ flexWrap: "wrap",
112
+ gap: "4px",
113
+ padding: "4px 8px",
114
+ opacity: disabled ? 0.6 : 1,
115
+ }}
116
+ onClick={() => !disabled && setOpen(!open)}
117
+ >
118
+ {selectedIds.length > 0 ? (
119
+ selectedIds.map(id => {
120
+ const opt = options.find(o => o.id === id);
121
+ const label = opt ? getLabel(opt) : id;
122
+ if (isMulti) {
123
+ return (
124
+ <span key={id} style={{
125
+ background: "#e0e0e0",
126
+ padding: "2px 6px",
127
+ borderRadius: "4px",
128
+ fontSize: "14px",
129
+ display: "flex",
130
+ alignItems: "center",
131
+ gap: "4px"
132
+ }}>
133
+ {label}
134
+ <button
135
+ type="button"
136
+ onClick={(e) => removeOption(e, id)}
137
+ style={{ background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontWeight: "bold" }}
138
+ >&times;</button>
139
+ </span>
140
+ );
141
+ }
142
+ return <span key={id}>{label}</span>;
143
+ })
144
+ ) : (
145
+ <span style={{ color: "#888" }}>Select...</span>
146
+ )}
147
+ </div>
148
+
149
+ {open && (
150
+ <div
151
+ style={{
152
+ position: "absolute",
153
+ zIndex: 10,
154
+ background: "white",
155
+ border: "1px solid #ccc",
156
+ borderRadius: "4px",
157
+ marginTop: "4px",
158
+ width: "100%",
159
+ maxHeight: "250px",
160
+ overflowY: "auto",
161
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)"
162
+ }}
163
+ >
164
+ <div style={{ padding: "8px", borderBottom: "1px solid #eee" }}>
165
+ <input
166
+ type="text"
167
+ placeholder="Search..."
168
+ value={search}
169
+ onChange={(e) => setSearch(e.target.value)}
170
+ onClick={(e) => e.stopPropagation()}
171
+ style={{
172
+ width: "100%",
173
+ padding: "4px 8px",
174
+ border: "1px solid #ccc",
175
+ borderRadius: "4px"
176
+ }}
177
+ />
178
+ </div>
179
+ {loading ? (
180
+ <div style={{ padding: "8px", color: "#888" }}>Loading...</div>
181
+ ) : filteredOptions.length === 0 ? (
182
+ <div style={{ padding: "8px", color: "#888" }}>No options found</div>
183
+ ) : (
184
+ <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
185
+ {filteredOptions.map((opt) => {
186
+ const isSelected = selectedIds.includes(opt.id);
187
+ return (
188
+ <li
189
+ key={opt.id}
190
+ style={{
191
+ padding: "8px 12px",
192
+ cursor: "pointer",
193
+ background: isSelected ? "#f0f0f0" : "transparent",
194
+ display: "flex",
195
+ justifyContent: "space-between"
196
+ }}
197
+ onMouseEnter={(e) => (e.currentTarget.style.background = "#f9f9f9")}
198
+ onMouseLeave={(e) => (e.currentTarget.style.background = isSelected ? "#f0f0f0" : "transparent")}
199
+ onClick={(e) => {
200
+ e.stopPropagation();
201
+ toggleOption(opt.id);
202
+ }}
203
+ >
204
+ {getLabel(opt)}
205
+ {isSelected && isMulti && <span style={{ color: "#417690" }}>✓</span>}
206
+ </li>
207
+ );
208
+ })}
209
+ </ul>
210
+ )}
211
+ </div>
212
+ )}
213
+ </div>
214
+ );
215
+ }