Skip to content
Open
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
103 changes: 87 additions & 16 deletions src/pages/GitRank.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo } from "react";
import { Search, Filter, Star, Trophy, RefreshCw, GitCommit, Calendar, BookOpen, AlertCircle, CheckCircle2 } from "lucide-react";
import { collection, query, where, onSnapshot, doc, runTransaction } from "firebase/firestore";
import { collection, query, where, doc, updateDoc, orderBy, limit, startAfter, onSnapshot, getDocs, runTransaction } from "firebase/firestore";

Check warning on line 3 in src/pages/GitRank.jsx

View workflow job for this annotation

GitHub Actions / Lint Check

'updateDoc' is defined but never used. Allowed unused vars must match /^React$/u

Check warning on line 3 in src/pages/GitRank.jsx

View workflow job for this annotation

GitHub Actions / Lint Check

'where' is defined but never used. Allowed unused vars must match /^React$/u
import { useSearchParams } from "react-router-dom";
import { db } from "../lib/firebase";
import { useAuth } from "../context/AuthContext";
Expand All @@ -14,6 +14,11 @@
const [searchParams] = useSearchParams();
const [searchTerm, setSearchTerm] = useState(searchParams.get("search") || "");
const [selectedLanguage, setSelectedLanguage] = useState("All");

// Pagination States
const [lastVisible, setLastVisible] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);

// Real-time leaderboard state
const [usersList, setUsersList] = useState([]);
Expand All @@ -31,19 +36,14 @@

const languages = ["All", "TypeScript", "Rust", "Go", "Python", "Kotlin", "Ruby", "JavaScript"];

// 1. Real-time Leaderboard Listener
// 1. Real-time Leaderboard Listener (Initial 50 Users)
useEffect(() => {
// Only listen if user is logged in (due to read rules requiring authentication)
if (!user) {
const timer = setTimeout(() => {
setLoadingUsers(false);
}, 0);
return () => clearTimeout(timer);
}
setLoadingUsers(true);

const q = query(
collection(db, "users"),
where("onboardingStatus", "==", "complete")
orderBy("points.totalPoints", "desc"),
limit(50)
);

const unsubscribe = onSnapshot(
Expand All @@ -54,16 +54,16 @@
users.push(doc.data());
});

// Sort by totalPoints descending
users.sort((a, b) => (b.points?.totalPoints || 0) - (a.points?.totalPoints || 0));

// Assign ranks
const ranked = users.map((u, i) => ({
...u,
rank: i + 1
}));

setUsersList(ranked);

// Setup pagination cursors
setLastVisible(snapshot.docs[snapshot.docs.length - 1]);
setHasMore(snapshot.docs.length === 50);
setLoadingUsers(false);
},
(error) => {
Expand All @@ -73,7 +73,50 @@
);

return () => unsubscribe();
}, [user]);
}, []);

// Pagination Function (Fetch next 50)
const loadMoreUsers = async () => {
if (!lastVisible || !hasMore || loadingMore) return;

setLoadingMore(true);
try {
const nextQuery = query(
collection(db, "users"),
orderBy("points.totalPoints", "desc"),
startAfter(lastVisible),
limit(50)
);

const snapshot = await getDocs(nextQuery);

if (snapshot.empty) {
setHasMore(false);
setLoadingMore(false);
return;
}

const newUsers = [];
snapshot.forEach((doc) => {
newUsers.push(doc.data());
});

setUsersList((prevUsers) => {
const combinedUsers = [...prevUsers, ...newUsers];
return combinedUsers.map((u, i) => ({
...u,
rank: i + 1
}));
});

setLastVisible(snapshot.docs[snapshot.docs.length - 1]);
setHasMore(snapshot.docs.length === 50);
} catch (error) {
console.error("Error fetching more users:", error);
} finally {
setLoadingMore(false);
}
};

// 2. Fetch GitHub Events/Repos for Charts
useEffect(() => {
Expand Down Expand Up @@ -901,8 +944,36 @@
)}
</div>
</Card>

{/* PAGINATION CONTROLS ADDED HERE */}
{hasMore && (
<div className="flex justify-center w-full mt-8 mb-4">
<button
onClick={loadMoreUsers}
disabled={loadingMore}
className="px-8 py-3 bg-gradient-to-r from-violet-600 to-indigo-600 hover:from-violet-700 hover:to-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold rounded-xl transition-all shadow-lg hover:shadow-violet-500/30 flex items-center gap-2"
>
{loadingMore ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
Loading...
</>
) : (
"Load More Developers"
)}
</button>
</div>
)}

{!hasMore && usersList.length > 0 && (
<div className="text-center text-slate-500 dark:text-slate-400 mt-6 pb-4 text-sm font-medium">
You've reached the end of the leaderboard! 🏆
</div>
)}
{/* 🚀 👆 YAHAN TAK 👆 🚀 */}

</div>
);
};

export default GitRank;
export default GitRank;
Loading