Skip to content

Commit d1866c1

Browse files
authored
Fixed the rate limit exceed of GitHub API on contributors page (#1426)
1 parent 8086579 commit d1866c1

File tree

4 files changed

+188
-56
lines changed

4 files changed

+188
-56
lines changed

assets/css_files/contributor.css

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,12 @@ body {
9797
display: flex;
9898
flex-direction: column;
9999
align-items: center;
100-
width: 120px;
100+
width: 120px;
101101
margin: 10px;
102102
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
103103
text-align: center;
104+
opacity: 0;
105+
animation: fadeIn 0.3s ease forwards;
104106
}
105107

106108
.contributor-card:hover {
@@ -123,3 +125,72 @@ body {
123125
margin-top: 5px;
124126
color: #c9d1d9; /* Ensure visibility */
125127
}
128+
129+
.contributions-count-bubble {
130+
font-size: 12px;
131+
color: #c9d1d9;
132+
background-color: #3A0088;
133+
padding: 5px;
134+
width: 30px;
135+
height: 30px;
136+
border-radius: 50%;
137+
display: flex;
138+
justify-content: center;
139+
align-items: center;
140+
position: relative;
141+
top: -130px;
142+
right: -40px;
143+
}
144+
145+
.lazy {
146+
filter: blur(5px);
147+
transition: filter 0.3s;
148+
}
149+
150+
.lazy.loaded {
151+
filter: blur(0);
152+
}
153+
154+
#loading-spinner {
155+
text-align: center;
156+
padding: 20px;
157+
}
158+
159+
.spinner {
160+
width: 40px;
161+
height: 40px;
162+
border: 4px solid #f3f3f3;
163+
border-top: 4px solid #3498db;
164+
border-radius: 50%;
165+
animation: spin 1s linear infinite;
166+
margin: 0 auto;
167+
}
168+
169+
@keyframes spin {
170+
0% {
171+
transform: rotate(0deg);
172+
}
173+
174+
100% {
175+
transform: rotate(360deg);
176+
}
177+
}
178+
179+
@keyframes fadeIn {
180+
from {
181+
opacity: 0;
182+
}
183+
184+
to {
185+
opacity: 1;
186+
}
187+
}
188+
189+
#error-message {
190+
color: white;
191+
padding: 20px;
192+
display: none;
193+
font-size: 19px;
194+
font-weight: 600;
195+
text-align: center;
196+
}

assets/html_files/contributor.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,15 @@
9494
<h1 class="heading">Our Valuable Contributors</h1>
9595
<p class="subheading">Meet the minds</p>
9696
</div>
97-
<div id="contributor"></div>
97+
<div id="contributor">
98+
<div id="loading-spinner">
99+
<div class="spinner"></div>
100+
<p>Loading contributors...</p>
101+
</div>
102+
</div>
103+
<div id="error-message">
104+
Failed to load the contributors. Please try again later.
105+
</div>
98106
</div>
99107

100108
<footer>

assets/images/avatar.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/js_files/contributor.js

Lines changed: 106 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,128 @@
11
const cont = document.getElementById('contributor');
2+
let currentPage = 1; // Start from page 1
3+
let isLoading = false; // Flag to track loading state
4+
let hasMore = true; // Flag to track if there's more data to load
5+
6+
// Loading spinner element
7+
const loadingSpinner = document.getElementById('loading-spinner');
8+
9+
// Error message element
10+
const errorMessage = document.getElementById('error-message');
11+
12+
// Intersection Observer for infinite scroll
13+
const observerOptions = {
14+
root: null,
15+
rootMargin: '0px',
16+
threshold: 0.1
17+
};
18+
19+
// Use when one page is loaded, and then scroll down to load more
20+
const intersectionObserver = new IntersectionObserver((entries) => {
21+
entries.forEach(entry => {
22+
if (entry.isIntersecting && !isLoading && hasMore) {
23+
// If intersecting, not in loading state, and has more data to load
24+
fetchContributors(currentPage);
25+
}
26+
});
27+
}, observerOptions);
28+
29+
// Lazy loading observer for avatars
30+
const lazyLoadObserver = new IntersectionObserver((entries) => {
31+
entries.forEach(entry => {
32+
if (entry.isIntersecting) {
33+
const img = entry.target;
34+
img.src = img.dataset.src;
35+
img.classList.remove('lazy');
36+
lazyLoadObserver.unobserve(img);
37+
}
38+
});
39+
}, observerOptions);
240

