Skip to content

Commit ec4d321

Browse files
committed
changes
1 parent 6f4baeb commit ec4d321

File tree

30 files changed

+1012
-384
lines changed

30 files changed

+1012
-384
lines changed

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
NEXT_PUBLIC_SUPABASE_ANON_KEY = ""
2+
NEXT_PUBLIC_SUPABASE_URL = ""
3+
DATABASE_URL = "postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

app/api/auth/register/route.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import prisma from "@/lib/database/prismaClient";
2+
import { supabase } from "@/lib/supabaseClient";
3+
import { hashPassword } from "@/utils/hashing";
4+
import { signupSchema } from "@/validations/validation";
5+
import { NextRequest, NextResponse } from "next/server";
6+
7+
export async function POST(request: NextRequest) {
8+
const body = await request.json();
9+
const { email, password, fullName, leetcodeUsername, gender } = body;
10+
try {
11+
const { success, error } = signupSchema.safeParse({ email, password, fullName, leetcodeUsername, gender });
12+
13+
if (!success) {
14+
return NextResponse.json({
15+
success: false,
16+
message: error.flatten().fieldErrors,
17+
}, { status: 400 });
18+
}
19+
20+
21+
const userExists = await prisma.user.findFirst({
22+
where: {
23+
email: email,
24+
},
25+
});
26+
27+
if (userExists) {
28+
return NextResponse.json({
29+
success: false,
30+
message: "User with this email already exists.",
31+
}, { status: 404 });
32+
}
33+
34+
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
35+
email,
36+
password: password,
37+
});
38+
39+
if (signUpError) {
40+
return NextResponse.json({
41+
success: false,
42+
message: signUpError.message,
43+
}, { status: 401 });
44+
}
45+
46+
const supabaseId = signUpData.user?.id;
47+
48+
if (!supabaseId) {
49+
return NextResponse.json({
50+
success: false,
51+
message: "Failed to retrieve Supabase user ID.",
52+
}, { status: 400 });
53+
}
54+
55+
const hashedPassword = await hashPassword(password);
56+
const user = await prisma.user.create({
57+
data: {
58+
email,
59+
password: hashedPassword,
60+
fullName,
61+
leetcodeUsername,
62+
gender,
63+
supabaseId: supabaseId,
64+
isVerified: false,
65+
}
66+
});
67+
68+
return NextResponse.json({
69+
success: true,
70+
message: "User created successfully",
71+
user,
72+
}, { status: 201 });
73+
74+
} catch (error: any) {
75+
return NextResponse.json({
76+
success: false,
77+
message: error.message
78+
}, { status: 500 });
79+
}
80+
}

app/api/auth/user/route.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import prisma from '@/lib/database/prismaClient';
2+
import { NextResponse } from 'next/server';
3+
4+
export async function POST(request: Request) {
5+
try {
6+
7+
const { supabaseId } = await request.json();
8+
const userData = await prisma.user.findUnique({
9+
where: { supabaseId },
10+
select: {
11+
email: true,
12+
fullName: true,
13+
leetcodeUsername: true,
14+
id: true,
15+
isVerified: true,
16+
gender: true,
17+
createdAt: true,
18+
supabaseId: true,
19+
}
20+
});
21+
22+
if (!userData) {
23+
return NextResponse.json({ message: 'User not found' }, { status: 404 });
24+
}
25+
26+
return NextResponse.json({ user: userData },{status: 200});
27+
} catch (err) {
28+
console.error('Error fetching user data:', err);
29+
return NextResponse.json({ message: 'Error fetching user data' }, { status: 500 });
30+
}
31+
}

app/signup/page.tsx app/auth/register/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SignupForm from "@/components/SignupForm";
1+
import SignupForm from "@/components/AuthComponent/SignupForm";
22

