@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,157 @@
1
+ "use client";
2
+
3
+ import type { CollectionDefinition, AccessConfig } from "../../core";
4
+ import Link from "next/link";
5
+
6
+ interface TableEngineProps {
7
+ collection: CollectionDefinition;
8
+ data: unknown[];
9
+ basePath: string;
10
+ role?: string;
11
+ }
12
+
13
+ function hasAccess(access: AccessConfig | undefined, operation: string, role?: string): boolean {
14
+ if (!access) return true;
15
+ const allowed = access[operation as keyof AccessConfig];
16
+ if (!allowed || !Array.isArray(allowed)) return true;
17
+ if (!role) return false;
18
+ return allowed.includes(role);
19
+ }
20
+
21
+ function formatRelativeTime(date: Date) {
22
+ const diff = Date.now() - date.getTime();
23
+ const minutes = Math.floor(diff / 60000);
24
+ const hours = Math.floor(minutes / 60);
25
+ const days = Math.floor(hours / 24);
26
+
27
+ if (isNaN(date.getTime())) return "Invalid date";
28
+ if (minutes < 1) return "Just now";
29
+ if (minutes < 60) return `${minutes}m ago`;
30
+ if (hours < 24) return `${hours}h ago`;
31
+ return `${days}d ago`;
32
+ }
33
+
34
+ function ActivityCell({ record }: { record: Record<string, unknown> }) {
35
+ const createdAtStr = (record.createdAt || record.created_at) as string;
36
+ const updatedAtStr = (record.updatedAt || record.updated_at) as string;
37
+
38
+ const createdAt = createdAtStr ? new Date(createdAtStr) : null;
39
+ const updatedAt = updatedAtStr ? new Date(updatedAtStr) : null;
40
+
41
+ const isUpdated = updatedAt && createdAt && updatedAt.getTime() > createdAt.getTime() + 1000;
42
+ const date = isUpdated ? updatedAt : createdAt;
43
+ const label = isUpdated ? "Updated" : "Created";
44
+
45
+ if (!date) return <span style={{ color: '#94a3b8', fontSize: '13px' }}>No activity</span>;
46
+
47
+ return (
48
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', minWidth: '120px' }}>
49
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' }}>
50
+ <span
51
+ style={{
52
+ fontSize: '10px',
53
+ padding: '1px 6px',
54
+ borderRadius: '10px',
55
+ background: isUpdated ? '#fef3c7' : '#dcfce7',
56
+ color: isUpdated ? '#92400e' : '#166534',
57
+ fontWeight: 700,
58
+ textTransform: 'uppercase'
59
+ }}
60
+ >
61
+ {label}
62
+ </span>
63
+ <span style={{ fontWeight: 500, color: 'var(--color-text)' }}>{formatRelativeTime(date)}</span>
64
+ </div>
65
+ <div style={{ fontSize: '11px', color: '#64748b' }}>
66
+ by <span style={{ color: '#0f172a', fontWeight: 600 }}>Admin</span>
67
+ </div>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ export function TableEngine({ collection, data, basePath, role }: TableEngineProps) {
73
+ const records = data as Record<string, unknown>[];
74
+ const fieldKeys = Object.keys(collection.fields);
75
+ const displayKeys = ["id", ...fieldKeys.slice(0, 4)]; // Show up to 4 fields + ID + Activity
76
+ const canDelete = hasAccess(collection.access, "delete", role);
77
+ const canUpdate = hasAccess(collection.access, "update", role);
78
+
79
+ return (
80
+ <div className="adminforge-table-wrapper">
81
+ <table className="adminforge-table">
82
+ <thead>
83
+ <tr>
84
+ {displayKeys.map((key) => <th key={key}>{key}</th>)}
85
+ <th>Activity</th>
86
+ <th style={{ textAlign: 'right', paddingRight: '20px' }}>Actions</th>
87
+ </tr>
88
+ </thead>
89
+ <tbody>
90
+ {records.map((record) => (
91
+ <tr key={record.id as string}>
92
+ {displayKeys.map((key) => (
93
+ <td key={key}>
94
+ {key === 'id' ? (
95
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
96
+ <span className="adminforge-id-badge" title={String(record[key])}>
97
+ {String(record[key]).substring(0, 8)}...
98
+ </span>
99
+ <button
100
+ type="button"
101
+ className="adminforge-btn-icon"
102
+ style={{ width: '24px', height: '24px', minWidth: '24px' }}
103
+ title="Copy ID"
104
+ onClick={(e) => {
105
+ e.stopPropagation();
106
+ navigator.clipboard.writeText(String(record[key]));
107
+ }}
108
+ >
109
+ <span className="material-symbols-outlined" style={{ fontSize: '14px' }}>content_copy</span>
110
+ </button>
111
+ </div>
112
+ ) : (
113
+ <div style={{ maxWidth: '140px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
114
+ {String(record[key] ?? "")}
115
+ </div>
116
+ )}
117
+ </td>
118
+ ))}
119
+ <td>
120
+ <ActivityCell record={record} />
121
+ </td>
122
+ <td style={{ textAlign: 'right', paddingRight: '20px' }}>
123
+ <div style={{ display: 'flex', gap: '4px', justifyContent: 'flex-end' }}>
124
+ {canUpdate && (
125
+ <Link href={`${basePath}/${record.id}`} className="adminforge-btn-icon" title="Edit">
126
+ <span className="material-symbols-outlined" style={{ fontSize: '20px' }}>edit</span>
127
+ </Link>
128
+ )}
129
+ {canDelete && (
130
+ <button
131
+ className="adminforge-btn-icon adminforge-btn-icon-danger"
132
+ title="Delete"
133
+ onClick={async () => {
134
+ if (confirm("Are you sure you want to delete this item?")) {
135
+ const res = await fetch(`/api/${collection.name}/${record.id}`, { method: 'DELETE' });
136
+ if (res.ok) {
137
+ window.location.reload();
138
+ } else {
139
+ const err = await res.json();
140
+ alert(err.error || "Failed to delete item");
141
+ }
142
+ }
143
+ }}
144
+ >
145
+ <span className="material-symbols-outlined" style={{ fontSize: '20px' }}>delete</span>
146
+ </button>
147
+ )}
148
+ </div>
149
+ </td>
150
+ </tr>
151
+ ))}
152
+ {records.length === 0 && <tr><td colSpan={displayKeys.length + 1}>No records found</td></tr>}
153
+ </tbody>
154
+ </table>
155
+ </div>
156
+ );
157
+ }
package/src/ui.ts ADDED
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ export * from "./ui/index.js";
3
+
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "baseUrl": ".",
7
+ "jsx": "react-jsx"
8
+ },
9
+ "include": ["src"]
10
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ const isDev = process.env.DEV === "true";
4
+
5
+ export default defineConfig({
6
+ entry: {
7
+ index: "src/index.ts",
8
+ ui: "src/ui.ts",
9
+ next: "src/next.ts",
10
+ auth: "src/auth/index.ts",
11
+ "auth-client": "src/auth/provider.tsx",
12
+ styles: "src/styles/adminforge.css",
13
+ },
14
+
15
+ format: ["esm", "cjs"],
16
+ dts: !isDev,
17
+ sourcemap: true,
18
+ clean: !isDev,
19
+ external: [
20
+ "react",
21
+ "react-dom",
22
+ "next",
23
+ "next-auth",
24
+ "@auth/core",
25
+ "@prisma/client",
26
+ "zod",
27
+ "jsonwebtoken",
28
+ "@tanstack/react-table",
29
+ "@tiptap/core",
30
+ "@tiptap/react",
31
+ "@tiptap/pm",
32
+ "@tiptap/starter-kit",
33
+ "@tiptap/extension-bubble-menu",
34
+ "@tiptap/extension-floating-menu",
35
+ "@tiptap/extension-highlight",
36
+ "@tiptap/extension-horizontal-rule",
37
+ "@tiptap/extension-image",
38
+ "@tiptap/extension-link",
39
+ "@tiptap/extension-placeholder",
40
+ "@tiptap/extension-subscript",
41
+ "@tiptap/extension-superscript",
42
+ "@tiptap/extension-task-item",
43
+ "@tiptap/extension-task-list",
44
+ "@tiptap/extension-text-align",
45
+ "@tiptap/extension-typography",
46
+ "@tiptap/extension-underline",
47
+ "prosemirror-view",
48
+ "prosemirror-state",
49
+ "prosemirror-model",
50
+ "prosemirror-transform",
51
+ "prosemirror-keymap",
52
+ ],
53
+ splitting: false,
54
+ });