stackpatch 1.1.4 → 1.1.5
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 +70 -67
- package/bin/stackpatch.ts +2441 -2
- package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +124 -0
- package/boilerplate/auth/app/api/auth/signup/route.ts +45 -0
- package/boilerplate/auth/app/auth/login/page.tsx +24 -50
- package/boilerplate/auth/app/auth/signup/page.tsx +56 -69
- package/boilerplate/auth/app/dashboard/page.tsx +82 -0
- package/boilerplate/auth/app/login/page.tsx +136 -0
- package/boilerplate/auth/app/page.tsx +48 -0
- package/boilerplate/auth/components/auth-button.tsx +43 -0
- package/boilerplate/auth/components/auth-navbar.tsx +118 -0
- package/boilerplate/auth/components/protected-route.tsx +74 -0
- package/boilerplate/auth/components/session-provider.tsx +11 -0
- package/boilerplate/auth/middleware.ts +51 -0
- package/package.json +2 -4
- package/boilerplate/auth/app/stackpatch/page.tsx +0 -269
- package/boilerplate/auth/components/auth-wrapper.tsx +0 -61
- package/src/auth/generator.ts +0 -569
- package/src/auth/index.ts +0 -372
- package/src/auth/setup.ts +0 -293
- package/src/commands/add.ts +0 -112
- package/src/commands/create.ts +0 -128
- package/src/commands/revert.ts +0 -389
- package/src/config.ts +0 -52
- package/src/fileOps/copy.ts +0 -224
- package/src/fileOps/layout.ts +0 -304
- package/src/fileOps/protected.ts +0 -67
- package/src/index.ts +0 -215
- package/src/manifest.ts +0 -87
- package/src/ui/logo.ts +0 -24
- package/src/ui/progress.ts +0 -82
- package/src/utils/dependencies.ts +0 -114
- package/src/utils/deps-check.ts +0 -45
- package/src/utils/files.ts +0 -58
- package/src/utils/paths.ts +0 -217
- package/src/utils/scanner.ts +0 -109
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import NextAuth from "next-auth";
|
|
2
|
+
import type { NextAuthOptions } from "next-auth";
|
|
3
|
+
import GoogleProvider from "next-auth/providers/google";
|
|
4
|
+
import GitHubProvider from "next-auth/providers/github";
|
|
5
|
+
import CredentialsProvider from "next-auth/providers/credentials";
|
|
6
|
+
|
|
7
|
+
export const authOptions: NextAuthOptions = {
|
|
8
|
+
providers: [
|
|
9
|
+
CredentialsProvider({
|
|
10
|
+
name: "Credentials",
|
|
11
|
+
credentials: {
|
|
12
|
+
email: { label: "Email", type: "email" },
|
|
13
|
+
password: { label: "Password", type: "password" },
|
|
14
|
+
},
|
|
15
|
+
async authorize(credentials) {
|
|
16
|
+
// ⚠️ DEMO MODE: This is a placeholder implementation
|
|
17
|
+
//
|
|
18
|
+
// TO IMPLEMENT REAL AUTHENTICATION:
|
|
19
|
+
// 1. Set up a database (PostgreSQL, MongoDB, Prisma, etc.)
|
|
20
|
+
// 2. Install bcrypt: npm install bcryptjs @types/bcryptjs
|
|
21
|
+
// 3. Replace this function with database lookup:
|
|
22
|
+
//
|
|
23
|
+
// Example implementation:
|
|
24
|
+
// ```ts
|
|
25
|
+
// import bcrypt from "bcryptjs";
|
|
26
|
+
// import { db } from "@/lib/db"; // Your database connection
|
|
27
|
+
//
|
|
28
|
+
// async authorize(credentials) {
|
|
29
|
+
// if (!credentials?.email || !credentials?.password) {
|
|
30
|
+
// return null;
|
|
31
|
+
// }
|
|
32
|
+
//
|
|
33
|
+
// // Find user in database
|
|
34
|
+
// const user = await db.user.findUnique({
|
|
35
|
+
// where: { email: credentials.email },
|
|
36
|
+
// });
|
|
37
|
+
//
|
|
38
|
+
// if (!user) {
|
|
39
|
+
// return null;
|
|
40
|
+
// }
|
|
41
|
+
//
|
|
42
|
+
// // Verify password
|
|
43
|
+
// const isValid = await bcrypt.compare(
|
|
44
|
+
// credentials.password,
|
|
45
|
+
// user.password
|
|
46
|
+
// );
|
|
47
|
+
//
|
|
48
|
+
// if (!isValid) {
|
|
49
|
+
// return null;
|
|
50
|
+
// }
|
|
51
|
+
//
|
|
52
|
+
// return {
|
|
53
|
+
// id: user.id,
|
|
54
|
+
// email: user.email,
|
|
55
|
+
// name: user.name,
|
|
56
|
+
// };
|
|
57
|
+
// }
|
|
58
|
+
// ```
|
|
59
|
+
//
|
|
60
|
+
// Current demo credentials (REMOVE IN PRODUCTION):
|
|
61
|
+
// Email: demo@example.com
|
|
62
|
+
// Password: demo123
|
|
63
|
+
|
|
64
|
+
if (!credentials?.email || !credentials?.password) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Demo check - REMOVE THIS IN PRODUCTION
|
|
69
|
+
if (
|
|
70
|
+
credentials.email === "demo@example.com" &&
|
|
71
|
+
credentials.password === "demo123"
|
|
72
|
+
) {
|
|
73
|
+
return {
|
|
74
|
+
id: "1",
|
|
75
|
+
email: credentials.email,
|
|
76
|
+
name: "Demo User",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
GoogleProvider({
|
|
84
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
85
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
86
|
+
}),
|
|
87
|
+
GitHubProvider({
|
|
88
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
89
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
pages: {
|
|
93
|
+
signIn: "/auth/login",
|
|
94
|
+
error: "/auth/error",
|
|
95
|
+
},
|
|
96
|
+
session: {
|
|
97
|
+
strategy: "jwt",
|
|
98
|
+
},
|
|
99
|
+
callbacks: {
|
|
100
|
+
async jwt({ token, user, account }) {
|
|
101
|
+
if (user) {
|
|
102
|
+
token.id = user.id;
|
|
103
|
+
token.email = user.email;
|
|
104
|
+
token.name = user.name;
|
|
105
|
+
}
|
|
106
|
+
if (account) {
|
|
107
|
+
token.accessToken = account.access_token;
|
|
108
|
+
token.provider = account.provider;
|
|
109
|
+
}
|
|
110
|
+
return token;
|
|
111
|
+
},
|
|
112
|
+
async session({ session, token }) {
|
|
113
|
+
if (session.user) {
|
|
114
|
+
session.user.id = token.id as string;
|
|
115
|
+
session.accessToken = token.accessToken as string;
|
|
116
|
+
}
|
|
117
|
+
return session;
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handler = NextAuth(authOptions);
|
|
123
|
+
|
|
124
|
+
export { handler as GET, handler as POST };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
export async function POST(request: Request) {
|
|
4
|
+
try {
|
|
5
|
+
const { email, password, name } = await request.json();
|
|
6
|
+
|
|
7
|
+
// TODO: Replace with your actual signup logic
|
|
8
|
+
// This is a placeholder - you should:
|
|
9
|
+
// 1. Validate input
|
|
10
|
+
// 2. Check if user already exists
|
|
11
|
+
// 3. Hash password (use bcrypt or similar)
|
|
12
|
+
// 4. Save user to database
|
|
13
|
+
// 5. Return success or error
|
|
14
|
+
|
|
15
|
+
// Example validation
|
|
16
|
+
if (!email || !password || !name) {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ error: "Missing required fields" },
|
|
19
|
+
{ status: 400 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (password.length < 6) {
|
|
24
|
+
return NextResponse.json(
|
|
25
|
+
{ error: "Password must be at least 6 characters" },
|
|
26
|
+
{ status: 400 }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Placeholder: In production, save to database here
|
|
31
|
+
// const hashedPassword = await bcrypt.hash(password, 10);
|
|
32
|
+
// const user = await db.user.create({ email, password: hashedPassword, name });
|
|
33
|
+
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ message: "Account created successfully" },
|
|
36
|
+
{ status: 201 }
|
|
37
|
+
);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Signup error:", error);
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: "Failed to create account" },
|
|
42
|
+
{ status: 500 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,59 +1,50 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { useState
|
|
5
|
-
import { useRouter
|
|
3
|
+
import { signIn } from "next-auth/react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
6
|
import toast from "react-hot-toast";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Login Page
|
|
10
|
-
*
|
|
11
|
-
* 📝 TO CHANGE THE REDIRECT ROUTE AFTER LOGIN:
|
|
12
|
-
* Change "/stackpatch" below to your desired route (e.g., "/dashboard", "/home")
|
|
13
|
-
* Also update the same route in:
|
|
14
|
-
* - app/page.tsx
|
|
15
|
-
* - app/auth/signup/page.tsx
|
|
16
|
-
*/
|
|
17
8
|
export default function LoginPage() {
|
|
18
9
|
const router = useRouter();
|
|
19
|
-
const searchParams = useSearchParams();
|
|
20
|
-
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
|
21
10
|
const [email, setEmail] = useState("");
|
|
22
11
|
const [password, setPassword] = useState("");
|
|
23
12
|
const [error, setError] = useState("");
|
|
24
13
|
const [loading, setLoading] = useState(false);
|
|
25
14
|
|
|
26
|
-
// 🔧 CHANGE THIS ROUTE: Update "/stackpatch" to your desired landing page route
|
|
27
|
-
const LANDING_PAGE_ROUTE = "/stackpatch";
|
|
28
|
-
|
|
29
|
-
// Get redirect URL from query params (set by middleware when protecting routes)
|
|
30
|
-
const redirectTo = searchParams.get("redirect") || LANDING_PAGE_ROUTE;
|
|
31
|
-
|
|
32
|
-
// Redirect if already signed in
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (!sessionLoading && session?.user) {
|
|
35
|
-
router.push(redirectTo);
|
|
36
|
-
}
|
|
37
|
-
}, [session, sessionLoading, router, redirectTo]);
|
|
38
|
-
|
|
39
15
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
40
16
|
e.preventDefault();
|
|
41
17
|
setError("");
|
|
42
18
|
setLoading(true);
|
|
43
19
|
|
|
44
20
|
try {
|
|
45
|
-
|
|
21
|
+
// ⚠️ DEMO MODE: This is a placeholder implementation
|
|
22
|
+
//
|
|
23
|
+
// TO IMPLEMENT REAL AUTHENTICATION:
|
|
24
|
+
// 1. Set up a database (PostgreSQL, MongoDB, etc.)
|
|
25
|
+
// 2. Create a user registration API route (app/api/auth/signup/route.ts)
|
|
26
|
+
// 3. Hash passwords using bcrypt or similar
|
|
27
|
+
// 4. Update the authorize function in app/api/auth/[...nextauth]/route.ts
|
|
28
|
+
// to check credentials against your database
|
|
29
|
+
// 5. Remove this demo check and implement proper database lookup
|
|
30
|
+
//
|
|
31
|
+
// Current demo credentials (REMOVE IN PRODUCTION):
|
|
32
|
+
// Email: demo@example.com
|
|
33
|
+
// Password: demo123
|
|
34
|
+
|
|
35
|
+
const result = await signIn("credentials", {
|
|
46
36
|
email,
|
|
47
37
|
password,
|
|
38
|
+
redirect: false,
|
|
48
39
|
});
|
|
49
40
|
|
|
50
|
-
if (result
|
|
51
|
-
setError(
|
|
52
|
-
toast.error(
|
|
41
|
+
if (result?.error) {
|
|
42
|
+
setError("Invalid email or password");
|
|
43
|
+
toast.error("Invalid email or password");
|
|
53
44
|
} else {
|
|
54
45
|
toast.success("Login successful! Redirecting...");
|
|
55
46
|
setTimeout(() => {
|
|
56
|
-
router.push(
|
|
47
|
+
router.push("/");
|
|
57
48
|
router.refresh();
|
|
58
49
|
}, 1000);
|
|
59
50
|
}
|
|
@@ -67,29 +58,12 @@ export default function LoginPage() {
|
|
|
67
58
|
|
|
68
59
|
const handleOAuthSignIn = async (provider: "google" | "github") => {
|
|
69
60
|
try {
|
|
70
|
-
await
|
|
61
|
+
await signIn(provider, { callbackUrl: "/" });
|
|
71
62
|
} catch (error) {
|
|
72
63
|
toast.error(`Failed to sign in with ${provider}`);
|
|
73
64
|
}
|
|
74
65
|
};
|
|
75
66
|
|
|
76
|
-
// Show loading state while checking session
|
|
77
|
-
if (sessionLoading) {
|
|
78
|
-
return (
|
|
79
|
-
<div className="flex min-h-screen items-center justify-center">
|
|
80
|
-
<div className="text-center">
|
|
81
|
-
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent"></div>
|
|
82
|
-
<p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading...</p>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Don't show login form if already signed in (will redirect)
|
|
89
|
-
if (session?.user) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
67
|
return (
|
|
94
68
|
<div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
|
|
95
69
|
<div className="w-full max-w-md space-y-8">
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { useRouter
|
|
5
|
-
import {
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { signIn } from "next-auth/react";
|
|
6
6
|
import toast from "react-hot-toast";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Signup Page
|
|
10
|
-
*
|
|
11
|
-
* 📝 TO CHANGE THE REDIRECT ROUTE AFTER SIGNUP:
|
|
12
|
-
* Change "/stackpatch" below to your desired route (e.g., "/dashboard", "/home")
|
|
13
|
-
* Also update the same route in:
|
|
14
|
-
* - app/page.tsx
|
|
15
|
-
* - app/auth/login/page.tsx
|
|
16
|
-
*/
|
|
17
8
|
export default function SignupPage() {
|
|
18
9
|
const router = useRouter();
|
|
19
|
-
const searchParams = useSearchParams();
|
|
20
|
-
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
|
21
10
|
const [email, setEmail] = useState("");
|
|
22
11
|
const [password, setPassword] = useState("");
|
|
23
12
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
@@ -25,19 +14,6 @@ export default function SignupPage() {
|
|
|
25
14
|
const [error, setError] = useState("");
|
|
26
15
|
const [loading, setLoading] = useState(false);
|
|
27
16
|
|
|
28
|
-
// 🔧 CHANGE THIS ROUTE: Update "/stackpatch" to your desired landing page route
|
|
29
|
-
const LANDING_PAGE_ROUTE = "/stackpatch";
|
|
30
|
-
|
|
31
|
-
// Get redirect URL from query params (set by middleware when protecting routes)
|
|
32
|
-
const redirectTo = searchParams.get("redirect") || LANDING_PAGE_ROUTE;
|
|
33
|
-
|
|
34
|
-
// Redirect if already signed in
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!sessionLoading && session?.user) {
|
|
37
|
-
router.push(redirectTo);
|
|
38
|
-
}
|
|
39
|
-
}, [session, sessionLoading, router, redirectTo]);
|
|
40
|
-
|
|
41
17
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
42
18
|
e.preventDefault();
|
|
43
19
|
setError("");
|
|
@@ -57,35 +33,63 @@ export default function SignupPage() {
|
|
|
57
33
|
setLoading(true);
|
|
58
34
|
|
|
59
35
|
try {
|
|
60
|
-
//
|
|
61
|
-
|
|
36
|
+
// ⚠️ DEMO MODE: This is a placeholder implementation
|
|
37
|
+
//
|
|
38
|
+
// TO IMPLEMENT REAL SIGNUP:
|
|
39
|
+
// 1. Set up a database (PostgreSQL, MongoDB, etc.)
|
|
40
|
+
// 2. Create app/api/auth/signup/route.ts with:
|
|
41
|
+
// - Password hashing (use bcrypt)
|
|
42
|
+
// - User creation in database
|
|
43
|
+
// - Email validation
|
|
44
|
+
// - Duplicate user checking
|
|
45
|
+
// 3. Update this fetch call to use your actual signup endpoint
|
|
46
|
+
// 4. Handle errors properly (duplicate email, weak password, etc.)
|
|
47
|
+
//
|
|
48
|
+
// Example signup route structure:
|
|
49
|
+
// ```ts
|
|
50
|
+
// // app/api/auth/signup/route.ts
|
|
51
|
+
// import bcrypt from "bcryptjs";
|
|
52
|
+
// import { db } from "@/lib/db"; // Your database connection
|
|
53
|
+
//
|
|
54
|
+
// export async function POST(req: Request) {
|
|
55
|
+
// const { email, password, name } = await req.json();
|
|
56
|
+
// const hashedPassword = await bcrypt.hash(password, 10);
|
|
57
|
+
// const user = await db.user.create({ data: { email, password: hashedPassword, name } });
|
|
58
|
+
// return Response.json({ user });
|
|
59
|
+
// }
|
|
60
|
+
// ```
|
|
61
|
+
|
|
62
|
+
const response = await fetch("/api/auth/signup", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({ email, password, name }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
setError(data.error || "Failed to create account");
|
|
71
|
+
toast.error(data.error || "Failed to create account");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toast.success("Account created successfully! Signing you in...");
|
|
76
|
+
|
|
77
|
+
// Auto sign in after successful signup
|
|
78
|
+
const result = await signIn("credentials", {
|
|
62
79
|
email,
|
|
63
80
|
password,
|
|
64
|
-
|
|
81
|
+
redirect: false,
|
|
65
82
|
});
|
|
66
83
|
|
|
67
|
-
if (result
|
|
68
|
-
setError(
|
|
69
|
-
toast.error(
|
|
84
|
+
if (result?.error) {
|
|
85
|
+
setError("Account created but sign in failed. Please try logging in.");
|
|
86
|
+
toast.error("Account created but sign in failed. Please try logging in.");
|
|
70
87
|
} else {
|
|
71
|
-
toast.success("
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
password,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (signInResult.error) {
|
|
80
|
-
setError("Account created but sign in failed. Please try logging in.");
|
|
81
|
-
toast.error("Account created but sign in failed. Please try logging in.");
|
|
82
|
-
} else {
|
|
83
|
-
toast.success("Welcome! Redirecting...");
|
|
84
|
-
setTimeout(() => {
|
|
85
|
-
router.push(redirectTo);
|
|
86
|
-
router.refresh();
|
|
87
|
-
}, 1000);
|
|
88
|
-
}
|
|
88
|
+
toast.success("Welcome! Redirecting...");
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
router.push("/");
|
|
91
|
+
router.refresh();
|
|
92
|
+
}, 1000);
|
|
89
93
|
}
|
|
90
94
|
} catch (err) {
|
|
91
95
|
setError("Something went wrong. Please try again.");
|
|
@@ -97,29 +101,12 @@ export default function SignupPage() {
|
|
|
97
101
|
|
|
98
102
|
const handleOAuthSignIn = async (provider: "google" | "github") => {
|
|
99
103
|
try {
|
|
100
|
-
await
|
|
104
|
+
await signIn(provider, { callbackUrl: "/" });
|
|
101
105
|
} catch (error) {
|
|
102
106
|
toast.error(`Failed to sign in with ${provider}`);
|
|
103
107
|
}
|
|
104
108
|
};
|
|
105
109
|
|
|
106
|
-
// Show loading state while checking session
|
|
107
|
-
if (sessionLoading) {
|
|
108
|
-
return (
|
|
109
|
-
<div className="flex min-h-screen items-center justify-center">
|
|
110
|
-
<div className="text-center">
|
|
111
|
-
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent"></div>
|
|
112
|
-
<p className="mt-4 text-sm text-zinc-600 dark:text-zinc-400">Loading...</p>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Don't show signup form if already signed in (will redirect)
|
|
119
|
-
if (session?.user) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
110
|
return (
|
|
124
111
|
<div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
|
|
125
112
|
<div className="w-full max-w-md space-y-8">
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useSession } from "next-auth/react";
|
|
5
|
+
import { ProtectedRoute } from "@/components/protected-route";
|
|
6
|
+
import { AuthNavbar } from "@/components/auth-navbar";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dashboard Page Example
|
|
10
|
+
*
|
|
11
|
+
* This is an example protected dashboard page.
|
|
12
|
+
*
|
|
13
|
+
* To use this:
|
|
14
|
+
* 1. Copy this file to your app/dashboard/page.tsx
|
|
15
|
+
* 2. The page is automatically protected using ProtectedRoute
|
|
16
|
+
* 3. The AuthNavbar shows session status and sign out button
|
|
17
|
+
*
|
|
18
|
+
* You can customize this page to show your dashboard content.
|
|
19
|
+
* If you have an existing navbar, replace AuthNavbar with your own.
|
|
20
|
+
*/
|
|
21
|
+
export default function DashboardPage() {
|
|
22
|
+
const { data: session } = useSession();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ProtectedRoute>
|
|
26
|
+
<div className="min-h-screen bg-zinc-50 dark:bg-black">
|
|
27
|
+
<AuthNavbar />
|
|
28
|
+
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
|
29
|
+
<div className="rounded-lg bg-white p-8 shadow dark:bg-zinc-900">
|
|
30
|
+
<h1 className="text-3xl font-bold text-zinc-900 dark:text-zinc-50">
|
|
31
|
+
Dashboard
|
|
32
|
+
</h1>
|
|
33
|
+
<p className="mt-2 text-zinc-600 dark:text-zinc-400">
|
|
34
|
+
Welcome to your protected dashboard!
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
{session && (
|
|
38
|
+
<div className="mt-6 rounded-md bg-zinc-100 p-4 dark:bg-zinc-800">
|
|
39
|
+
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
|
|
40
|
+
Session Information
|
|
41
|
+
</h2>
|
|
42
|
+
<div className="mt-2 space-y-1 text-sm text-zinc-600 dark:text-zinc-400">
|
|
43
|
+
<p>
|
|
44
|
+
<span className="font-medium">Name:</span>{" "}
|
|
45
|
+
{session.user?.name || "Not provided"}
|
|
46
|
+
</p>
|
|
47
|
+
<p>
|
|
48
|
+
<span className="font-medium">Email:</span>{" "}
|
|
49
|
+
{session.user?.email || "Not provided"}
|
|
50
|
+
</p>
|
|
51
|
+
{session.user?.image && (
|
|
52
|
+
<p>
|
|
53
|
+
<span className="font-medium">Image:</span>{" "}
|
|
54
|
+
<img
|
|
55
|
+
src={session.user.image}
|
|
56
|
+
alt={session.user.name || "User"}
|
|
57
|
+
className="mt-2 h-16 w-16 rounded-full"
|
|
58
|
+
/>
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
<div className="mt-8">
|
|
66
|
+
<h2 className="text-xl font-semibold text-zinc-900 dark:text-zinc-50">
|
|
67
|
+
Getting Started
|
|
68
|
+
</h2>
|
|
69
|
+
<p className="mt-2 text-zinc-600 dark:text-zinc-400">
|
|
70
|
+
This is a protected page. Only authenticated users can see this
|
|
71
|
+
content.
|
|
72
|
+
</p>
|
|
73
|
+
<p className="mt-2 text-zinc-600 dark:text-zinc-400">
|
|
74
|
+
Customize this page to add your dashboard features.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</main>
|
|
79
|
+
</div>
|
|
80
|
+
</ProtectedRoute>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { signIn } from "next-auth/react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
export default function LoginPage() {
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
const [email, setEmail] = useState("");
|
|
10
|
+
const [password, setPassword] = useState("");
|
|
11
|
+
const [error, setError] = useState("");
|
|
12
|
+
const [loading, setLoading] = useState(false);
|
|
13
|
+
|
|
14
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
setError("");
|
|
17
|
+
setLoading(true);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const result = await signIn("credentials", {
|
|
21
|
+
email,
|
|
22
|
+
password,
|
|
23
|
+
redirect: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result?.error) {
|
|
27
|
+
setError("Invalid email or password");
|
|
28
|
+
} else {
|
|
29
|
+
router.push("/");
|
|
30
|
+
router.refresh();
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
setError("Something went wrong. Please try again.");
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex min-h-screen items-center justify-center bg-zinc-50 px-4 py-12 dark:bg-black sm:px-6 lg:px-8">
|
|
41
|
+
<div className="w-full max-w-md space-y-8">
|
|
42
|
+
<div>
|
|
43
|
+
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">
|
|
44
|
+
Sign in to your account
|
|
45
|
+
</h2>
|
|
46
|
+
<p className="mt-2 text-center text-sm text-zinc-600 dark:text-zinc-400">
|
|
47
|
+
Or{" "}
|
|
48
|
+
<a
|
|
49
|
+
href="/auth/signup"
|
|
50
|
+
className="font-medium text-zinc-950 hover:text-zinc-700 dark:text-zinc-50 dark:hover:text-zinc-300"
|
|
51
|
+
>
|
|
52
|
+
create a new account
|
|
53
|
+
</a>
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
|
57
|
+
{error && (
|
|
58
|
+
<div className="rounded-md bg-red-50 p-4 dark:bg-red-900/20">
|
|
59
|
+
<p className="text-sm text-red-800 dark:text-red-200">{error}</p>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
<div className="-space-y-px rounded-md shadow-sm">
|
|
63
|
+
<div>
|
|
64
|
+
<label htmlFor="email" className="sr-only">
|
|
65
|
+
Email address
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
id="email"
|
|
69
|
+
name="email"
|
|
70
|
+
type="email"
|
|
71
|
+
autoComplete="email"
|
|
72
|
+
required
|
|
73
|
+
value={email}
|
|
74
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
75
|
+
className="relative block w-full rounded-t-md border-0 px-3 py-2 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-800 dark:text-zinc-50 dark:ring-zinc-700 dark:placeholder:text-zinc-500 dark:focus:ring-zinc-400 sm:text-sm sm:leading-6"
|
|
76
|
+
placeholder="Email address"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
<label htmlFor="password" className="sr-only">
|
|
81
|
+
Password
|
|
82
|
+
</label>
|
|
83
|
+
<input
|
|
84
|
+
id="password"
|
|
85
|
+
name="password"
|
|
86
|
+
type="password"
|
|
87
|
+
autoComplete="current-password"
|
|
88
|
+
required
|
|
89
|
+
value={password}
|
|
90
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
91
|
+
className="relative block w-full rounded-b-md border-0 px-3 py-2 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-800 dark:text-zinc-50 dark:ring-zinc-700 dark:placeholder:text-zinc-500 dark:focus:ring-zinc-400 sm:text-sm sm:leading-6"
|
|
92
|
+
placeholder="Password"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="flex items-center justify-between">
|
|
98
|
+
<div className="flex items-center">
|
|
99
|
+
<input
|
|
100
|
+
id="remember-me"
|
|
101
|
+
name="remember-me"
|
|
102
|
+
type="checkbox"
|
|
103
|
+
className="h-4 w-4 rounded border-zinc-300 text-zinc-600 focus:ring-zinc-600 dark:border-zinc-700 dark:bg-zinc-800"
|
|
104
|
+
/>
|
|
105
|
+
<label
|
|
106
|
+
htmlFor="remember-me"
|
|
107
|
+
className="ml-2 block text-sm text-zinc-900 dark:text-zinc-50"
|
|
108
|
+
>
|
|
109
|
+
Remember me
|
|
110
|
+
</label>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="text-sm">
|
|
114
|
+
<a
|
|
115
|
+
href="/auth/forgot-password"
|
|
116
|
+
className="font-medium text-zinc-600 hover:text-zinc-500 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
117
|
+
>
|
|
118
|
+
Forgot your password?
|
|
119
|
+
</a>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div>
|
|
124
|
+
<button
|
|
125
|
+
type="submit"
|
|
126
|
+
disabled={loading}
|
|
127
|
+
className="group relative flex w-full justify-center rounded-md bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-600 disabled:opacity-50 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
128
|
+
>
|
|
129
|
+
{loading ? "Signing in..." : "Sign in"}
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</form>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|