Skip to content

Commit e6f8e54

Browse files
committed
showing leetcode information
1 parent ea8ba50 commit e6f8e54

18 files changed

+1310
-115
lines changed

Diff for: app/api/leetcode/route.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { supabase } from '@/lib/supabaseClient';
2+
import axios from 'axios';
3+
import { NextRequest, NextResponse } from 'next/server';
4+
5+
// Fetch LeetCode stats
6+
const fetchLeetCodeStats = async (username: string) => {
7+
const query = `
8+
query getUserProfile($username: String!) {
9+
matchedUser(username: $username) {
10+
username
11+
profile {
12+
realName
13+
ranking
14+
}
15+
submitStats {
16+
acSubmissionNum {
17+
difficulty
18+
count
19+
}
20+
}
21+
}
22+
}
23+
`;
24+
try {
25+
const variables = { username };
26+
const { data } = await axios.post('https://leetcode.com/graphql', {
27+
query,
28+
variables,
29+
});
30+
return data.data.matchedUser;
31+
} catch (error) {
32+
console.error('Error fetching LeetCode data:', error);
33+
return null;
34+
}
35+
};
36+
37+
// Store transformed user stats in Supabase
38+
const storeUserStats = async (userId: string, stats: any) => {
39+
const entry = {
40+
user_id: String(userId),
41+
ranking: stats.profile.ranking,
42+
solved_easy: stats.submitStats.acSubmissionNum.find((item: any) => item.difficulty === 'Easy')?.count || "0",
43+
solved_medium: stats.submitStats.acSubmissionNum.find((item: any) => item.difficulty === 'Medium')?.count || "0",
44+
solved_hard: stats.submitStats.acSubmissionNum.find((item: any) => item.difficulty === 'Hard')?.count || "0",
45+
};
46+
const { data, error } = await supabase.from('user_info').upsert([entry]);
47+
48+
if (error) {
49+
console.error('Error storing data in Supabase:', error);
50+
}
51+
52+
return data;
53+
};
54+
55+
// Transform LeetCode data into a UI-friendly structure
56+
const transformLeetCodeData = (stats: any) => {
57+
return {
58+
username: stats.username,
59+
profile: {
60+
realName: stats.profile.realName || "Unknown",
61+
ranking: stats.profile.ranking?.toString() || "0",
62+
},
63+
submitStats: {
64+
acSubmissionNum: stats.submitStats.acSubmissionNum.map((item: any) => ({
65+
difficulty: item.difficulty,
66+
count: item.count?.toString() || "0",
67+
})),
68+
},
69+
};
70+
};
71+
72+
// API POST Handler
73+
export async function POST(req: NextRequest, res: NextResponse) {
74+
const searchParams = req.nextUrl.searchParams;
75+
const username = searchParams.get('username');
76+
const userId = searchParams.get('userId');
77+
78+
if (!username || !userId) {
79+
return NextResponse.json({ error: "Username and userId are required" }, { status: 400 });
80+
}
81+
82+
const stats = await fetchLeetCodeStats(username);
83+
84+
if (!stats) {
85+
return NextResponse.json({ error: "User not found" }, { status: 404 });
86+
}
87+
88+
const transformedStats = transformLeetCodeData(stats);
89+
90+
console.log('Transformed Stats:', JSON.stringify(transformedStats, null, 2));
91+
92+
await storeUserStats(userId, transformedStats);
93+
94+
return NextResponse.json({ message: "Success", stats:transformedStats });
95+
}

Diff for: app/api/storeUserStats/route.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { supabase } from '@/lib/supabaseClient';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
const storeUserStats = async (userId: string, stats: any) => {
5+
const { data, error } = await supabase.from('user_info').upsert([
6+
{
7+
user_id: userId,
8+
name: stats.profile.realName,
9+
email: stats.profile.realName,
10+
leetcode_username: stats.username,
11+
ranking: stats.profile.ranking,
12+
solved_easy: stats.submitStats.acSubmissionNum[0].count,
13+
solved_medium: stats.submitStats.acSubmissionNum[1].count,
14+
solved_hard: stats.submitStats.acSubmissionNum[2].count,
15+
total_contests: stats.contestRanking.attendedContestsCount || 0,
16+
rating: stats.contestRanking.rating || 0,
17+
},
18+
]);
19+
20+
if (error) {
21+
console.error('Error storing data in Supabase:', error);
22+
}
23+
24+
return data;
25+
};
26+
27+
export async function POST(req: NextRequest) {
28+
const { userId, stats } = await req.json();
29+
30+
if (!userId || !stats) {
31+
return NextResponse.json({ error: "User ID and stats are required" }, { status: 400 });
32+
}
33+
34+
const data = await storeUserStats(userId, stats);
35+
36+
if (!data) {
37+
return NextResponse.json({ error: "Error storing data in Supabase" }, { status: 500 });
38+
}
39+
40+
return NextResponse.json({ message: "Data successfully stored in Supabase" }, { status: 200 });
41+
}

