@jhits/dashboard 0.0.5 → 0.0.7
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.
- package/package.json +51 -22
- package/src/api/pluginRouter.ts +9 -1
- package/src/config.ts +116 -145
- package/src/empty-loader.js +4 -0
- package/src/empty.js +2 -0
- package/src/index.client.tsx +16 -0
- package/src/index.server.tsx +63 -0
- package/src/index.tsx +14 -67
- package/src/lib/modules-config.ts +0 -2
- package/src/lib/plugin-registry.tsx +53 -32
- package/src/api/README.md +0 -72
- package/src/assets/public/Logo_JH_black.jpg +0 -0
- package/src/assets/public/Logo_JH_black.png +0 -0
- package/src/assets/public/Logo_JH_white.png +0 -0
- package/src/assets/public/noimagefound.jpg +0 -0
package/package.json
CHANGED
|
@@ -1,46 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhits/dashboard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A comprehensive dashboard system built to manage custom built websites - plugin based SaaS system.",
|
|
5
5
|
"main": "./src/index.tsx",
|
|
6
6
|
"types": "./src/index.tsx",
|
|
7
|
+
"browser": {
|
|
8
|
+
"./src/lib/mongodb.ts": false,
|
|
9
|
+
"./src/lib/auth.ts": false,
|
|
10
|
+
"./src/server.ts": false,
|
|
11
|
+
"./src/index.server.tsx": false,
|
|
12
|
+
"./src/api/masterRouter.ts": false,
|
|
13
|
+
"./src/api/pluginRouter.ts": false,
|
|
14
|
+
"mongodb": false,
|
|
15
|
+
"bcrypt": false,
|
|
16
|
+
"jsonwebtoken": false,
|
|
17
|
+
"server-only": false
|
|
18
|
+
},
|
|
7
19
|
"exports": {
|
|
8
20
|
".": {
|
|
9
21
|
"types": "./src/index.tsx",
|
|
22
|
+
"import": "./src/index.tsx",
|
|
10
23
|
"default": "./src/index.tsx"
|
|
11
24
|
},
|
|
25
|
+
"./lib/*": {
|
|
26
|
+
"types": "./src/lib/*.ts",
|
|
27
|
+
"default": "./src/lib/*.ts"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./src/index.client.tsx",
|
|
31
|
+
"import": "./src/index.client.tsx",
|
|
32
|
+
"default": "./src/index.client.tsx"
|
|
33
|
+
},
|
|
34
|
+
"./server": {
|
|
35
|
+
"types": "./src/index.server.tsx",
|
|
36
|
+
"import": "./src/index.server.tsx",
|
|
37
|
+
"default": "./src/index.server.tsx"
|
|
38
|
+
},
|
|
12
39
|
"./config": {
|
|
13
40
|
"types": "./src/config.ts",
|
|
14
41
|
"default": "./src/config.ts"
|
|
15
42
|
},
|
|
16
|
-
"./server": {
|
|
17
|
-
"types": "./src/server.ts",
|
|
18
|
-
"default": "./src/server.ts",
|
|
19
|
-
"node": "./src/server.ts"
|
|
20
|
-
},
|
|
21
43
|
"./catch-all": {
|
|
22
44
|
"types": "./src/app/[locale]/catch-all/page.tsx",
|
|
23
45
|
"default": "./src/app/[locale]/catch-all/page.tsx"
|
|
24
46
|
},
|
|
25
|
-
"./
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"default": "./src/
|
|
47
|
+
"./src/empty-loader.js": {
|
|
48
|
+
"default": "./src/empty-loader.js"
|
|
49
|
+
},
|
|
50
|
+
"./src/empty.js": {
|
|
51
|
+
"default": "./src/empty.js"
|
|
30
52
|
}
|
|
31
53
|
},
|
|
32
54
|
"scripts": {
|
|
33
|
-
"lint": "eslint"
|
|
55
|
+
"lint": "eslint",
|
|
56
|
+
"type-check": "tsc --noEmit",
|
|
57
|
+
"type-check:all": "bash ../check-types.sh"
|
|
34
58
|
},
|
|
35
59
|
"dependencies": {
|
|
36
|
-
"@jhits/plugin-
|
|
37
|
-
"@jhits/plugin-content": "^0.0.1",
|
|
38
|
-
"@jhits/plugin-dep": "^0.0.1",
|
|
39
|
-
"@jhits/plugin-images": "^0.0.1",
|
|
40
|
-
"@jhits/plugin-telemetry": "^0.0.1",
|
|
41
|
-
"@jhits/plugin-users": "^0.0.1",
|
|
42
|
-
"@jhits/plugin-website": "^0.0.1",
|
|
43
|
-
"@jhits/plugin-newsletter": "^0.0.1",
|
|
60
|
+
"@jhits/plugin-core": "^0.0.1",
|
|
44
61
|
"@types/jsonwebtoken": "^9.0.10",
|
|
45
62
|
"bcrypt": "^6.0.0",
|
|
46
63
|
"framer-motion": "^12.23.26",
|
|
@@ -54,7 +71,14 @@
|
|
|
54
71
|
"peerDependencies": {
|
|
55
72
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
56
73
|
"react": "^18.0.0 || ^19.0.0",
|
|
57
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
74
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
75
|
+
"@jhits/plugin-blog": "*",
|
|
76
|
+
"@jhits/plugin-content": "*",
|
|
77
|
+
"@jhits/plugin-images": "*",
|
|
78
|
+
"@jhits/plugin-users": "*",
|
|
79
|
+
"@jhits/plugin-website": "*",
|
|
80
|
+
"@jhits/plugin-newsletter": "*",
|
|
81
|
+
"@jhits/plugin-telemetry": "*"
|
|
58
82
|
},
|
|
59
83
|
"devDependencies": {
|
|
60
84
|
"@tailwindcss/postcss": "^4",
|
|
@@ -71,7 +95,12 @@
|
|
|
71
95
|
"typescript": "^5"
|
|
72
96
|
},
|
|
73
97
|
"files": [
|
|
74
|
-
"src",
|
|
98
|
+
"src/**/*.{ts,tsx,js,jsx,css,json,svg}",
|
|
99
|
+
"!src/**/*.md",
|
|
100
|
+
"!src/**/README.md",
|
|
101
|
+
"!README.md",
|
|
102
|
+
"package.json",
|
|
103
|
+
"empty.js",
|
|
75
104
|
"next.config.ts",
|
|
76
105
|
"postcss.config.mjs",
|
|
77
106
|
"tailwind.config.ts"
|
package/src/api/pluginRouter.ts
CHANGED
|
@@ -119,11 +119,19 @@ export async function handlePluginApi(
|
|
|
119
119
|
getDb: config.mongoClient ? createGetDb(config.mongoClient) : undefined,
|
|
120
120
|
// Add getUserId - uses NextAuth session token (works with or without jwtSecret)
|
|
121
121
|
getUserId: (req: NextRequest) => getUserIdFromRequest(req, config.jwtSecret),
|
|
122
|
-
// authOptions is
|
|
122
|
+
// Explicitly ensure authOptions is passed through for plugin-users
|
|
123
|
+
authOptions: config.authOptions,
|
|
123
124
|
// emailConfig and baseUrl are already in config, but ensure they're passed through for plugin-newsletter
|
|
124
125
|
emailConfig: config.emailConfig,
|
|
125
126
|
baseUrl: config.baseUrl,
|
|
126
127
|
};
|
|
128
|
+
|
|
129
|
+
// Debug logging for plugin-users to verify authOptions are present
|
|
130
|
+
if (normalizedId === 'plugin-users' && !adaptedConfig.authOptions) {
|
|
131
|
+
console.warn('[PluginRouter] WARNING: authOptions missing for plugin-users');
|
|
132
|
+
console.warn('[PluginRouter] Config keys:', Object.keys(config));
|
|
133
|
+
console.warn('[PluginRouter] authOptions in config:', !!config.authOptions);
|
|
134
|
+
}
|
|
127
135
|
|
|
128
136
|
return await handler(req, path, adaptedConfig);
|
|
129
137
|
|
package/src/config.ts
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
// packages/jhits-dashboard/src/config.ts
|
|
2
2
|
import type { NextConfig } from "next";
|
|
3
|
-
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
3
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Automatically creates a catch-all route that handles dashboard routes
|
|
8
|
-
* This runs at build time when withJhitsDashboard() is called
|
|
9
|
-
* No dashboard folder needed - routes are handled via catch-all at [locale] level
|
|
10
8
|
*/
|
|
11
9
|
async function ensureDashboardRoutes() {
|
|
12
10
|
try {
|
|
13
|
-
// Find the host app directory (where next.config.ts is)
|
|
14
11
|
let appDir = process.cwd();
|
|
15
|
-
const possiblePaths = [
|
|
16
|
-
appDir,
|
|
17
|
-
join(appDir, '..'),
|
|
18
|
-
join(appDir, '..', '..'),
|
|
19
|
-
];
|
|
12
|
+
const possiblePaths = [appDir, join(appDir, '..'), join(appDir, '..', '..')];
|
|
20
13
|
|
|
21
14
|
for (const basePath of possiblePaths) {
|
|
22
|
-
|
|
23
|
-
if (existsSync(configPath)) {
|
|
15
|
+
if (existsSync(join(basePath, 'next.config.ts'))) {
|
|
24
16
|
appDir = basePath;
|
|
25
17
|
break;
|
|
26
18
|
}
|
|
@@ -30,17 +22,11 @@ async function ensureDashboardRoutes() {
|
|
|
30
22
|
const catchAllDir = join(localeDir, '[...path]');
|
|
31
23
|
const catchAllPath = join(catchAllDir, 'page.tsx');
|
|
32
24
|
|
|
33
|
-
//
|
|
25
|
+
// If it exists, try to inject the dashboard handler
|
|
34
26
|
if (existsSync(catchAllPath)) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (existingContent.includes('@jhits/dashboard') || existingContent.includes('DashboardRouter')) {
|
|
39
|
-
// Already set up, skip
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// If it exists but doesn't handle dashboard, we need to modify it
|
|
43
|
-
// Read the file and prepend dashboard handling
|
|
27
|
+
const existingContent = readFileSync(catchAllPath, 'utf8');
|
|
28
|
+
if (existingContent.includes('@jhits/dashboard') || existingContent.includes('DashboardRouter')) return;
|
|
29
|
+
|
|
44
30
|
const dashboardHandler = `
|
|
45
31
|
// Dashboard route handling (auto-added by @jhits/dashboard)
|
|
46
32
|
if (path.length > 0 && path[0] === 'dashboard') {
|
|
@@ -53,178 +39,163 @@ async function ensureDashboardRoutes() {
|
|
|
53
39
|
);
|
|
54
40
|
}
|
|
55
41
|
`;
|
|
56
|
-
// Insert dashboard handling at the beginning of the function body
|
|
57
42
|
const modifiedContent = existingContent.replace(
|
|
58
43
|
/(export default async function \w+\([^)]+\) \{[\s\S]*?const resolvedParams = await props\.params;[\s\S]*?const path = resolvedParams\.path \|\| \[\];)/,
|
|
59
44
|
`$1${dashboardHandler}`
|
|
60
45
|
);
|
|
61
|
-
|
|
62
|
-
if (modifiedContent !== existingContent) {
|
|
63
|
-
fs.writeFileSync(catchAllPath, modifiedContent);
|
|
64
|
-
} else {
|
|
65
|
-
// If replacement didn't work, append at a safe location
|
|
66
|
-
const safeInsertPoint = existingContent.indexOf('const path = resolvedParams.path');
|
|
67
|
-
if (safeInsertPoint > -1) {
|
|
68
|
-
const before = existingContent.substring(0, safeInsertPoint);
|
|
69
|
-
const after = existingContent.substring(safeInsertPoint);
|
|
70
|
-
const newContent = before + dashboardHandler.trim() + '\n ' + after;
|
|
71
|
-
fs.writeFileSync(catchAllPath, newContent);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
46
|
+
writeFileSync(catchAllPath, modifiedContent);
|
|
74
47
|
return;
|
|
75
48
|
}
|
|
76
49
|
|
|
77
|
-
// Create new catch-all
|
|
50
|
+
// Create new catch-all if none exists
|
|
78
51
|
mkdirSync(catchAllDir, { recursive: true });
|
|
79
|
-
writeFileSync(catchAllPath, `// Auto-generated by @jhits/dashboard - do not edit manually
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} catch (error) {
|
|
83
|
-
// Silently fail - routes might already exist or be manually created
|
|
52
|
+
writeFileSync(catchAllPath, `// Auto-generated by @jhits/dashboard - do not edit manually\nexport { default } from '@jhits/dashboard/catch-all';\n`);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.warn('[@jhits/dashboard] Could not auto-generate dashboard routes:', e);
|
|
84
55
|
}
|
|
85
56
|
}
|
|
86
57
|
|
|
87
58
|
/**
|
|
88
|
-
*
|
|
89
|
-
* Reads dependencies and devDependencies to build transpilePackages list
|
|
90
|
-
*
|
|
91
|
-
* This function finds the app's package.json by looking for next.config.ts
|
|
92
|
-
* in the same directory, ensuring we read from the correct location in monorepos
|
|
59
|
+
* Finds all @jhits/* packages to ensure they are transpiled
|
|
93
60
|
*/
|
|
94
61
|
function findJhitsPackages(): string[] {
|
|
95
62
|
try {
|
|
96
|
-
// Find the app directory (where next.config.ts is located)
|
|
97
63
|
const appDir = process.cwd();
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
join(appDir, '..'),
|
|
101
|
-
join(appDir, '..', '..'),
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
let packageJsonPath: string | null = null;
|
|
105
|
-
|
|
106
|
-
for (const basePath of possiblePaths) {
|
|
107
|
-
const configPath = join(basePath, 'next.config.ts');
|
|
108
|
-
const pkgPath = join(basePath, 'package.json');
|
|
109
|
-
if (existsSync(configPath) && existsSync(pkgPath)) {
|
|
110
|
-
packageJsonPath = pkgPath;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Fallback to process.cwd() if we can't find next.config.ts
|
|
116
|
-
if (!packageJsonPath) {
|
|
117
|
-
packageJsonPath = join(process.cwd(), 'package.json');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!existsSync(packageJsonPath)) {
|
|
121
|
-
// Fallback to wildcard if package.json not found
|
|
122
|
-
return ['@jhits'];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Use readFileSync instead of require() to avoid module resolution issues
|
|
126
|
-
const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
|
|
127
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
128
|
-
|
|
129
|
-
const allDeps = {
|
|
130
|
-
...(packageJson.dependencies || {}),
|
|
131
|
-
...(packageJson.devDependencies || {}),
|
|
132
|
-
};
|
|
64
|
+
const packageJsonPath = join(appDir, 'package.json');
|
|
65
|
+
if (!existsSync(packageJsonPath)) return ['@jhits'];
|
|
133
66
|
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Always include the base @jhits pattern for any other packages
|
|
140
|
-
return jhitsPackages.length > 0 ? jhitsPackages : ['@jhits'];
|
|
141
|
-
} catch (error) {
|
|
142
|
-
// Fallback to wildcard on any error
|
|
143
|
-
console.warn('[withJhitsDashboard] Could not read package.json, using @jhits wildcard');
|
|
144
|
-
return ['@jhits'];
|
|
145
|
-
}
|
|
67
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
68
|
+
const allDeps = { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) };
|
|
69
|
+
return Object.keys(allDeps).filter(pkg => pkg.startsWith('@jhits/'));
|
|
70
|
+
} catch { return ['@jhits']; }
|
|
146
71
|
}
|
|
147
72
|
|
|
148
|
-
/**
|
|
149
|
-
* JHITS Dashboard Plugin Configuration
|
|
150
|
-
*
|
|
151
|
-
* Usage:
|
|
152
|
-
* import { withJhitsDashboard } from '@jhits/dashboard/config';
|
|
153
|
-
* export default withJhitsDashboard(nextConfig);
|
|
154
|
-
*
|
|
155
|
-
* This automatically sets up all dashboard routes - no manual setup needed!
|
|
156
|
-
*/
|
|
157
73
|
export function withJhitsDashboard(nextConfig: NextConfig = {}): NextConfig {
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (typeof window === 'undefined') {
|
|
162
|
-
try {
|
|
163
|
-
ensureDashboardRoutes();
|
|
164
|
-
// ensureUserManagementRoutes(); // Disabled - routes are handled by plugin-users via plugin router
|
|
165
|
-
} catch (error) {
|
|
166
|
-
// Ignore errors - routes might already exist
|
|
167
|
-
}
|
|
74
|
+
// Only run file system modifications on the server during initialization
|
|
75
|
+
if (typeof window === 'undefined' && process.env.NEXT_PHASE !== 'phase-production-build') {
|
|
76
|
+
ensureDashboardRoutes().catch(() => { });
|
|
168
77
|
}
|
|
169
78
|
|
|
79
|
+
const emptyFile = "@jhits/dashboard/src/empty.js";
|
|
80
|
+
// Use package-relative path for the loader - Turbopack can resolve this
|
|
81
|
+
// This works both in development (monorepo) and when installed as a package
|
|
82
|
+
const emptyLoaderPath = "@jhits/dashboard/src/empty-loader.js";
|
|
83
|
+
|
|
170
84
|
return {
|
|
171
85
|
...nextConfig,
|
|
172
86
|
|
|
173
|
-
// 1. AUTO-TRANSPILE: Dynamically find all @jhits/* packages from package.json
|
|
174
|
-
// This ensures Turbopack knows exactly which workspace folders to link
|
|
175
87
|
transpilePackages: [
|
|
176
88
|
...(nextConfig.transpilePackages || []),
|
|
177
89
|
...findJhitsPackages()
|
|
178
90
|
],
|
|
179
91
|
|
|
92
|
+
turbopack: {
|
|
93
|
+
...nextConfig.turbopack,
|
|
94
|
+
rules: {
|
|
95
|
+
...nextConfig.turbopack?.rules,
|
|
96
|
+
// Handle .md files by treating them as empty JavaScript modules
|
|
97
|
+
// This prevents "Unknown module type" errors for README.md files
|
|
98
|
+
// According to Next.js docs: pattern should be *.md with as: *.js
|
|
99
|
+
// Using our custom empty-loader to return empty module
|
|
100
|
+
// Loaders can be strings (package names or file paths)
|
|
101
|
+
'*.md': {
|
|
102
|
+
loaders: [emptyLoaderPath],
|
|
103
|
+
as: '*.js',
|
|
104
|
+
},
|
|
105
|
+
'*.mdx': {
|
|
106
|
+
loaders: [emptyLoaderPath],
|
|
107
|
+
as: '*.js',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
resolveAlias: {
|
|
111
|
+
...nextConfig.turbopack?.resolveAlias,
|
|
112
|
+
// CRITICAL: We only alias specific toxic libraries.
|
|
113
|
+
// We DO NOT alias 'fs', 'path', or 'child_process' here because
|
|
114
|
+
// it breaks the Next.js internal build tools (WASM/SWC).
|
|
115
|
+
"mongodb": emptyFile,
|
|
116
|
+
"bcrypt": emptyFile,
|
|
117
|
+
"nodemailer": emptyFile,
|
|
118
|
+
// "dns": emptyFile,
|
|
119
|
+
"server-only": emptyFile,
|
|
120
|
+
// Shield the auth logic from the client
|
|
121
|
+
"@jhits/dashboard/lib/auth": emptyFile,
|
|
122
|
+
// "wasi_snapshot_preview1": emptyFile,
|
|
123
|
+
// Alias specific .md files to empty file (Turbopack doesn't support wildcards in resolveAlias)
|
|
124
|
+
// Add aliases for known README.md locations in @jhits packages
|
|
125
|
+
"README.md": emptyFile,
|
|
126
|
+
"@jhits/dashboard/README.md": emptyFile,
|
|
127
|
+
"@jhits/plugin-blog/README.md": emptyFile,
|
|
128
|
+
"@jhits/plugin-content/README.md": emptyFile,
|
|
129
|
+
"@jhits/plugin-images/README.md": emptyFile,
|
|
130
|
+
"@jhits/plugin-users/README.md": emptyFile,
|
|
131
|
+
"@jhits/plugin-website/README.md": emptyFile,
|
|
132
|
+
"@jhits/plugin-newsletter/README.md": emptyFile,
|
|
133
|
+
"@jhits/plugin-telemetry/README.md": emptyFile,
|
|
134
|
+
"@jhits/plugin-dep/README.md": emptyFile,
|
|
135
|
+
"@jhits/plugin-core/README.md": emptyFile,
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
180
139
|
experimental: {
|
|
181
140
|
...nextConfig.experimental,
|
|
182
|
-
// 2. MONOREPO RESOLUTION: This is the secret sauce.
|
|
183
|
-
// It allows Next.js to follow symlinks in the pnpm workspace
|
|
184
|
-
// even if they aren't explicitly listed.
|
|
185
141
|
externalDir: true,
|
|
186
142
|
},
|
|
187
143
|
|
|
188
|
-
// Exclude server-only third-party packages from client bundles
|
|
189
|
-
// Note: @jhits/* packages should NOT be here - they're in transpilePackages instead
|
|
190
|
-
// Adding them here would conflict with transpilePackages in Turbopack
|
|
191
144
|
serverExternalPackages: [
|
|
192
145
|
...(nextConfig.serverExternalPackages || []),
|
|
193
|
-
'mongodb',
|
|
194
|
-
'bcrypt',
|
|
195
|
-
'bcryptjs',
|
|
196
|
-
'jsonwebtoken',
|
|
197
|
-
'nodemailer'
|
|
146
|
+
'mongodb', 'bcrypt', 'bcryptjs', 'jsonwebtoken', 'nodemailer'
|
|
198
147
|
],
|
|
199
148
|
|
|
200
149
|
webpack: (config, { isServer }) => {
|
|
201
|
-
// Ensure Node.js modules are not bundled for client
|
|
202
150
|
if (!isServer) {
|
|
151
|
+
// Webpack is safer for building "fallbacks" than Turbopack's global alias
|
|
203
152
|
config.resolve.fallback = {
|
|
204
153
|
...config.resolve.fallback,
|
|
205
154
|
fs: false,
|
|
206
|
-
path: false,
|
|
207
|
-
'fs/promises': false,
|
|
208
|
-
child_process: false,
|
|
209
155
|
net: false,
|
|
210
156
|
tls: false,
|
|
211
|
-
|
|
157
|
+
child_process: false,
|
|
158
|
+
os: false,
|
|
212
159
|
};
|
|
213
|
-
|
|
214
|
-
// Prevent server-only plugins from being bundled into client
|
|
215
|
-
// plugin-dep is server-only and should never be imported by client code
|
|
216
|
-
config.externals = config.externals || [];
|
|
217
|
-
if (Array.isArray(config.externals)) {
|
|
218
|
-
config.externals.push('@jhits/plugin-dep');
|
|
219
|
-
} else if (typeof config.externals === 'object') {
|
|
220
|
-
config.externals['@jhits/plugin-dep'] = '@jhits/plugin-dep';
|
|
221
|
-
}
|
|
222
160
|
}
|
|
161
|
+
|
|
162
|
+
// Ignore .md files (README.md, etc.) from node_modules
|
|
163
|
+
// This prevents Next.js from trying to process them as modules
|
|
164
|
+
try {
|
|
165
|
+
const webpack = require('webpack');
|
|
166
|
+
config.plugins = config.plugins || [];
|
|
167
|
+
// Ignore all .md and .mdx files completely
|
|
168
|
+
config.plugins.push(
|
|
169
|
+
new webpack.IgnorePlugin({
|
|
170
|
+
resourceRegExp: /\.md$/,
|
|
171
|
+
}),
|
|
172
|
+
new webpack.IgnorePlugin({
|
|
173
|
+
resourceRegExp: /\.mdx$/,
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
// webpack might not be available in some contexts
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add resolve alias for .md files to prevent module resolution errors
|
|
181
|
+
config.resolve.alias = {
|
|
182
|
+
...config.resolve.alias,
|
|
183
|
+
'README.md': false,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Add a rule to handle .md files as empty modules if they somehow get imported
|
|
187
|
+
// This is a fallback in case IgnorePlugin doesn't catch everything
|
|
188
|
+
config.module = config.module || {};
|
|
189
|
+
config.module.rules = config.module.rules || [];
|
|
190
|
+
// Insert at the beginning to catch .md files before other rules
|
|
191
|
+
config.module.rules.unshift({
|
|
192
|
+
test: /\.mdx?$/,
|
|
193
|
+
use: {
|
|
194
|
+
loader: require.resolve('./empty-loader.js'),
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
223
198
|
return config;
|
|
224
199
|
},
|
|
225
|
-
|
|
226
|
-
// Routes are automatically created and managed by the plugin
|
|
227
|
-
// The dashboard folder is completely transparent - you never need to touch it
|
|
228
|
-
// Just use withJhitsDashboard() and everything works!
|
|
229
200
|
};
|
|
230
201
|
}
|
package/src/empty.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Main export file for the dashboard plugin - CLIENT-SAFE ONLY
|
|
2
|
+
// This file should NEVER import or export server-only code
|
|
3
|
+
|
|
4
|
+
'use client';
|
|
5
|
+
|
|
6
|
+
// Client-safe components only
|
|
7
|
+
export { PluginRegistry } from './lib/plugin-registry';
|
|
8
|
+
export { PluginNotFound } from './components/PluginNotFound';
|
|
9
|
+
export { WebsiteProvider, useWebsite } from './lib/website-context';
|
|
10
|
+
export type { WebsiteInfo } from './lib/website-context';
|
|
11
|
+
export { Providers, AuthGuard } from './components/Providers';
|
|
12
|
+
|
|
13
|
+
// Client components
|
|
14
|
+
export { default as Sidebar } from './components/dashboard/Sidebar';
|
|
15
|
+
export { default as Topbar } from './components/dashboard/Topbar';
|
|
16
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Server-only exports for the dashboard plugin
|
|
2
|
+
// This file contains all server-side functionality
|
|
3
|
+
|
|
4
|
+
import 'server-only';
|
|
5
|
+
|
|
6
|
+
// Router components - server-only
|
|
7
|
+
import DashboardLayout from './app/[locale]/dashboard/layout';
|
|
8
|
+
import DashboardHome from './app/[locale]/dashboard/page';
|
|
9
|
+
import DashboardPreferences from './app/[locale]/dashboard/preferences/page';
|
|
10
|
+
import DashboardProfile from './app/[locale]/dashboard/profile/page';
|
|
11
|
+
import DashboardPluginRoute from './app/[locale]/dashboard/[...pluginRoute]/page';
|
|
12
|
+
|
|
13
|
+
export async function DashboardRouter({
|
|
14
|
+
path,
|
|
15
|
+
params
|
|
16
|
+
}: {
|
|
17
|
+
path: string[];
|
|
18
|
+
params: Promise<{ locale: string }>;
|
|
19
|
+
}) {
|
|
20
|
+
const resolvedParams = await params;
|
|
21
|
+
const locale = resolvedParams.locale;
|
|
22
|
+
|
|
23
|
+
// Get the actual route (first segment of path)
|
|
24
|
+
const route = path.length === 0 ? 'home' : path[0];
|
|
25
|
+
|
|
26
|
+
// Handle different dashboard routes
|
|
27
|
+
switch (route) {
|
|
28
|
+
case 'preferences':
|
|
29
|
+
return <DashboardPreferences />;
|
|
30
|
+
case 'profile':
|
|
31
|
+
return <DashboardProfile />;
|
|
32
|
+
case 'home':
|
|
33
|
+
case '':
|
|
34
|
+
return <DashboardHome />;
|
|
35
|
+
default:
|
|
36
|
+
// This is a plugin route - pass the full path as pluginRoute
|
|
37
|
+
return <DashboardPluginRoute params={Promise.resolve({
|
|
38
|
+
locale,
|
|
39
|
+
pluginRoute: path
|
|
40
|
+
})} />;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Layout wrapper
|
|
45
|
+
export function DashboardRouterLayout({ children }: { children: React.ReactNode }) {
|
|
46
|
+
return <DashboardLayout>{children}</DashboardLayout>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Server-only components
|
|
50
|
+
export { default as DashboardCatchAll } from './components/DashboardCatchAll';
|
|
51
|
+
|
|
52
|
+
// Server-only config and utilities
|
|
53
|
+
export { withJhitsDashboard } from './config';
|
|
54
|
+
export { getDashboardMessages } from './i18n/translations';
|
|
55
|
+
export { PLATFORM_MODULES } from './lib/modules-config';
|
|
56
|
+
|
|
57
|
+
// Server-only lib exports (for advanced usage)
|
|
58
|
+
export { authOptions } from './lib/auth';
|
|
59
|
+
export { default as clientPromise } from './lib/mongodb';
|
|
60
|
+
|
|
61
|
+
// API router exports
|
|
62
|
+
export { handlePluginApi, type PluginRouterConfig } from './api/pluginRouter';
|
|
63
|
+
export { handleDashboardApi, createNextRequestFromRequest } from './api/masterRouter';
|
package/src/index.tsx
CHANGED
|
@@ -1,69 +1,16 @@
|
|
|
1
1
|
// Main export file for the dashboard plugin
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
params: Promise<{ locale: string }>;
|
|
17
|
-
}) {
|
|
18
|
-
const resolvedParams = await params;
|
|
19
|
-
const locale = resolvedParams.locale;
|
|
20
|
-
|
|
21
|
-
// Get the actual route (first segment of path)
|
|
22
|
-
const route = path.length === 0 ? 'home' : path[0];
|
|
23
|
-
|
|
24
|
-
// Handle different dashboard routes
|
|
25
|
-
switch (route) {
|
|
26
|
-
case 'preferences':
|
|
27
|
-
return <DashboardPreferences />;
|
|
28
|
-
case 'profile':
|
|
29
|
-
return <DashboardProfile />;
|
|
30
|
-
case 'home':
|
|
31
|
-
case '':
|
|
32
|
-
return <DashboardHome />;
|
|
33
|
-
default:
|
|
34
|
-
// This is a plugin route - pass the full path as pluginRoute
|
|
35
|
-
return <DashboardPluginRoute params={Promise.resolve({
|
|
36
|
-
locale,
|
|
37
|
-
pluginRoute: path
|
|
38
|
-
})} />;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Layout wrapper
|
|
43
|
-
export function DashboardRouterLayout({ children }: { children: React.ReactNode }) {
|
|
44
|
-
return <DashboardLayout>{children}</DashboardLayout>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Components (for advanced usage)
|
|
48
|
-
export { default as Sidebar } from './components/dashboard/Sidebar';
|
|
49
|
-
export { default as Topbar } from './components/dashboard/Topbar';
|
|
50
|
-
export { PluginNotFound } from './components/PluginNotFound';
|
|
51
|
-
export { Providers, AuthGuard } from './components/Providers';
|
|
52
|
-
|
|
53
|
-
// Plugin system
|
|
54
|
-
export { PluginRegistry } from './lib/plugin-registry';
|
|
55
|
-
export { PLATFORM_MODULES } from './lib/modules-config';
|
|
56
|
-
|
|
57
|
-
// Config
|
|
58
|
-
export { withJhitsDashboard } from './config';
|
|
59
|
-
|
|
60
|
-
// Translations
|
|
61
|
-
export { getDashboardMessages } from './i18n/translations';
|
|
62
|
-
|
|
63
|
-
// Catch-all route handler (for client app integration)
|
|
64
|
-
export { default as DashboardCatchAll } from './components/DashboardCatchAll';
|
|
65
|
-
|
|
66
|
-
// Website context (for accessing website info in dashboard components)
|
|
67
|
-
export { WebsiteProvider, useWebsite } from './lib/website-context';
|
|
68
|
-
export type { WebsiteInfo } from './lib/website-context';
|
|
2
|
+
// Re-exports client-safe code by default
|
|
3
|
+
// For server-only code, import from '@jhits/dashboard/server'
|
|
4
|
+
|
|
5
|
+
// Re-export client-safe code
|
|
6
|
+
export * from './index.client';
|
|
7
|
+
|
|
8
|
+
// Note: Server-only exports are available via '@jhits/dashboard/server'
|
|
9
|
+
// This includes:
|
|
10
|
+
// - DashboardRouter, DashboardRouterLayout
|
|
11
|
+
// - DashboardCatchAll
|
|
12
|
+
// - withJhitsDashboard
|
|
13
|
+
// - getDashboardMessages
|
|
14
|
+
// - PLATFORM_MODULES
|
|
15
|
+
// - authOptions, clientPromise
|
|
69
16
|
|
|
@@ -11,23 +11,23 @@ const library = pluginData as PluginManifest[];
|
|
|
11
11
|
// Cache for resolved components to avoid recreating them
|
|
12
12
|
const componentCache = new Map<string, React.ComponentType<PluginProps>>();
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
14
|
+
/**
|
|
15
|
+
* EXPLICIT LOADER MAP
|
|
16
|
+
* This is the fix for the "Module Not Found" (tls, net, fs) errors.
|
|
17
|
+
* By explicitly defining the imports, Turbopack/Webpack knows exactly
|
|
18
|
+
* which files to bundle and can safely ignore the /server.ts files.
|
|
19
|
+
*/
|
|
20
|
+
const pluginLoaders: Record<string, () => Promise<any>> = {
|
|
21
|
+
'plugin-blog': () => import('@jhits/plugin-blog'),
|
|
22
|
+
'plugin-users': () => import('@jhits/plugin-users'),
|
|
23
|
+
'plugin-dep': () => import('@jhits/plugin-dep'),
|
|
24
|
+
'plugin-telemetry': () => import('@jhits/plugin-telemetry'),
|
|
25
|
+
'plugin-website': () => import('@jhits/plugin-website'),
|
|
26
|
+
'plugin-images': () => import('@jhits/plugin-images'),
|
|
27
|
+
'plugin-content': () => import('@jhits/plugin-content'),
|
|
28
|
+
'plugin-newsletter': () => import('@jhits/plugin-newsletter'),
|
|
29
|
+
// Add any other plugins that appear in your plugins.json here
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
export const PluginRegistry = {
|
|
33
33
|
// Returns the static data from JSON
|
|
@@ -35,37 +35,58 @@ export const PluginRegistry = {
|
|
|
35
35
|
return library.find(p => p.routePrefix === prefix);
|
|
36
36
|
},
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the actual React Component with caching
|
|
40
|
+
* Uses the explicit loader map to ensure clean bundling
|
|
41
|
+
*/
|
|
41
42
|
resolveComponent: (repoName: string) => {
|
|
42
|
-
// Check cache first
|
|
43
|
+
// 1. Check cache first
|
|
43
44
|
if (componentCache.has(repoName)) {
|
|
44
45
|
return componentCache.get(repoName)!;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
// Verify the plugin exists in the JSON configuration
|
|
48
|
+
// 2. Verify the plugin exists in the JSON configuration
|
|
48
49
|
const pluginManifest = library.find(p => p.repo === repoName);
|
|
49
50
|
if (!pluginManifest) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Plugin "${repoName}" not found in plugins.json. ` +
|
|
52
|
-
`Available plugins: ${library.map(p => `${p.repo} (${p.id})`).join(', ')}`
|
|
53
|
-
);
|
|
51
|
+
throw new Error(`Plugin "${repoName}" not found in plugins.json.`);
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
if (!pluginManifest.enabled) {
|
|
57
55
|
throw new Error(`Plugin "${repoName}" is disabled in plugins.json`);
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
//
|
|
61
|
-
const loader =
|
|
58
|
+
// 3. Get the explicit loader
|
|
59
|
+
const loader = pluginLoaders[repoName];
|
|
60
|
+
|
|
61
|
+
if (!loader) {
|
|
62
|
+
console.error(`[PluginRegistry] No loader found for "${repoName}". Add it to the pluginLoaders map.`);
|
|
63
|
+
// Fallback component so the whole app doesn't crash
|
|
64
|
+
return () => (
|
|
65
|
+
<div className="p-10 border-2 border-dashed border-red-500 text-red-500">
|
|
66
|
+
Configuration Error: <b>{repoName}</b> is not registered in the loader map.
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
62
70
|
|
|
63
|
-
// Create and cache the component using dynamic import
|
|
71
|
+
// 4. Create and cache the component using dynamic import
|
|
64
72
|
const Component = dynamic<PluginProps>(
|
|
65
|
-
|
|
73
|
+
async () => {
|
|
74
|
+
try {
|
|
75
|
+
const pluginModule = await loader();
|
|
76
|
+
// Supports both 'export default' and 'export const Index'
|
|
77
|
+
return pluginModule.default || pluginModule.Index;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Failed to load plugin "${repoName}":`, error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
66
83
|
{
|
|
67
|
-
loading: () =>
|
|
68
|
-
|
|
84
|
+
loading: () => (
|
|
85
|
+
<div className="p-10 animate-pulse font-black uppercase text-zinc-500">
|
|
86
|
+
Mounting_{repoName.replace('-', '_')}...
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
ssr: false // Prevents server-side Node.js errors during hydration
|
|
69
90
|
}
|
|
70
91
|
);
|
|
71
92
|
|
package/src/api/README.md
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Dashboard API Master Router
|
|
2
|
-
|
|
3
|
-
Centralized API routing system for all dashboard plugin endpoints.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The master router automatically routes API requests to the appropriate plugin handler based on the URL path prefix. This eliminates the need for the client app to know about individual plugin endpoints.
|
|
8
|
-
|
|
9
|
-
## Architecture
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
Client App
|
|
13
|
-
└── /api/dashboard/[...path]/route.ts (catch-all)
|
|
14
|
-
└── handleDashboardApi() (master router)
|
|
15
|
-
├── /telemetry → @jhits/plugin-telemetry/api/route
|
|
16
|
-
├── /blog → @jhits/plugin-blog/api/route (TODO)
|
|
17
|
-
└── /users → @jhits/plugin-users/api/route (TODO)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
### In Client App
|
|
23
|
-
|
|
24
|
-
Create a catch-all route at `src/app/api/dashboard/[...path]/route.ts`:
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { NextRequest } from 'next/server';
|
|
28
|
-
import { handleDashboardApi } from '@jhits/dashboard/server';
|
|
29
|
-
|
|
30
|
-
export async function POST(
|
|
31
|
-
req: NextRequest,
|
|
32
|
-
{ params }: { params: Promise<{ path: string[] }> }
|
|
33
|
-
) {
|
|
34
|
-
const { path } = await params;
|
|
35
|
-
return handleDashboardApi(req, path);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Add GET, PUT, DELETE, PATCH as needed
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Adding New Plugin Handlers
|
|
42
|
-
|
|
43
|
-
1. Create your plugin handler in `@jhits/plugin-{name}/api/route.ts`:
|
|
44
|
-
```typescript
|
|
45
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
46
|
-
|
|
47
|
-
export async function POST(req: NextRequest): Promise<NextResponse> {
|
|
48
|
-
// Your handler logic
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
2. Add routing logic in `masterRouter.ts`:
|
|
53
|
-
```typescript
|
|
54
|
-
if (pluginId === 'your-plugin') {
|
|
55
|
-
const { POST: yourPluginPOST } = await import('@jhits/plugin-your-plugin/api/route');
|
|
56
|
-
return await yourPluginPOST(req);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Endpoints
|
|
61
|
-
|
|
62
|
-
- `POST /api/dashboard/telemetry` - Telemetry logging
|
|
63
|
-
- `POST /api/dashboard/blog` - Blog API (TODO)
|
|
64
|
-
- `POST /api/dashboard/users` - Users API (TODO)
|
|
65
|
-
|
|
66
|
-
## Benefits
|
|
67
|
-
|
|
68
|
-
1. **Centralized Routing**: All plugin APIs go through one entry point
|
|
69
|
-
2. **Easy Plugin Addition**: Add new plugins without modifying client app
|
|
70
|
-
3. **Type Safety**: Full TypeScript support
|
|
71
|
-
4. **Server-Only**: Handlers are only imported on the server side
|
|
72
|
-
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|