-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #134 from s1lvax/dev
Add LeetCode Integration
- Loading branch information
Showing
14 changed files
with
376 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<script lang="ts"> | ||
import * as Form from '$lib/components/ui/form'; | ||
import { Input } from '$lib/components/ui/input'; | ||
import { leetCodeSchema, type LeetCodeSchema } from '$lib/schemas/integration-leetcode'; | ||
import { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms'; | ||
import { zodClient } from 'sveltekit-superforms/adapters'; | ||
export let data: SuperValidated<Infer<LeetCodeSchema>>; | ||
const form = superForm(data, { | ||
validators: zodClient(leetCodeSchema), | ||
resetForm: true | ||
}); | ||
const { form: formData, enhance } = form; | ||
</script> | ||
|
||
<form | ||
method="POST" | ||
use:enhance | ||
action="?/createLeetCode" | ||
class="flex items-center justify-between space-x-4" | ||
> | ||
<div class="flex items-start space-x-2"> | ||
<Form.Field {form} name="username"> | ||
<Form.Control let:attrs> | ||
<Form.Label>Username</Form.Label> | ||
<Input {...attrs} bind:value={$formData.username} /> | ||
</Form.Control> | ||
<Form.FieldErrors /> | ||
</Form.Field> | ||
<div class="space-y-2"> | ||
<span class="invisible block">a</span> | ||
<Form.Button>Add</Form.Button> | ||
</div> | ||
</div> | ||
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<script lang="ts"> | ||
import { onMount } from 'svelte'; | ||
import type { LeetCodeStats } from '$lib/types/LeetCodeData'; | ||
import { Flame, Calendar, Medal } from 'lucide-svelte'; | ||
import * as Card from '$lib/components/ui/card'; | ||
import * as Table from '$lib/components/ui/table'; | ||
export let leetCodeUsername: string; | ||
let data: LeetCodeStats | null = null; | ||
let loading = true; | ||
// Problem-solving counts by difficulty | ||
let easyCount = 0; | ||
let mediumCount = 0; | ||
let hardCount = 0; | ||
// Fetch data | ||
onMount(async () => { | ||
try { | ||
const response = await fetch(`/api/leetcode?leetCodeUsername=${leetCodeUsername}`); | ||
if (response.ok) { | ||
const result = await response.json(); | ||
data = result.data.matchedUser; | ||
// Extract counts for each difficulty | ||
easyCount = data?.submitStatsGlobal.acSubmissionNum.find(item => item.difficulty === "Easy")?.count || 0; | ||
mediumCount = data?.submitStatsGlobal.acSubmissionNum.find(item => item.difficulty === "Medium")?.count || 0; | ||
hardCount = data?.submitStatsGlobal.acSubmissionNum.find(item => item.difficulty === "Hard")?.count || 0; | ||
} | ||
} catch (error) { | ||
console.error('Error fetching LeetCode stats:', error); | ||
} finally { | ||
loading = false; | ||
} | ||
}); | ||
</script> | ||
|
||
<Card.Root class="max-h-[400px] overflow-y-auto"> | ||
<Card.Header> | ||
<Card.Title>LeetCode</Card.Title> | ||
<Card.Description>{leetCodeUsername}'s LeetCode stats</Card.Description> | ||
</Card.Header> | ||
|
||
<Card.Content> | ||
{#if loading} | ||
<p>Loading...</p> | ||
{:else if data} | ||
<!-- Problem Solving Summary --> | ||
<h4 class="text-lg font-semibold mt-6 mb-4">Problem Solving Summary</h4> | ||
<Table.Root class="w-full mb-8"> | ||
<Table.Header> | ||
<Table.Row> | ||
<Table.Head>Difficulty</Table.Head> | ||
<Table.Head>Count</Table.Head> | ||
</Table.Row> | ||
</Table.Header> | ||
<Table.Body> | ||
<Table.Row> | ||
<Table.Cell>Easy</Table.Cell> | ||
<Table.Cell>{easyCount}</Table.Cell> | ||
</Table.Row> | ||
<Table.Row> | ||
<Table.Cell>Medium</Table.Cell> | ||
<Table.Cell>{mediumCount}</Table.Cell> | ||
</Table.Row> | ||
<Table.Row> | ||
<Table.Cell>Hard</Table.Cell> | ||
<Table.Cell>{hardCount}</Table.Cell> | ||
</Table.Row> | ||
</Table.Body> | ||
</Table.Root> | ||
|
||
<!-- Badges --> | ||
{#if data.userCalendar.dccBadges.length > 0} | ||
<h4 class="text-lg font-semibold mb-4">Badges</h4> | ||
<div class="badge-grid grid grid-cols-3 gap-4 mb-8"> | ||
{#each data.userCalendar.dccBadges as badge} | ||
<div class="badge relative w-12 h-12"> | ||
<img src={`https://leetcode.com/${badge.badge.icon}`} alt={badge.badge.name} class="w-full h-auto" /> | ||
<div class="tooltip absolute bottom-[-24px] left-1/2 transform -translate-x-1/2 bg-black text-white text-xs rounded px-2 py-1 hidden group-hover:block"> | ||
{badge.badge.name} | ||
</div> | ||
</div> | ||
{/each} | ||
</div> | ||
{/if} | ||
|
||
<!-- Metrics --> | ||
<h4 class="text-lg font-semibold mb-4">Metrics</h4> | ||
<Table.Root class="w-full"> | ||
<Table.Body> | ||
<Table.Row class="flex items-center space-x-4 mb-2"> | ||
<Flame class="text-orange-500 w-6 h-6" /> | ||
<Table.Cell class="font-medium">Longest Streak:</Table.Cell> | ||
<Table.Cell>{data.userCalendar.streak} days</Table.Cell> | ||
</Table.Row> | ||
<Table.Row class="flex items-center space-x-4 mb-2"> | ||
<Calendar class="text-blue-500 w-6 h-6" /> | ||
<Table.Cell class="font-medium">Active Days:</Table.Cell> | ||
<Table.Cell>{data.userCalendar.totalActiveDays}</Table.Cell> | ||
</Table.Row> | ||
<Table.Row class="flex items-center space-x-4"> | ||
<Medal class="text-yellow-500 w-6 h-6" /> | ||
<Table.Cell class="font-medium">Ranking:</Table.Cell> | ||
<Table.Cell>{data.profile.ranking}</Table.Cell> | ||
</Table.Row> | ||
</Table.Body> | ||
</Table.Root> | ||
{:else} | ||
<p>No data available.</p> | ||
{/if} | ||
</Card.Content> | ||
</Card.Root> | ||
|
||
<style> | ||
.badge-grid { | ||
display: grid; | ||
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr)); | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { z } from 'zod'; | ||
|
||
export const leetCodeSchema = z.object({ | ||
username: z.string().min(3).max(20) | ||
}); | ||
|
||
|
||
export type LeetCodeSchema = typeof leetCodeSchema; | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export type LeetCodeStats = { | ||
username: string; | ||
userCalendar: { | ||
activeYears: number[]; | ||
streak: number; | ||
totalActiveDays: number; | ||
dccBadges: { | ||
timestamp: string; | ||
badge: { | ||
name: string; | ||
icon: string; | ||
}; | ||
}[]; | ||
}; | ||
profile: { | ||
ranking: number; | ||
}; | ||
submitStatsGlobal:{ | ||
acSubmissionNum: { | ||
difficulty: string; | ||
count: number; | ||
}[]; | ||
} | ||
}; | ||
|
||
export type TagProblem = { | ||
tagName: string; | ||
tagSlug: string; | ||
problemsSolved: number; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import type { RequestHandler } from '@sveltejs/kit'; | ||
|
||
export const GET: RequestHandler = async ({ url }) => { | ||
const username = url.searchParams.get('leetCodeUsername'); | ||
const query = ` | ||
query userProfileCalendar($username: String!) { | ||
matchedUser(username: $username) { | ||
username | ||
userCalendar { | ||
activeYears | ||
streak | ||
totalActiveDays | ||
dccBadges { | ||
timestamp | ||
badge { | ||
name | ||
icon | ||
} | ||
} | ||
} | ||
profile { | ||
ranking | ||
} | ||
submitStatsGlobal { | ||
acSubmissionNum { | ||
difficulty | ||
count | ||
} | ||
} | ||
} | ||
}`; | ||
const variables = { | ||
username, | ||
}; | ||
|
||
const response = await fetch('https://leetcode.com/graphql', { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
operationName: 'userProfileCalendar', | ||
query, | ||
variables | ||
}) | ||
}); | ||
|
||
const data = await response.json(); | ||
return new Response(JSON.stringify(data), { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.