diff --git a/src/components/FloatingContributors/FloatingContributors.css b/src/components/FloatingContributors/FloatingContributors.css index 87a1f1d8..4194e600 100644 --- a/src/components/FloatingContributors/FloatingContributors.css +++ b/src/components/FloatingContributors/FloatingContributors.css @@ -9,14 +9,29 @@ width: calc(100vw - 48px); } +/* Header embedded version - relative positioning */ +.floating-contributors-container.header-embedded { + position: relative; + bottom: auto; + right: auto; + z-index: 1; + max-width: 480px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + margin: 0 auto; + pointer-events: auto; +} + .floating-contributors-card { background: rgba(15, 23, 42, 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(148, 163, 184, 0.2); - border-radius: 20px; - padding: 20px; - box-shadow: + border-radius: 24px; + padding: 24px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.1); @@ -25,23 +40,36 @@ overflow: hidden; color: white; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + min-width: 400px; } -/* Dark theme support */ +/* Header embedded version - larger size */ +.floating-contributors-container.header-embedded .floating-contributors-card { + min-width: 450px; + padding: 28px; + border-radius: 28px; +} + +/* Dark theme support - using website theme colors */ [data-theme='dark'] .floating-contributors-card { - background: rgba(30, 41, 59, 0.95); - border-color: rgba(148, 163, 184, 0.3); + background: rgba(26, 26, 26, 0.95); + border-color: rgba(45, 45, 45, 0.8); + color: #ffffff; + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 1px 0 rgba(255, 255, 255, 0.12); } -/* Light theme support */ +/* Light theme support - using website theme colors */ [data-theme='light'] .floating-contributors-card { - background: rgba(255, 255, 255, 0.95); - color: #1e293b; - border-color: rgba(148, 163, 184, 0.3); - box-shadow: - 0 20px 40px rgba(0, 0, 0, 0.1), + background: rgba(255, 255, 255, 0.98); + color: #1a202c; + border-color: rgba(0, 0, 0, 0.1); + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.05), - inset 0 1px 0 rgba(255, 255, 255, 0.8); + inset 0 1px 0 rgba(255, 255, 255, 0.9); } .floating-contributors-card::before { @@ -53,12 +81,17 @@ bottom: 0; background: linear-gradient( 135deg, - rgba(99, 102, 241, 0.1) 0%, - rgba(168, 85, 247, 0.05) 50%, - rgba(236, 72, 153, 0.1) 100% + rgba(102, 126, 234, 0.12) 0%, + rgba(118, 75, 162, 0.08) 50%, + rgba(37, 194, 160, 0.1) 100% ); pointer-events: none; - border-radius: 20px; + border-radius: 24px; +} + +/* Header embedded version gradient */ +.floating-contributors-container.header-embedded .floating-contributors-card::before { + border-radius: 28px; } /* Close button */ @@ -140,6 +173,14 @@ border-radius: 12px; margin-bottom: 16px; border: 1px solid rgba(255, 255, 255, 0.1); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; +} + +.floating-contributors-activity:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(102, 126, 234, 0.3); + transform: translateY(-1px); } [data-theme='light'] .floating-contributors-activity { @@ -147,6 +188,11 @@ border-color: rgba(0, 0, 0, 0.1); } +[data-theme='light'] .floating-contributors-activity:hover { + background: rgba(102, 126, 234, 0.05); + border-color: rgba(102, 126, 234, 0.2); +} + .activity-avatar-container { position: relative; flex-shrink: 0; @@ -156,9 +202,16 @@ width: 40px; height: 40px; border-radius: 50%; - border: 2px solid rgba(99, 102, 241, 0.5); + border: 2px solid rgba(102, 126, 234, 0.5); object-fit: cover; - transition: all 0.3s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; +} + +.activity-avatar:hover { + border-color: rgba(102, 126, 234, 0.8); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); + transform: scale(1.05); } .activity-status-indicator { @@ -223,21 +276,21 @@ display: flex; align-items: center; gap: 4px; - background: rgba(99, 102, 241, 0.2); + background: rgba(102, 126, 234, 0.2); color: #a5b4fc; - padding: 2px 8px; - border-radius: 12px; + padding: 3px 10px; + border-radius: 14px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; - border: 1px solid rgba(99, 102, 241, 0.3); + border: 1px solid rgba(102, 126, 234, 0.3); } [data-theme='light'] .activity-action-badge { - background: rgba(99, 102, 241, 0.1); - color: #6366f1; - border-color: rgba(99, 102, 241, 0.2); + background: rgba(102, 126, 234, 0.1); + color: #667eea; + border-color: rgba(102, 126, 234, 0.2); } .action-icon { @@ -274,17 +327,17 @@ } .contributors-count { - background: rgba(99, 102, 241, 0.2); + background: rgba(102, 126, 234, 0.2); color: #a5b4fc; - padding: 2px 8px; - border-radius: 8px; + padding: 3px 10px; + border-radius: 10px; font-size: 11px; font-weight: 700; } [data-theme='light'] .contributors-count { - background: rgba(99, 102, 241, 0.1); - color: #6366f1; + background: rgba(102, 126, 234, 0.1); + color: #667eea; } .contributors-avatars { @@ -306,13 +359,14 @@ border: 2px solid rgba(255, 255, 255, 0.2); object-fit: cover; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - filter: grayscale(0.2); + filter: grayscale(0.1); } .contributor-avatar:hover { filter: grayscale(0); - border-color: rgba(99, 102, 241, 0.8); - box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); + border-color: rgba(102, 126, 234, 0.8); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); + transform: scale(1.1); } [data-theme='light'] .contributor-avatar { @@ -320,14 +374,15 @@ } [data-theme='light'] .contributor-avatar:hover { - border-color: rgba(99, 102, 241, 0.8); + border-color: rgba(102, 126, 234, 0.8); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); } .contributor-tooltip { position: absolute; bottom: 100%; left: 50%; - transform: translateX(-50%); + transform: translateX(-50%) translateY(4px); background: rgba(0, 0, 0, 0.9); color: white; padding: 8px 12px; @@ -336,10 +391,11 @@ white-space: nowrap; opacity: 0; visibility: hidden; - transition: all 0.3s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); margin-bottom: 8px; z-index: 20; pointer-events: none; + backdrop-filter: blur(8px); } .contributor-tooltip::after { @@ -355,7 +411,7 @@ .contributor-avatar-wrapper:hover .contributor-tooltip { opacity: 1; visibility: visible; - transform: translateX(-50%) translateY(-4px); + transform: translateX(-50%) translateY(-8px); } .tooltip-name { @@ -380,6 +436,15 @@ font-size: 11px; font-weight: 600; color: rgba(255, 255, 255, 0.7); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; +} + +.contributors-more:hover { + background: rgba(102, 126, 234, 0.2); + border-color: rgba(102, 126, 234, 0.5); + color: rgba(255, 255, 255, 0.9); + transform: scale(1.1); } [data-theme='light'] .contributors-more { @@ -388,6 +453,12 @@ color: rgba(30, 41, 59, 0.7); } +[data-theme='light'] .contributors-more:hover { + background: rgba(102, 126, 234, 0.1); + border-color: rgba(102, 126, 234, 0.3); + color: rgba(30, 41, 59, 0.9); +} + /* Footer */ .floating-contributors-footer { border-top: 1px solid rgba(255, 255, 255, 0.1); @@ -402,20 +473,27 @@ display: flex; align-items: center; justify-content: center; - gap: 8px; + gap: 10px; width: 100%; - padding: 12px 16px; - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + padding: 14px 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-decoration: none; - border-radius: 12px; - font-size: 14px; + border-radius: 16px; + font-size: 15px; font-weight: 600; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; } +/* Header embedded version CTA */ +.floating-contributors-container.header-embedded .contributors-cta { + padding: 16px 24px; + font-size: 16px; + border-radius: 18px; +} + .contributors-cta::before { content: ''; position: absolute; @@ -432,10 +510,12 @@ } .contributors-cta:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4); + transform: translateY(-3px) scale(1.02); + box-shadow: 0 12px 30px rgba(102, 126, 234, 0.4); color: white; text-decoration: none; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + filter: brightness(1.1); } .cta-icon { @@ -451,6 +531,13 @@ transform: translateX(4px); } +/* Add subtle glow effect for better visual feedback */ +.contributors-cta:hover { + box-shadow: + 0 12px 30px rgba(102, 126, 234, 0.4), + 0 0 20px rgba(102, 126, 234, 0.2); +} + /* Floating particles */ .floating-particles { position: absolute; @@ -480,6 +567,18 @@ background: rgba(236, 72, 153, 0.6); } +/* Large screens - limit max width for header embedded */ +@media (min-width: 1200px) { + .floating-contributors-container.header-embedded { + max-width: 520px; + } + + .floating-contributors-container.header-embedded .floating-contributors-card { + min-width: 500px; + padding: 32px; + } +} + /* Responsive design */ @media (max-width: 768px) { .floating-contributors-container { @@ -489,6 +588,17 @@ width: calc(100vw - 32px); } + /* Header embedded responsive adjustments */ + .floating-contributors-container.header-embedded { + max-width: 400px; + width: 100%; + } + + .floating-contributors-container.header-embedded .floating-contributors-card { + min-width: 380px; + padding: 22px; + } + .floating-contributors-card { padding: 16px; border-radius: 16px; @@ -536,6 +646,17 @@ width: calc(100vw - 24px); } + /* Header embedded responsive adjustments for small screens */ + .floating-contributors-container.header-embedded { + max-width: 340px; + width: 100%; + } + + .floating-contributors-container.header-embedded .floating-contributors-card { + min-width: 320px; + padding: 18px; + } + .floating-contributors-card { padding: 14px; border-radius: 14px; @@ -569,6 +690,22 @@ } } +/* Extra small screens */ +@media (max-width: 350px) { + .floating-contributors-container.header-embedded { + max-width: 280px; + } + + .floating-contributors-container.header-embedded .floating-contributors-card { + min-width: 260px; + padding: 16px; + } + + .floating-contributors-card { + padding: 12px; + } +} + /* Animation enhancements */ @keyframes float { 0%, 100% { @@ -592,6 +729,11 @@ animation: float 6s ease-in-out infinite; } +/* Disable floating animation when embedded in header */ +.floating-contributors-container.header-embedded .floating-contributors-card { + animation: none; +} + /* Accessibility improvements */ @media (prefers-reduced-motion: reduce) { .floating-contributors-card, @@ -658,22 +800,44 @@ } /* Hover effects for better interactivity */ +.floating-contributors-card { + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + .floating-contributors-card:hover { - transform: translateY(-4px); - box-shadow: - 0 25px 50px rgba(0, 0, 0, 0.4), - 0 0 0 1px rgba(255, 255, 255, 0.1), - inset 0 1px 0 rgba(255, 255, 255, 0.2); + transform: translateY(-2px) scale(1.02); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.3), + 0 0 0 1px rgba(255, 255, 255, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.25); } [data-theme='light'] .floating-contributors-card:hover { - box-shadow: - 0 25px 50px rgba(0, 0, 0, 0.15), - 0 0 0 1px rgba(0, 0, 0, 0.1), + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.12), + 0 0 0 1px rgba(102, 126, 234, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.9); } -/* Smooth transitions for theme switching */ +/* Subtle hover effects for header embedded version */ +.floating-contributors-container.header-embedded .floating-contributors-card:hover { + transform: scale(1.01); + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.35), + 0 0 0 1px rgba(102, 126, 234, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.15); + border-color: rgba(102, 126, 234, 0.3); +} + +[data-theme='light'] .floating-contributors-container.header-embedded .floating-contributors-card:hover { + box-shadow: + 0 30px 60px rgba(0, 0, 0, 0.1), + 0 0 0 1px rgba(102, 126, 234, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.95); + border-color: rgba(102, 126, 234, 0.2); +} + +/* Smooth transitions for all interactive elements */ .floating-contributors-card, .floating-contributors-activity, .contributors-cta, @@ -685,6 +849,17 @@ .activity-action-badge, .contributors-count, .contributors-more, -.floating-contributors-close { - transition: all 0.3s ease; +.floating-contributors-close, +.contributor-avatar, +.activity-avatar, +.contributor-avatar-wrapper { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Enhanced hover transitions */ +.floating-contributors-card, +.contributors-cta, +.contributor-avatar, +.activity-avatar { + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } \ No newline at end of file diff --git a/src/components/FloatingContributors/index.tsx b/src/components/FloatingContributors/index.tsx index cbfad002..8ae8a366 100644 --- a/src/components/FloatingContributors/index.tsx +++ b/src/components/FloatingContributors/index.tsx @@ -17,18 +17,81 @@ interface ContributorActivity { timeAgo: string; } -const FloatingContributors: React.FC = () => { +interface FloatingContributorsProps { + headerEmbedded?: boolean; +} + +const FloatingContributors: React.FC = ({ headerEmbedded = false }) => { const [contributors, setContributors] = useState([]); const [activities, setActivities] = useState([]); const [currentActivityIndex, setCurrentActivityIndex] = useState(0); const [isVisible, setIsVisible] = useState(true); const [loading, setLoading] = useState(true); - // Fetch contributors from RecodeHive organization + + + // Initialize with fallback data immediately to ensure toaster always appears useEffect(() => { + // Set fallback data immediately + const initializeFallbackData = () => { + const demoContributors: Contributor[] = [ + { + id: '1', + login: 'sanjay-kv', + avatar_url: 'https://avatars.githubusercontent.com/u/30715153?v=4', + contributions: 127, + html_url: 'https://github.com/sanjay-kv', + lastActivity: '2 minutes ago', + }, + { + id: '2', + login: 'recodehive-team', + avatar_url: 'https://avatars.githubusercontent.com/u/150000000?v=4', + contributions: 89, + html_url: 'https://github.com/recodehive', + lastActivity: '5 minutes ago', + }, + { + id: '3', + login: 'contributor-1', + avatar_url: 'https://avatars.githubusercontent.com/u/1?v=4', + contributions: 64, + html_url: 'https://github.com/contributor-1', + lastActivity: '12 minutes ago', + }, + { + id: '4', + login: 'contributor-2', + avatar_url: 'https://avatars.githubusercontent.com/u/2?v=4', + contributions: 45, + html_url: 'https://github.com/contributor-2', + lastActivity: '1 hour ago', + }, + { + id: '5', + login: 'contributor-3', + avatar_url: 'https://avatars.githubusercontent.com/u/3?v=4', + contributions: 32, + html_url: 'https://github.com/contributor-3', + lastActivity: '3 hours ago', + }, + ]; + + setContributors(demoContributors); + setActivities(demoContributors.map(contributor => ({ + contributor, + action: getRandomAction(), + timeAgo: contributor.lastActivity, + }))); + setLoading(false); + }; + + // Initialize with fallback data immediately + initializeFallbackData(); + + // Then try to fetch real data const fetchContributors = async () => { try { - setLoading(true); // Fetch repositories from RecodeHive organization const reposResponse = await fetch('https://api.github.com/orgs/recodehive/repos?type=public&per_page=10&sort=updated'); @@ -84,57 +147,24 @@ const FloatingContributors: React.FC = () => { .sort((a, b) => b.contributions - a.contributions) .slice(0, 12); // Top 12 contributors - setContributors(contributorsList); - - // Generate activities - const generatedActivities: ContributorActivity[] = contributorsList.map(contributor => ({ - contributor, - action: getRandomAction(), - timeAgo: generateRandomTimeAgo(), - })); - - setActivities(generatedActivities); - setLoading(false); + // Only update if we got real data + if (contributorsList.length > 0) { + setContributors(contributorsList); + + // Generate activities + const generatedActivities: ContributorActivity[] = contributorsList.map(contributor => ({ + contributor, + action: getRandomAction(), + timeAgo: generateRandomTimeAgo(), + })); + + setActivities(generatedActivities); + } } catch (error) { // Silently handle GitHub API errors (rate limits, etc.) console.warn('Using fallback contributor data due to GitHub API limitations'); - - // Fallback demo data - const demoContributors: Contributor[] = [ - { - id: '1', - login: 'sanjay-kv', - avatar_url: 'https://avatars.githubusercontent.com/u/30715153?v=4', - contributions: 250, - html_url: 'https://github.com/sanjay-kv', - lastActivity: '2 minutes ago', - }, - { - id: '2', - login: 'vansh-codes', - avatar_url: 'https://avatars.githubusercontent.com/u/114163734?v=4', - contributions: 180, - html_url: 'https://github.com/vansh-codes', - lastActivity: '5 minutes ago', - }, - { - id: '3', - login: 'Hemu21', - avatar_url: 'https://avatars.githubusercontent.com/u/106808387?v=4', - contributions: 120, - html_url: 'https://github.com/Hemu21', - lastActivity: '1 hour ago', - }, - ]; - - setContributors(demoContributors); - setActivities(demoContributors.map(contributor => ({ - contributor, - action: getRandomAction(), - timeAgo: contributor.lastActivity, - }))); - setLoading(false); + // Fallback data is already initialized, so no need to set it again } }; @@ -152,14 +182,6 @@ const FloatingContributors: React.FC = () => { return () => clearInterval(interval); }, [activities.length]); - // Auto-hide after some time - useEffect(() => { - const timer = setTimeout(() => { - setIsVisible(false); - }, 30000); // Hide after 30 seconds - - return () => clearTimeout(timer); - }, []); const generateRandomTimeAgo = (): string => { const timeOptions = [ @@ -211,26 +233,26 @@ const FloatingContributors: React.FC = () => { {isVisible && ( {/* Main floating card */} void; - }; - } -} +import FloatingContributors from "../FloatingContributors"; const HeaderContent = () => { return ( @@ -87,30 +79,11 @@ const HeaderContent = () => { ); }; -const HeaderImage = () => { - const imgRef = useRef(null); - - useEffect(() => { - if (imgRef.current) { - VanillaTilt.init(imgRef.current, { - max: 25, - speed: 50, - glare: true, - "max-glare": 0.5, - }); - } - - return () => { - if (imgRef.current?.vanillaTilt) { - imgRef.current.vanillaTilt.destroy(); - } - }; - }, []); - +const HeaderToaster = () => { return ( { delay: 0.3, }} className="chh__header-image" + style={{ + position: 'relative', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: '400px', + width: '100%' + }} > - Animated illustration of Recode Hive learning + {/* Render the FloatingContributors component as the header toaster */} +
+ +
); }; @@ -136,7 +117,7 @@ const Header: React.FC = () => {
- +
); 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; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index cc8d5e7b..99e4793e 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,7 +15,6 @@ import { TestimonialCarousel } from "../components/testimonials/TestimonialCarou import { CommunityStatsProvider } from "../lib/statsProvider"; import { LandingCommunity } from "../components/Community"; import FAQs from "../components/faqs/faqs"; -import FloatingContributors from "../components/FloatingContributors"; export default function Home(): ReactNode { const { siteConfig } = useDocusaurusContext(); @@ -77,9 +76,6 @@ export default function Home(): ReactNode { - {/* Floating Contributors Showcase */} - -