From 951f90c25929f73da68d59e36773d314a789f8cd Mon Sep 17 00:00:00 2001 From: "HanuCh@udhary" <137854084+hanuchaudhary@users.noreply.github.com> Date: Tue, 28 Jan 2025 22:09:33 +0530 Subject: [PATCH 1/3] Edit --- GQL_Queries/userProfile.ts | 6 +- app/api/leetcode/userDetails/route.ts | 32 +++++++ app/dashboard/page.tsx | 112 +++++++++++++++++++++++- next.config.ts | 8 ++ prisma/schema.prisma | 28 +++++- store/LeetcodeStore/useLeetcodeStore.ts | 77 ++++++++++++++++ utils/leetcode/leetcodeContollers.ts | 13 +++ utils/leetcode/queryLeetCodeAPI.ts | 21 +++++ 8 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 app/api/leetcode/userDetails/route.ts create mode 100644 store/LeetcodeStore/useLeetcodeStore.ts create mode 100644 utils/leetcode/leetcodeContollers.ts create mode 100644 utils/leetcode/queryLeetCodeAPI.ts diff --git a/GQL_Queries/userProfile.ts b/GQL_Queries/userProfile.ts index e3ac5d3..3326635 100644 --- a/GQL_Queries/userProfile.ts +++ b/GQL_Queries/userProfile.ts @@ -20,7 +20,7 @@ * * @param {string} $username - The username of the user whose profile is to be fetched. */ -const query = `#graphql +export const userProfileQuery = `#graphql query getUserProfile($username: String!) { allQuestionsCount { difficulty @@ -87,6 +87,4 @@ query getUserProfile($username: String!) { statusDisplay lang } -}`; - -export default query; +}`; \ No newline at end of file diff --git a/app/api/leetcode/userDetails/route.ts b/app/api/leetcode/userDetails/route.ts new file mode 100644 index 0000000..0303cba --- /dev/null +++ b/app/api/leetcode/userDetails/route.ts @@ -0,0 +1,32 @@ +import prisma from "@/lib/database/prismaClient"; +import { getLeetCodeUserDetails } from "@/utils/leetcode/leetcodeContollers"; +import { createClient } from "@/utils/supabase/server"; +import { NextResponse } from "next/server"; + +export async function GET() { + try { + const supabase = await createClient(); + const supabaseUser = (await supabase.auth.getUser()).data.user; + + if (!supabaseUser) { + return NextResponse.json({ error: "User not authenticated" }, { status: 401 }); + } + + const user = await prisma.user.findFirst({ + where: { + supabaseId: supabaseUser.id, + }, + }); + + if (!user) { + return NextResponse.json({ error: "User not found" }, { status: 404 }); + } + + const LeetCodeUsername = user.leetcodeUsername; + + const result = await getLeetCodeUserDetails(LeetCodeUsername) + return NextResponse.json(result); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index b8a8062..ace6bd2 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,9 +1,113 @@ -import React from "react"; +"use client" + +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Github, Linkedin, Twitter } from "lucide-react" +import Image from "next/image" +import { useLeetcodeStore } from "@/store/LeetcodeStore/useLeetcodeStore" +import React from "react" export default function Dashboard() { + const { fetchLeetcodeUserProfile, leetcodeUserProfile } = useLeetcodeStore() + + React.useEffect(() => { + fetchLeetcodeUserProfile() + }, [fetchLeetcodeUserProfile]) + + if (!leetcodeUserProfile) { + return
Loading...
+ } + return ( -
-

This is the dashboard

+
+ + + {leetcodeUserProfile.profile.userAvatar && ( +
+ Profile Image +
+ )} +
+ {leetcodeUserProfile.profile.realName} + @{leetcodeUserProfile.username} +
+ {leetcodeUserProfile.badges.map((badge) => ( +
+ {badge.displayName} +
+ {badge.displayName} +
+
+ ))} +
+
+
+ +
+
+
+ + {leetcodeUserProfile.submitStats.acSubmissionNum[0].count || 0} + + Problems Solved +
+
+ {leetcodeUserProfile.contributions.points} + Contribution Points +
+
+ {leetcodeUserProfile.profile.starRating} + Star Rating +
+
+
+ {leetcodeUserProfile.githubUrl && ( + + + + )} + {leetcodeUserProfile.linkedinUrl && ( + + + + )} + {leetcodeUserProfile.twitterUrl && ( + + + + )} +
+
+
+
- ); + ) } + diff --git a/next.config.ts b/next.config.ts index 02f7357..23be141 100644 --- a/next.config.ts +++ b/next.config.ts @@ -5,6 +5,14 @@ const nextConfig: NextConfig = { eslint: { ignoreDuringBuilds: true, // Ignores all ESLint warnings and errors during builds }, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '**', // Match any hostname + }, + ], + }, }; export default nextConfig; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b617548..1797a61 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,9 +27,35 @@ model User { email String @unique fullName String gender String - leetcodeUsername String + leetcodeUsername String password String isVerified Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +// model leetcodeUser { +// id String @id @unique @default(uuid()) +// username String +// githubURL String +// twitterUrl String +// linkedinUrl String +// userAvatar String +// countryName String +// skillTags String[] +// starRating Int +// badges Badges[] +// } + +// model Badges { +// id String @id @unique @default(uuid()) +// displayName String +// icon String +// creationDate String +// leetcodeUserId String? +// leetcodeUser leetcodeUser? @relation(fields: [leetcodeUserId], references: [id]) +// } + + + + diff --git a/store/LeetcodeStore/useLeetcodeStore.ts b/store/LeetcodeStore/useLeetcodeStore.ts new file mode 100644 index 0000000..d616dce --- /dev/null +++ b/store/LeetcodeStore/useLeetcodeStore.ts @@ -0,0 +1,77 @@ +import axios from "axios"; +import { create } from "zustand"; + +interface leetcodeProfile { + username: string; + githubUrl: string; + twitterUrl: string | null; + linkedinUrl: string; + contributions: { + points: number; + questionCount: number; + testcaseCount: number; + }; + profile: { + realName: string; + userAvatar: string; + birthday: string | null; + ranking: number; + reputation: number; + websites: string[]; + countryName: string; + company: string | null; + school: string | null; + skillTags: string[]; + aboutMe: string; + starRating: number; + }; + badges: { + id: string; + displayName: string; + icon: string; + creationDate: string; + }[]; + upcomingBadges: { + name: string; + icon: string; + }[]; + activeBadge: { + id: string; + displayName: string; + icon: string; + creationDate: string; + }; + submitStats: { + totalSubmissionNum: { + difficulty: "All" | "Easy" | "Medium" | "Hard"; + count: number; + submissions: number; + }[]; + acSubmissionNum: { + difficulty: "All" | "Easy" | "Medium" | "Hard"; + count: number; + submissions: number; + }[]; + }; + submissionCalendar: string; // JSON string representing submission data + } + + +interface LeetcodeStore { + leetcodeUserProfile : leetcodeProfile | null; + fetchLeetcodeUserProfile : VoidFunction +} + +export const useLeetcodeStore = create((set) => ({ + leetcodeUserProfile: null, + fetchLeetcodeUserProfile: async () => { + try { + const response = await axios.get("/api/leetcode/userDetails"); + const data = response.data; + console.log("data", data); + set({ leetcodeUserProfile: data }); + } catch (error) { + console.error(error); + } + } +})) diff --git a/utils/leetcode/leetcodeContollers.ts b/utils/leetcode/leetcodeContollers.ts new file mode 100644 index 0000000..19d4b74 --- /dev/null +++ b/utils/leetcode/leetcodeContollers.ts @@ -0,0 +1,13 @@ +import { userProfileQuery } from "@/GQL_Queries/userProfile"; +import { queryLeetCodeAPI } from "./queryLeetCodeAPI"; + +export const getLeetCodeUserDetails = async (username: string) => { + const response = await queryLeetCodeAPI(userProfileQuery, { + username: username, + }); + + console.log(response); + return response.data.matchedUser; +} + + diff --git a/utils/leetcode/queryLeetCodeAPI.ts b/utils/leetcode/queryLeetCodeAPI.ts new file mode 100644 index 0000000..7b87e9e --- /dev/null +++ b/utils/leetcode/queryLeetCodeAPI.ts @@ -0,0 +1,21 @@ +import axios from "axios"; + +const API_URL = process.env.LEETCODE_API_URL || 'https://leetcode.com/graphql'; + +export async function queryLeetCodeAPI(query: string, variables: any) { + try { + const response = await axios.post(API_URL, { query, variables }); + if (response.data.errors) { + throw new Error(response.data.errors[0].message); + } + return response.data; + } catch (error: any) { + if (error.response) { + throw new Error(`Error from LeetCode API: ${error.response.data}`); + } else if (error.request) { + throw new Error('No response received from LeetCode API'); + } else { + throw new Error(`Error in setting up the request: ${error.message}`); + } + } +} \ No newline at end of file From 1a02045719d66a6df72e2f8473e8faecab4a0454 Mon Sep 17 00:00:00 2001 From: "HanuCh@udhary" <137854084+hanuchaudhary@users.noreply.github.com> Date: Tue, 28 Jan 2025 22:15:32 +0530 Subject: [PATCH 2/3] Any --- app/dashboard/page.tsx | 268 +++++++++++++++++++++++++++-------------- 1 file changed, 178 insertions(+), 90 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index ace6bd2..6df58c7 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,113 +1,201 @@ -"use client" +"use client"; -import { Badge } from "@/components/ui/badge" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Github, Linkedin, Twitter } from "lucide-react" -import Image from "next/image" -import { useLeetcodeStore } from "@/store/LeetcodeStore/useLeetcodeStore" -import React from "react" +import React from "react"; +import Image from "next/image"; +import { useLeetcodeStore } from "@/store/LeetcodeStore/useLeetcodeStore"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Github, Linkedin, Twitter, Award, Book, Star } from "lucide-react"; export default function Dashboard() { - const { fetchLeetcodeUserProfile, leetcodeUserProfile } = useLeetcodeStore() + const { fetchLeetcodeUserProfile, leetcodeUserProfile } = useLeetcodeStore(); React.useEffect(() => { - fetchLeetcodeUserProfile() - }, [fetchLeetcodeUserProfile]) + fetchLeetcodeUserProfile(); + }, [fetchLeetcodeUserProfile]); if (!leetcodeUserProfile) { - return
Loading...
+ return ; } return ( -
- - - {leetcodeUserProfile.profile.userAvatar && ( -
- Profile Image -
- )} -
- {leetcodeUserProfile.profile.realName} - @{leetcodeUserProfile.username} -
- {leetcodeUserProfile.badges.map((badge) => ( -
- {badge.displayName} -
- {badge.displayName} -
-
+
+ + +
+ Profile Image +
+
+ + {leetcodeUserProfile.profile.realName} + +

+ @{leetcodeUserProfile.username} +

+
+ {leetcodeUserProfile.profile.skillTags.map((skill, index) => ( + + {skill} + ))}
- -
-
-
- - {leetcodeUserProfile.submitStats.acSubmissionNum[0].count || 0} - - Problems Solved -
-
- {leetcodeUserProfile.contributions.points} - Contribution Points -
-
- {leetcodeUserProfile.profile.starRating} - Star Rating + +
+
+

Statistics

+
+ } + value={ + leetcodeUserProfile.submitStats.acSubmissionNum[0].count || + 0 + } + label="Problems Solved" + /> + } + value={leetcodeUserProfile.contributions.points} + label="Contribution Points" + /> + } + value={leetcodeUserProfile.profile.starRating} + label="Star Rating" + /> + } + value={leetcodeUserProfile.profile.ranking} + label="Global Ranking" + />
-
- {leetcodeUserProfile.githubUrl && ( - - - - )} - {leetcodeUserProfile.linkedinUrl && ( - - - - )} - {leetcodeUserProfile.twitterUrl && ( - - - - )} +
+

Recent Badges

+
+ {leetcodeUserProfile.badges.slice(0, 6).map((badge) => ( +
+
+ {badge.displayName} +
+ + {badge.displayName} + +
+ ))} +
+
+ } + /> + } + /> + } + /> +
- ) + ); +} + +function StatItem({ + icon, + value, + label, +}: { + icon: React.ReactNode; + value: number; + label: string; +}) { + return ( +
+ {icon} +
+
{value}
+
{label}
+
+
+ ); } +function SocialLink({ href, icon }: { href: string; icon: React.ReactNode }) { + if (!href) return null; + return ( + + {icon} + + ); +} + +function DashboardSkeleton() { + return ( +
+ + + +
+ + +
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+
+ +
+ {[1, 2].map((section) => ( +
+ +
+ {[1, 2, 3, 4].map((item) => ( + + ))} +
+
+ ))} +
+
+ {[1, 2, 3].map((icon) => ( + + ))} +
+
+
+
+ ); +} From d1af0280e58f772d00c82544082276155093d37e Mon Sep 17 00:00:00 2001 From: "HanuCh@udhary" <137854084+hanuchaudhary@users.noreply.github.com> Date: Tue, 28 Jan 2025 22:18:57 +0530 Subject: [PATCH 3/3] Build fix --- GQL_Queries/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GQL_Queries/index.ts b/GQL_Queries/index.ts index ac32ffe..f138464 100644 --- a/GQL_Queries/index.ts +++ b/GQL_Queries/index.ts @@ -6,6 +6,6 @@ */ export { default as AcSubmissionQuery } from './recentAcSubmit'; export { default as contestQuery } from './contest'; -export { default as userProfileQuery } from './userProfile'; +export { userProfileQuery } from './userProfile'; export { default as submissionQuery } from './recentSubmit'; export { default as languageStatsQuery } from './languageStats'; \ No newline at end of file