Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ecc996b

Browse files
added homepage
1 parent 166d206 commit ecc996b

File tree

2 files changed

+349
-1
lines changed

2 files changed

+349
-1
lines changed

‎index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
const express = require('express')
22
const app = express();
33
const cors = require('cors')
4+
const path = require('path');
5+
app.use(express.static('public'));
46

57
app.use(cors({
68
origin: '*'
79
}))
810

911
let leet = require('./leetcode');
1012
app.get('/', (req, res) => {
11-
res.send(`<b>API URL:</b>/<b style="color:crimson;">yourLeetcodeUsername</b>`)
13+
res.sendFile(path.join(__dirname,'public','index.html'));
1214
});
1315
app.get('/:id', leet.leetcode);
1416

‎public/index.html

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>LeetCode User Stats</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9+
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
10+
<script>
11+
tailwind.config = {
12+
theme: {
13+
extend: {
14+
colors: {
15+
primary: '#58a6ff',
16+
},
17+
backgroundImage: {
18+
'gradient': 'linear-gradient(45deg, hsl(240deg 100% 20%) 0%, hsl(281deg 100% 21%) 8%, hsl(304deg 100% 23%) 17%, hsl(319deg 100% 30%) 25%, hsl(329deg 100% 36%) 33%, hsl(336deg 100% 41%) 42%, hsl(346deg 83% 51%) 50%, hsl(3deg 95% 61%) 58%, hsl(17deg 100% 59%) 67%, hsl(30deg 100% 55%) 75%, hsl(40deg 100% 50%) 83%, hsl(48deg 100% 50%) 92%, hsl(55deg 100% 50%) 100%)',
19+
},
20+
}
21+
}
22+
}
23+
</script>
24+
<style>
25+
.glassmorphism {
26+
background: rgba(255, 255, 255, 0.05);
27+
backdrop-filter: blur(10px);
28+
border-radius: 10px;
29+
border: 1px solid rgba(255, 255, 255, 0.1);
30+
}
31+
.glass-2{
32+
background: rgba(255, 255, 255, 0.05);
33+
backdrop-filter: blur(10px);
34+
border: 1px solid rgba(255, 255, 255, 0.1);
35+
}
36+
.spinner {
37+
border: 4px solid rgba(255, 255, 255, 0.3);
38+
border-radius: 50%;
39+
border-top: 4px solid #ffffff;
40+
width: 40px;
41+
height: 40px;
42+
animation: spin 1s linear infinite;
43+
}
44+
45+
@keyframes spin {
46+
0% { transform: rotate(0deg); }
47+
100% { transform: rotate(360deg); }
48+
}
49+
50+
.contribution-cell {
51+
width: 10px;
52+
height: 10px;
53+
margin: 2px;
54+
border-radius: 2px;
55+
transition: all 0.2s ease;
56+
}
57+
58+
.contribution-cell:hover {
59+
transform: scale(1.2);
60+
}
61+
62+
.level-0 { background-color: #161b22; }
63+
.level-1 { background-color: #0e4429; }
64+
.level-2 { background-color: #006d32; }
65+
.level-3 { background-color: #26a641; }
66+
.level-4 { background-color: #39d353; }
67+
</style>
68+
</head>
69+
<body class="bg-gradient text-white min-h-screen">
70+
<div class="container mx-auto px-4 py-8">
71+
<h1 class="text-2xl flex items-center justify-center gap-2 font-bold text-white mb-8 text-center"><img class="h-10 " src="https://upload.wikimedia.org/wikipedia/commons/0/0a/LeetCode_Logo_black_with_text.svg"/> api</h1>
72+
<div class="mb-6 flex justify-center">
73+
<input type="text" id="username" placeholder="Username" class="bg-white bg-opacity-20 text-white px-4 py-2 rounded-l-md focus:outline-none focus:ring-0 focus:ring-none placeholder-gray-300">
74+
<button onclick="fetchUserStats()" class="glass-2 text-white px-4 py-2 rounded-r-md hover:bg-opacity-90 transition-colors">Okay</button>
75+
</div>
76+
<div id="userStats" class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"></div>
77+
<div id="heatmap" class="mb-8 glassmorphism p-4">
78+
79+
<div id="heatmapChart"></div>
80+
</div>
81+
<div id="recentSubmissions" class="glassmorphism p-4">
82+
83+
</div>
84+
<div id="loadingSpinner" class="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-black bg-opacity-50 z-50 hidden">
85+
<div class="spinner"></div>
86+
</div>
87+
88+
89+
</div>
90+
<div class="glassmorphism py-3"><a target="_blank" href="https://github.com/faisal-shohag/leetcode_api"> <div class="flex items-center gap-2 font-semibold justify-center">
91+
Star at <img class="h-6 w-6" src="https://seeklogo.com/images/G/github-logo-7880D80B8D-seeklogo.com.png"/>
92+
</div></a></div>
93+
94+
<script>
95+
async function fetchUserStats() {
96+
const username = document.getElementById('username').value;
97+
document.getElementById('loadingSpinner').classList.remove('hidden');
98+
try {
99+
const response = await fetch(`https://leetcode-api-faisalshohag.vercel.app/${username}`);
100+
const data = await response.json();
101+
displayUserStats(data);
102+
createHeatmap(data.submissionCalendar);
103+
displayRecentSubmissions(data.recentSubmissions);
104+
} catch (error) {
105+
console.error('Error fetching user stats:', error);
106+
} finally {
107+
document.getElementById('loadingSpinner').classList.add('hidden');
108+
}
109+
}
110+
111+
function displayUserStats(data) {
112+
// Previous displayUserStats code remains the same
113+
const statsHtml = `
114+
<div class="glassmorphism p-6 shadow-lg">
115+
<h3 class="text-xl font-semibold text-white mb-2">Total Solved</h3>
116+
<div class="flex items-center justify-between">
117+
<p class="text-3xl font-bold">${data.totalSolved}/${data.totalQuestions}</p>
118+
<canvas id="totalSolvedChart" width="80" height="80"></canvas>
119+
</div>
120+
</div>
121+
<div class="glassmorphism p-6 shadow-lg">
122+
<h3 class="text-xl font-semibold text-white mb-2">Ranking</h3>
123+
<p class="text-3xl font-bold">${data.ranking}</p>
124+
</div>
125+
<div class="glassmorphism p-6 shadow-lg">
126+
<h3 class="text-xl font-semibold text-white mb-2">Easy Solved</h3>
127+
<div class="flex items-center justify-between">
128+
<p class="text-3xl font-bold">${data.easySolved}/${data.totalEasy}</p>
129+
<canvas id="easySolvedChart" width="80" height="80"></canvas>
130+
</div>
131+
</div>
132+
<div class="glassmorphism p-6 shadow-lg">
133+
<h3 class="text-xl font-semibold text-white mb-2">Medium Solved</h3>
134+
<div class="flex items-center justify-between">
135+
<p class="text-3xl font-bold">${data.mediumSolved}/${data.totalMedium}</p>
136+
<canvas id="mediumSolvedChart" width="80" height="80"></canvas>
137+
</div>
138+
</div>
139+
<div class="glassmorphism p-6 shadow-lg">
140+
<h3 class="text-xl font-semibold text-white mb-2">Hard Solved</h3>
141+
<div class="flex items-center justify-between">
142+
<p class="text-3xl font-bold">${data.hardSolved}/${data.totalHard}</p>
143+
<canvas id="hardSolvedChart" width="80" height="80"></canvas>
144+
</div>
145+
</div>
146+
<div class="glassmorphism p-6 shadow-lg">
147+
<h3 class="text-xl font-semibold text-white mb-2">Contribution Points</h3>
148+
<p class="text-3xl font-bold">${data.contributionPoint}</p>
149+
</div>
150+
`;
151+
document.getElementById('userStats').innerHTML = statsHtml;
152+
153+
createDonutChart('totalSolvedChart', data.totalSolved, data.totalQuestions, '#3B82F6');
154+
createDonutChart('easySolvedChart', data.easySolved, data.totalEasy, '#10B981');
155+
createDonutChart('mediumSolvedChart', data.mediumSolved, data.totalMedium, '#FBBF24');
156+
createDonutChart('hardSolvedChart', data.hardSolved, data.totalHard, '#EF4444');
157+
}
158+
159+
function createDonutChart(elementId, value, total, color) {
160+
const ctx = document.getElementById(elementId).getContext('2d');
161+
new Chart(ctx, {
162+
type: 'doughnut',
163+
data: {
164+
datasets: [{
165+
data: [value, total - value],
166+
backgroundColor: [color, 'rgba(255, 255, 255, 0.2)'],
167+
borderWidth: 0
168+
}]
169+
},
170+
options: {
171+
cutout: '70%',
172+
responsive: false,
173+
maintainAspectRatio: false,
174+
plugins: {
175+
legend: {
176+
display: false
177+
},
178+
tooltip: {
179+
enabled: false
180+
}
181+
}
182+
}
183+
});
184+
}
185+
186+
function createHeatmap(submissionCalendar) {
187+
const heatmapContainer = document.querySelector("#heatmapChart");
188+
heatmapContainer.innerHTML = '<h2 class="text-2xl font-bold text-white mb-4">Submission Heatmap</h2>'; // Clear existing content
189+
190+
// Create container for the entire heatmap
191+
const container = document.createElement('div');
192+
container.className = 'flex flex-col gap-2';
193+
194+
// Create month labels
195+
const monthsContainer = document.createElement('div');
196+
monthsContainer.className = 'flex text-xs text-gray-400 mb-1';
197+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
198+
months.forEach(month => {
199+
const monthLabel = document.createElement('div');
200+
monthLabel.className = 'flex-1 text-center';
201+
monthLabel.textContent = month;
202+
monthsContainer.appendChild(monthLabel);
203+
});
204+
container.appendChild(monthsContainer);
205+
206+
// Create grid container
207+
const gridContainer = document.createElement('div');
208+
gridContainer.className = 'flex gap-2';
209+
210+
// Add day labels
211+
const dayLabels = document.createElement('div');
212+
dayLabels.className = 'flex flex-col gap-8 text-xs text-gray-400 pr-2';
213+
['Mon', 'Wed', 'Fri'].forEach(day => {
214+
const dayLabel = document.createElement('div');
215+
dayLabel.textContent = day;
216+
dayLabels.appendChild(dayLabel);
217+
});
218+
gridContainer.appendChild(dayLabels);
219+
220+
// Process and create contribution grid
221+
const { weeks } = processCalendarData(submissionCalendar);
222+
const contributionGrid = document.createElement('div');
223+
contributionGrid.className = 'flex gap-1';
224+
225+
weeks.forEach(week => {
226+
const weekContainer = document.createElement('div');
227+
weekContainer.className = 'flex flex-col gap-1';
228+
229+
week.forEach(({ date, count }) => {
230+
const cell = document.createElement('div');
231+
cell.className = `contribution-cell level-${getContributionLevel(count)}`;
232+
cell.title = `${date}: ${count} submissions`;
233+
weekContainer.appendChild(cell);
234+
});
235+
236+
contributionGrid.appendChild(weekContainer);
237+
});
238+
239+
gridContainer.appendChild(contributionGrid);
240+
container.appendChild(gridContainer);
241+
242+
// Add legend
243+
const legendContainer = document.createElement('div');
244+
legendContainer.className = 'flex items-center gap-2 text-xs text-gray-400 mt-4';
245+
legendContainer.innerHTML = `
246+
<span>Less</span>
247+
<div class="flex gap-1">
248+
<div class="contribution-cell level-0"></div>
249+
<div class="contribution-cell level-1"></div>
250+
<div class="contribution-cell level-2"></div>
251+
<div class="contribution-cell level-3"></div>
252+
<div class="contribution-cell level-4"></div>
253+
</div>
254+
<span>More</span>
255+
`;
256+
container.appendChild(legendContainer);
257+
258+
heatmapContainer.appendChild(container);
259+
}
260+
261+
function processCalendarData(submissionCalendar) {
262+
const weeks = [];
263+
let currentWeek = [];
264+
265+
// Convert timestamps to date objects and sort them
266+
const dates = Object.keys(submissionCalendar)
267+
.map(timestamp => ({
268+
date: new Date(timestamp * 1000),
269+
count: submissionCalendar[timestamp]
270+
}))
271+
.sort((a, b) => a.date - b.date);
272+
273+
// Fill in missing dates with zero contributions
274+
const startDate = dates[0].date;
275+
const endDate = dates[dates.length - 1].date;
276+
const dateMap = new Map(dates.map(d => [d.date.toISOString().split('T')[0], d.count]));
277+
278+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
279+
const dateStr = d.toISOString().split('T')[0];
280+
const count = dateMap.get(dateStr) || 0;
281+
const dayOfWeek = d.getDay();
282+
283+
if (dayOfWeek === 0 && currentWeek.length > 0) {
284+
weeks.push(currentWeek);
285+
currentWeek = [];
286+
}
287+
288+
currentWeek.push({
289+
date: dateStr,
290+
count: count
291+
});
292+
293+
if (currentWeek.length === 7) {
294+
weeks.push(currentWeek);
295+
currentWeek = [];
296+
}
297+
}
298+
299+
if (currentWeek.length > 0) {
300+
weeks.push(currentWeek);
301+
}
302+
303+
return { weeks };
304+
}
305+
306+
function getContributionLevel(count) {
307+
if (count === 0) return 0;
308+
if (count <= 3) return 1;
309+
if (count <= 6) return 2;
310+
if (count <= 9) return 3;
311+
return 4;
312+
}
313+
function displayRecentSubmissions(submissions) {
314+
let tableHtml = `
315+
<h2 class="text-2xl font-bold text-white mb-4">Recent Submissions</h2>
316+
<div class="overflow-x-auto">
317+
<table class="w-full rounded-lg overflow-hidden">
318+
<thead class="bg-white bg-opacity-10">
319+
<tr>
320+
<th class="px-4 py-2 text-left">Title</th>
321+
<th class="px-4 py-2 text-left">Status</th>
322+
<th class="px-4 py-2 text-left">Language</th>
323+
<th class="px-4 py-2 text-left">Timestamp</th>
324+
</tr>
325+
</thead>
326+
<tbody>
327+
`;
328+
329+
submissions.forEach(sub => {
330+
const date = new Date(sub.timestamp * 1000).toLocaleString();
331+
tableHtml += `
332+
<tr class="border-t border-white border-opacity-10">
333+
<td class="px-4 py-2 underline"><a target="_blank" href="https://leetcode.com/problems/${sub.titleSlug}" target="_blank" rel="noopener noreferrer" class="text-blue-300 hover:text-blue-100">${sub.title}</a></td>
334+
<td class="px-4 py-2 font-bold ${sub.statusDisplay == "Accepted" ? "text-green-400" : "text-red-400"}">${sub.statusDisplay}</td>
335+
<td class="px-4 py-2">${sub.lang}</td>
336+
<td class="px-4 py-2">${date}</td>
337+
</tr>
338+
`;
339+
});
340+
341+
tableHtml += '</tbody></table></div>';
342+
document.getElementById('recentSubmissions').innerHTML = tableHtml;
343+
}
344+
</script>
345+
</body>
346+
</html>

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /