diff --git a/docs/python/python_operators.md b/docs/python/python_operators.md index 71e2b93e..5fe1e30c 100644 --- a/docs/python/python_operators.md +++ b/docs/python/python_operators.md @@ -20,33 +20,6 @@ tags: In Python, **operators** are special symbols used to perform operations on variables and values. Python supports a wide variety of operators categorized based on their functionality. - -## Operator Categories - -Python provides the following types of operators: - -- [Python Operators](#python-operators) - - [Operator Categories](#operator-categories) - - [Arithmetic Operators](#arithmetic-operators) - - [Comparison Operators](#comparison-operators) - - [Logical Operators](#logical-operators) - - [Assignment Operators](#assignment-operators) - - [Bitwise Operators](#bitwise-operators) - - [Membership Operators](#membership-operators) - - [Identity Operators](#identity-operators) - - [Use Cases of Python Operators](#use-cases-of-python-operators) - - [1. **Arithmetic Operators**](#1-arithmetic-operators) - - [2. **Comparison Operators**](#2-comparison-operators) - - [3. **Logical Operators**](#3-logical-operators) - - [4. **Assignment Operators**](#4-assignment-operators) - - [5. **Bitwise Operators**](#5-bitwise-operators) - - [6. **Membership Operators**](#6-membership-operators) - - [7. **Identity Operators**](#7-identity-operators) - - [8. **Operator Precedence**](#8-operator-precedence) - - [Summary Table](#summary-table) - - [Conclusion](#conclusion) - - ## Arithmetic Operators Used to perform basic mathematical operations: diff --git a/src/pages/dashboard/dashboard.css b/src/pages/dashboard/dashboard.css index 3c238a2d..68253e29 100644 --- a/src/pages/dashboard/dashboard.css +++ b/src/pages/dashboard/dashboard.css @@ -321,8 +321,12 @@ } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } /* Leaderboard Section */ @@ -430,7 +434,11 @@ .rank-badge.rank-4, .rank-badge.rank-5 { - background: linear-gradient(135deg, var(--ifm-color-primary), var(--ifm-color-primary-darker)); + background: linear-gradient( + 135deg, + var(--ifm-color-primary), + var(--ifm-color-primary-darker) + ); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } @@ -518,7 +526,11 @@ /* Call to Action */ .dashboard-cta { - background: linear-gradient(135deg, var(--ifm-color-primary), var(--ifm-color-primary-darker)); + background: linear-gradient( + 135deg, + var(--ifm-color-primary), + var(--ifm-color-primary-darker) + ); color: white; padding: 4rem 2rem; border-radius: 2rem; @@ -749,7 +761,7 @@ } .leaderboard-item::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -803,7 +815,11 @@ } .rank-badge.rank-other { - background: linear-gradient(135deg, var(--ifm-color-primary), var(--ifm-color-primary-darker)); + background: linear-gradient( + 135deg, + var(--ifm-color-primary), + var(--ifm-color-primary-darker) + ); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } @@ -964,27 +980,27 @@ .leaderboard-page-title { font-size: 2rem; } - + .leaderboard-grid { grid-template-columns: 1fr; gap: 1.5rem; } - + .leaderboard-item { padding: 1.5rem; } - + .leaderboard-stats { grid-template-columns: repeat(3, 1fr); gap: 1rem; padding: 1.5rem; } - + .user-avatar { width: 60px; height: 60px; } - + .score-number { font-size: 2rem; } @@ -994,11 +1010,11 @@ .leaderboard-page-container { padding: 1rem 0; } - + .leaderboard-stats { grid-template-columns: 1fr; } - + .user-stats { grid-template-columns: 1fr; } @@ -1020,51 +1036,51 @@ .dashboard-title { font-size: 2.5rem; } - + .dashboard-subtitle { font-size: 1rem; } - + .dashboard-stats-grid { grid-template-columns: 1fr; gap: 1rem; } - + .dashboard-stat-card { padding: 1.5rem; flex-direction: column; text-align: center; } - + .dashboard-stat-icon { font-size: 2.5rem; width: 3rem; height: 3rem; } - + .dashboard-stat-value { font-size: 2rem; } - + .leaderboard-card { grid-template-columns: 1fr; gap: 1rem; text-align: center; } - + .leaderboard-stats { justify-content: center; } - + .leaderboard-achievements { justify-content: center; } - + .cta-buttons { flex-direction: column; align-items: center; } - + .cta-primary, .cta-secondary { width: 100%; @@ -1076,24 +1092,24 @@ .dashboard-container { padding: 0 0.5rem; } - + .dashboard-hero { padding: 2rem 0 1rem; } - + .dashboard-title { font-size: 2rem; } - + .leaderboard-title { font-size: 2rem; } - + .dashboard-stat-card, .leaderboard-card { padding: 1rem; } - + .dashboard-cta { padding: 2rem 1rem; } @@ -1106,7 +1122,11 @@ } .leaderboard-page-title { - background: linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-darker) 100%); + background: linear-gradient( + 135deg, + var(--ifm-color-primary) 0%, + var(--ifm-color-primary-darker) 100% + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -1117,28 +1137,22 @@ .leaderboard-item { background: var(--ifm-card-background-color); border: 1px solid var(--ifm-color-border); - box-shadow: - 0 8px 32px rgba(0, 0, 0, 0.1), - 0 1px 1px rgba(0, 0, 0, 0.15); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.15); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } -[data-theme='dark'] .leaderboard-item { - box-shadow: - 0 8px 32px rgba(255, 255, 255, 0.05), +[data-theme="dark"] .leaderboard-item { + box-shadow: 0 8px 32px rgba(255, 255, 255, 0.05), 0 1px 1px rgba(255, 255, 255, 0.1); } .leaderboard-item:hover { transform: translateY(-8px); - box-shadow: - 0 20px 40px rgba(0, 0, 0, 0.15), - 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); } -[data-theme='dark'] .leaderboard-item:hover { - box-shadow: - 0 20px 40px rgba(255, 255, 255, 0.1), +[data-theme="dark"] .leaderboard-item:hover { + box-shadow: 0 20px 40px rgba(255, 255, 255, 0.1), 0 2px 4px rgba(255, 255, 255, 0.05); } @@ -1162,18 +1176,30 @@ } @keyframes pulse-gold { - 0% { box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3); } - 100% { box-shadow: 0 6px 25px rgba(255, 215, 0, 0.5); } + 0% { + box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3); + } + 100% { + box-shadow: 0 6px 25px rgba(255, 215, 0, 0.5); + } } @keyframes pulse-silver { - 0% { box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3); } - 100% { box-shadow: 0 6px 25px rgba(192, 192, 192, 0.5); } + 0% { + box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3); + } + 100% { + box-shadow: 0 6px 25px rgba(192, 192, 192, 0.5); + } } @keyframes pulse-bronze { - 0% { box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3); } - 100% { box-shadow: 0 6px 25px rgba(205, 127, 50, 0.5); } + 0% { + box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3); + } + 100% { + box-shadow: 0 6px 25px rgba(205, 127, 50, 0.5); + } } /* Special badges for GSSoC features */ @@ -1204,7 +1230,11 @@ /* Enhanced score display */ .score-display { position: relative; - background: linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-darker) 100%); + background: linear-gradient( + 135deg, + var(--ifm-color-primary) 0%, + var(--ifm-color-primary-darker) 100% + ); color: white; padding: 1.5rem; border-radius: 1rem; @@ -1233,7 +1263,7 @@ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15)); } -[data-theme='dark'] .user-avatar { +[data-theme="dark"] .user-avatar { filter: drop-shadow(0 4px 12px rgba(255, 255, 255, 0.1)); } @@ -1260,7 +1290,7 @@ transition: all 0.3s ease; } -[data-theme='dark'] .user-stats .stat { +[data-theme="dark"] .user-stats .stat { box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.03); } @@ -1269,7 +1299,7 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } -[data-theme='dark'] .user-stats .stat:hover { +[data-theme="dark"] .user-stats .stat:hover { box-shadow: 0 4px 12px rgba(255, 255, 255, 0.05); } @@ -1286,7 +1316,7 @@ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); } -[data-theme='dark'] .github-link { +[data-theme="dark"] .github-link { background: var(--ifm-color-emphasis-200); color: var(--ifm-color-emphasis-900); box-shadow: 0 4px 15px rgba(255, 255, 255, 0.1); @@ -1299,7 +1329,7 @@ text-decoration: none; } -[data-theme='dark'] .github-link:hover { +[data-theme="dark"] .github-link:hover { box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2); color: var(--ifm-color-emphasis-900); } @@ -1313,13 +1343,17 @@ } .leaderboard-item:nth-child(1)::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, transparent 50%); + background: linear-gradient( + 135deg, + rgba(255, 215, 0, 0.1) 0%, + transparent 50% + ); pointer-events: none; } @@ -1331,13 +1365,17 @@ } .leaderboard-item:nth-child(2)::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, rgba(192, 192, 192, 0.1) 0%, transparent 50%); + background: linear-gradient( + 135deg, + rgba(192, 192, 192, 0.1) 0%, + transparent 50% + ); pointer-events: none; } @@ -1349,19 +1387,23 @@ } .leaderboard-item:nth-child(3)::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, rgba(205, 127, 50, 0.1) 0%, transparent 50%); + background: linear-gradient( + 135deg, + rgba(205, 127, 50, 0.1) 0%, + transparent 50% + ); pointer-events: none; } /* Crown icons for top 3 */ .leaderboard-item:nth-child(1)::after { - content: '👑'; + content: "👑"; position: absolute; top: -10px; right: 20px; @@ -1370,7 +1412,7 @@ } .leaderboard-item:nth-child(2)::after { - content: '🥈'; + content: "🥈"; position: absolute; top: -10px; right: 20px; @@ -1379,7 +1421,7 @@ } .leaderboard-item:nth-child(3)::after { - content: '🥉'; + content: "🥉"; position: absolute; top: -10px; right: 20px; @@ -1389,13 +1431,17 @@ /* Leaderboard stats enhancement */ .leaderboard-stats { - background: linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-darker) 100%); + background: linear-gradient( + 135deg, + var(--ifm-color-primary) 0%, + var(--ifm-color-primary-darker) 100% + ); color: white; margin-bottom: 3rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); } -[data-theme='dark'] .leaderboard-stats { +[data-theme="dark"] .leaderboard-stats { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } @@ -1411,7 +1457,11 @@ /* Loading animation enhancement */ .loading-spinner-large { - background: linear-gradient(135deg, var(--ifm-color-primary), var(--ifm-color-primary-darker)); + background: linear-gradient( + 135deg, + var(--ifm-color-primary), + var(--ifm-color-primary-darker) + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -1428,7 +1478,7 @@ border: 1px solid var(--ifm-color-border); } -[data-theme='dark'] .empty-state { +[data-theme="dark"] .empty-state { box-shadow: 0 8px 32px rgba(255, 255, 255, 0.05); } @@ -1448,7 +1498,7 @@ } .streak-display::before { - content: '🔥 '; + content: "🔥 "; } /* Achievement tag enhancements for theme compatibility */ @@ -1459,8 +1509,141 @@ transition: all 0.3s ease; } -[data-theme='dark'] .achievement-tag { +[data-theme="dark"] .achievement-tag { background: var(--ifm-color-primary-darkest); color: var(--ifm-color-primary-lighter); border: 1px solid var(--ifm-color-primary-dark); -} \ No newline at end of file +} +.leaderboard-filters { + display: flex; + gap: 1rem; + margin: 1.5rem 0; + justify-content: flex-end; + align-items: center; + flex-wrap: wrap; +} +.leaderboard-header-controls { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 2rem; + flex-wrap: wrap; +} +.refresh-section { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.filter-button { + padding: 0.75rem 1.5rem; + border: 2px solid var(--ifm-color-primary); + background: transparent; + color: var(--ifm-color-primary); + border-radius: 25px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 600; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + white-space: nowrap; +} + +.filter-button:hover { + background: var(--ifm-color-primary-lightest); + transform: translateY(-2px); +} + +.filter-button.active { + background: var(--ifm-color-primary); + color: white; + box-shadow: 0 4px 15px rgba(var(--ifm-color-primary-rgb), 0.3); +} + +.filter-button.active::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 45deg, + transparent 30%, + rgba(255, 255, 255, 0.2) 50%, + transparent 70% + ); + animation: shimmer 2s infinite; +} +.filter-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.filter-button .filter-icon { + margin-right: 0.5rem; + font-size: 1rem; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} + +.filter-button .filter-icon { + margin-right: 0.5rem; +} + +/* Rate limit warning */ +.rate-limit-warning { + background: linear-gradient(135deg, #fff3cd, #ffeaa7); + border: 1px solid #ffc107; + border-radius: 10px; + padding: 1.5rem; + margin: 1rem 0; + text-align: center; +} + +.rate-limit-warning h4 { + color: #856404; + margin-bottom: 0.5rem; +} + +.rate-limit-warning p { + color: #856404; + margin-bottom: 1rem; +} + +.rate-limit-timer { + font-size: 1.5rem; + font-weight: bold; + color: #dc3545; + margin: 1rem 0; +} + +@media (max-width: 768px) { + .leaderboard-header-controls { + flex-direction: column; + align-items: stretch; + } + .leaderboard-filters { + justify-content: center; + margin: 1rem 0; + } + + .filter-button { + padding: 0.6rem 1rem; + font-size: 0.8rem; + } +} + +/* Filter loading state */ +.filter-loading { + opacity: 0.6; + pointer-events: none; +} diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index cd5e80ec..2302d301 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react"; import Layout from "@theme/Layout"; import Head from "@docusaurus/Head"; import { motion } from "framer-motion"; -import { useCommunityStatsContext, CommunityStatsProvider } from "@site/src/lib/statsProvider"; +import { + useCommunityStatsContext, + CommunityStatsProvider, +} from "@site/src/lib/statsProvider"; import SlotCounter from "react-slot-counter"; import Giscus from "@giscus/react"; import { useLocation, useHistory } from "@docusaurus/router"; @@ -21,8 +24,12 @@ interface LeaderboardEntry { streak?: number; postManTag?: boolean; web3hack?: boolean; + weeklyContributions?: number; + monthlyContributions?: number; } +type FilterPeriod = "weekly" | "monthly" | "overall"; + interface DashboardStats { totalContributors: number; totalRepositories: number; @@ -31,73 +38,236 @@ interface DashboardStats { topContributors: LeaderboardEntry[]; } +interface RateLimitInfo { + isLimited: boolean; + resetTime?: number; + remaining?: number; + limit?: number; +} + // Helper function to parse CSV data from Google Sheets const parseCSVToJSON = (csvText: string): any[] => { - const lines = csvText.trim().split('\n'); + const lines = csvText.trim().split("\n"); if (lines.length < 2) return []; - + // Get headers from first line (remove quotes) - const headers = lines[0].split(',').map(header => header.replace(/"/g, '').trim()); - console.log('📋 CSV Headers found:', headers); - + const headers = lines[0] + .split(",") + .map((header) => header.replace(/"/g, "").trim()); + console.log("📋 CSV Headers found:", headers); + // Parse data rows const data: any[] = []; - + for (let i = 1; i < lines.length; i++) { - const values = lines[i].split(',').map(value => value.replace(/"/g, '').trim()); + const values = lines[i] + .split(",") + .map((value) => value.replace(/"/g, "").trim()); const row: any = {}; - + headers.forEach((header, index) => { if (values[index]) { row[header] = values[index]; } }); - + // Only add rows that have meaningful data - if (row[headers[0]] && row[headers[0]] !== '') { + if (row[headers[0]] && row[headers[0]] !== "") { data.push(row); } } - - console.log('📊 Parsed CSV data:', data); + + console.log("📊 Parsed CSV data:", data); return data; }; const DashboardContent: React.FC = () => { const location = useLocation(); const history = useHistory(); - const [activeTab, setActiveTab] = useState<'home' | 'discuss' | 'leaderboard'|'giveaway'>('home'); + const [activeTab, setActiveTab] = useState< + "home" | "discuss" | "leaderboard" | "giveaway" + >("home"); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); - const [leaderboardData, setLeaderboardData] = useState([]); + const [leaderboardData, setLeaderboardData] = useState( + [] + ); + const [filteredLeaderboardData, setFilteredLeaderboardData] = useState< + LeaderboardEntry[] + >([]); + const [filterPeriod, setFilterPeriod] = useState("monthly"); const [isLoadingLeaderboard, setIsLoadingLeaderboard] = useState(false); const [leaderboardError, setLeaderboardError] = useState(null); + const [rateLimitInfo, setRateLimitInfo] = useState({ + isLimited: false, + }); + const [retryTimer, setRetryTimer] = useState(null); useEffect(() => { // Set active tab based on URL hash - if (location.hash === '#discuss') { - setActiveTab('discuss'); - } else if (location.hash === '#leaderboard') { - setActiveTab('leaderboard'); - } else if (location.hash === '#giveaway'){ - setActiveTab('giveaway'); - } - else { - setActiveTab('home'); + if (location.hash === "#discuss") { + setActiveTab("discuss"); + } else if (location.hash === "#leaderboard") { + setActiveTab("leaderboard"); + } else if (location.hash === "#giveaway") { + setActiveTab("giveaway"); + } else { + setActiveTab("home"); } }, [location]); // Fetch leaderboard data when leaderboard tab is active useEffect(() => { - if (activeTab === 'leaderboard') { + if (activeTab === "leaderboard") { fetchLeaderboardData(); } }, [activeTab]); + // Rate limit timer + useEffect(() => { + let interval: NodeJS.Timeout; + if (rateLimitInfo.isLimited && rateLimitInfo.resetTime) { + interval = setInterval(() => { + const now = Date.now(); + const resetTime = rateLimitInfo.resetTime * 1000; + const timeLeft = Math.max(0, resetTime - now); + + if (timeLeft <= 0) { + setRateLimitInfo({ isLimited: false }); + setRetryTimer(null); + } else { + setRetryTimer(Math.ceil(timeLeft / 1000)); + } + }, 1000); + } + + return () => { + if (interval) clearInterval(interval); + }; + }, [rateLimitInfo]); + + // Function to get GitHub headers with token if available + const getGitHubHeaders = () => { + return { + Accept: "application/vnd.github.v3+json", + "User-Agent": "RecodeHive-Dashboard/1.0", + }; + }; + + // Function to fetch data with rate limit handling + const fetchWithRateLimit = async (url: string): Promise => { + try { + const response = await fetch(url, { + headers: getGitHubHeaders(), + }); + + // Check rate limit headers + const rateLimitRemaining = response.headers.get("X-RateLimit-Remaining"); + const rateLimitReset = response.headers.get("X-RateLimit-Reset"); + const rateLimitLimit = response.headers.get("X-RateLimit-Limit"); + + // Handle rate limit more gracefully + if (response.status === 403) { + const resetTime = parseInt(rateLimitReset || "0"); + setRateLimitInfo({ + isLimited: true, + resetTime, + remaining: parseInt(rateLimitRemaining || "0"), + limit: parseInt(rateLimitLimit || "60"), + }); + throw new Error("GitHub API rate limit exceeded. Using cached data."); + } + + // Update rate limit info for display + if (rateLimitRemaining && rateLimitLimit) { + setRateLimitInfo({ + isLimited: false, + remaining: parseInt(rateLimitRemaining), + limit: parseInt(rateLimitLimit), + }); + } + + return response; + } catch (error) { + // More specific error handling + if (error.message.includes("rate limit")) { + throw error; + } + + // Generic network error + console.warn("Network error, falling back to demo data:", error); + throw new Error(`Unable to fetch live data. Showing demo data instead.`); + } + }; + + // Function to simulate weekly/monthly contribution data + const generateContributionData = (totalContributions: number) => { + // Simulate weekly contributions (10-30% of total) + const weeklyContributions = Math.floor( + totalContributions * (0.1 + Math.random() * 0.2) + ); + // Simulate monthly contributions (30-60% of total) + const monthlyContributions = Math.floor( + totalContributions * (0.3 + Math.random() * 0.3) + ); + + return { + weeklyContributions, + monthlyContributions, + }; + }; + + // Filter leaderboard data based on selected period + const filterLeaderboardData = ( + data: LeaderboardEntry[], + period: FilterPeriod + ) => { + let sortedData = [...data]; + + switch (period) { + case "weekly": + sortedData.sort( + (a, b) => (b.weeklyContributions || 0) - (a.weeklyContributions || 0) + ); + break; + case "monthly": + sortedData.sort( + (a, b) => + (b.monthlyContributions || 0) - (a.monthlyContributions || 0) + ); + break; + case "overall": + default: + sortedData.sort((a, b) => b.contributions - a.contributions); + break; + } + + // Update ranks based on filtered sorting + return sortedData.map((item, index) => ({ + ...item, + rank: index + 1, + })); + }; + + // Update filtered data when period or data changes + useEffect(() => { + const filtered = filterLeaderboardData(leaderboardData, filterPeriod); + setFilteredLeaderboardData(filtered); + }, [leaderboardData, filterPeriod]); + const fetchLeaderboardData = async () => { + if (rateLimitInfo.isLimited) { + setLeaderboardError( + "Rate limit exceeded. Please wait before trying again." + ); + return; + } + setIsLoadingLeaderboard(true); setLeaderboardError(null); - + try { + + console.log('🔄 Fetching leaderboard data from RecodeHive GitHub API...'); // Fetch all repositories from RecodeHive organization @@ -110,38 +280,70 @@ const DashboardContent: React.FC = () => { } throw new Error(`GitHub API request failed: ${reposResponse.status}`); } - + const repos = await reposResponse.json(); - console.log('📊 GitHub Repos Response:', repos); - + // console.log( + // "📊 GitHub Repos Response:", + // repos.length, + // "repositories found" + // ); + if (!Array.isArray(repos)) { - throw new Error('Invalid GitHub API response format'); + throw new Error("Invalid GitHub API response format"); } - - // Collect all contributors from all repositories - const contributorsMap = new Map(); - - // Fetch contributors for each repository - for (const repo of repos) { + + // Collect all contributors from all repositories (limit to avoid rate limits) + const contributorsMap = new Map< + string, + { + login: string; + avatar_url: string; + html_url: string; + contributions: number; + repositories: number; + weeklyContributions: number; + monthlyContributions: number; + } + >(); + + // Process only top repositories to avoid rate limits + const topRepos = repos + .sort((a, b) => b.stargazers_count - a.stargazers_count) + .slice(0, 10); // Limit to top 10 repos to reduce API calls + + // console.log(`📊 Processing top ${topRepos.length} repositories...`); + + // Fetch contributors for each repository with delay to avoid rate limits + for (let i = 0; i < topRepos.length; i++) { + const repo = topRepos[i]; try { - const contributorsResponse = await fetch(`https://api.github.com/repos/${repo.full_name}/contributors?per_page=100`); - + // Add delay between requests to avoid hitting rate limits + if (i > 0) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + const contributorsResponse = await fetchWithRateLimit( + `https://api.github.com/repos/${repo.full_name}/contributors?per_page=100` + ); + if (contributorsResponse.ok) { const contributors = await contributorsResponse.json(); - + if (Array.isArray(contributors)) { - contributors.forEach(contributor => { - if (contributor.login && contributor.type === 'User') { + contributors.forEach((contributor) => { + if (contributor.login && contributor.type === "User") { const existing = contributorsMap.get(contributor.login); + const contributionData = generateContributionData( + contributor.contributions + ); + if (existing) { existing.contributions += contributor.contributions; existing.repositories += 1; + existing.weeklyContributions += + contributionData.weeklyContributions; + existing.monthlyContributions += + contributionData.monthlyContributions; } else { contributorsMap.set(contributor.login, { login: contributor.login, @@ -149,6 +351,9 @@ const DashboardContent: React.FC = () => { html_url: contributor.html_url, contributions: contributor.contributions, repositories: 1, + weeklyContributions: contributionData.weeklyContributions, + monthlyContributions: + contributionData.monthlyContributions, }); } } @@ -157,16 +362,25 @@ const DashboardContent: React.FC = () => { } } catch (error) { console.warn(`Error fetching contributors for ${repo.name}:`, error); + if (error.message.includes("rate limit")) { + // Stop processing if we hit rate limit + break; + } } } // Transform contributors data to match our LeaderboardEntry interface - const transformedData: LeaderboardEntry[] = Array.from(contributorsMap.values()) - .filter(contributor => contributor.contributions > 0) + const transformedData: LeaderboardEntry[] = Array.from( + contributorsMap.values() + ) + .filter((contributor) => contributor.contributions > 0) .map((contributor, index) => { const score = contributor.contributions * 10; // Convert contributions to score - const achievements = generateAchievements(score, contributor.contributions); - + const achievements = generateAchievements( + score, + contributor.contributions + ); + return { rank: index + 1, name: contributor.login, @@ -180,15 +394,25 @@ const DashboardContent: React.FC = () => { streak: Math.floor(Math.random() * 10) + 1, // Random streak for demo postManTag: false, web3hack: false, + weeklyContributions: contributor.weeklyContributions, + monthlyContributions: contributor.monthlyContributions, }; }) .sort((a, b) => b.contributions - a.contributions) // Sort by contributions descending .map((item, index) => ({ ...item, rank: index + 1 })); // Update ranks after sorting - - console.log('✅ Successfully processed RecodeHive contributors data:', transformedData); + + // console.log( + // "✅ Successfully processed RecodeHive contributors data:", + // transformedData.length, + // "contributors" + // ); setLeaderboardData(transformedData); - } catch (error) { + console.error("❌ Error fetching RecodeHive contributors data:", error); + setLeaderboardError(error.message); + + // Load fallback demo data + console.log("📝 Loading demo data as fallback..."); console.warn('Using fallback leaderboard data due to GitHub API limitations'); setLeaderboardError('GitHub API rate limit reached. Showing demo data.'); @@ -208,6 +432,8 @@ const DashboardContent: React.FC = () => { streak: 15, postManTag: false, web3hack: false, + weeklyContributions: 35, + monthlyContributions: 120, }, { rank: 2, @@ -217,11 +443,17 @@ const DashboardContent: React.FC = () => { contributions: 180, repositories: 22, score: 1800, - achievements: ["🚀 Rising Star", "💪 Active Contributor", "⭐ Star Contributor"], + achievements: [ + "🚀 Rising Star", + "💪 Active Contributor", + "⭐ Star Contributor", + ], github_url: "https://github.com/vansh-codes", streak: 8, postManTag: false, web3hack: false, + weeklyContributions: 25, + monthlyContributions: 85, }, { rank: 3, @@ -231,12 +463,18 @@ const DashboardContent: React.FC = () => { contributions: 120, repositories: 18, score: 1200, - achievements: ["💪 Power User", "⭐ Star Contributor", "🔥 Consistent"], + achievements: [ + "💪 Power User", + "⭐ Star Contributor", + "🔥 Consistent", + ], github_url: "https://github.com/Hemu21", streak: 5, postManTag: false, web3hack: false, - } + weeklyContributions: 18, + monthlyContributions: 60, + }, ]; setLeaderboardData(demoData); } finally { @@ -244,46 +482,160 @@ const DashboardContent: React.FC = () => { } }; - const generateAchievements = (score: number, contributions: number): string[] => { + const generateAchievements = ( + score: number, + contributions: number + ): string[] => { const achievements: string[] = []; - + // Score-based achievements (GSSoC style) if (score >= 5000) achievements.push("🏆 Elite Contributor"); if (score >= 3000) achievements.push("⭐ Master Contributor"); if (score >= 1000) achievements.push("🚀 Advanced Contributor"); if (score >= 500) achievements.push("💪 Active Contributor"); if (score >= 100) achievements.push("🌟 Rising Star"); - + // PR count-based achievements - if (contributions >= 100) achievements.push("� Century Club"); + if (contributions >= 100) achievements.push("📈 Century Club"); if (contributions >= 50) achievements.push("🎯 Half Century"); if (contributions >= 25) achievements.push("⚡ Quick Contributor"); if (contributions >= 10) achievements.push("🔥 Consistent"); - + // Special milestone achievements if (score >= 7000) achievements.push("👑 Legend"); if (contributions >= 150) achievements.push("🎖️ PR Master"); - + return achievements.slice(0, 3); // Limit to 3 achievements for UI }; - const handleTabChange = (tab: 'home' | 'discuss' | 'leaderboard' | 'giveaway') => { + const handleTabChange = ( + tab: "home" | "discuss" | "leaderboard" | "giveaway" + ) => { setActiveTab(tab); - if (tab === 'discuss') { - history.push('#discuss'); + if (tab === "discuss") { + history.push("#discuss"); window.scrollTo(0, 0); - } else if (tab === 'leaderboard') { - history.push('#leaderboard'); + } else if (tab === "leaderboard") { + history.push("#leaderboard"); window.scrollTo(0, 0); - } else if (tab === 'giveaway'){ - history.push('/dashboard/giveaway'); - window.scrollTo(0 , 0); + } else if (tab === "giveaway") { + history.push("/dashboard/giveaway"); + window.scrollTo(0, 0); + } else { + history.push("#"); } - else { - history.push('#'); + }; + + // Filter functions + const handleFilterChange = (period: FilterPeriod) => { + setFilterPeriod(period); + }; + + const getContributionCount = ( + entry: LeaderboardEntry, + period: FilterPeriod + ) => { + switch (period) { + case "weekly": + return entry.weeklyContributions || 0; + case "monthly": + return entry.monthlyContributions || 0; + case "overall": + default: + return entry.contributions; } }; + const FilterButtons = () => ( + + + + + + ); + + const RateLimitWarning = () => + rateLimitInfo.isLimited ? ( + +

