Skip to content

Commit 746f845

Browse files
Merge pull request #113 from hanuchaudhary/api
Add main leetcode API route feat
2 parents 51ee7fe + d1af028 commit 746f845

File tree

9 files changed

+375
-8
lines changed

9 files changed

+375
-8
lines changed

GQL_Queries/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
*/
77
export { default as AcSubmissionQuery } from './recentAcSubmit';
88
export { default as contestQuery } from './contest';
9-
export { default as userProfileQuery } from './userProfile';
9+
export { userProfileQuery } from './userProfile';
1010
export { default as submissionQuery } from './recentSubmit';
1111
export { default as languageStatsQuery } from './languageStats';

GQL_Queries/userProfile.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @param {string} $username - The username of the user whose profile is to be fetched.
2222
*/
23-
const query = `#graphql
23+
export const userProfileQuery = `#graphql
2424
query getUserProfile($username: String!) {
2525
allQuestionsCount {
2626
difficulty
@@ -87,6 +87,4 @@ query getUserProfile($username: String!) {
8787
statusDisplay
8888
lang
8989
}
90-
}`;
91-
92-
export default query;
90+
}`;

app/api/leetcode/userDetails/route.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import prisma from "@/lib/database/prismaClient";
2+
import { getLeetCodeUserDetails } from "@/utils/leetcode/leetcodeContollers";
3+
import { createClient } from "@/utils/supabase/server";
4+
import { NextResponse } from "next/server";
5+
6+
export async function GET() {
7+
try {
8+
const supabase = await createClient();
9+
const supabaseUser = (await supabase.auth.getUser()).data.user;
10+
11+
if (!supabaseUser) {
12+
return NextResponse.json({ error: "User not authenticated" }, { status: 401 });
13+
}
14+
15+
const user = await prisma.user.findFirst({
16+
where: {
17+
supabaseId: supabaseUser.id,
18+
},
19+
});
20+
21+
if (!user) {
22+
return NextResponse.json({ error: "User not found" }, { status: 404 });
23+
}
24+
25+
const LeetCodeUsername = user.leetcodeUsername;
26+
27+
const result = await getLeetCodeUserDetails(LeetCodeUsername)
28+
return NextResponse.json(result);
29+
} catch (error: any) {
30+
return NextResponse.json({ error: error.message }, { status: 500 });
31+
}
32+
}

app/dashboard/page.tsx

Lines changed: 194 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,201 @@
1+
"use client";
2+
13
import React from "react";
4+
import Image from "next/image";
5+
import { useLeetcodeStore } from "@/store/LeetcodeStore/useLeetcodeStore";
6+
import { Badge } from "@/components/ui/badge";
7+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8+
import { Skeleton } from "@/components/ui/skeleton";
9+
import { Github, Linkedin, Twitter, Award, Book, Star } from "lucide-react";
210

311
export default function Dashboard() {
12+
const { fetchLeetcodeUserProfile, leetcodeUserProfile } = useLeetcodeStore();
13+
14+
React.useEffect(() => {
15+
fetchLeetcodeUserProfile();
16+
}, [fetchLeetcodeUserProfile]);
17+
18+
if (!leetcodeUserProfile) {
19+
return <DashboardSkeleton />;
20+
}
21+
22+
return (
23+
<div className="flex flex-col min-h-screen p-4 sm:p-6 md:p-8">
24+
<Card className="w-full max-w-4xl mx-auto shadow-xl border-none">
25+
<CardHeader className="flex flex-col sm:flex-row items-center gap-6 pb-6 border-b">
26+
<div className="relative w-32 h-32 sm:w-40 sm:h-40">
27+
<Image
28+
src={leetcodeUserProfile.profile.userAvatar || "/placeholder.svg"}
29+
alt="Profile Image"
30+
fill
31+
className="rounded-full object-cover border-4 border-primary shadow-md"
32+
priority
33+
/>
34+
</div>
35+
<div className="flex flex-col items-center sm:items-start text-center sm:text-left">
36+
<CardTitle className="text-3xl font-bold mb-2">
37+
{leetcodeUserProfile.profile.realName}
38+
</CardTitle>
39+
<p className="text-xl text-muted-foreground mb-4">
40+
@{leetcodeUserProfile.username}
41+
</p>
42+
<div className="flex flex-wrap justify-center sm:justify-start gap-2">
43+
{leetcodeUserProfile.profile.skillTags.map((skill, index) => (
44+
<Badge
45+
key={index}
46+
variant="secondary"
47+
className="text-sm px-3 py-1"
48+
>
49+
{skill}
50+
</Badge>
51+
))}
52+
</div>
53+
</div>
54+
</CardHeader>
55+
<CardContent className="pt-6">
56+
<div className="grid gap-6 md:grid-cols-2">
57+
<div className="space-y-4">
58+
<h3 className="text-xl font-semibold mb-3">Statistics</h3>
59+
<div className="grid grid-cols-2 gap-4">
60+
<StatItem
61+
icon={<Book className="w-5 h-5" />}
62+
value={
63+
leetcodeUserProfile.submitStats.acSubmissionNum[0].count ||
64+
0
65+
}
66+
label="Problems Solved"
67+
/>
68+
<StatItem
69+
icon={<Award className="w-5 h-5" />}
70+
value={leetcodeUserProfile.contributions.points}
71+
label="Contribution Points"
72+
/>
73+
<StatItem
74+
icon={<Star className="w-5 h-5" />}
75+
value={leetcodeUserProfile.profile.starRating}
76+
label="Star Rating"
77+
/>
78+
<StatItem
79+
icon={<Award className="w-5 h-5" />}
80+
value={leetcodeUserProfile.profile.ranking}
81+
label="Global Ranking"
82+
/>
83+
</div>
84+
</div>
85+
<div className="space-y-4">
86+
<h3 className="text-xl font-semibold mb-3">Recent Badges</h3>
87+
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
88+
{leetcodeUserProfile.badges.slice(0, 6).map((badge) => (
89+
<div
90+
key={badge.id}
91+
className="flex flex-col items-center p-2 bg-secondary rounded-lg"
92+
>
93+
<div className="relative w-12 h-12 mb-2">
94+
<Image
95+
src={badge.icon || "/placeholder.svg"}
96+
alt={badge.displayName}
97+
fill
98+
className="object-contain"
99+
/>
100+
</div>
101+
<span className="text-xs text-center">
102+
{badge.displayName}
103+
</span>
104+
</div>
105+
))}
106+
</div>
107+
</div>
108+
</div>
109+
<div className="flex justify-center gap-6 mt-8">
110+
<SocialLink
111+
href={leetcodeUserProfile.githubUrl}
112+
icon={<Github className="w-6 h-6" />}
113+
/>
114+
<SocialLink
115+
href={leetcodeUserProfile.linkedinUrl}
116+
icon={<Linkedin className="w-6 h-6" />}
117+
/>
118+
<SocialLink
119+
href={leetcodeUserProfile.twitterUrl!}
120+
icon={<Twitter className="w-6 h-6" />}
121+
/>
122+
</div>
123+
</CardContent>
124+
</Card>
125+
</div>
126+
);
127+
}
128+
129+
function StatItem({
130+
icon,
131+
value,
132+
label,
133+
}: {
134+
icon: React.ReactNode;
135+
value: number;
136+
label: string;
137+
}) {
138+
return (
139+
<div className="flex items-center space-x-3 bg-secondary/50 rounded-lg p-3">
140+
{icon}
141+
<div>
142+
<div className="text-2xl font-bold">{value}</div>
143+
<div className="text-sm text-muted-foreground">{label}</div>
144+
</div>
145+
</div>
146+
);
147+
}
148+
149+
function SocialLink({ href, icon }: { href: string; icon: React.ReactNode }) {
150+
if (!href) return null;
151+
return (
152+
<a
153+
href={href}
154+
className="text-muted-foreground hover:text-primary transition-colors"
155+
target="_blank"
156+
rel="noopener noreferrer"
157+
>
158+
{icon}
159+
</a>
160+
);
161+
}
162+
163+
function DashboardSkeleton() {
4164
return (
5-
<div className="flex flex-col items-center justify-center h-full">
6-
<h1 className="text-6xl font-bold">This is the dashboard</h1>
165+
<div className="flex flex-col min-h-screen bg-gradient-to-b from-gray-100 to-gray-200 dark:from-gray-900 dark:to-gray-800 p-4 sm:p-6 md:p-8">
166+
<Card className="w-full max-w-4xl mx-auto shadow-xl">
167+
<CardHeader className="flex flex-col sm:flex-row items-center gap-6 pb-6 border-b">
168+
<Skeleton className="w-32 h-32 sm:w-40 sm:h-40 rounded-full" />
169+
<div className="flex flex-col items-center sm:items-start space-y-4 w-full">
170+
<Skeleton className="h-8 w-48" />
171+
<Skeleton className="h-6 w-36" />
172+
<div className="flex flex-wrap justify-center sm:justify-start gap-2">
173+
{[1, 2, 3].map((i) => (
174+
<Skeleton key={i} className="h-6 w-20" />
175+
))}
176+
</div>
177+
</div>
178+
</CardHeader>
179+
<CardContent className="pt-6">
180+
<div className="grid gap-6 md:grid-cols-2">
181+
{[1, 2].map((section) => (
182+
<div key={section} className="space-y-4">
183+
<Skeleton className="h-7 w-32" />
184+
<div className="grid grid-cols-2 gap-4">
185+
{[1, 2, 3, 4].map((item) => (
186+
<Skeleton key={item} className="h-20 w-full" />
187+
))}
188+
</div>
189+
</div>
190+
))}
191+
</div>
192+
<div className="flex justify-center gap-6 mt-8">
193+
{[1, 2, 3].map((icon) => (
194+
<Skeleton key={icon} className="w-6 h-6 rounded-full" />
195+
))}
196+
</div>
197+
</CardContent>
198+
</Card>
7199
</div>
8200
);
9201
}

next.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ const nextConfig: NextConfig = {
55
eslint: {
66
ignoreDuringBuilds: true, // Ignores all ESLint warnings and errors during builds
77
},
8+
images: {
9+
remotePatterns: [
10+
{
11+
protocol: 'https',
12+
hostname: '**', // Match any hostname
13+
},
14+
],
15+
},
816
};
917

1018
export default nextConfig;

prisma/schema.prisma

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,35 @@ model User {
2727
email String @unique
2828
fullName String
2929
gender String
30-
leetcodeUsername String
30+
leetcodeUsername String
3131
password String
3232
isVerified Boolean @default(false)
3333
createdAt DateTime @default(now())
3434
updatedAt DateTime @updatedAt
3535
}
36+
37+
// model leetcodeUser {
38+
// id String @id @unique @default(uuid())
39+
// username String
40+
// githubURL String
41+
// twitterUrl String
42+
// linkedinUrl String
43+
// userAvatar String
44+
// countryName String
45+
// skillTags String[]
46+
// starRating Int
47+
// badges Badges[]
48+
// }
49+
50+
// model Badges {
51+
// id String @id @unique @default(uuid())
52+
// displayName String
53+
// icon String
54+
// creationDate String
55+
// leetcodeUserId String?
56+
// leetcodeUser leetcodeUser? @relation(fields: [leetcodeUserId], references: [id])
57+
// }
58+
59+
60+
61+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import axios from "axios";
2+
import { create } from "zustand";
3+
4+
interface leetcodeProfile {
5+
username: string;
6+
githubUrl: string;
7+
twitterUrl: string | null;
8+
linkedinUrl: string;
9+
contributions: {
10+
points: number;
11+
questionCount: number;
12+
testcaseCount: number;
13+
};
14+
profile: {
15+
realName: string;
16+
userAvatar: string;
17+
birthday: string | null;
18+
ranking: number;
19+
reputation: number;
20+
websites: string[];
21+
countryName: string;
22+
company: string | null;
23+
school: string | null;
24+
skillTags: string[];
25+
aboutMe: string;
26+
starRating: number;
27+
};
28+
badges: {
29+
id: string;
30+
displayName: string;
31+
icon: string;
32+
creationDate: string;
33+
}[];
34+
upcomingBadges: {
35+
name: string;
36+
icon: string;
37+
}[];
38+
activeBadge: {
39+
id: string;
40+
displayName: string;
41+
icon: string;
42+
creationDate: string;
43+
};
44+
submitStats: {
45+
totalSubmissionNum: {
46+
difficulty: "All" | "Easy" | "Medium" | "Hard";
47+
count: number;
48+
submissions: number;
49+
}[];
50+
acSubmissionNum: {
51+
difficulty: "All" | "Easy" | "Medium" | "Hard";
52+
count: number;
53+
submissions: number;
54+
}[];
55+
};
56+
submissionCalendar: string; // JSON string representing submission data
57+
}
58+
59+
60+
interface LeetcodeStore {
61+
leetcodeUserProfile : leetcodeProfile | null;
62+
fetchLeetcodeUserProfile : VoidFunction
63+
}
64+
65+
export const useLeetcodeStore = create<LeetcodeStore>((set) => ({
66+
leetcodeUserProfile: null,
67+
fetchLeetcodeUserProfile: async () => {
68+
try {
69+
const response = await axios.get("/api/leetcode/userDetails");
70+
const data = response.data;
71+
console.log("data", data);
72+
set({ leetcodeUserProfile: data });
73+
} catch (error) {
74+
console.error(error);
75+
}
76+
}
77+
}))

0 commit comments

Comments
 (0)