Skip to content

Added One Leetcode Api route #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion GQL_Queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
6 changes: 2 additions & 4 deletions GQL_Queries/userProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -87,6 +87,4 @@ query getUserProfile($username: String!) {
statusDisplay
lang
}
}`;

export default query;
}`;
32 changes: 32 additions & 0 deletions app/api/leetcode/userDetails/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
196 changes: 194 additions & 2 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <DashboardSkeleton />;
}

return (
<div className="flex flex-col min-h-screen p-4 sm:p-6 md:p-8">
<Card className="w-full max-w-4xl mx-auto shadow-xl border-none">
<CardHeader className="flex flex-col sm:flex-row items-center gap-6 pb-6 border-b">
<div className="relative w-32 h-32 sm:w-40 sm:h-40">
<Image
src={leetcodeUserProfile.profile.userAvatar || "/placeholder.svg"}
alt="Profile Image"
fill
className="rounded-full object-cover border-4 border-primary shadow-md"
priority
/>
</div>
<div className="flex flex-col items-center sm:items-start text-center sm:text-left">
<CardTitle className="text-3xl font-bold mb-2">
{leetcodeUserProfile.profile.realName}
</CardTitle>
<p className="text-xl text-muted-foreground mb-4">
@{leetcodeUserProfile.username}
</p>
<div className="flex flex-wrap justify-center sm:justify-start gap-2">
{leetcodeUserProfile.profile.skillTags.map((skill, index) => (
<Badge
key={index}
variant="secondary"
className="text-sm px-3 py-1"
>
{skill}
</Badge>
))}
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-4">
<h3 className="text-xl font-semibold mb-3">Statistics</h3>
<div className="grid grid-cols-2 gap-4">
<StatItem
icon={<Book className="w-5 h-5" />}
value={
leetcodeUserProfile.submitStats.acSubmissionNum[0].count ||
0
}
label="Problems Solved"
/>
<StatItem
icon={<Award className="w-5 h-5" />}
value={leetcodeUserProfile.contributions.points}
label="Contribution Points"
/>
<StatItem
icon={<Star className="w-5 h-5" />}
value={leetcodeUserProfile.profile.starRating}
label="Star Rating"
/>
<StatItem
icon={<Award className="w-5 h-5" />}
value={leetcodeUserProfile.profile.ranking}
label="Global Ranking"
/>
</div>
</div>
<div className="space-y-4">
<h3 className="text-xl font-semibold mb-3">Recent Badges</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{leetcodeUserProfile.badges.slice(0, 6).map((badge) => (
<div
key={badge.id}
className="flex flex-col items-center p-2 bg-secondary rounded-lg"
>
<div className="relative w-12 h-12 mb-2">
<Image
src={badge.icon || "/placeholder.svg"}
alt={badge.displayName}
fill
className="object-contain"
/>
</div>
<span className="text-xs text-center">
{badge.displayName}
</span>
</div>
))}
</div>
</div>
</div>
<div className="flex justify-center gap-6 mt-8">
<SocialLink
href={leetcodeUserProfile.githubUrl}
icon={<Github className="w-6 h-6" />}
/>
<SocialLink
href={leetcodeUserProfile.linkedinUrl}
icon={<Linkedin className="w-6 h-6" />}
/>
<SocialLink
href={leetcodeUserProfile.twitterUrl!}
icon={<Twitter className="w-6 h-6" />}
/>
</div>
</CardContent>
</Card>
</div>
);
}

function StatItem({
icon,
value,
label,
}: {
icon: React.ReactNode;
value: number;
label: string;
}) {
return (
<div className="flex items-center space-x-3 bg-secondary/50 rounded-lg p-3">
{icon}
<div>
<div className="text-2xl font-bold">{value}</div>
<div className="text-sm text-muted-foreground">{label}</div>
</div>
</div>
);
}

function SocialLink({ href, icon }: { href: string; icon: React.ReactNode }) {
if (!href) return null;
return (
<a
href={href}
className="text-muted-foreground hover:text-primary transition-colors"
target="_blank"
rel="noopener noreferrer"
>
{icon}
</a>
);
}

function DashboardSkeleton() {
return (
<div className="flex flex-col items-center justify-center h-full">
<h1 className="text-6xl font-bold">This is the dashboard</h1>
<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">
<Card className="w-full max-w-4xl mx-auto shadow-xl">
<CardHeader className="flex flex-col sm:flex-row items-center gap-6 pb-6 border-b">
<Skeleton className="w-32 h-32 sm:w-40 sm:h-40 rounded-full" />
<div className="flex flex-col items-center sm:items-start space-y-4 w-full">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-6 w-36" />
<div className="flex flex-wrap justify-center sm:justify-start gap-2">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-6 w-20" />
))}
</div>
</div>
</CardHeader>
<CardContent className="pt-6">
<div className="grid gap-6 md:grid-cols-2">
{[1, 2].map((section) => (
<div key={section} className="space-y-4">
<Skeleton className="h-7 w-32" />
<div className="grid grid-cols-2 gap-4">
{[1, 2, 3, 4].map((item) => (
<Skeleton key={item} className="h-20 w-full" />
))}
</div>
</div>
))}
</div>
<div className="flex justify-center gap-6 mt-8">
{[1, 2, 3].map((icon) => (
<Skeleton key={icon} className="w-6 h-6 rounded-full" />
))}
</div>
</CardContent>
</Card>
</div>
);
}
8 changes: 8 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
28 changes: 27 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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])
// }




77 changes: 77 additions & 0 deletions store/LeetcodeStore/useLeetcodeStore.ts
Original file line number Diff line number Diff line change
@@ -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<LeetcodeStore>((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);
}
}
}))
Loading