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 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..6df58c7 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,9 +1,201 @@ +"use client"; + 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(); + + React.useEffect(() => { + fetchLeetcodeUserProfile(); + }, [fetchLeetcodeUserProfile]); + + if (!leetcodeUserProfile) { + return ; + } + + return ( +
+ + +
+ Profile Image +
+
+ + {leetcodeUserProfile.profile.realName} + +

+ @{leetcodeUserProfile.username} +

+
+ {leetcodeUserProfile.profile.skillTags.map((skill, index) => ( + + {skill} + + ))} +
+
+
+ +
+
+

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" + /> +
+
+
+

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 ( -
-

This is the dashboard

+
+ + + +
+ + +
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+
+ +
+ {[1, 2].map((section) => ( +
+ +
+ {[1, 2, 3, 4].map((item) => ( + + ))} +
+
+ ))} +
+
+ {[1, 2, 3].map((icon) => ( + + ))} +
+
+
); } 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