Skip to content

Commit cab9263

Browse files
committed
Add authentication and dashboard features
- Introduced a new authentication module in `auth.ts` using NextAuth with Google and credentials providers. - Created API routes for user authentication, including login, registration, password reset, and OTP verification. - Added a dashboard overview API to fetch user-specific data. - Implemented new components for the dashboard, including activity overview, stats cards, and suggested next steps. - Refactored existing components to improve structure and maintainability, including the removal of unused imports and components. - Enhanced middleware for session management and improved error handling in API responses.
1 parent 7f5b985 commit cab9263

40 files changed

Lines changed: 2506 additions & 554 deletions

app/(dashboard)/dashboard/page.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
1-
"use client";
2-
import MyContributionsPage from "@/components/contributions";
3-
import Dashboard from "@/components/dashboard";
4-
import { useUserStore } from "@/store/userStore";
5-
import React from "react";
1+
'use client';
2+
import Dashboard from '@/components/dashboard';
3+
import React from 'react';
64

75
const Page = () => {
8-
const {
9-
user,
10-
isLoading,
11-
// fetchUserProfile,
12-
hasCreatedProject,
13-
} = useUserStore();
14-
15-
if (isLoading) {
16-
return <p>Loading...</p>;
17-
}
18-
return (
19-
<>
20-
{hasCreatedProject() ? (
21-
<Dashboard />
22-
) : (
23-
<MyContributionsPage username={user?.name} />
24-
)}
25-
</>
26-
);
6+
return (
7+
<>
8+
<Dashboard />
9+
</>
10+
);
2711
};
2812

2913
export default Page;
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
1-
import { authOptions } from "@/lib/auth.config";
2-
import NextAuth from "next-auth";
3-
4-
const handler = NextAuth(authOptions);
5-
6-
export { handler as GET, handler as POST };
1+
import { handlers } from "@/auth" // Referring to the auth.ts we just created
2+
export const { GET, POST } = handlers
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { NextResponse } from "next/server";
2+
import { googleAuth } from "@/lib/api/auth";
3+
import { z } from "zod";
4+
5+
const googleSchema = z.object({
6+
token: z.string().min(1, "Google token is required"),
7+
});
8+
9+
export async function POST(req: Request) {
10+
try {
11+
const body = await req.json();
12+
const { token } = googleSchema.parse(body);
13+
const apiRes = await googleAuth({ token });
14+
return NextResponse.json(apiRes, { status: 200 });
15+
} catch (error) {
16+
if (error instanceof z.ZodError) {
17+
return NextResponse.json({ message: error.errors[0].message }, { status: 400 });
18+
}
19+
if (typeof error === "object" && error !== null && "message" in error) {
20+
return NextResponse.json({ message: String((error as { message: unknown }).message) }, { status: 500 });
21+
}
22+
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
23+
}
24+
}
25+
Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { sendPasswordResetEmail } from "@/lib/email";
2-
import { PrismaClient } from "@prisma/client";
31
import { NextResponse } from "next/server";
2+
import { forgotPassword } from "@/lib/api/auth";
43
import { z } from "zod";
54

