helix-lang 11.0.0
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 +168 -0
- package/dist/architect.d.ts +14 -0
- package/dist/architect.d.ts.map +1 -0
- package/dist/architect.js +127 -0
- package/dist/architect.js.map +1 -0
- package/dist/bin/helix.d.ts +20 -0
- package/dist/bin/helix.d.ts.map +1 -0
- package/dist/bin/helix.js +921 -0
- package/dist/bin/helix.js.map +1 -0
- package/dist/commands/collaborate/index.d.ts +2 -0
- package/dist/commands/collaborate/index.d.ts.map +1 -0
- package/dist/commands/collaborate/index.js +129 -0
- package/dist/commands/collaborate/index.js.map +1 -0
- package/dist/commands/collaborate/server.d.ts +31 -0
- package/dist/commands/collaborate/server.d.ts.map +1 -0
- package/dist/commands/collaborate/server.js +159 -0
- package/dist/commands/collaborate/server.js.map +1 -0
- package/dist/commands/deploy/index.d.ts +25 -0
- package/dist/commands/deploy/index.d.ts.map +1 -0
- package/dist/commands/deploy/index.js +130 -0
- package/dist/commands/deploy/index.js.map +1 -0
- package/dist/commands/deploy/platforms/fly.d.ts +9 -0
- package/dist/commands/deploy/platforms/fly.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/fly.js +68 -0
- package/dist/commands/deploy/platforms/fly.js.map +1 -0
- package/dist/commands/deploy/platforms/railway.d.ts +9 -0
- package/dist/commands/deploy/platforms/railway.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/railway.js +115 -0
- package/dist/commands/deploy/platforms/railway.js.map +1 -0
- package/dist/commands/deploy/platforms/vercel.d.ts +10 -0
- package/dist/commands/deploy/platforms/vercel.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/vercel.js +126 -0
- package/dist/commands/deploy/platforms/vercel.js.map +1 -0
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +56 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/evolve/analyzers/performance.d.ts +13 -0
- package/dist/commands/evolve/analyzers/performance.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/performance.js +591 -0
- package/dist/commands/evolve/analyzers/performance.js.map +1 -0
- package/dist/commands/evolve/analyzers/security.d.ts +21 -0
- package/dist/commands/evolve/analyzers/security.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/security.js +280 -0
- package/dist/commands/evolve/analyzers/security.js.map +1 -0
- package/dist/commands/evolve/index.d.ts +2 -0
- package/dist/commands/evolve/index.d.ts.map +1 -0
- package/dist/commands/evolve/index.js +122 -0
- package/dist/commands/evolve/index.js.map +1 -0
- package/dist/commands/generate.d.ts +6 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +277 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +176 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/library/index.d.ts +27 -0
- package/dist/commands/library/index.d.ts.map +1 -0
- package/dist/commands/library/index.js +126 -0
- package/dist/commands/library/index.js.map +1 -0
- package/dist/commands/migrate.d.ts +5 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +258 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +6 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +195 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/preflight.d.ts +20 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +182 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/preview.d.ts +13 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +260 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +96 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/spawn.d.ts +11 -0
- package/dist/commands/spawn.d.ts.map +1 -0
- package/dist/commands/spawn.js +916 -0
- package/dist/commands/spawn.js.map +1 -0
- package/dist/compiler.d.ts +12 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +92 -0
- package/dist/compiler.js.map +1 -0
- package/dist/core/file-writer.d.ts +36 -0
- package/dist/core/file-writer.d.ts.map +1 -0
- package/dist/core/file-writer.js +268 -0
- package/dist/core/file-writer.js.map +1 -0
- package/dist/core/registry.d.ts +57 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +222 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/self-healing.d.ts +47 -0
- package/dist/core/self-healing.d.ts.map +1 -0
- package/dist/core/self-healing.js +250 -0
- package/dist/core/self-healing.js.map +1 -0
- package/dist/core/types.d.ts +126 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generators/databases/mongodb.d.ts +10 -0
- package/dist/generators/databases/mongodb.d.ts.map +1 -0
- package/dist/generators/databases/mongodb.js +83 -0
- package/dist/generators/databases/mongodb.js.map +1 -0
- package/dist/generators/databases/redis.d.ts +2 -0
- package/dist/generators/databases/redis.d.ts.map +1 -0
- package/dist/generators/databases/redis.js +140 -0
- package/dist/generators/databases/redis.js.map +1 -0
- package/dist/generators/flutter.d.ts +32 -0
- package/dist/generators/flutter.d.ts.map +1 -0
- package/dist/generators/flutter.js +628 -0
- package/dist/generators/flutter.js.map +1 -0
- package/dist/openrouter.d.ts +68 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +241 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/page-generator.d.ts +22 -0
- package/dist/page-generator.d.ts.map +1 -0
- package/dist/page-generator.js +192 -0
- package/dist/page-generator.js.map +1 -0
- package/dist/parser.d.ts +76 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +691 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompts/master-architect.d.ts +9 -0
- package/dist/prompts/master-architect.d.ts.map +1 -0
- package/dist/prompts/master-architect.js +150 -0
- package/dist/prompts/master-architect.js.map +1 -0
- package/dist/researcher.d.ts +12 -0
- package/dist/researcher.d.ts.map +1 -0
- package/dist/researcher.js +85 -0
- package/dist/researcher.js.map +1 -0
- package/dist/self-heal.d.ts +29 -0
- package/dist/self-heal.d.ts.map +1 -0
- package/dist/self-heal.js +260 -0
- package/dist/self-heal.js.map +1 -0
- package/dist/services/SupabaseDeployer.d.ts +9 -0
- package/dist/services/SupabaseDeployer.d.ts.map +1 -0
- package/dist/services/SupabaseDeployer.js +50 -0
- package/dist/services/SupabaseDeployer.js.map +1 -0
- package/dist/test-generator.d.ts +18 -0
- package/dist/test-generator.d.ts.map +1 -0
- package/dist/test-generator.js +180 -0
- package/dist/test-generator.js.map +1 -0
- package/dist/themes/index.d.ts +52 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +273 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/constitutional-validator.d.ts +73 -0
- package/dist/utils/constitutional-validator.d.ts.map +1 -0
- package/dist/utils/constitutional-validator.js +249 -0
- package/dist/utils/constitutional-validator.js.map +1 -0
- package/library/auth-flow/app/api/auth/[...nextauth]/route.ts +31 -0
- package/library/auth-flow/app/api/auth/login/route.ts +90 -0
- package/library/auth-flow/app/api/auth/register/route.ts +91 -0
- package/library/auth-flow/components/auth/AuthMiddleware.tsx +139 -0
- package/library/auth-flow/components/auth/LoginForm.tsx +125 -0
- package/library/auth-flow/components/auth/RegisterForm.tsx +168 -0
- package/library/auth-flow/components/auth/nextauth-config.ts +99 -0
- package/library/auth-flow/manifest.json +29 -0
- package/library/auth-flow/schema.prisma +45 -0
- package/library/dashboard-analytics/components/dashboard/ActivityFeed.tsx +109 -0
- package/library/dashboard-analytics/components/dashboard/LineChart.tsx +180 -0
- package/library/dashboard-analytics/components/dashboard/StatsCard.tsx +47 -0
- package/library/dashboard-analytics/components/dashboard/SummaryGrid.tsx +39 -0
- package/library/dashboard-analytics/manifest.json +19 -0
- package/library/data-table/components/table/BulkActions.tsx +59 -0
- package/library/data-table/components/table/ColumnToggle.tsx +65 -0
- package/library/data-table/components/table/DataTable.tsx +318 -0
- package/library/data-table/components/table/ExportCSV.tsx +52 -0
- package/library/data-table/components/table/SearchFilter.tsx +48 -0
- package/library/data-table/manifest.json +20 -0
- package/library/file-upload/app/api/upload/route.ts +107 -0
- package/library/file-upload/components/upload/DropZone.tsx +268 -0
- package/library/file-upload/components/upload/FilePreview.tsx +82 -0
- package/library/file-upload/components/upload/UploadProgress.tsx +92 -0
- package/library/file-upload/components/upload/fileStorage.ts +142 -0
- package/library/file-upload/manifest.json +21 -0
- package/library/notification-system/app/api/notifications/route.ts +121 -0
- package/library/notification-system/components/notifications/NotificationBell.tsx +154 -0
- package/library/notification-system/components/notifications/NotificationProvider.tsx +161 -0
- package/library/notification-system/components/notifications/Toast.tsx +112 -0
- package/library/notification-system/manifest.json +20 -0
- package/package.json +66 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
export interface AuthenticatedUser {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface SessionRecord {
|
|
10
|
+
id: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
token: string;
|
|
13
|
+
expiresAt: string;
|
|
14
|
+
user: AuthenticatedUser;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the bearer token from an Authorization header.
|
|
19
|
+
*/
|
|
20
|
+
function extractToken(request: NextRequest): string | null {
|
|
21
|
+
const authHeader = request.headers.get('authorization');
|
|
22
|
+
if (!authHeader) return null;
|
|
23
|
+
|
|
24
|
+
const [scheme, token] = authHeader.split(' ');
|
|
25
|
+
if (scheme?.toLowerCase() !== 'bearer' || !token) return null;
|
|
26
|
+
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Middleware that validates a session token against the database.
|
|
32
|
+
* Wrap your API route handler with this to protect it.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* import { withAuth } from '@/components/auth/AuthMiddleware';
|
|
36
|
+
*
|
|
37
|
+
* export const GET = withAuth(async (request, user) => {
|
|
38
|
+
* return NextResponse.json({ message: `Hello ${user.email}` });
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
41
|
+
export function withAuth(
|
|
42
|
+
handler: (request: NextRequest, user: AuthenticatedUser) => Promise<NextResponse>,
|
|
43
|
+
options: { sessionLookup?: (token: string) => Promise<SessionRecord | null> } = {}
|
|
44
|
+
) {
|
|
45
|
+
return async (request: NextRequest): Promise<NextResponse> => {
|
|
46
|
+
const token = extractToken(request);
|
|
47
|
+
|
|
48
|
+
if (!token) {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: 'Authentication required' },
|
|
51
|
+
{ status: 401 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
let session: SessionRecord | null = null;
|
|
57
|
+
|
|
58
|
+
if (options.sessionLookup) {
|
|
59
|
+
session = await options.sessionLookup(token);
|
|
60
|
+
} else {
|
|
61
|
+
// Default: call internal session validation endpoint
|
|
62
|
+
const validateRes = await fetch(
|
|
63
|
+
new URL('/api/auth/validate', request.url).toString(),
|
|
64
|
+
{
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ token }),
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (validateRes.ok) {
|
|
72
|
+
const data = await validateRes.json();
|
|
73
|
+
session = data.session;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!session) {
|
|
78
|
+
return NextResponse.json(
|
|
79
|
+
{ error: 'Invalid or expired session' },
|
|
80
|
+
{ status: 401 }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check expiry
|
|
85
|
+
if (new Date(session.expiresAt) < new Date()) {
|
|
86
|
+
return NextResponse.json(
|
|
87
|
+
{ error: 'Session expired' },
|
|
88
|
+
{ status: 401 }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return handler(request, session.user);
|
|
93
|
+
} catch {
|
|
94
|
+
return NextResponse.json(
|
|
95
|
+
{ error: 'Authentication failed' },
|
|
96
|
+
{ status: 500 }
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Next.js middleware config helper for protecting routes at the edge.
|
|
104
|
+
* Add to middleware.ts:
|
|
105
|
+
*
|
|
106
|
+
* import { authMiddlewareConfig } from '@/components/auth/AuthMiddleware';
|
|
107
|
+
* export const config = authMiddlewareConfig(['/dashboard', '/settings']);
|
|
108
|
+
*/
|
|
109
|
+
export function authMiddlewareConfig(protectedPaths: string[]) {
|
|
110
|
+
return {
|
|
111
|
+
matcher: protectedPaths.map((p) => `${p}/:path*`),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Edge middleware handler for Next.js middleware.ts
|
|
117
|
+
*/
|
|
118
|
+
export function createAuthMiddleware(protectedPaths: string[]) {
|
|
119
|
+
return async function middleware(request: NextRequest) {
|
|
120
|
+
const { pathname } = request.nextUrl;
|
|
121
|
+
const isProtected = protectedPaths.some((p) => pathname.startsWith(p));
|
|
122
|
+
|
|
123
|
+
if (!isProtected) {
|
|
124
|
+
return NextResponse.next();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const token =
|
|
128
|
+
request.cookies.get('authToken')?.value ||
|
|
129
|
+
extractToken(request);
|
|
130
|
+
|
|
131
|
+
if (!token) {
|
|
132
|
+
const loginUrl = new URL('/login', request.url);
|
|
133
|
+
loginUrl.searchParams.set('callbackUrl', pathname);
|
|
134
|
+
return NextResponse.redirect(loginUrl);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return NextResponse.next();
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type FormEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
interface LoginFormProps {
|
|
6
|
+
onSuccess?: (user: { id: string; email: string; name: string | null }) => void;
|
|
7
|
+
onRegisterClick?: () => void;
|
|
8
|
+
redirectTo?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function LoginForm({ onSuccess, onRegisterClick, redirectTo = '/dashboard' }: LoginFormProps) {
|
|
12
|
+
const [email, setEmail] = useState('');
|
|
13
|
+
const [password, setPassword] = useState('');
|
|
14
|
+
const [error, setError] = useState('');
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (e: FormEvent) => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
setError('');
|
|
20
|
+
setLoading(true);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch('/api/auth/login', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({ email, password }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
setError(data.error || 'Login failed');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
localStorage.setItem('authToken', data.token);
|
|
37
|
+
|
|
38
|
+
if (onSuccess) {
|
|
39
|
+
onSuccess(data.user);
|
|
40
|
+
} else {
|
|
41
|
+
window.location.href = redirectTo;
|
|
42
|
+
}
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
const message = err instanceof Error ? err.message : 'An unexpected error occurred';
|
|
45
|
+
setError(message);
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="w-full max-w-md mx-auto">
|
|
53
|
+
<div className="backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl p-8 shadow-2xl">
|
|
54
|
+
<h2 className="text-2xl font-bold text-white mb-2">Welcome back</h2>
|
|
55
|
+
<p className="text-white/50 mb-6">Sign in to your account</p>
|
|
56
|
+
|
|
57
|
+
{error && (
|
|
58
|
+
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 text-red-400 rounded-lg text-sm">
|
|
59
|
+
{error}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
64
|
+
<div>
|
|
65
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
66
|
+
Email
|
|
67
|
+
</label>
|
|
68
|
+
<input
|
|
69
|
+
type="email"
|
|
70
|
+
value={email}
|
|
71
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
72
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
73
|
+
placeholder="you@example.com"
|
|
74
|
+
required
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div>
|
|
79
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
80
|
+
Password
|
|
81
|
+
</label>
|
|
82
|
+
<input
|
|
83
|
+
type="password"
|
|
84
|
+
value={password}
|
|
85
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
86
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
87
|
+
placeholder="Enter your password"
|
|
88
|
+
required
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<button
|
|
93
|
+
type="submit"
|
|
94
|
+
disabled={loading}
|
|
95
|
+
className="w-full py-2.5 px-4 bg-cyan-500 text-white font-medium rounded-lg hover:bg-cyan-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shadow-lg shadow-cyan-500/25"
|
|
96
|
+
>
|
|
97
|
+
{loading ? (
|
|
98
|
+
<span className="flex items-center justify-center gap-2">
|
|
99
|
+
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
|
100
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
|
101
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
102
|
+
</svg>
|
|
103
|
+
Signing in...
|
|
104
|
+
</span>
|
|
105
|
+
) : (
|
|
106
|
+
'Sign in'
|
|
107
|
+
)}
|
|
108
|
+
</button>
|
|
109
|
+
</form>
|
|
110
|
+
|
|
111
|
+
{onRegisterClick && (
|
|
112
|
+
<p className="mt-6 text-center text-sm text-white/50">
|
|
113
|
+
Don't have an account?{' '}
|
|
114
|
+
<button
|
|
115
|
+
onClick={onRegisterClick}
|
|
116
|
+
className="text-cyan-400 hover:text-cyan-300 font-medium transition-colors"
|
|
117
|
+
>
|
|
118
|
+
Create one
|
|
119
|
+
</button>
|
|
120
|
+
</p>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type FormEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
interface RegisterFormProps {
|
|
6
|
+
onSuccess?: (user: { id: string; email: string; name: string }) => void;
|
|
7
|
+
onLoginClick?: () => void;
|
|
8
|
+
redirectTo?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function RegisterForm({ onSuccess, onLoginClick, redirectTo = '/dashboard' }: RegisterFormProps) {
|
|
12
|
+
const [name, setName] = useState('');
|
|
13
|
+
const [email, setEmail] = useState('');
|
|
14
|
+
const [password, setPassword] = useState('');
|
|
15
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
16
|
+
const [error, setError] = useState('');
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
const handleSubmit = async (e: FormEvent) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
setError('');
|
|
22
|
+
|
|
23
|
+
if (password !== confirmPassword) {
|
|
24
|
+
setError('Passwords do not match');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (password.length < 8) {
|
|
29
|
+
setError('Password must be at least 8 characters');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setLoading(true);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch('/api/auth/register', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ name, email, password }),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
setError(data.error || 'Registration failed');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
localStorage.setItem('authToken', data.token);
|
|
50
|
+
|
|
51
|
+
if (onSuccess) {
|
|
52
|
+
onSuccess(data.user);
|
|
53
|
+
} else {
|
|
54
|
+
window.location.href = redirectTo;
|
|
55
|
+
}
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
const message = err instanceof Error ? err.message : 'An unexpected error occurred';
|
|
58
|
+
setError(message);
|
|
59
|
+
} finally {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="w-full max-w-md mx-auto">
|
|
66
|
+
<div className="backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl p-8 shadow-2xl">
|
|
67
|
+
<h2 className="text-2xl font-bold text-white mb-2">Create account</h2>
|
|
68
|
+
<p className="text-white/50 mb-6">Get started with your new account</p>
|
|
69
|
+
|
|
70
|
+
{error && (
|
|
71
|
+
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 text-red-400 rounded-lg text-sm">
|
|
72
|
+
{error}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
77
|
+
<div>
|
|
78
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
79
|
+
Full name
|
|
80
|
+
</label>
|
|
81
|
+
<input
|
|
82
|
+
type="text"
|
|
83
|
+
value={name}
|
|
84
|
+
onChange={(e) => setName(e.target.value)}
|
|
85
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
86
|
+
placeholder="John Doe"
|
|
87
|
+
required
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div>
|
|
92
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
93
|
+
Email
|
|
94
|
+
</label>
|
|
95
|
+
<input
|
|
96
|
+
type="email"
|
|
97
|
+
value={email}
|
|
98
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
99
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
100
|
+
placeholder="you@example.com"
|
|
101
|
+
required
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div>
|
|
106
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
107
|
+
Password
|
|
108
|
+
</label>
|
|
109
|
+
<input
|
|
110
|
+
type="password"
|
|
111
|
+
value={password}
|
|
112
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
113
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
114
|
+
placeholder="Min. 8 characters"
|
|
115
|
+
minLength={8}
|
|
116
|
+
required
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div>
|
|
121
|
+
<label className="block text-sm font-medium text-white/70 mb-1.5">
|
|
122
|
+
Confirm password
|
|
123
|
+
</label>
|
|
124
|
+
<input
|
|
125
|
+
type="password"
|
|
126
|
+
value={confirmPassword}
|
|
127
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
128
|
+
className="w-full px-4 py-2.5 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/30 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 transition-all"
|
|
129
|
+
placeholder="Re-enter your password"
|
|
130
|
+
minLength={8}
|
|
131
|
+
required
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<button
|
|
136
|
+
type="submit"
|
|
137
|
+
disabled={loading}
|
|
138
|
+
className="w-full py-2.5 px-4 bg-cyan-500 text-white font-medium rounded-lg hover:bg-cyan-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shadow-lg shadow-cyan-500/25"
|
|
139
|
+
>
|
|
140
|
+
{loading ? (
|
|
141
|
+
<span className="flex items-center justify-center gap-2">
|
|
142
|
+
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
|
143
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
|
144
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
145
|
+
</svg>
|
|
146
|
+
Creating account...
|
|
147
|
+
</span>
|
|
148
|
+
) : (
|
|
149
|
+
'Create account'
|
|
150
|
+
)}
|
|
151
|
+
</button>
|
|
152
|
+
</form>
|
|
153
|
+
|
|
154
|
+
{onLoginClick && (
|
|
155
|
+
<p className="mt-6 text-center text-sm text-white/50">
|
|
156
|
+
Already have an account?{' '}
|
|
157
|
+
<button
|
|
158
|
+
onClick={onLoginClick}
|
|
159
|
+
className="text-cyan-400 hover:text-cyan-300 font-medium transition-colors"
|
|
160
|
+
>
|
|
161
|
+
Sign in
|
|
162
|
+
</button>
|
|
163
|
+
</p>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NextAuth.js configuration boilerplate for Helix projects.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* 1. Copy this file to src/lib/auth.ts
|
|
6
|
+
* 2. Create app/api/auth/[...nextauth]/route.ts with:
|
|
7
|
+
* import NextAuth from 'next-auth';
|
|
8
|
+
* import { authOptions } from '@/lib/auth';
|
|
9
|
+
* const handler = NextAuth(authOptions);
|
|
10
|
+
* export { handler as GET, handler as POST };
|
|
11
|
+
* 3. Set NEXTAUTH_SECRET and NEXTAUTH_URL in .env
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { NextAuthOptions, Session, User } from 'next-auth';
|
|
15
|
+
import type { JWT } from 'next-auth/jwt';
|
|
16
|
+
import CredentialsProvider from 'next-auth/providers/credentials';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Stub for password verification -- replace with your actual bcrypt compare.
|
|
20
|
+
*/
|
|
21
|
+
async function verifyPassword(_plain: string, _hash: string): Promise<boolean> {
|
|
22
|
+
// Replace with: return bcrypt.compare(plain, hash);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Stub for user lookup -- replace with your Prisma/DB query.
|
|
28
|
+
*/
|
|
29
|
+
async function findUserByEmail(_email: string): Promise<{
|
|
30
|
+
id: string;
|
|
31
|
+
email: string;
|
|
32
|
+
name: string | null;
|
|
33
|
+
passwordHash: string;
|
|
34
|
+
} | null> {
|
|
35
|
+
// Replace with: return prisma.user.findUnique({ where: { email } });
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const authOptions: NextAuthOptions = {
|
|
40
|
+
providers: [
|
|
41
|
+
CredentialsProvider({
|
|
42
|
+
name: 'Credentials',
|
|
43
|
+
credentials: {
|
|
44
|
+
email: { label: 'Email', type: 'email', placeholder: 'you@example.com' },
|
|
45
|
+
password: { label: 'Password', type: 'password' },
|
|
46
|
+
},
|
|
47
|
+
async authorize(credentials) {
|
|
48
|
+
if (!credentials?.email || !credentials?.password) {
|
|
49
|
+
throw new Error('Email and password are required');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const user = await findUserByEmail(credentials.email);
|
|
53
|
+
|
|
54
|
+
if (!user) {
|
|
55
|
+
throw new Error('Invalid credentials');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const isValid = await verifyPassword(credentials.password, user.passwordHash);
|
|
59
|
+
|
|
60
|
+
if (!isValid) {
|
|
61
|
+
throw new Error('Invalid credentials');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
id: user.id,
|
|
66
|
+
email: user.email,
|
|
67
|
+
name: user.name,
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
|
|
73
|
+
session: {
|
|
74
|
+
strategy: 'jwt',
|
|
75
|
+
maxAge: 7 * 24 * 60 * 60, // 7 days
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
pages: {
|
|
79
|
+
signIn: '/login',
|
|
80
|
+
error: '/login',
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
callbacks: {
|
|
84
|
+
async jwt({ token, user }: { token: JWT; user?: User }) {
|
|
85
|
+
if (user) {
|
|
86
|
+
token.id = user.id;
|
|
87
|
+
}
|
|
88
|
+
return token;
|
|
89
|
+
},
|
|
90
|
+
async session({ session, token }: { session: Session; token: JWT }) {
|
|
91
|
+
if (session.user && token.id) {
|
|
92
|
+
(session.user as Record<string, unknown>).id = token.id;
|
|
93
|
+
}
|
|
94
|
+
return session;
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
secret: process.env.NEXTAUTH_SECRET,
|
|
99
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auth-flow",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Complete authentication flow with login, registration, session middleware, and NextAuth configuration",
|
|
5
|
+
"author": "AD AI Engine",
|
|
6
|
+
"category": "auth",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"next-auth",
|
|
9
|
+
"@prisma/client",
|
|
10
|
+
"bcrypt",
|
|
11
|
+
"zod"
|
|
12
|
+
],
|
|
13
|
+
"files": {
|
|
14
|
+
"schema": "schema.prisma",
|
|
15
|
+
"api": [
|
|
16
|
+
"app/api/auth/login/route.ts",
|
|
17
|
+
"app/api/auth/register/route.ts",
|
|
18
|
+
"app/api/auth/[...nextauth]/route.ts"
|
|
19
|
+
],
|
|
20
|
+
"components": [
|
|
21
|
+
"components/auth/LoginForm.tsx",
|
|
22
|
+
"components/auth/RegisterForm.tsx",
|
|
23
|
+
"components/auth/AuthMiddleware.tsx",
|
|
24
|
+
"components/auth/nextauth-config.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"schema": "schema.prisma",
|
|
28
|
+
"instructions": "1. Install dependencies: npm install next-auth @prisma/client bcrypt zod\n2. Add NEXTAUTH_SECRET and NEXTAUTH_URL to .env\n3. Run npx prisma db push to create User/Session tables\n4. Wrap your app with SessionProvider from next-auth/react\n5. Use AuthMiddleware in API routes to protect endpoints"
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Prisma Schema additions for auth-flow component
|
|
2
|
+
// Merge into your existing schema.prisma
|
|
3
|
+
|
|
4
|
+
model User {
|
|
5
|
+
id String @id @default(uuid())
|
|
6
|
+
email String @unique
|
|
7
|
+
passwordHash String
|
|
8
|
+
name String?
|
|
9
|
+
image String?
|
|
10
|
+
emailVerified DateTime?
|
|
11
|
+
createdAt DateTime @default(now())
|
|
12
|
+
updatedAt DateTime @updatedAt
|
|
13
|
+
sessions Session[]
|
|
14
|
+
accounts Account[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
model Session {
|
|
18
|
+
id String @id @default(uuid())
|
|
19
|
+
userId String
|
|
20
|
+
token String @unique
|
|
21
|
+
expiresAt DateTime
|
|
22
|
+
createdAt DateTime @default(now())
|
|
23
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
24
|
+
|
|
25
|
+
@@index([token])
|
|
26
|
+
@@index([userId])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
model Account {
|
|
30
|
+
id String @id @default(uuid())
|
|
31
|
+
userId String
|
|
32
|
+
type String
|
|
33
|
+
provider String
|
|
34
|
+
providerAccountId String
|
|
35
|
+
refresh_token String?
|
|
36
|
+
access_token String?
|
|
37
|
+
expires_at Int?
|
|
38
|
+
token_type String?
|
|
39
|
+
scope String?
|
|
40
|
+
id_token String?
|
|
41
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
42
|
+
|
|
43
|
+
@@unique([provider, providerAccountId])
|
|
44
|
+
@@index([userId])
|
|
45
|
+
}
|