Diff for: app/dashboard/page.tsx

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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';
8+
9+
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();
14+
15+
useEffect(() => {
16+
const fetchData = async () => {
17+
try {
18+
const { data, error } = await supabase.auth.getSession();
19+
20+
if (error) throw new Error("Error fetching session.");
21+
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();
33+
34+
if (userInfoError) throw userInfoError;
35+
36+
setUserData(userInfo);
37+
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+
};
46+
47+
fetchData();
48+
}, [router]);
49+
50+
if (loading) return <p>Loading...</p>;
51+
52+
if (error) {
53+
return (
54+
<div>
55+
<Navbar />
56+
<p className="text-red-500">{error}</p>
57+
</div>
58+
);
59+
}
60+
61+
return (
62+
<div>
63+
<Navbar />
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+
<p>User ID: {userData.user_id}</p>
70+
</div>
71+
<button
72+
className="px-4 py-2 bg-red-500 text-white rounded"
73+
onClick={() => supabase.auth.signOut().then(() => router.push('/login'))}
74+
>
75+
Sign Out
76+
</button>
77+
<div className="mt-6">
78+
<h2 className="text-lg font-bold mb-2">LeetCode Stats</h2>
79+
<StatsCard leetcodeUsername={userData.leetcode_username} userId={userData.user_id} />
80+
</div>
81+
</div>
82+
</div>
83+
);
84+
}

Diff for: app/login/page.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import LoginForm from "@/components/LoginForm";
2+
3+
export default function SignupPage() {
4+
return (
5+
<div className="">
6+
<LoginForm />
7+
</div>
8+
);
9+
}

Diff for: app/signup/page.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import SignupForm from "@/components/SignupForm";
2+
3+
export default function SignupPage() {
4+
return (
5+
<div className="">
6+
<SignupForm />
7+
</div>
8+
);
9+
}

Diff for: components/LoginForm.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use client';
2+
3+
import React, { useState } from 'react';
4+
import { Button } from '@/components/ui/button';
5+
import { Input } from '@/components/ui/input';
6+
import { Label } from '@/components/ui/label';
7+
import { useRouter } from 'next/navigation';
8+
import { supabase } from '@/lib/supabaseClient';
9+
10+
const LoginForm: React.FC = () => {
11+
const router = useRouter();
12+
const [formData, setFormData] = useState<{ email: string; password: string }>({
13+
email: '',
14+
password: '',
15+
});
16+
17+
const [loading, setLoading] = useState(false);
18+
const [error, setError] = useState<string | null>(null);
19+
20+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
21+
const { name, value } = e.target;
22+
setFormData((prev) => ({ ...prev, [name]: value }));
23+
};
24+
25+
const handleSubmit = async (e: React.FormEvent) => {
26+
e.preventDefault();
27+
setLoading(true);
28+
setError(null);
29+
30+
const { email, password } = formData;
31+
32+
try {
33+
// Attempt user login
34+
const { data, error: loginError } = await supabase.auth.signInWithPassword({ email, password });
35+
36+
if (loginError) {
37+
throw new Error(loginError.message);
38+
}
39+
40+
// Redirect to dashboard if login succeeds
41+
if (data.session) {
42+
router.push('/dashboard');
43+
} else {
44+
throw new Error('Unable to retrieve session after login.');
45+
}
46+
} catch (err: any) {
47+
console.error('Login Error:', err);
48+
setError(err.message || 'Something went wrong.');
49+
} finally {
50+
setLoading(false);
51+
}
52+
};
53+
54+
return (
55+
<main className="w-full h-screen flex flex-col items-center justify-center px-4">
56+
<div className="max-w-sm w-full text-gray-600">
57+
<div className="p-6 rounded-lg shadow-lg ">
58+
<h2 className="text-2xl font-bold mb-4">Log In</h2>
59+
<form onSubmit={handleSubmit}>
60+
{/* Email Field */}
61+
<div className="mb-4">
62+
<Label htmlFor="email">Email</Label>
63+
<Input
64+
id="email"
65+
name="email"
66+
type="email"
67+
placeholder="[email protected]"
68+
value={formData.email}
69+
onChange={handleChange}
70+
required
71+
/>
72+
</div>
73+
74+
{/* Password Field */}
75+
<div className="mb-4">
76+
<Label htmlFor="password">Password</Label>
77+
<Input
78+
id="password"
79+
name="password"
80+
type="password"
81+
placeholder="********"
82+
value={formData.password}
83+
onChange={handleChange}
84+
required
85+
/>
86+
</div>
87+
88+
{/* Error Message */}
89+
{error && <p className="text-red-500 mb-4">{error}</p>}
90+
91+
{/* Submit Button */}
92+
<Button type="submit" disabled={loading} className="w-full">
93+
{loading ? 'Logging in...' : 'Log In'}
94+
</Button>
95+
</form>
96+
</div>
97+
</div>
98+
</main>
99+
);
100+
};
101+
102+
export default LoginForm;

0 commit comments

Comments
 (0)