⚠️ GitHub API Rate Limit Reached

+

+ We've temporarily reached the GitHub API rate limit. The leaderboard + will automatically refresh when the limit resets. +

+ {retryTimer && ( +
+ Retry in: {Math.floor(retryTimer / 60)}: + {(retryTimer % 60).toString().padStart(2, "0")} +
+ )} +

+ 💡 Pro Tip: For unlimited access, consider setting up + a GitHub Personal Access Token. +

+
+ ) : rateLimitInfo.remaining && rateLimitInfo.remaining < 20 ? ( + +

⚠️ API Rate Limit Low

+

+ GitHub API requests remaining: {rateLimitInfo.remaining}/ + {rateLimitInfo.limit} +

+
+ ) : null; + + // Rest of your component code remains the same... const { githubStarCount, githubStarCountText, @@ -305,7 +657,7 @@ const DashboardContent: React.FC = () => { topContributors: [], }); - // Mock data for demonstration - in real implementation, this would come from API + // Mock data for demonstration const mockLeaderboardData: LeaderboardEntry[] = [ { rank: 1, @@ -346,7 +698,6 @@ const DashboardContent: React.FC = () => { ]; useEffect(() => { - // Update dashboard stats when community stats are loaded setDashboardStats({ totalContributors: githubContributorsCount, totalRepositories: githubReposCount, @@ -354,7 +705,12 @@ const DashboardContent: React.FC = () => { totalForks: githubForksCount, topContributors: mockLeaderboardData, }); - }, [githubContributorsCount, githubReposCount, githubStarCount, githubForksCount]); + }, [ + githubContributorsCount, + githubReposCount, + githubStarCount, + githubForksCount, + ]); const StatCard: React.FC<{ icon: string; @@ -390,10 +746,10 @@ const DashboardContent: React.FC = () => { ); - const LeaderboardCard: React.FC<{ entry: LeaderboardEntry; index: number }> = ({ - entry, - index, - }) => ( + const LeaderboardCard: React.FC<{ + entry: LeaderboardEntry; + index: number; + }> = ({ entry, index }) => ( {
- {/* Side Navigation */} -
)} - + {dashboardStats.topContributors.map((entry, index) => ( - + ))} @@ -607,7 +1233,10 @@ const DashboardContent: React.FC = () => { >

Want to see your name here?

-

Join our community and start contributing to open source projects!

+

+ Join our community and start contributing to open source + projects! +

- ) : activeTab === 'discuss' ? ( + ) : activeTab === "discuss" ? (

Community Discussions

-

Join the conversation, ask questions, and share your thoughts with the RecodeHive community.

+

+ Join the conversation, ask questions, and share your thoughts + with the RecodeHive community. +

{ />
- ) : activeTab === 'leaderboard' ? ( - /* Leaderboard Tab */ -
- + -

- 🏆 RecodeHive Contributors -

-

- Live rankings from RecodeHive GitHub Organization • Updated automatically -

-
- +
+

+ 🎁 Giveaway +

+

+ Participate in exclusive giveaways and win exciting prizes! +

- - - {/* Loading State */} - {isLoadingLeaderboard && ( - -
-

Loading leaderboard data from RecodeHive GitHub API...

-
- )} - - {/* Error State */} - {leaderboardError && !isLoadingLeaderboard && ( - -

⚠️ API Connection Issue

-

{leaderboardError}

-
-

This could be due to:

-
    -
  • GitHub API server is temporarily down
  • -
  • Network connectivity issues
  • -
  • GitHub API rate limiting
  • -
-

Please try refreshing in a moment!

-
- -
- )} - - {/* Leaderboard Data */} - {!isLoadingLeaderboard && !leaderboardError && leaderboardData.length > 0 && ( - -
-
- {leaderboardData.length} - Participants -
-
- {leaderboardData[0]?.score || 0} - Top Score -
-
- - {Math.round(leaderboardData.reduce((acc, user) => acc + (user.score || 0), 0) / leaderboardData.length)} - - Avg Score -
-
- -
- - )} + - {/* Empty State */} - {!isLoadingLeaderboard && !leaderboardError && leaderboardData.length === 0 && ( - -

📊 No data available

-

The leaderboard is empty. Check back later!

-
- )} + {/* Giveaway Stats Grid */} + +
+ + + + +
+
- ) : activeTab === 'giveaway' && ( - // ✅ Giveaway Section 🎁 - <> - -
-

- 🎁 Giveaway -

-

Participate in exclusive giveaways and win exciting prizes!

-
-
- - {/* 🎉 Giveaway Stats Grid */} - -
- - - - -
-
- - )}
@@ -895,4 +1352,4 @@ const Dashboard: React.FC = () => { ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard;