Skip to content
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

Fixed the rate limit exceed of GitHub API on contributors page #1426

Merged
merged 7 commits into from
Dec 29, 2024
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
73 changes: 72 additions & 1 deletion assets/css_files/contributor.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ body {
display: flex;
flex-direction: column;
align-items: center;
width: 120px;
width: 120px;
margin: 10px;
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
text-align: center;
opacity: 0;
animation: fadeIn 0.3s ease forwards;
}

.contributor-card:hover {
Expand All @@ -123,3 +125,72 @@ body {
margin-top: 5px;
color: #c9d1d9; /* Ensure visibility */
}

.contributions-count-bubble {
font-size: 12px;
color: #c9d1d9;
background-color: #3A0088;
padding: 5px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
top: -130px;
right: -40px;
}

.lazy {
filter: blur(5px);
transition: filter 0.3s;
}

.lazy.loaded {
filter: blur(0);
}

#loading-spinner {
text-align: center;
padding: 20px;
}

.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}

100% {
transform: rotate(360deg);
}
}

@keyframes fadeIn {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

#error-message {
color: white;
padding: 20px;
display: none;
font-size: 19px;
font-weight: 600;
text-align: center;
}
10 changes: 9 additions & 1 deletion assets/html_files/contributor.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,15 @@
<h1 class="heading">Our Valuable Contributors</h1>
<p class="subheading">Meet the minds</p>
</div>
<div id="contributor"></div>
<div id="contributor">
<div id="loading-spinner">
<div class="spinner"></div>
<p>Loading contributors...</p>
</div>
</div>
<div id="error-message">
Failed to load the contributors. Please try again later.
</div>
</div>

<footer>
Expand Down
1 change: 1 addition & 0 deletions assets/images/avatar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 106 additions & 54 deletions assets/js_files/contributor.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,128 @@
const cont = document.getElementById('contributor');
let currentPage = 1; // Start from page 1
let isLoading = false; // Flag to track loading state
let hasMore = true; // Flag to track if there's more data to load

// Loading spinner element
const loadingSpinner = document.getElementById('loading-spinner');

// Error message element
const errorMessage = document.getElementById('error-message');

// Intersection Observer for infinite scroll
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 0.1
};

// Use when one page is loaded, and then scroll down to load more
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !isLoading && hasMore) {
// If intersecting, not in loading state, and has more data to load
fetchContributors(currentPage);
}
});
}, observerOptions);

// Lazy loading observer for avatars
const lazyLoadObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
lazyLoadObserver.unobserve(img);
}
});
}, observerOptions);

// Loads a single page of contributors
async function fetchContributors(pageNumber) {
const perPage = 100;
const apiUrl = '/.netlify/functions/contributors'; // Netlify serverless function path
if (isLoading) return;

const response = await fetch(`${apiUrl}?page=${pageNumber}&per_page=${perPage}`);

if (!response.ok) {
throw new Error(`Failed to fetch the contributors data. Status code: ${response.status}`);
}
isLoading = true;
const perPage = 20; // Number of items per page
const apiUrl = '/.netlify/functions/contributors';

const contributorsData = await response.json();
return contributorsData;
}
try {
// Show loading spinner at the bottom
loadingSpinner.style.display = 'block';

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

try {
// Fetch all contributors in parallel using Promise.all()
const fetchPromises = [];
if (!response.ok) {
throw new Error(`Failed to fetch contributors. Status: ${response.status}`);
}

const contributorsData = await response.json();

// Fetch data for multiple pages concurrently
for (let i = 1; i <= maxPages; i++) {
fetchPromises.push(fetchContributors(i));
// Check if we have more data to load
hasMore = contributorsData.length === perPage;

// Create and append contributor cards
await displayContributors(contributorsData);

currentPage++;
} catch (error) {
errorMessage.style.display = 'block';
console.error('Error fetching contributors:', error);
} finally {
isLoading = false;
loadingSpinner.style.display = 'none';

// Add observer to the last card for infinite scroll
const allCards = cont.querySelectorAll('.contributor-card');
if (allCards.length > 0) {
intersectionObserver.observe(allCards[allCards.length - 1]);
}
}
}

const contributorsArray = await Promise.all(fetchPromises);
// Displays the contributors on the page
async function displayContributors(contributors) {
const fragment = document.createDocumentFragment();

// Combine all the results
contributorsArray.forEach(contributorsData => {
allContributors = allContributors.concat(contributorsData);
});
for (const contributor of contributors) {
if (contributor.login === 'Rakesh9100') continue; // Skip owner

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

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

const avatarImg = document.createElement('img');
avatarImg.src = contributor.avatar_url;
avatarImg.alt = `${contributor.login}'s Picture`;
const loginLink = document.createElement('a');
loginLink.href = contributor.html_url;
loginLink.target = '_blank';
loginLink.appendChild(avatarImg);

const loginLink = document.createElement('a');
loginLink.href = contributor.html_url;
loginLink.target = '_blank';
loginLink.appendChild(avatarImg);
const displayName = contributor.login;

// Fetch detailed info for the name
fetch(contributor.url)
.then(contributorDetails => contributorDetails.json())
.then(contributorData => {
const displayName = contributorData.name || contributor.login;
const nameDiv = document.createElement('div');
nameDiv.classList.add('contributor-name');
nameDiv.textContent = displayName;

const nameDiv = document.createElement('div');
nameDiv.classList.add('contributor-name');
nameDiv.textContent = displayName;
const contributionsCountBubbleDiv = document.createElement('div');
contributionsCountBubbleDiv.classList.add('contributions-count-bubble');
contributionsCountBubbleDiv.textContent = contributor.contributions;

contributorCard.appendChild(loginLink);
contributorCard.appendChild(nameDiv);
contributorCard.appendChild(loginLink);
contributorCard.appendChild(nameDiv);
contributorCard.appendChild(contributionsCountBubbleDiv);
fragment.appendChild(contributorCard);

cont.appendChild(contributorCard);
})
.catch(error => console.error('Error fetching the contributor details:', error));
});
} catch (error) {
console.error('Error fetching the contributors:', error);
// Observe the image for lazy loading
lazyLoadObserver.observe(avatarImg);
}

cont.appendChild(fragment);
}

fetchAllContributors();
// Initial load
fetchContributors(currentPage);
Loading