41+
// Loads a single page of contributors
342
async function fetchContributors(pageNumber) {
4-
const perPage = 100;
5-
const apiUrl = '/.netlify/functions/contributors'; // Netlify serverless function path
43+
if (isLoading) return;
644

7-
const response = await fetch(`${apiUrl}?page=${pageNumber}&per_page=${perPage}`);
8-
9-
if (!response.ok) {
10-
throw new Error(`Failed to fetch the contributors data. Status code: ${response.status}`);
11-
}
45+
isLoading = true;
46+
const perPage = 20; // Number of items per page
47+
const apiUrl = '/.netlify/functions/contributors';
1248

13-
const contributorsData = await response.json();
14-
return contributorsData;
15-
}
49+
try {
50+
// Show loading spinner at the bottom
51+
loadingSpinner.style.display = 'block';
1652

17-
async function fetchAllContributors() {
18-
let allContributors = [];
19-
let pageNumber = 1;
20-
const maxPages = 10; // Limiting the number of pages to avoid overload (can be adjusted)
53+
const response = await fetch(`${apiUrl}?page=${pageNumber}&per_page=${perPage}`);
2154

22-
try {
23-
// Fetch all contributors in parallel using Promise.all()
24-
const fetchPromises = [];
55+
if (!response.ok) {
56+
throw new Error(`Failed to fetch contributors. Status: ${response.status}`);
57+
}
58+
59+
const contributorsData = await response.json();
2560

26-
// Fetch data for multiple pages concurrently
27-
for (let i = 1; i <= maxPages; i++) {
28-
fetchPromises.push(fetchContributors(i));
61+
// Check if we have more data to load
62+
hasMore = contributorsData.length === perPage;
63+
64+
// Create and append contributor cards
65+
await displayContributors(contributorsData);
66+
67+
currentPage++;
68+
} catch (error) {
69+
errorMessage.style.display = 'block';
70+
console.error('Error fetching contributors:', error);
71+
} finally {
72+
isLoading = false;
73+
loadingSpinner.style.display = 'none';
74+
75+
// Add observer to the last card for infinite scroll
76+
const allCards = cont.querySelectorAll('.contributor-card');
77+
if (allCards.length > 0) {
78+
intersectionObserver.observe(allCards[allCards.length - 1]);
2979
}
80+
}
81+
}
3082

31-
const contributorsArray = await Promise.all(fetchPromises);
83+
// Displays the contributors on the page
84+
async function displayContributors(contributors) {
85+
const fragment = document.createDocumentFragment();
3286

33-
// Combine all the results
34-
contributorsArray.forEach(contributorsData => {
35-
allContributors = allContributors.concat(contributorsData);
36-
});
87+
for (const contributor of contributors) {
88+
if (contributor.login === 'Rakesh9100') continue; // Skip owner
3789

38-
// Display contributor cards
39-
allContributors.forEach((contributor) => {
40-
if (contributor.login === 'Rakesh9100') return; // Skip owner
90+
const contributorCard = document.createElement('div');
91+
contributorCard.classList.add('contributor-card');
4192

42-
const contributorCard = document.createElement('div');
43-
contributorCard.classList.add('contributor-card');
93+
// Create avatar with lazy loading
94+
const avatarImg = document.createElement('img');
95+
avatarImg.classList.add('lazy');
96+
avatarImg.src = '../images/avatar.svg'; // Add a placeholder image
97+
avatarImg.dataset.src = contributor.avatar_url;
98+
avatarImg.alt = `${contributor.login}'s Picture`;
4499

45-
const avatarImg = document.createElement('img');
46-
avatarImg.src = contributor.avatar_url;
47-
avatarImg.alt = `${contributor.login}'s Picture`;
100+
const loginLink = document.createElement('a');
101+
loginLink.href = contributor.html_url;
102+
loginLink.target = '_blank';
103+
loginLink.appendChild(avatarImg);
48104

49-
const loginLink = document.createElement('a');
50-
loginLink.href = contributor.html_url;
51-
loginLink.target = '_blank';
52-
loginLink.appendChild(avatarImg);
105+
const displayName = contributor.login;
53106

54-
// Fetch detailed info for the name
55-
fetch(contributor.url)
56-
.then(contributorDetails => contributorDetails.json())
57-
.then(contributorData => {
58-
const displayName = contributorData.name || contributor.login;
107+
const nameDiv = document.createElement('div');
108+
nameDiv.classList.add('contributor-name');
109+
nameDiv.textContent = displayName;
59110

60-
const nameDiv = document.createElement('div');
61-
nameDiv.classList.add('contributor-name');
62-
nameDiv.textContent = displayName;
111+
const contributionsCountBubbleDiv = document.createElement('div');
112+
contributionsCountBubbleDiv.classList.add('contributions-count-bubble');
113+
contributionsCountBubbleDiv.textContent = contributor.contributions;
63114

64-
contributorCard.appendChild(loginLink);
65-
contributorCard.appendChild(nameDiv);
115+
contributorCard.appendChild(loginLink);
116+
contributorCard.appendChild(nameDiv);
117+
contributorCard.appendChild(contributionsCountBubbleDiv);
118+
fragment.appendChild(contributorCard);
66119

67-
cont.appendChild(contributorCard);
68-
})
69-
.catch(error => console.error('Error fetching the contributor details:', error));
70-
});
71-
} catch (error) {
72-
console.error('Error fetching the contributors:', error);
120+
// Observe the image for lazy loading
121+
lazyLoadObserver.observe(avatarImg);
73122
}
123+
124+
cont.appendChild(fragment);
74125
}
75126

76-
fetchAllContributors();
127+
// Initial load
128+
fetchContributors(currentPage);

0 commit comments

Comments
 (0)