@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 CHANGED
@@ -1,46 +1,63 @@
1
1
  {
2
2
  "name": "@jhits/dashboard",
3
- "version": "0.0.5",
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
- "./components/*": "./src/components/*",
26
- "./lib/*": "./src/lib/*",
27
- "./lib/auth": {
28
- "types": "./src/lib/auth.ts",
29
- "default": "./src/lib/auth.ts"
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-blog": "^0.0.1",
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"
@@ -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 already in config, but ensure it's passed through for plugin-users
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
- const configPath = join(basePath, 'next.config.ts');
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
- // Check if catch-all already exists
25
+ // If it exists, try to inject the dashboard handler
34
26
  if (existsSync(catchAllPath)) {
35
- // Read existing file and check if it already handles dashboard
36
- const fs = await import('fs');
37
- const existingContent = fs.readFileSync(catchAllPath, 'utf8');
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 route that handles dashboard routes
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
- export { default } from '@jhits/dashboard/catch-all';
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
- * Dynamically find all @jhits/* packages from package.json
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 possiblePaths = [
99
- appDir,
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
- // Filter for packages starting with @jhits/
135
- const jhitsPackages = Object.keys(allDeps).filter(
136
- (pkg) => pkg.startsWith('@jhits/')
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
- // Auto-create dashboard routes at build time
159
- // NOTE: User management routes are now handled by plugin-users via the unified plugin router
160
- // No need to auto-generate /api/users routes anymore
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
- crypto: false,
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
  }
@@ -0,0 +1,4 @@
1
+ // Simple loader that returns an empty module for .md files
2
+ module.exports = function(source) {
3
+ return 'module.exports = {};';
4
+ };
package/src/empty.js ADDED
@@ -0,0 +1,2 @@
1
+ // packages/jhits-dashboard/empty.js
2
+ module.exports = {};
@@ -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
- // This exports all the dashboard components and pages that can be used by host apps
3
-
4
- // Router components - inlined to avoid module resolution issues
5
- import DashboardLayout from './app/[locale]/dashboard/layout';
6
- import DashboardHome from './app/[locale]/dashboard/page';
7
- import DashboardPreferences from './app/[locale]/dashboard/preferences/page';
8
- import DashboardProfile from './app/[locale]/dashboard/profile/page';
9
- import DashboardPluginRoute from './app/[locale]/dashboard/[...pluginRoute]/page';
10
-
11
- export async function DashboardRouter({
12
- path,
13
- params
14
- }: {
15
- path: string[];
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
 
@@ -1,5 +1,3 @@
1
- import 'server-only';
2
-
3
1
  // src/lib/modules-config.ts
4
2
  import { BarChart3, Newspaper, Globe, Users, Mail, type LucideIcon } from 'lucide-react';
5
3
 
@@ -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
- // Dynamic plugin loader factory
15
- // This creates a loader function that dynamically imports plugins based on the JSON configuration
16
- // Plugins are loaded from @jhits/{repoName} where repoName comes from plugins.json
17
- function createDynamicPluginLoader(repoName: string): () => Promise<React.ComponentType<PluginProps>> {
18
- return async () => {
19
- try {
20
- // Dynamically import the plugin package
21
- // The package name follows the pattern: @jhits/{repoName}
22
- const pluginModule = await import(`@jhits/${repoName}`);
23
- // Return the default export or Index export
24
- return pluginModule.default || pluginModule.Index;
25
- } catch (error) {
26
- console.error(`Failed to load plugin "${repoName}":`, error);
27
- throw new Error(`Plugin "${repoName}" could not be loaded. Make sure it's installed and the package name matches @jhits/${repoName}`);
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
- // Resolves the actual React Component with caching
39
- // Dynamically loads plugins based on the JSON configuration
40
- // No hardcoded plugin list - everything comes from plugins.json
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
- // Create dynamic loader for this plugin
61
- const loader = createDynamicPluginLoader(repoName);
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
- loader,
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: () => <div className="p-10 animate-pulse font-black uppercase">Mounting_Module...</div>,
68
- ssr: false
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