33
export default function SignupPage() {
44
return (
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import LoginForm from "@/components/LoginForm";
1+
import SigninForm from "@/components/AuthComponent/SigninForm";
22

33
export default function SignupPage() {
44
return (
55
<div className="">
6-
<LoginForm />
6+
<SigninForm />
77
</div>
88
);
99
}

app/auth/verify/page.tsx

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { Button } from '@/components/ui/button'
5+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
6+
import { Alert, AlertDescription } from '@/components/ui/alert'
7+
import { Mail } from 'lucide-react'
8+
9+
export default function VerifyPage() {
10+
const [resendStatus, setResendStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
11+
12+
const handleResendEmail = async () => {
13+
setResendStatus('sending')
14+
await new Promise(resolve => setTimeout(resolve, 2000))
15+
setResendStatus('sent')
16+
}
17+
18+
return (
19+
<main className="flex min-h-screen items-center justify-center px-4">
20+
<Card className="w-full max-w-md rounded-2xl">
21+
<CardHeader>
22+
<div className="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-blue-100">
23+
<Mail className="h-10 w-10 text-blue-600" />
24+
</div>
25+
<CardTitle className="text-center text-2xl">Verify Your Email</CardTitle>
26+
<CardDescription className="text-center">
27+
We've sent a verification link to your registered email address.
28+
</CardDescription>
29+
</CardHeader>
30+
<CardContent className="text-center">
31+
<p className="text-sm text-neutral-600">
32+
Please check your inbox and click on the verification link to complete your registration.
33+
If you don't see the email, please check your spam folder.
34+
</p>
35+
</CardContent>
36+
{/* <CardFooter className="flex flex-col items-center space-y-4">
37+
<Button
38+
onClick={handleResendEmail}
39+
disabled={resendStatus === 'sending' || resendStatus === 'sent'}
40+
>
41+
{resendStatus === 'sending' ? 'Resending...' : 'Resend Verification Email'}
42+
</Button>
43+
{resendStatus === 'sent' && (
44+
<Alert variant="default" className="mt-4">
45+
<AlertDescription>Verification email has been resent successfully.</AlertDescription>
46+
</Alert>
47+
)}
48+
{resendStatus === 'error' && (
49+
<Alert variant="destructive" className="mt-4">
50+
<AlertDescription>Failed to resend verification email. Please try again later.</AlertDescription>
51+
</Alert>
52+
)}
53+
</CardFooter> */}
54+
</Card>
55+
</main>
56+
)
57+
}
58+

app/dashboard/page.tsx

+84-65
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,97 @@
1-
"use client"
2-
import { useEffect, useState } from 'react';
3-
import { supabase } from '@/lib/supabaseClient';
4-
import { useRouter } from 'next/navigation';
5-
import Navbar from '@/components/header';
6-
import StatsCard from '@/components/Stats';
7-
import { fetchLeetCodeStats } from '@/lib/utils';
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import { useRouter } from "next/navigation";
5+
import { useAuthStore } from "@/store/AuthStore/useAuthStore";
6+
import Navbar from "@/components/header";
7+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8+
import { Skeleton } from "@/components/ui/skeleton";
89

910
export default function Dashboard() {
10-
const [userData, setUserData] = useState<any>(null);
11-
const [loading, setLoading] = useState(true);
12-
const [error, setError] = useState<string | null>(null);
13-
const router = useRouter();
11+
const router = useRouter();
12+
const { authUser, fetchAuthUser, authUserLoading } = useAuthStore();
1413

15-
useEffect(() => {
16-
const fetchData = async () => {
17-
try {
18-
const { data, error } = await supabase.auth.getSession();
14+
useEffect(() => {
15+
fetchAuthUser();
16+
}, [fetchAuthUser]);
1917

20-
if (error) throw new Error("Error fetching session.");
18+
useEffect(() => {
19+
if (!authUserLoading && !authUser) {
20+
router.push("/auth/signin");
21+
}
22+
}, [authUserLoading, authUser, router]);
2123

22-
const session = data.session;
23-
if (!session) {
24-
router.push('/login');
25-
return;
26-
}
27-
// Fetch user-specific data in a single call
28-
const { data: userInfo, error: userInfoError } = await supabase
29-
.from('user_info')
30-
.select('*')
31-
.eq('user_id', session.user.id)
32-
.single();
24+
if (authUserLoading) {
25+
return <DashboardSkeleton />;
26+
}
3327

34-
if (userInfoError) throw userInfoError;
28+
if (!authUser) {
29+
return null;
30+
}
3531

36-
setUserData(userInfo);
32+
return (
33+
<div className="min-h-screen">
34+
<Navbar userId={authUser.id} />
35+
<main className="container mx-auto p-4 space-y-6">
36+
<h1 className="text-3xl font-bold">Welcome, {authUser.fullName}</h1>
3737

38-
} catch (err: any) {
39-
console.error(err);
40-
setError(err.message || 'An error occurred.');
41-
router.push('/login');
42-
} finally {
43-
setLoading(false);
44-
}
45-
};
38+
<Card>
39+
<CardHeader>
40+
<CardTitle>Your Profile</CardTitle>
41+
</CardHeader>
42+
<CardContent className="space-y-2">
43+
<p>
44+
<span className="font-medium">LeetCode Username:</span>{" "}
45+
{authUser.leetcodeUsername}
46+
</p>
47+
<p>
48+
<span className="font-medium">Gender:</span> {authUser.gender}
49+
</p>
50+
</CardContent>
51+
</Card>
4652

47-
fetchData();
48-
}, [router]);
53+
<Card>
54+
<CardHeader>
55+
<CardTitle>LeetCode Stats</CardTitle>
56+
</CardHeader>
57+
<CardContent>
58+
{/* LeetCode stats component will go here */}
59+
<p className="text-muted-foreground">
60+
LeetCode stats are coming soon!
61+
</p>
62+
</CardContent>
63+
</Card>
64+
</main>
65+
</div>
66+
);
67+
}
4968

50-
if (loading) return <p>Loading...</p>;
69+
function DashboardSkeleton() {
70+
return (
71+
<div className="min-h-screen">
72+
{/* <Navbar userId={} /> */}
73+
<main className="container mx-auto p-4 space-y-6">
74+
<Skeleton className="h-10 w-[250px]" />
5175

52-
if (error) {
53-
return (
54-
<div>
55-
<Navbar userId={userData.user_id} />
56-
<p className="text-red-500">{error}</p>
57-
</div>
58-
);
59-
}
76+
<Card>
77+
<CardHeader>
78+
<Skeleton className="h-7 w-[100px]" />
79+
</CardHeader>
80+
<CardContent className="space-y-2">
81+
<Skeleton className="h-5 w-full" />
82+
<Skeleton className="h-5 w-full" />
83+
</CardContent>
84+
</Card>
6085

61-
return (
62-
<div>
63-
<Navbar userId={userData?.user_id} />
64-
<div className="container mx-auto p-4">
65-
<h1 className="text-xl font-bold mb-4">Welcome, {userData.name}</h1>
66-
<div className="mb-4">
67-
<p>LeetCode Username: {userData.leetcode_username}</p>
68-
<p>Gender: {userData.gender}</p>
69-
</div>
70-
71-
<div className="mt-6">
72-
<h2 className="text-lg font-bold mb-2">LeetCode Stats</h2>
73-
<StatsCard leetcodeUsername={userData.leetcode_username} id={userData.id} />
74-
</div>
75-
</div>
76-
</div>
77-
);
86+
<Card>
87+
<CardHeader>
88+
<Skeleton className="h-7 w-[120px]" />
89+
</CardHeader>
90+
<CardContent>
91+
<Skeleton className="h-20 w-full" />
92+
</CardContent>
93+
</Card>
94+
</main>
95+
</div>
96+
);
7897
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Link from "next/link";
2+
import React from "react";
3+
4+
interface AuthBottomProps {
5+
href: string;
6+
title: string;
7+
toTitle: string;
8+
}
9+
10+
export default function AuthBottom({ href, title, toTitle }: AuthBottomProps) {
11+
return (
12+
<Link href={href} className="text-sm font-semibold">
13+
{title} <span className="text-blue-500 hover:underline">{toTitle}</span>
14+
</Link>
15+
);
16+
}

0 commit comments

Comments
 (0)