@jhits/dashboard 0.0.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.
- package/README.md +36 -0
- package/next.config.ts +32 -0
- package/package.json +79 -0
- package/postcss.config.mjs +7 -0
- package/src/api/README.md +72 -0
- package/src/api/masterRouter.ts +150 -0
- package/src/api/pluginRouter.ts +135 -0
- package/src/app/[locale]/(auth)/layout.tsx +30 -0
- package/src/app/[locale]/(auth)/login/page.tsx +201 -0
- package/src/app/[locale]/catch-all/page.tsx +10 -0
- package/src/app/[locale]/dashboard/[...pluginRoute]/page.tsx +98 -0
- package/src/app/[locale]/dashboard/layout.tsx +42 -0
- package/src/app/[locale]/dashboard/page.tsx +121 -0
- package/src/app/[locale]/dashboard/preferences/page.tsx +295 -0
- package/src/app/[locale]/dashboard/profile/page.tsx +491 -0
- package/src/app/[locale]/layout.tsx +28 -0
- package/src/app/actions/preferences.ts +40 -0
- package/src/app/actions/user.ts +191 -0
- package/src/app/api/auth/[...nextauth]/route.ts +6 -0
- package/src/app/api/plugin-images/list/route.ts +96 -0
- package/src/app/api/plugin-images/upload/route.ts +88 -0
- package/src/app/api/telemetry/log/route.ts +10 -0
- package/src/app/api/telemetry/route.ts +12 -0
- package/src/app/api/uploads/[filename]/route.ts +33 -0
- package/src/app/globals.css +181 -0
- package/src/app/layout.tsx +4 -0
- package/src/assets/locales/en/common.json +47 -0
- package/src/assets/locales/nl/common.json +48 -0
- package/src/assets/locales/sv/common.json +48 -0
- package/src/assets/plugins.json +42 -0
- 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/animated-logo-white.svg +5 -0
- package/src/assets/public/logo_black.svg +5 -0
- package/src/assets/public/logo_white.svg +5 -0
- package/src/assets/public/noimagefound.jpg +0 -0
- package/src/components/DashboardCatchAll.tsx +95 -0
- package/src/components/DashboardRootLayout.tsx +37 -0
- package/src/components/PluginNotFound.tsx +24 -0
- package/src/components/Providers.tsx +59 -0
- package/src/components/dashboard/Sidebar.tsx +263 -0
- package/src/components/dashboard/Topbar.tsx +363 -0
- package/src/components/page.tsx +130 -0
- package/src/config.ts +230 -0
- package/src/i18n/navigation.ts +7 -0
- package/src/i18n/request.ts +41 -0
- package/src/i18n/routing.ts +35 -0
- package/src/i18n/translations.ts +20 -0
- package/src/index.tsx +69 -0
- package/src/lib/auth.ts +159 -0
- package/src/lib/db.ts +11 -0
- package/src/lib/get-website-info.ts +78 -0
- package/src/lib/modules-config.ts +68 -0
- package/src/lib/mongodb.ts +32 -0
- package/src/lib/plugin-registry.tsx +77 -0
- package/src/lib/website-context.tsx +39 -0
- package/src/proxy.ts +55 -0
- package/src/router.tsx +45 -0
- package/src/routes.tsx +3 -0
- package/src/server.ts +8 -0
- package/src/types/plugin.ts +24 -0
- package/src/types/preferences.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import createNextIntlPlugin from 'next-intl/plugin';
|
|
2
|
+
|
|
3
|
+
const withNextIntl = createNextIntlPlugin(
|
|
4
|
+
'./src/i18n/request.ts' // Path to your config file
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
/** @type {import('next').NextConfig} */
|
|
8
|
+
const nextConfig = {
|
|
9
|
+
logging: {
|
|
10
|
+
fetches: {
|
|
11
|
+
fullUrl: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
allowedDevOrigins: ["localhost:3000", "192.168.68.104:3000"],
|
|
15
|
+
// Exclude server-only modules from client bundles
|
|
16
|
+
serverComponentsExternalPackages: [
|
|
17
|
+
'@jhits/plugin-telemetry/api/handler'
|
|
18
|
+
],
|
|
19
|
+
webpack: (config, { isServer }) => {
|
|
20
|
+
// Ensure handler is only bundled on server
|
|
21
|
+
if (!isServer) {
|
|
22
|
+
config.resolve.fallback = {
|
|
23
|
+
...config.resolve.fallback,
|
|
24
|
+
fs: false,
|
|
25
|
+
path: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return config;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default withNextIntl(nextConfig);
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jhits/dashboard",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A comprehensive dashboard system built to manage custom built websites - plugin based SaaS system.",
|
|
5
|
+
"main": "./src/index.tsx",
|
|
6
|
+
"types": "./src/index.tsx",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.tsx",
|
|
10
|
+
"default": "./src/index.tsx"
|
|
11
|
+
},
|
|
12
|
+
"./config": {
|
|
13
|
+
"types": "./src/config.ts",
|
|
14
|
+
"default": "./src/config.ts"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./src/server.ts",
|
|
18
|
+
"default": "./src/server.ts",
|
|
19
|
+
"node": "./src/server.ts"
|
|
20
|
+
},
|
|
21
|
+
"./catch-all": {
|
|
22
|
+
"types": "./src/app/[locale]/catch-all/page.tsx",
|
|
23
|
+
"default": "./src/app/[locale]/catch-all/page.tsx"
|
|
24
|
+
},
|
|
25
|
+
"./components/*": "./src/components/*",
|
|
26
|
+
"./lib/*": "./src/lib/*",
|
|
27
|
+
"./lib/auth": {
|
|
28
|
+
"types": "./src/lib/auth.ts",
|
|
29
|
+
"default": "./src/lib/auth.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"lint": "eslint"
|
|
34
|
+
},
|
|
35
|
+
"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",
|
|
44
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
45
|
+
"bcrypt": "^6.0.0",
|
|
46
|
+
"framer-motion": "^12.23.26",
|
|
47
|
+
"jsonwebtoken": "^9.0.3",
|
|
48
|
+
"lucide-react": "^0.562.0",
|
|
49
|
+
"mongodb": "^7.0.0",
|
|
50
|
+
"next-auth": "^4.24.13",
|
|
51
|
+
"next-intl": "^4.6.1",
|
|
52
|
+
"next-themes": "^0.4.6"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
56
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
57
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@tailwindcss/postcss": "^4",
|
|
61
|
+
"@types/bcrypt": "^6.0.0",
|
|
62
|
+
"@types/node": "^20.19.27",
|
|
63
|
+
"@types/react": "^19",
|
|
64
|
+
"@types/react-dom": "^19",
|
|
65
|
+
"eslint": "^9",
|
|
66
|
+
"eslint-config-next": "16.1.1",
|
|
67
|
+
"next": "16.1.1",
|
|
68
|
+
"react": "19.2.3",
|
|
69
|
+
"react-dom": "19.2.3",
|
|
70
|
+
"tailwindcss": "^4",
|
|
71
|
+
"typescript": "^5"
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"src",
|
|
75
|
+
"next.config.ts",
|
|
76
|
+
"postcss.config.mjs",
|
|
77
|
+
"tailwind.config.ts"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master Dashboard API Router
|
|
3
|
+
* Centralized router for all plugin API endpoints
|
|
4
|
+
*
|
|
5
|
+
* This router automatically routes requests to the appropriate plugin handler
|
|
6
|
+
* based on the path prefix (e.g., /api/dashboard/telemetry -> telemetry handler)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract user/session info from request headers or cookies
|
|
13
|
+
* Helper function for extracting context from NextRequest
|
|
14
|
+
*/
|
|
15
|
+
function extractContext(request: NextRequest): { userId?: string; sessionId?: string } {
|
|
16
|
+
// Try to get user ID from headers (custom header)
|
|
17
|
+
const userId = request.headers.get('x-user-id') ||
|
|
18
|
+
request.headers.get('x-userid') ||
|
|
19
|
+
undefined;
|
|
20
|
+
|
|
21
|
+
// Try to get session ID from headers or cookies
|
|
22
|
+
const sessionId = request.headers.get('x-session-id') ||
|
|
23
|
+
request.headers.get('x-sessionid') ||
|
|
24
|
+
request.cookies.get('sessionId')?.value ||
|
|
25
|
+
request.cookies.get('session')?.value ||
|
|
26
|
+
undefined;
|
|
27
|
+
|
|
28
|
+
return { userId, sessionId };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handle dashboard API requests
|
|
33
|
+
* Routes requests to appropriate plugin handlers based on path prefix
|
|
34
|
+
*
|
|
35
|
+
* @param req - The incoming request
|
|
36
|
+
* @param path - Array of path segments from the catch-all route
|
|
37
|
+
* @returns Response from the appropriate plugin handler
|
|
38
|
+
*/
|
|
39
|
+
export async function handleDashboardApi(
|
|
40
|
+
req: NextRequest,
|
|
41
|
+
path: string[]
|
|
42
|
+
): Promise<NextResponse> {
|
|
43
|
+
// Extract the first path segment as the plugin identifier
|
|
44
|
+
const pluginId = path[0] || '';
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Route to telemetry handler
|
|
48
|
+
if (pluginId === 'telemetry') {
|
|
49
|
+
const { telemetryHandler } = await import('@jhits/plugin-telemetry/server');
|
|
50
|
+
|
|
51
|
+
// Extract context from request
|
|
52
|
+
const { userId, sessionId } = extractContext(req);
|
|
53
|
+
|
|
54
|
+
// Parse request body (handle empty bodies for GET requests)
|
|
55
|
+
let data: unknown;
|
|
56
|
+
try {
|
|
57
|
+
// Try to parse JSON body, but handle cases where body might be empty
|
|
58
|
+
const text = await req.text();
|
|
59
|
+
data = text ? JSON.parse(text) : null;
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// If parsing fails, return error
|
|
63
|
+
return NextResponse.json(
|
|
64
|
+
{ success: false, error: 'Invalid JSON in request body' },
|
|
65
|
+
{ status: 400 }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Call telemetry handler with data and context
|
|
70
|
+
const result = await telemetryHandler(data, { userId, sessionId });
|
|
71
|
+
|
|
72
|
+
// Return appropriate response based on result
|
|
73
|
+
if (result.success) {
|
|
74
|
+
return NextResponse.json(result);
|
|
75
|
+
} else {
|
|
76
|
+
return NextResponse.json(result, { status: 400 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Route to blog handler
|
|
81
|
+
if (pluginId === 'blog') {
|
|
82
|
+
// TODO: Import and call blog API handler when available
|
|
83
|
+
// For now, return a placeholder
|
|
84
|
+
return NextResponse.json(
|
|
85
|
+
{
|
|
86
|
+
success: false,
|
|
87
|
+
error: 'Blog API handler not yet implemented',
|
|
88
|
+
path: path.join('/')
|
|
89
|
+
},
|
|
90
|
+
{ status: 501 }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Route to users handler (if needed)
|
|
95
|
+
if (pluginId === 'users') {
|
|
96
|
+
// Users API is already handled by separate routes
|
|
97
|
+
// This could be used for plugin-specific user operations
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{
|
|
100
|
+
success: false,
|
|
101
|
+
error: 'Users API handler not yet implemented',
|
|
102
|
+
path: path.join('/')
|
|
103
|
+
},
|
|
104
|
+
{ status: 501 }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Unknown plugin
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{
|
|
111
|
+
success: false,
|
|
112
|
+
error: `Unknown plugin: ${pluginId}`,
|
|
113
|
+
availablePlugins: ['telemetry', 'blog', 'users'],
|
|
114
|
+
path: path.join('/')
|
|
115
|
+
},
|
|
116
|
+
{ status: 404 }
|
|
117
|
+
);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('[Master Router] Error routing request:', error);
|
|
120
|
+
return NextResponse.json(
|
|
121
|
+
{
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'Internal server error',
|
|
124
|
+
message: error instanceof Error ? error.message : String(error)
|
|
125
|
+
},
|
|
126
|
+
{ status: 500 }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Convert standard Request to NextRequest for compatibility
|
|
133
|
+
* This is a helper for when the router is called from non-Next.js contexts
|
|
134
|
+
*/
|
|
135
|
+
export function createNextRequestFromRequest(
|
|
136
|
+
req: Request,
|
|
137
|
+
path: string[]
|
|
138
|
+
): NextRequest {
|
|
139
|
+
// Create a new URL with the path
|
|
140
|
+
const url = new URL(req.url);
|
|
141
|
+
url.pathname = `/api/dashboard/${path.join('/')}`;
|
|
142
|
+
|
|
143
|
+
// Create NextRequest from the modified request
|
|
144
|
+
return new NextRequest(url, {
|
|
145
|
+
method: req.method,
|
|
146
|
+
headers: req.headers,
|
|
147
|
+
body: req.body,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Plugin API Router (Core Engine)
|
|
3
|
+
* Location: packages/jhits-dashboard/src/api/pluginRouter.ts
|
|
4
|
+
*/
|
|
5
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
6
|
+
import { getToken } from 'next-auth/jwt';
|
|
7
|
+
|
|
8
|
+
export interface PluginRouterConfig {
|
|
9
|
+
mongoClient?: Promise<MongoClient>;
|
|
10
|
+
authOptions?: AuthOptions;
|
|
11
|
+
jwtSecret?: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
localesDir?: string;
|
|
14
|
+
uploadsDir?: string;
|
|
15
|
+
emailConfig?: {
|
|
16
|
+
host: string; port: number; user: string; password: string; from: string;
|
|
17
|
+
};
|
|
18
|
+
deploymentPaths?: { flagPath: string; scriptPath: string; };
|
|
19
|
+
githubWebhookSecret?: string;
|
|
20
|
+
plugins?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper function to get user ID from request
|
|
25
|
+
* Uses NextAuth's getToken to extract user ID from session
|
|
26
|
+
*/
|
|
27
|
+
async function getUserIdFromRequest(req: NextRequest, jwtSecret?: string): Promise<string | null> {
|
|
28
|
+
try {
|
|
29
|
+
// Use NextAuth's getToken to get the session token
|
|
30
|
+
// This works with NextAuth's cookie-based sessions
|
|
31
|
+
const token = await getToken({
|
|
32
|
+
req,
|
|
33
|
+
secret: process.env.NEXTAUTH_SECRET || jwtSecret
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!token || !token.sub) return null;
|
|
37
|
+
|
|
38
|
+
// NextAuth stores user ID in token.sub
|
|
39
|
+
return token.sub;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Helper function to create getDb function from mongoClient
|
|
47
|
+
*/
|
|
48
|
+
import type { MongoClient, Db } from 'mongodb';
|
|
49
|
+
import { AuthOptions } from 'next-auth';
|
|
50
|
+
|
|
51
|
+
function createGetDb(mongoClient?: Promise<MongoClient>) {
|
|
52
|
+
return async () => {
|
|
53
|
+
if (!mongoClient) {
|
|
54
|
+
throw new Error('MongoDB client not configured');
|
|
55
|
+
}
|
|
56
|
+
const client = await mongoClient;
|
|
57
|
+
return { db: (dbName?: string): Db => client.db(dbName) };
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Static import map for Turbopack compatibility
|
|
63
|
+
*
|
|
64
|
+
* NOTE: Turbopack has a known limitation with subpath exports and relative imports
|
|
65
|
+
* in workspace packages. If /server imports fail, we fall back to root imports.
|
|
66
|
+
*/
|
|
67
|
+
const PLUGIN_SERVER_MAP: Record<string, () => Promise<unknown>> = {
|
|
68
|
+
'plugin-blog': () => import('@jhits/plugin-blog/server'),
|
|
69
|
+
'plugin-dep': () => import('@jhits/plugin-dep/server'),
|
|
70
|
+
'plugin-images': () => import('@jhits/plugin-images/server'),
|
|
71
|
+
'plugin-users': () => import('@jhits/plugin-users/server'),
|
|
72
|
+
'plugin-content': () => import('@jhits/plugin-content/server'),
|
|
73
|
+
'plugin-website': () => import('@jhits/plugin-website/server'),
|
|
74
|
+
'plugin-newsletter': () => import('@jhits/plugin-newsletter/server'),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Main Handler
|
|
79
|
+
* Expects normalized pluginIds (e.g., 'plugin-blog')
|
|
80
|
+
*/
|
|
81
|
+
export async function handlePluginApi(
|
|
82
|
+
req: NextRequest,
|
|
83
|
+
pluginId: string,
|
|
84
|
+
path: string[],
|
|
85
|
+
config: PluginRouterConfig
|
|
86
|
+
): Promise<NextResponse> {
|
|
87
|
+
try {
|
|
88
|
+
const normalizedId = pluginId.startsWith('plugin-') ? pluginId : `plugin-${pluginId}`;
|
|
89
|
+
|
|
90
|
+
// Try server subpath export first
|
|
91
|
+
const importFn = PLUGIN_SERVER_MAP[normalizedId];
|
|
92
|
+
|
|
93
|
+
if (!importFn) {
|
|
94
|
+
console.error(`[PluginRouter] Unknown plugin: ${normalizedId}`);
|
|
95
|
+
return NextResponse.json({ error: `Plugin ${normalizedId} not found` }, { status: 404 });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let pluginModule;
|
|
99
|
+
try {
|
|
100
|
+
pluginModule = await importFn();
|
|
101
|
+
} catch (serverImportError: unknown) {
|
|
102
|
+
const message = serverImportError instanceof Error ? serverImportError.message : String(serverImportError);
|
|
103
|
+
// Turbopack workaround: If /server import fails, try root import
|
|
104
|
+
// This happens because Turbopack can't resolve relative imports in subpath exports
|
|
105
|
+
console.warn(`[PluginRouter] Server import failed for ${normalizedId}, trying root import:`, message);
|
|
106
|
+
pluginModule = await import(`@jhits/${normalizedId}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handler = pluginModule.handleApi;
|
|
110
|
+
|
|
111
|
+
if (typeof handler !== 'function') {
|
|
112
|
+
return NextResponse.json({ error: "Handler not found" }, { status: 500 });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Adapt config for plugins that need getDb, getUserId, authOptions, emailConfig, or baseUrl
|
|
116
|
+
const adaptedConfig = {
|
|
117
|
+
...config,
|
|
118
|
+
// Add getDb if mongoClient is available (for plugin-blog, plugin-users, plugin-newsletter)
|
|
119
|
+
getDb: config.mongoClient ? createGetDb(config.mongoClient) : undefined,
|
|
120
|
+
// Add getUserId - uses NextAuth session token (works with or without jwtSecret)
|
|
121
|
+
getUserId: (req: NextRequest) => getUserIdFromRequest(req, config.jwtSecret),
|
|
122
|
+
// authOptions is already in config, but ensure it's passed through for plugin-users
|
|
123
|
+
// emailConfig and baseUrl are already in config, but ensure they're passed through for plugin-newsletter
|
|
124
|
+
emailConfig: config.emailConfig,
|
|
125
|
+
baseUrl: config.baseUrl,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return await handler(req, path, adaptedConfig);
|
|
129
|
+
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
+
console.error(`[PluginRouter] Resolution Error:`, message);
|
|
133
|
+
return NextResponse.json({ error: "Plugin load failure" }, { status: 404 });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { motion } from 'framer-motion';
|
|
4
|
+
import '../../globals.css';
|
|
5
|
+
|
|
6
|
+
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-950 flex items-center justify-center p-4 relative overflow-hidden font-sans">
|
|
9
|
+
|
|
10
|
+
{/* 1. Ambient Background Glows */}
|
|
11
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
12
|
+
<div className="absolute -top-[10%] -left-[10%] size-[50%] rounded-full bg-primary/5 blur-[120px] dark:bg-primary/10" />
|
|
13
|
+
<div className="absolute -bottom-[10%] -right-[10%] size-[50%] rounded-full bg-purple-500/5 blur-[120px] dark:bg-purple-500/10" />
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
{/* 2. Structural Wrapper */}
|
|
17
|
+
<motion.div
|
|
18
|
+
initial={{ opacity: 0 }}
|
|
19
|
+
animate={{ opacity: 1 }}
|
|
20
|
+
transition={{ duration: 0.5 }}
|
|
21
|
+
className="w-full relative z-10"
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</motion.div>
|
|
25
|
+
|
|
26
|
+
{/* 3. Subtle Grid Overlay (Optional - adds a technical "blueprint" feel) */}
|
|
27
|
+
<div className="absolute inset-0 bg-[url('/grid.svg')] bg-center [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))] pointer-events-none opacity-[0.03] dark:opacity-[0.05]" />
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|