6-
const prisma = new PrismaClient();
75
const emailSchema = z.object({
86
email: z.string().email("Invalid email address"),
97
});
@@ -12,52 +10,16 @@ export async function POST(req: Request) {
1210
try {
1311
const body = await req.json();
1412
const { email } = emailSchema.parse(body);
15-
16-
const user = await prisma.user.findUnique({
17-
where: { email },
18-
});
19-
20-
if (!user) {
21-
return NextResponse.json(
22-
{ message: "User email not found" },
23-
{ status: 404 },
24-
);
13+
const apiRes = await forgotPassword({ email });
14+
return NextResponse.json(apiRes, { status: 200 });
15+
} catch (error) {
16+
if (error instanceof z.ZodError) {
17+
return NextResponse.json({ message: error.errors[0].message }, { status: 400 });
2518
}
26-
27-
const otp = Math.floor(100000 + Math.random() * 900000).toString();
28-
const otpExpiry = new Date(Date.now() + 10 * 60 * 1000);
29-
30-
await prisma.verificationToken.create({
31-
data: {
32-
identifier: email,
33-
token: otp,
34-
expires: otpExpiry,
35-
},
36-
});
37-
38-
try {
39-
await sendPasswordResetEmail(user.email || "", user.name || "", otp);
40-
return NextResponse.json({
41-
success: true,
42-
message: "Password reset email sent",
43-
status: 200,
44-
});
45-
} catch (emailError) {
46-
console.error("Error sending password reset email:", emailError);
47-
return NextResponse.json(
48-
{ error: "Failed to send password reset email" },
49-
{ status: 500 },
50-
);
19+
// If backend returns an error, try to forward it
20+
if (typeof error === 'object' && error !== null && 'message' in error) {
21+
return NextResponse.json({ message: String((error as { message: unknown }).message) }, { status: 500 });
5122
}
52-
} catch (error) {
53-
console.error("Forgot Password API Error:", error);
54-
55-
return NextResponse.json(
56-
{
57-
message: "Internal Server Error",
58-
error: error instanceof Error ? error.message : "Unknown error",
59-
},
60-
{ status: 500 },
61-
);
23+
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
6224
}
6325
}

app/api/auth/register/route.ts

Lines changed: 13 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import { sendVerificationEmail } from "@/lib/email";
2-
import { PrismaClient } from "@prisma/client";
3-
import { hash } from "bcrypt";
1+
import { register } from "@/lib/api/auth";
42
import { NextResponse } from "next/server";
53
import { z } from "zod";
64

7-
const prisma = new PrismaClient();
85
const userSchema = z.object({
96
name: z.string().min(2, "Name must be at least 2 characters"),
107
email: z.string().email("Invalid email address"),
@@ -15,64 +12,26 @@ export async function POST(req: Request) {
1512
try {
1613
const body = await req.json();
1714
const { name, email, password } = userSchema.parse(body);
18-
19-
const existingUser = await prisma.user.findUnique({
20-
where: { email },
21-
});
22-
23-
if (existingUser) {
24-
return NextResponse.json(
25-
{ message: "User already exists" },
26-
{ status: 400 },
27-
);
28-
}
29-
30-
const hashedPassword = await hash(password, 12);
31-
32-
const user = await prisma.user.create({
33-
data: {
34-
name,
35-
email,
36-
password: hashedPassword,
37-
},
38-
});
39-
40-
// Generate OTP
41-
const otp = Math.floor(100000 + Math.random() * 900000).toString();
42-
const otpExpires = new Date(Date.now() + 10 * 60 * 1000);
43-
44-
// Save OTP to database
45-
await prisma.oTP.create({
46-
data: {
47-
userId: user.id,
48-
token: otp,
49-
expires: otpExpires,
50-
},
51-
});
52-
53-
try {
54-
await sendVerificationEmail(email, name, otp);
55-
console.log("Verification email sent successfully");
56-
} catch (error) {
57-
console.error("Error sending verification email:", error);
58-
}
59-
return NextResponse.json(
60-
{
61-
message: "User created successfully. Please check your email for OTP.",
62-
userId: user.id,
63-
},
64-
{ status: 201 },
65-
);
15+
// Compose RegisterRequest
16+
const registerData = {
17+
email,
18+
password,
19+
firstName: name, // or split name if needed
20+
lastName: name, // You may want to parse this from name or add to schema
21+
username: email.split("@")[0],
22+
};
23+
const response = await register(registerData);
24+
return NextResponse.json(response, { status: 201 });
6625
} catch (error) {
6726
if (error instanceof z.ZodError) {
6827
return NextResponse.json(
6928
{ message: error.errors[0].message },
7029
{ status: 400 },
7130
);
7231
}
73-
console.error("Error during registration:", error);
32+
console.error(error)
7433
return NextResponse.json(
75-
{ message: "An error occurred during registration" },
34+
{ message: error},
7635
{ status: 500 },
7736
);
7837
}

app/api/auth/resend-otp/route.ts

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,13 @@
1-
import { resendOTP } from "@/lib/email";
2-
import { PrismaClient } from "@prisma/client";
1+
import { resendOtp } from "@/lib/api/auth";
32
import { NextResponse } from "next/server";
43

5-
const prisma = new PrismaClient();
6-
74
export async function POST(req: Request) {
85
try {
96
const { email } = await req.json();
107

11-
const user = await prisma.user.findUnique({
12-
where: { email },
13-
});
14-
15-
if (!user) {
16-
return NextResponse.json({ message: "User not found" }, { status: 400 });
17-
}
8+
const response = await resendOtp({ email });
189

19-
// Generate new OTP
20-
const otp = Math.floor(100000 + Math.random() * 900000).toString();
21-
const otpExpires = new Date(Date.now() + 10 * 60 * 1000);
22-
await prisma.oTP.deleteMany({
23-
where: { userId: user.id },
24-
});
25-
await prisma.oTP.create({
26-
data: {
27-
userId: user.id,
28-
token: otp,
29-
expires: otpExpires,
30-
},
31-
});
32-
try {
33-
await resendOTP(user.email || "", user.name || "", otp);
34-
return NextResponse.json({
35-
success: true,
36-
message: "OTP code succesfully resent",
37-
status: 2000,
38-
});
39-
} catch (error) {
40-
console.error("Error sending password reset email:", error);
41-
return NextResponse.json(
42-
{ error: "Failed to resend OTP" },
43-
{ status: 500 },
44-
);
45-
}
10+
return NextResponse.json(response, { status: 200 });
4611
} catch (error) {
4712
console.error("Error in resending OTP:", error);
4813
return NextResponse.json(
Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,66 @@
1-
import { PrismaClient } from "@prisma/client";
2-
import { hash } from "bcrypt";
31
import { NextResponse } from "next/server";
2+
import { resetPassword } from "@/lib/api/auth";
43
import { z } from "zod";
54

6-
const prisma = new PrismaClient();
7-
85
const resetSchema = z.object({
9-
email: z.string().email("Invalid email address"),
10-
password: z.string().min(8, "Password must be at least 8 characters"),
11-
otp: z.string().length(6, "Invalid OTP"), // Ensure OTP is exactly 6 digits
6+
token: z.string().min(1, "Token is required"),
7+
newPassword: z.string().min(8, "Password must be at least 8 characters"),
128
});
139

14-
export async function POST(req: Request) {
15-
try {
16-
const body = await req.json();
17-
const { email, password, otp } = resetSchema.parse(body);
18-
19-
// Verify OTP
20-
const verificationToken = await prisma.verificationToken.findFirst({
21-
where: { identifier: email, token: otp },
22-
});
10+
const tokenSchema = z.object({
11+
token: z.string().min(1, "Token is required"),
12+
});
2313

24-
if (!verificationToken) {
25-
return NextResponse.json(
26-
{ message: "Invalid email or expired OTP" },
27-
{ status: 400 },
28-
);
14+
// GET method to verify token
15+
export async function GET(req: Request) {
16+
try {
17+
const { searchParams } = new URL(req.url);
18+
const token = searchParams.get('token');
19+
20+
if (!token) {
21+
return NextResponse.json({ message: "Token is required" }, { status: 400 });
2922
}
3023

31-
// Hash new password
32-
const hashedPassword = await hash(password, 12);
24+
const parsedToken = tokenSchema.parse({ token });
25+
26+
// You might want to add a verifyToken function to your auth.ts
27+
// For now, we'll just return success if token exists
28+
return NextResponse.json({
29+
message: "Token is valid",
30+
token: parsedToken.token
31+
}, { status: 200 });
3332

34-
// Update user's password
35-
await prisma.user.update({
36-
where: { email },
37-
data: { password: hashedPassword },
38-
});
39-
40-
// Delete OTP after successful password reset
41-
await prisma.verificationToken.delete({
42-
where: { id: verificationToken.id },
43-
});
33+
} catch (error) {
34+
if (error instanceof z.ZodError) {
35+
console.log(error);
36+
return NextResponse.json({ message: error.errors[0].message }, { status: 400 });
37+
}
38+
return NextResponse.json({ message: "Invalid token" }, { status: 400 });
39+
}
40+
}
4441

45-
return NextResponse.json(
46-
{ message: "Password reset successfully" },
47-
{ status: 200 },
48-
);
42+
// POST method to reset password
43+
export async function POST(req: Request) {
44+
try {
45+
const body = await req.json();
46+
47+
// Log the body to debug
48+
console.log('Request body:', body);
49+
50+
const { token, newPassword } = resetSchema.parse(body);
51+
const apiRes = await resetPassword({ token, newPassword });
52+
return NextResponse.json(apiRes, { status: 200 });
4953
} catch (error) {
5054
if (error instanceof z.ZodError) {
51-
return NextResponse.json(
52-
{ message: error.errors[0].message },
53-
{ status: 400 },
54-
);
55+
console.log(error.errors)
56+
return NextResponse.json({ message: error.errors[0].message }, { status: 400 });
57+
}
58+
// If backend returns an error, try to forward it
59+
console.log(error)
60+
if (typeof error === 'object' && error !== null && 'message' in error) {
61+
62+
return NextResponse.json({ message: String((error as { message: unknown }).message) }, { status: 500 });
5563
}
56-
return NextResponse.json({ message: "An error occurred" }, { status: 500 });
64+
return NextResponse.json({ message: error }, { status: 500 });
5765
}
5866
}

0 commit comments

Comments
 (0)