From 875308e7d6ed9988918c99a44991c7320fdb8edc Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 12:00:53 +0530 Subject: [PATCH 01/11] Added Leadrboard files --- docusaurus.config.ts | 2 + package.json | 2 +- .../dashboard/LeaderBoard/leaderboard.css | 624 ++++++++++++++++++ .../dashboard/LeaderBoard/leaderboard.tsx | 496 ++++++++++++++ 4 files changed, 1123 insertions(+), 1 deletion(-) create mode 100644 src/pages/dashboard/LeaderBoard/leaderboard.css create mode 100644 src/pages/dashboard/LeaderBoard/leaderboard.tsx diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 33815a3d..22581bdb 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -3,6 +3,8 @@ import type { Config } from "@docusaurus/types"; import type * as Preset from "@docusaurus/preset-classic"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; +import * as dotenv from "dotenv"; +dotenv.config(); // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) diff --git a/package.json b/package.json index 20b30ee6..16603219 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "dotenv": "^16.5.0", + "dotenv": "^16.6.1", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "firebase": "^9.22.2", diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.css b/src/pages/dashboard/LeaderBoard/leaderboard.css new file mode 100644 index 00000000..2173d5da --- /dev/null +++ b/src/pages/dashboard/LeaderBoard/leaderboard.css @@ -0,0 +1,624 @@ +/* LeaderBoard.css */ + +.leaderboard-container { + min-height: 100vh; + padding: 32px 8px; + transition: background-color 0.3s ease; +} + +.leaderboard-container.light { + background: #f6f6f6; +} + +.leaderboard-container.dark { + background: #23272f; +} + +.leaderboard-content { + max-width: 1100px; + margin: 0 auto; +} + +/* Header */ +.header { + text-align: center; + margin-bottom: 48px; + padding: 0 8px; +} + +.title { + font-size: 32px; + font-weight: 700; + margin-bottom: 12px; + color: #6366f1; +} + +.subtitle { + font-size: 17px; + max-width: 600px; + margin: 0 auto; + line-height: 1.6; +} + +.light .subtitle { + color: #555; +} + +.dark .subtitle { + color: #b3b3b3; +} + +/* Search */ +.search-container { + display: flex; + justify-content: center; + margin-bottom: 24px; +} + +.search-wrapper { + position: relative; + width: 100%; + max-width: 320px; +} + +.search-input { + width: 100%; + padding: 8px 16px 8px 36px; + border-radius: 8px; + font-size: 15px; + outline: none; + transition: border-color 0.3s ease, background-color 0.3s ease; +} + +.light .search-input { + border: 1px solid #ddd; + background: #fff; + color: #222; +} + +.dark .search-input { + border: 1px solid #444; + background: #23272f; + color: #fff; +} + +.search-icon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); +} + +.light .search-icon { + color: #aaa; +} + +.dark .search-icon { + color: #b3b3b3; +} + +/* Stats Grid */ +.stats-grid { + display: flex; + gap: 18px; + margin-bottom: 48px; + flex-wrap: wrap; +} + +.stat-card { + flex: 1; + min-width: 220px; + padding: 24px; + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + transition: background 0.3s ease, border-color 0.3s ease; +} + +.light .stat-card { + background: linear-gradient(135deg, #e0e7ff, #f3f4f6); + border: 1px solid #eee; +} + +.dark .stat-card { + background: linear-gradient(135deg, #23272f, #1a1d23); + border: 1px solid #444; +} + +.stat-content { + display: flex; + align-items: center; +} + +.stat-icon { + padding: 12px; + border-radius: 12px; + font-size: 22px; + margin-right: 16px; +} + +.stat-icon.users { + background: rgba(59, 130, 246, 0.2); + color: #2563eb; +} + +.dark .stat-icon.users { + background: rgba(59, 130, 246, 0.2); + color: #60a5fa; +} + +.stat-icon.prs { + background: rgba(16, 185, 129, 0.2); + color: #059669; +} + +.dark .stat-icon.prs { + background: rgba(16, 185, 129, 0.2); + color: #34d399; +} + +.stat-icon.points { + background: rgba(139, 92, 246, 0.2); + color: #7c3aed; +} + +.dark .stat-icon.points { + background: rgba(139, 92, 246, 0.2); + color: #a78bfa; +} + +.stat-label { + font-size: 14px; + margin: 0; +} + +.light .stat-label { + color: #555; +} + +.dark .stat-label { + color: #b3b3b3; +} + +.stat-value { + font-size: 22px; + font-weight: 700; + margin: 0; +} + +.light .stat-value { + color: #222; +} + +.dark .stat-value { + color: #fff; +} + +/* Skeleton Loader */ +.skeleton-loader { + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + overflow: hidden; + margin-bottom: 24px; +} + +.skeleton-loader.light { + background: #fff; + border: 1px solid #eee; +} + +.skeleton-loader.dark { + background: #23272f; + border: 1px solid #444; +} + +.skeleton-header { + display: flex; + justify-content: space-between; + padding: 16px; + font-size: 15px; + font-weight: 500; + background: #f6f6f6; + border-bottom: 1px solid #eee; +} + +.skeleton-row { + display: flex; + align-items: center; + padding: 16px; + gap: 16px; +} + +.skeleton-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: #e5e7eb; + animation: pulse 1.5s infinite; +} + +.skeleton-avatar.large { + width: 40px; + height: 40px; +} + +.skeleton-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; +} + +.skeleton-bar { + height: 16px; + width: 96px; + border-radius: 8px; + background: #e5e7eb; + animation: pulse 1.5s infinite; +} + +.skeleton-badges { + display: flex; + gap: 8px; +} + +.skeleton-badge { + width: 48px; + height: 24px; + border-radius: 9999px; + background: #e5e7eb; + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.5; } + 100% { opacity: 1; } +} + +/* Contributors Container */ +.contributors-container { + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + overflow: hidden; + margin: 0 8px; +} + +.light .contributors-container { + border: 1px solid #eee; +} + +.dark .contributors-container { + border: 1px solid #444; +} + +/* Contributors List */ +.contributors-list { + /* No additional styles needed */ +} + +.no-contributors { + text-align: center; + padding: 32px 0; +} + +.light .no-contributors { + color: #555; +} + +.dark .no-contributors { + color: #b3b3b3; +} + +.contributor-row { + display: flex; + align-items: center; + padding: 16px 24px; + transition: background-color 0.3s ease; +} + +.light .contributor-row { + border-bottom: 1px solid #eee; +} + +.dark .contributor-row { + border-bottom: 1px solid #444; +} + +.light .contributor-row.even { + background: #fff; +} + +.light .contributor-row.odd { + background: #f6f6f6; +} + +.dark .contributor-row.even { + background: #23272f; +} + +.dark .contributor-row.odd { + background: #1a1d23; +} + +/* Rank Badge */ +.rank-badge { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 500; + margin-right: 16px; +} + +.rank-badge.top-1 { + background: rgba(253, 224, 71, 0.2); + color: #ca8a04; +} + +.dark .rank-badge.top-1 { + background: rgba(253, 224, 71, 0.2); + color: #fde047; +} + +.rank-badge.top-2 { + background: rgba(156, 163, 175, 0.2); + color: #6b7280; +} + +.dark .rank-badge.top-2 { + background: rgba(156, 163, 175, 0.2); + color: #d1d5db; +} + +.rank-badge.top-3 { + background: rgba(251, 191, 36, 0.2); + color: #f59e42; +} + +.dark .rank-badge.top-3 { + background: rgba(251, 191, 36, 0.2); + color: #fbbf24; +} + +.rank-badge.regular { + background: #e5e7eb; + color: #555; +} + +.dark .rank-badge.regular { + background: #23272f; + color: #b3b3b3; +} + +/* Avatar */ +.avatar { + width: 40px; + height: 40px; + border-radius: 50%; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); + margin-right: 16px; +} + +.light .avatar { + border: 2px solid #fff; +} + +.dark .avatar { + border: 2px solid #444; +} + +/* User Info */ +.user-info { + flex: 1; +} + +.username-link { + font-weight: 500; + font-size: 15px; + text-decoration: none; + margin-bottom: 4px; + display: block; + transition: color 0.3s ease; +} + +.light .username-link { + color: #222; +} + +.dark .username-link { + color: #fff; +} + +.username-link:hover { + text-decoration: underline; +} + +.contributions-link { + font-size: 13px; + text-decoration: none; + margin-bottom: 4px; + display: block; + transition: color 0.3s ease; +} + +.light .contributions-link { + color: #555; +} + +.dark .contributions-link { + color: #b3b3b3; +} + +.contributions-link:hover { + text-decoration: underline; +} + +.badges-container { + display: flex; + gap: 8px; + margin-top: 8px; +} + +/* Badge */ +.badge { + display: flex; + align-items: center; + padding: 4px 12px; + border-radius: 9999px; + font-size: 13px; + font-weight: 500; +} + +/* Pagination */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + padding: 16px 0; +} + +.light .pagination { + border-top: 1px solid #eee; +} + +.dark .pagination { + border-top: 1px solid #444; +} + +.pagination-btn, +.page-btn { + padding: 8px 16px; + border-radius: 8px; + border: none; + font-weight: 500; + cursor: pointer; + margin: 0 4px; + transition: all 0.3s ease; +} + +.light .pagination-btn, +.light .page-btn { + background: #e0e7ff; + color: #3730a3; +} + +.dark .pagination-btn, +.dark .page-btn { + background: #23272f; + color: #a78bfa; +} + +.pagination-btn:hover:not(.disabled), +.page-btn:hover:not(.active) { + opacity: 0.8; +} + +.pagination-btn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.page-btn.active { + background: #2563eb; + color: #fff; +} + +.dark .page-btn.active { + background: #3b82f6; + color: #fff; +} + +.page-numbers { + display: flex; + gap: 8px; +} + +/* CTA Footer */ +.cta-footer { + padding: 16px 24px; + text-align: center; +} + +.light .cta-footer { + border-top: 1px solid #eee; + background: #f6f6f6; +} + +.dark .cta-footer { + border-top: 1px solid #444; + background: #23272f; +} + +.cta-text { + font-size: 14px; + margin-bottom: 12px; +} + +.light .cta-text { + color: #555; +} + +.dark .cta-text { + color: #b3b3b3; +} + +.cta-button { + display: inline-flex; + align-items: center; + padding: 8px 16px; + background: #6366f1; + color: #fff; + font-size: 15px; + font-weight: 500; + border-radius: 8px; + text-decoration: none; + transition: background-color 0.3s ease; +} + +.dark .cta-button { + background: #3b82f6; +} + +.cta-button:hover { + opacity: 0.9; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .leaderboard-container { + padding: 16px 4px; + } + + .title { + font-size: 24px; + } + + .subtitle { + font-size: 15px; + } + + .stats-grid { + flex-direction: column; + gap: 12px; + } + + .stat-card { + min-width: auto; + } + + .contributor-row { + padding: 12px 16px; + } + + .username-link { + font-size: 14px; + } + + .contributions-link { + font-size: 12px; + } +} \ No newline at end of file diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx new file mode 100644 index 00000000..cb5d15e4 --- /dev/null +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -0,0 +1,496 @@ +import React, { JSX, useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { FaTrophy, FaStar, FaCode, FaUsers, FaGithub, FaSearch, FaBook, FaBookOpen } from "react-icons/fa"; +import { ChevronRight, ChevronLeft } from "lucide-react"; +// import { useTheme } from "../ThemeContext"; // Theme context +// import { useNavigate } from 'react-router-dom' + +const GITHUB_REPO = "recodehive/recode-website"; // Change to your repo +const token = process.env.GIT_TOKEN; // Add your GitHub token here or use environment variables + + + +// Points configuration for different PR levels +const POINTS: Record = { + "level-1": 3, // Easy + "level-2": 7, // Medium + "level-3": 10, // Hard/Feature +}; + +// Types +interface Contributor { + username: string; + avatar: string; + profile: string; + points: number; + prs: number; +} + +interface Stats { + flooredTotalPRs: number; + totalContributors: number; + flooredTotalPoints: number; +} + +interface BadgeProps { + count: number; + label: string; + color: { background: string; color: string }; +} + +interface SkeletonLoaderProps { + isDark: boolean; +} + +// Badge component for PR counts +const Badge: React.FC = ({ count, label, color }) => ( +
+ + + {count} {label} + + +
+); + +// Skeleton Loader Component +const SkeletonLoader: React.FC = ({ isDark }) => ( +
+
+
#
+
Contributor
+
Contributions
+
+
+ {[...Array(10)].map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ +
+); + +export default function LeaderBoard(): JSX.Element { + const [contributors, setContributors] = useState([]); + const [loading, setLoading] = useState(true); + const [stats, setStats] = useState({ + flooredTotalPRs: 0, + totalContributors: 0, + flooredTotalPoints: 0, + }); + const [searchTerm, setSearchTerm] = useState(""); + const { theme } = useTheme(); + const isDark = theme === 'dark'; + const navigate = useNavigate() + + useEffect(() => { + const fetchContributorsWithPoints = async (): Promise => { + try { + let contributorsMap: Record = {}; + let page = 1; + const MAX_PAGES = 10; + let keepFetching = true; + + while (keepFetching && page <= MAX_PAGES) { + const res = await fetch( + `https://api.github.com/repos/${GITHUB_REPO}/pulls?state=closed&per_page=100&page=${page}`, + { + headers: { + Accept: "application/vnd.github.v3+json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + } + ); + const prs = await res.json(); + + if (!Array.isArray(prs) || prs.length === 0 || (prs.length === 1 && prs[0].message)) { + keepFetching = false; + break; + } + + prs.forEach((pr: any) => { + if (!pr.merged_at) return; + const labels = pr.labels.map((l: any) => l.name.toLowerCase()); + if (!labels.includes("gssoc'25")) return; + + const author = pr.user.login; + let points = 0; + labels.forEach((label: string) => { + if (POINTS[label]) points += POINTS[label]; + }); + + if (!contributorsMap[author]) { + contributorsMap[author] = { + username: author, + avatar: pr.user.avatar_url, + profile: pr.user.html_url, + points: 0, + prs: 0, + }; + } + + contributorsMap[author].points += points; + contributorsMap[author].prs += 1; + }); + + page += 1; + } + + setContributors( + Object.values(contributorsMap).sort((a, b) => b.points - a.points) + ); + } catch (error) { + console.error("Error fetching contributors:", error); + } finally { + setLoading(false); + } + }; + + fetchContributorsWithPoints(); + }, []); + + useEffect(() => { + if (contributors.length > 0) { + const totalPRs = contributors.reduce((sum, c) => sum + Number(c.prs), 0); + const totalPoints = contributors.reduce( + (sum, c) => sum + Number(c.points), + 0 + ); + + const flooredTotalPRs = Math.floor(totalPRs / 10) * 10; + const flooredTotalPoints = Math.floor(totalPoints / 10) * 10; + const flooredContributorsCount = + Math.floor(contributors.length / 10) * 10; + + setStats({ + flooredTotalPRs, + totalContributors: flooredContributorsCount, + flooredTotalPoints, + }); + } + }, [contributors]); + + // Filter contributors by search term (username) + const filteredContributors = contributors.filter((c) => + c.username.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Pagination variables and states + const PAGE_SIZE = 10; + const [currentPage, setCurrentPage] = useState(1); + + // Calculate which contributors to show on current page + const indexOfLast = currentPage * PAGE_SIZE; + const indexOfFirst = indexOfLast - PAGE_SIZE; + const currentContributors = filteredContributors.slice(indexOfFirst, indexOfLast); + + const totalPages = Math.ceil(filteredContributors.length / PAGE_SIZE); + + // Gradient backgrounds from Footer.jsx theme + const gradientBg = isDark + ? "bg-gradient-to-br from-dark-bg-primary to-dark-bg-secondary" + : "bg-gradient-to-br from-blue-50 to-indigo-50 backdrop-blur-xl"; + + const cardBg = isDark + ? "bg-dark-bg-tertiary border-dark-border" + : "bg-light-bg-tertiary border-light-border"; + + // Button styles + const buttonStyle: React.CSSProperties = { + padding: '8px 16px', + borderRadius: '8px', + background: isDark ? '#23272f' : '#e0e7ff', + color: isDark ? '#a78bfa' : '#3730a3', + border: 'none', + fontWeight: '500', + cursor: 'pointer', + margin: '0 4px', + opacity: 1, + transition: 'opacity 0.2s', + }; + const buttonActiveStyle: React.CSSProperties = { + ...buttonStyle, + background: isDark ? '#3b82f6' : '#2563eb', + color: '#fff', + }; + const buttonDisabledStyle: React.CSSProperties = { + ...buttonStyle, + opacity: 0.5, + cursor: 'not-allowed', + }; + + return ( +
+
+ {/* Header */} + +

+ GSSoC'25 Leaderboard +

+

+ Celebrating the amazing contributions from GSSoC'25 participants. Join us in building something incredible together! +

+
+ + {/* Search Bar */} +
+
+ { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + style={{ + width: '100%', + padding: '8px 16px 8px 36px', + borderRadius: 8, + border: `1px solid ${isDark ? '#444' : '#ddd'}`, + background: isDark ? '#23272f' : '#fff', + color: isDark ? '#fff' : '#222', + fontSize: 15, + outline: 'none', + }} + /> + +
+
+ + {/* Stats Cards */} +
+
+
+
+ +
+
+

Contributors

+

{loading ? '...' : stats.totalContributors}+

+
+
+
+
+
+
+ +
+
+

Pull Requests

+

{loading ? '...' : stats.flooredTotalPRs}+

+
+
+
+
+
+
+ +
+
+

Total Points

+

{loading ? '...' : stats.flooredTotalPoints}+

+
+
+
+
+ + {loading ? ( + + ) : ( +
+ {/* Contributors List */} +
+ {currentContributors.length === 0 ? ( +
+ No contributors found. +
+ ) : ( + currentContributors.map((contributor, index) => ( + + {/* Rank Badge */} +
+ {indexOfFirst + index + 1} +
+ {/* Avatar */} + {contributor.username} + {/* User Info */} + +
+ )) + )} +
+ + {/* Pagination Controls */} +
+ +
+ {Array.from( + { length: Math.ceil(filteredContributors.length / PAGE_SIZE) }, + (_, i) => ( + + ) + )} +
+ +
+ + {/* CTA Footer */} +
+

+ Want to see your name here? Join GSSoC'25 and start contributing! +

+ + Join GSSoC'25 + +
+
+ )} +
+
+ ); +} \ No newline at end of file From 0de704221945cd6b1e5b3b3ad94f90e7b468fd4d Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 13:44:46 +0530 Subject: [PATCH 02/11] somewhat fixed --- docusaurus.config.ts | 131 +-- .../dashboard/LeaderBoard/leaderboard.tsx | 864 +++++++------- src/pages/dashboard/index.tsx | 1040 ++++------------- 3 files changed, 749 insertions(+), 1286 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 22581bdb..96281b9a 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -8,12 +8,10 @@ dotenv.config(); // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) - const config: Config = { title: "Recode Hive", tagline: "Dinosaurs are cool", favicon: "img/favicon.ico", - url: "https://recodehive.com", baseUrl: "/", @@ -27,18 +25,18 @@ const config: Config = { // Google Analytics and Theme Scripts scripts: [ { - src: '/instant-theme.js', + src: "/instant-theme.js", async: false, }, { - src: 'https://www.googletagmanager.com/gtag/js?id=G-W02Z2VJYCR', + src: "https://www.googletagmanager.com/gtag/js?id=G-W02Z2VJYCR", async: true, }, { - src: '/gtag-init.js', + src: "/gtag-init.js", }, { - src: '/pinterest-init.js', + src: "/pinterest-init.js", }, ], @@ -49,14 +47,14 @@ const config: Config = { presets: [ [ - 'classic', + "classic", { docs: { - path: "docs", + path: "docs", routeBasePath: "docs", sidebarPath: require.resolve("./sidebars.ts"), editUrl: ({ docPath }) => - `https://github.com/recodehive/recode-website/tree/main/docs/${docPath}`, + `https://github.com/recodehive/recode-website/tree/main/docs/${docPath}`, }, blog: { showReadingTime: true, @@ -64,8 +62,7 @@ const config: Config = { type: ["rss", "atom"], xslt: true, }, - editUrl: - "https://github.com/recodehive/recode-website/tree/main", + editUrl: "https://github.com/recodehive/recode-website/tree/main", onInlineTags: "warn", onInlineAuthors: "warn", onUntruncatedBlogPosts: "warn", @@ -74,7 +71,7 @@ const config: Config = { customCss: require.resolve("./src/css/custom.css"), }, gtag: { - trackingID: 'G-W02Z2VJYCR', + trackingID: "G-W02Z2VJYCR", anonymizeIP: false, }, } satisfies Preset.Options, @@ -84,14 +81,13 @@ const config: Config = { themeConfig: { image: "img/docusaurus-social-card.jpg", colorMode: { - defaultMode: 'light', + defaultMode: "light", disableSwitch: false, respectPrefersColorScheme: false, // Let users manually control theme }, navbar: { - title:"Recode Hive", + title: "Recode Hive", logo: { - alt: "RecodeHive Logo", src: "img/logo.png", }, @@ -104,14 +100,14 @@ const config: Config = { { type: "html", value: `
- Tutorials -
- SQL - Python - GitHub - Nextjs -
-
`, + Tutorials +
+ SQL + Python + GitHub + Nextjs +
+
`, }, { type: "html", @@ -120,13 +116,13 @@ const config: Config = { { type: "html", value: `
- Courses -
- git - Postman - Google -
-
`, + Courses +
+ git + Postman + Google +
+
`, }, { type: "html", @@ -135,12 +131,12 @@ const config: Config = { { type: "html", value: ``, + Interview Prep + +
`, }, ], }, @@ -171,7 +167,7 @@ const config: Config = { { label: "🎖️ GitHub Badges", to: "/badges/github-badges/", - }, + }, ], }, { @@ -215,47 +211,10 @@ const config: Config = { position: "right", value: '
', }, - // { - // type: "dropdown", - // html: '🏷️ Tags', - // position: "left", - // items: [ - // { - // label: "🏷️ Tutorial Tags 📚", - // to: "/docs/tags/", - // activeBaseRegex: "/docs/tags/", - // }, - // { - // label: "🏷️ Courses Tags 🎓", - // to: "/courses/tags/", - // activeBaseRegex: "/courses/tags/", - // }, - // { - // label: "🏷️ DSA Tags 🧠", - // to: "/dsa/tags/", - // activeBaseRegex: "/dsa/tags/", - // }, - // ], - // }, - - // { - // href: "https://github.com/codeharborhub/codeharborhub", - // position: "right", - // className: "header-github-link", - // "aria-label": "GitHub repository", - // }, - // { - // href: "https://www.codeharborhub.live/register", - // position: "right", - // className: "header-signup-link", - // "aria-label": "Auth", - // label: "Auth", - // }, ], - // hideOnScroll: true, }, footer: { - style: 'dark', + style: "dark", links: [], copyright: `Copyright © ${new Date().getFullYear()} recodehive. Built with Docusaurus.`, }, @@ -296,22 +255,12 @@ const config: Config = { disableInDev: false, }, ], - // Commented out to use TSX-based community page instead - // [ - // "@docusaurus/plugin-content-docs", - // { - // id: "community", - // path: "community", - // routeBasePath: "community", - // sidebarPath: require.resolve("./sidebarsCommunity.js"), - // remarkPlugins: [remarkMath], - // rehypePlugins: [rehypeKatex], - // showLastUpdateAuthor: true, - // showLastUpdateTime: true, - // }, - // ], ], - // scripts: [], + + // ✅ Add this customFields object to expose the token to the client-side + customFields: { + gitToken: process.env.DOCUSAURUS_GIT_TOKEN, + }, }; -export default config; +export default config; \ No newline at end of file diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx index cb5d15e4..53d151c5 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -1,14 +1,20 @@ import React, { JSX, useEffect, useState } from "react"; import { motion } from "framer-motion"; -import { FaTrophy, FaStar, FaCode, FaUsers, FaGithub, FaSearch, FaBook, FaBookOpen } from "react-icons/fa"; +import { + FaTrophy, + FaStar, + FaCode, + FaUsers, + FaGithub, + FaSearch, +} from "react-icons/fa"; import { ChevronRight, ChevronLeft } from "lucide-react"; -// import { useTheme } from "../ThemeContext"; // Theme context -// import { useNavigate } from 'react-router-dom' +import { useColorMode } from "@docusaurus/theme-common"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; // ✅ New import const GITHUB_REPO = "recodehive/recode-website"; // Change to your repo -const token = process.env.GIT_TOKEN; // Add your GitHub token here or use environment variables - - +// ❌ Remove this line, it will cause the "process is not defined" error +// const token = process.env.DOCUSAURUS_GIT_TOKEN; // Points configuration for different PR levels const POINTS: Record = { @@ -38,459 +44,507 @@ interface BadgeProps { color: { background: string; color: string }; } -interface SkeletonLoaderProps { - isDark: boolean; +interface User { + login: string; + avatar_url: string; + html_url: string; +} + +interface PullRequest { + user: User; + labels: { name: string }[]; } -// Badge component for PR counts -const Badge: React.FC = ({ count, label, color }) => ( -
- - +function Badge({ count, label, color }: BadgeProps) { + return ( + {count} {label} - -
-); - -// Skeleton Loader Component -const SkeletonLoader: React.FC = ({ isDark }) => ( -
-
-
#
-
Contributor
-
Contributions
-
-
- {[...Array(10)].map((_, i) => ( -
-
-
-
-
-
-
-
-
-
-
- ))} -
- -
-); + ); +} export default function LeaderBoard(): JSX.Element { + // ✅ Use this hook to get the custom fields from docusaurus.config.ts + const { + siteConfig: { customFields }, + } = useDocusaurusContext(); + const token = customFields.gitToken; // ✅ Access the token from customFields + + const { colorMode } = useColorMode(); + const isDark = colorMode === "dark"; + const [contributors, setContributors] = useState([]); - const [loading, setLoading] = useState(true); - const [stats, setStats] = useState({ - flooredTotalPRs: 0, - totalContributors: 0, - flooredTotalPoints: 0, - }); - const [searchTerm, setSearchTerm] = useState(""); - const { theme } = useTheme(); - const isDark = theme === 'dark'; - const navigate = useNavigate() + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; useEffect(() => { - const fetchContributorsWithPoints = async (): Promise => { + const fetchLeaderboard = async () => { + if (!token) { + setError("GitHub token not found. Please set DOCUSAURUS_GIT_TOKEN."); + setLoading(false); + return; + } + + setLoading(true); + setError(null); + try { - let contributorsMap: Record = {}; - let page = 1; - const MAX_PAGES = 10; - let keepFetching = true; - - while (keepFetching && page <= MAX_PAGES) { - const res = await fetch( - `https://api.github.com/repos/${GITHUB_REPO}/pulls?state=closed&per_page=100&page=${page}`, - { - headers: { - Accept: "application/vnd.github.v3+json", - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, + const headers = { + Authorization: `token ${token}`, + }; + + const fetchPRs = async (state: "open" | "closed") => { + let allPRs: PullRequest[] = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const response = await fetch( + `https://api.github.com/repos/${GITHUB_REPO}/pulls?state=${state}&per_page=100&page=${page}`, + { headers } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch pull requests: ${response.statusText}` + ); } - ); - const prs = await res.json(); - if (!Array.isArray(prs) || prs.length === 0 || (prs.length === 1 && prs[0].message)) { - keepFetching = false; - break; + const prs: PullRequest[] = await response.json(); + allPRs = allPRs.concat(prs); + hasNextPage = prs.length === 100; + page++; } - - prs.forEach((pr: any) => { - if (!pr.merged_at) return; - const labels = pr.labels.map((l: any) => l.name.toLowerCase()); - if (!labels.includes("gssoc'25")) return; - - const author = pr.user.login; - let points = 0; - labels.forEach((label: string) => { - if (POINTS[label]) points += POINTS[label]; + return allPRs; + }; + + const [openPRs, closedPRs] = await Promise.all([ + fetchPRs("open"), + fetchPRs("closed"), + ]); + + const allPRs = [...openPRs, ...closedPRs]; + + const contributorMap = new Map(); + + for (const pr of allPRs) { + const username = pr.user.login; + if (!contributorMap.has(username)) { + contributorMap.set(username, { + username, + avatar: pr.user.avatar_url, + profile: pr.user.html_url, + points: 0, + prs: 0, }); + } - if (!contributorsMap[author]) { - contributorsMap[author] = { - username: author, - avatar: pr.user.avatar_url, - profile: pr.user.html_url, - points: 0, - prs: 0, - }; - } - - contributorsMap[author].points += points; - contributorsMap[author].prs += 1; - }); + const contributor = contributorMap.get(username)!; + contributor.prs++; - page += 1; + let prPoints = 0; + const label = pr.labels.find((l) => l.name in POINTS)?.name; + if (label) { + prPoints = POINTS[label]; + } + contributor.points += prPoints; } - setContributors( - Object.values(contributorsMap).sort((a, b) => b.points - a.points) + const sortedContributors = Array.from(contributorMap.values()).sort( + (a, b) => b.points - a.points || b.prs - a.prs ); - } catch (error) { - console.error("Error fetching contributors:", error); - } finally { + + setContributors(sortedContributors); + setStats({ + flooredTotalPRs: allPRs.length, + totalContributors: sortedContributors.length, + flooredTotalPoints: sortedContributors.reduce( + (sum, c) => sum + c.points, + 0 + ), + }); + setLoading(false); + } catch (e: any) { + setError(e.message); setLoading(false); } }; - fetchContributorsWithPoints(); - }, []); + fetchLeaderboard(); + }, [token]); // The token is now a dependency of the useEffect hook - useEffect(() => { - if (contributors.length > 0) { - const totalPRs = contributors.reduce((sum, c) => sum + Number(c.prs), 0); - const totalPoints = contributors.reduce( - (sum, c) => sum + Number(c.points), - 0 - ); - - const flooredTotalPRs = Math.floor(totalPRs / 10) * 10; - const flooredTotalPoints = Math.floor(totalPoints / 10) * 10; - const flooredContributorsCount = - Math.floor(contributors.length / 10) * 10; - - setStats({ - flooredTotalPRs, - totalContributors: flooredContributorsCount, - flooredTotalPoints, - }); - } - }, [contributors]); - - // Filter contributors by search term (username) - const filteredContributors = contributors.filter((c) => - c.username.toLowerCase().includes(searchTerm.toLowerCase()) + const filteredContributors = contributors.filter((contributor) => + contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) ); - // Pagination variables and states - const PAGE_SIZE = 10; - const [currentPage, setCurrentPage] = useState(1); - - // Calculate which contributors to show on current page - const indexOfLast = currentPage * PAGE_SIZE; - const indexOfFirst = indexOfLast - PAGE_SIZE; - const currentContributors = filteredContributors.slice(indexOfFirst, indexOfLast); - - const totalPages = Math.ceil(filteredContributors.length / PAGE_SIZE); - - // Gradient backgrounds from Footer.jsx theme - const gradientBg = isDark - ? "bg-gradient-to-br from-dark-bg-primary to-dark-bg-secondary" - : "bg-gradient-to-br from-blue-50 to-indigo-50 backdrop-blur-xl"; - - const cardBg = isDark - ? "bg-dark-bg-tertiary border-dark-border" - : "bg-light-bg-tertiary border-light-border"; - - // Button styles - const buttonStyle: React.CSSProperties = { - padding: '8px 16px', - borderRadius: '8px', - background: isDark ? '#23272f' : '#e0e7ff', - color: isDark ? '#a78bfa' : '#3730a3', - border: 'none', - fontWeight: '500', - cursor: 'pointer', - margin: '0 4px', - opacity: 1, - transition: 'opacity 0.2s', - }; - const buttonActiveStyle: React.CSSProperties = { - ...buttonStyle, - background: isDark ? '#3b82f6' : '#2563eb', - color: '#fff', - }; - const buttonDisabledStyle: React.CSSProperties = { - ...buttonStyle, - opacity: 0.5, - cursor: 'not-allowed', + const totalPages = Math.ceil(filteredContributors.length / itemsPerPage); + const indexOfLast = currentPage * itemsPerPage; + const indexOfFirst = indexOfLast - itemsPerPage; + const currentItems = filteredContributors.slice(indexOfFirst, indexOfLast); + + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + + const renderPaginationButtons = () => { + const pages = []; + for (let i = 1; i <= totalPages; i++) { + pages.push( + + ); + } + return pages; }; return ( -
-
- {/* Header */} - -

- GSSoC'25 Leaderboard +
+
+
+

+ + Recode Hive Leaderboard

-

- Celebrating the amazing contributions from GSSoC'25 participants. Join us in building something incredible together! -

- - - {/* Search Bar */} -
-
- { - setSearchTerm(e.target.value); - setCurrentPage(1); - }} +

+ Top contributors to the open-source project + - -

+ > + + +

- {/* Stats Cards */} -
-
-
-
- + {stats && ( +
+
+ +
+ {stats.flooredTotalPRs}
-
-

Contributors

-

{loading ? '...' : stats.totalContributors}+

+
+ Total PRs
-
-
-
-
- +
+ +
+ {stats.flooredTotalPoints}
-
-

Pull Requests

-

{loading ? '...' : stats.flooredTotalPRs}+

+
+ Total Points
-
-
-
-
- +
+ +
+ {stats.totalContributors}
-
-

Total Points

-

{loading ? '...' : stats.flooredTotalPoints}+

+
+ Total Contributors
+ )} + +
+ + { + setSearchQuery(e.target.value); + setCurrentPage(1); + }} + style={{ + width: "100%", + padding: "12px 16px 12px 48px", + borderRadius: 9999, + border: isDark ? "1px solid #4b5563" : "1px solid #d1d5db", + background: isDark ? "#374151" : "#fff", + color: isDark ? "#f3f4f6" : "#1f2937", + fontSize: 16, + outline: "none", + }} + />
- {loading ? ( - - ) : ( -
- {/* Contributors List */} -
- {currentContributors.length === 0 ? ( -
- No contributors found. -
- ) : ( - currentContributors.map((contributor, index) => ( - - {/* Rank Badge */} -
- {indexOfFirst + index + 1} -
- {/* Avatar */} - {contributor.username} - {/* User Info */} - -
- )) - )} -
+ {loading && ( +
+
+

Loading leaderboard...

+
+ )} + + {error && ( +
+

Error: {error}

+
+ )} + + {!loading && !error && filteredContributors.length === 0 && ( +
+

No contributors found.

+
+ )} - {/* Pagination Controls */} -
- -
- {Array.from( - { length: Math.ceil(filteredContributors.length / PAGE_SIZE) }, - (_, i) => ( - - ) - )} +
#
+
User
+
Points
- -
- {/* CTA Footer */} -
-

- Want to see your name here? Join GSSoC'25 and start contributing! -

- - Join GSSoC'25 - + {currentItems.map((contributor, index) => ( + +
+ {indexOfFirst + index + 1} +
+ {contributor.username} + +
+ ))}
)} + + {/* Pagination Controls */} + {totalPages > 1 && ( +
+ +
{renderPaginationButtons()}
+ +
+ )}
+
); } \ No newline at end of file diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index c60fb34e..0279882c 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -33,6 +33,7 @@ import { import NavbarIcon from "@site/src/components/navbar/NavbarIcon"; import "@site/src/components/discussions/discussions.css"; import "./dashboard.css"; +import LeaderBoard from "./LeaderBoard/leaderboard"; // ✅ NEW IMPORT FOR LEADERBOARD COMPONENT type DiscussionTab = "discussions" | "trending" | "unanswered"; type SortOption = "most_popular" | "latest" | "oldest"; @@ -126,6 +127,9 @@ const DashboardContent: React.FC = () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showDashboardMenu]); + + // ❌ REMOVE THE FOLLOWING STATE VARIABLES FOR THE LEADERBOARD + /* const [leaderboardData, setLeaderboardData] = useState( [] ); @@ -139,6 +143,8 @@ const DashboardContent: React.FC = () => { isLimited: false, }); const [retryTimer, setRetryTimer] = useState(null); + */ + useEffect(() => { // Set active tab based on URL hash @@ -176,13 +182,18 @@ const DashboardContent: React.FC = () => { } }; + // ❌ REMOVE THE FOLLOWING useEffect HOOK FOR LEADERBOARD DATA + /* // Fetch leaderboard data when contributors tab is active or on initial load useEffect(() => { if (activeTab === "contributors") { fetchLeaderboardData(); } }, [activeTab]); + */ + // ❌ REMOVE THE FOLLOWING useEffect HOOK FOR INITIAL DEMO DATA + /* // Load initial demo data if no data exists useEffect(() => { if (leaderboardData.length === 0) { @@ -239,6 +250,7 @@ const DashboardContent: React.FC = () => { setLeaderboardData(initialData); } }, [leaderboardData.length]); + */ // Discussion handlers const handleDiscussionTabChange = (tab: DiscussionTab) => { @@ -378,6 +390,8 @@ const DashboardContent: React.FC = () => { [discussions, activeDiscussionTab, selectedCategory, searchQuery, sortBy] ); + // ❌ REMOVE THE FOLLOWING RATE LIMIT AND LEADERBOARD DATA FETCHING LOGIC + /* // Rate limit timer useEffect(() => { let interval: NodeJS.Timeout; @@ -729,6 +743,7 @@ const DashboardContent: React.FC = () => { if (contributions >= 150) achievements.push("PR Master"); return achievements.slice(0, 3); }; + */ const handleTabChange = ( tab: "home" | "discuss" | "giveaway" | "contributors" @@ -749,6 +764,8 @@ const DashboardContent: React.FC = () => { } }; + // ❌ REMOVE THE FOLLOWING FILTER FUNCTIONS FOR THE LEADERBOARD + /* // Filter functions const handleFilterChange = (period: FilterPeriod) => { setFilterPeriod(period); @@ -854,17 +871,14 @@ const DashboardContent: React.FC = () => {

) : null; + */ // Rest of your component code remains the same... const { githubStarCount, - githubStarCountText, githubContributorsCount, - githubContributorsCountText, githubForksCount, - githubForksCountText, githubReposCount, - githubReposCountText, loading, error, } = useCommunityStatsContext(); @@ -874,7 +888,7 @@ const DashboardContent: React.FC = () => { totalRepositories: 0, totalStars: 0, totalForks: 0, - topContributors: [], + topContributors: [], // You can keep this as an empty array or an initial state if needed }); useEffect(() => { @@ -883,14 +897,13 @@ const DashboardContent: React.FC = () => { totalRepositories: githubReposCount, totalStars: githubStarCount, totalForks: githubForksCount, - topContributors: leaderboardData.slice(0, 4), + topContributors: [], // This will be handled by the new LeaderBoard component }); }, [ githubContributorsCount, githubReposCount, githubStarCount, githubForksCount, - leaderboardData, ]); const StatCard: React.FC<{ @@ -927,55 +940,6 @@ const DashboardContent: React.FC = () => { ); - const LeaderboardCard: React.FC<{ - entry: LeaderboardEntry; - index: number; - }> = ({ entry, index }) => ( - -
- #{entry.rank} -
-
- {entry.name} -
-
-

{entry.name}

-
- - {entry.contributions} Contributions - - - {entry.repositories} Repositories - -
-
- {entry.achievements.map((achievement, i) => ( - - {achievement} - - ))} -
-
- -
- ); - return (
{/* Dashboard Menu Button - Only visible on mobile */} @@ -1017,10 +981,7 @@ const DashboardContent: React.FC = () => { setShowDashboardMenu(false); }} > - - - - Home + Home
{ setShowDashboardMenu(false); }} > - - - - Discuss + Discussions
-
{ - handleTabChange("giveaway"); + handleTabChange("contributors"); setShowDashboardMenu(false); }} > - - - - Giveaway + Contributors
{ - handleTabChange("contributors"); + handleTabChange("giveaway"); setShowDashboardMenu(false); }} > - - - - Contributors + Giveaways
- - {/* Sidebar Navigation - Hidden on mobile */} - -
isSidebarCollapsed && setIsSidebarCollapsed(false)} - > - {activeTab === "home" ? ( - // Home tab content -
- -
-

- Community Dashboard -

-

- Track our community's growth, celebrate top contributors, - and explore project statistics -

-
-
- - {/* Stats Grid */} - -
- - - - -
-
- - {/* Leaderboard Section */} - -
-

- Top Contributors Board -

-

- Celebrating our most active community members who make - RecodeHive awesome! -

-
-
- {error && ( -
-

Some data may be cached or incomplete

-
- )} +
+
+ +
+
+ } + text="Home" + active={activeTab === "home"} + onClick={() => handleTabChange("home")} + /> + } + text="Discussions" + active={activeTab === "discuss"} + onClick={() => handleTabChange("discuss")} + /> + } + text="Contributors" + active={activeTab === "contributors"} + onClick={() => handleTabChange("contributors")} + /> + } + text="Giveaways" + active={activeTab === "giveaway"} + onClick={() => handleTabChange("giveaway")} + /> +
+
- {dashboardStats.topContributors.map((entry, index) => ( - - ))} -
-
- - {/* Call to Action */} - -
-

Want to see your name here?

-

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

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

- Community{" "} - - Discussions - -

-

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

+
+ + Dashboard | Recode Hive + + + {activeTab === "home" && ( + +

Recode Hive Community Dashboard

+

+ Welcome to the Recode Hive community hub! Explore our stats, engage in discussions, and connect with fellow contributors. +

+ +
+

Community At a Glance

+
+ } + title="Total Stars" + value={dashboardStats.totalStars} + valueText={ + useCommunityStatsContext().githubStarCountText || "0" + } + description="Stars across all our public repositories." + /> + } + title="Total Contributors" + value={dashboardStats.totalContributors} + valueText={ + useCommunityStatsContext().githubContributorsCountText || "0" + } + description="Users who have contributed to our repos." + /> + } + title="Total Repositories" + value={dashboardStats.totalRepositories} + valueText={ + useCommunityStatsContext().githubReposCountText || "0" + } + description="Public repositories in our organization." + /> + } + title="Total Forks" + value={dashboardStats.totalForks} + valueText={ + useCommunityStatsContext().githubForksCountText || "0" + } + description="Total forks of all our repositories." + />
+
+
+ )} + {activeTab === "discuss" && ( + +
+

Community Discussions

+

+ Engage with the community, ask questions, and share your projects. +

+
+
-
- - - -
+ > + All Discussions + -
- -
- {categories.map((category) => ( - - ))} + > + Unanswered +
- -
-
- +
+
+
-
- -
+ +
- -
- - {filteredDiscussions.length} discussion - {filteredDiscussions.length !== 1 ? "s" : ""} found - +
+
+
+

Categories

+
    + {categories.map((cat) => ( +
  • handleCategoryChange(cat)} + > + {getCategoryIcon(cat)} {getCategoryDisplayName(cat)} +
  • + ))} +
- - {discussionsLoading ? ( -
-
-

Loading discussions...

-
- ) : discussionsError ? ( -
-
!
-

Unable to load discussions

-

{discussionsError}

- -
- ) : ( - <> -
- {filteredDiscussions.length > 0 ? ( - filteredDiscussions.map((discussion, index) => ( - - )) - ) : ( -
-
No discussions
-

No discussions found

-

- {searchQuery || selectedCategory !== "all" - ? "Try adjusting your filters or search terms." - : "Be the first to start a discussion!"} -

- - Start a Discussion - -
- )} +
+ {discussionsLoading && ( +
+
- - {/* Community Engagement Section */} -
-

- 💬 Join the Conversation -

-

- Connect with our community! Share ideas, ask questions, and showcase your amazing work. -

-
- - ❓ Ask Question - - - 💡 Share Idea - - - 🎉 Show Work - -
+ )} + {discussionsError && ( +
+

{discussionsError}

- - )} - - ) : activeTab === "giveaway" ? ( - /* Giveaway Tab - Empty for now */ -
- -

- Giveaway -

-
+ )} + {!discussionsLoading && + !discussionsError && + filteredDiscussions.length === 0 && ( +
+

No discussions found matching your criteria.

+
+ )} + {!discussionsLoading && + !discussionsError && + filteredDiscussions.map((discussion) => ( + + ))} +
- ) : activeTab === "contributors" ? ( - /* Contributors Tab - Shows the list of contributors */ -
- -

- RecodeHive Contributors -

-

- Live rankings from RecodeHive GitHub Organization • Updated - automatically -

- - {/* Header Controls: Refresh Button + Filter Buttons */} -
-
- - {rateLimitInfo.remaining && ( -

- API calls remaining: {rateLimitInfo.remaining}/ - {rateLimitInfo.limit} -

- )} -
- - {/* Filter Buttons positioned to the right */} - -
-
- - {/* Rate Limit Warning */} - + + )} - {/* Loading State */} - {isLoadingLeaderboard && ( - -
Loading...
-

- Fetching latest contributor data... -

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

Unable to Load Latest Data

-

{leaderboardError}

- {!rateLimitInfo.isLimited && ( - - )} -

- Showing cached data below. The contributors page will - automatically refresh when possible. -

-
- )} - - {/* Contributors Content */} - {!isLoadingLeaderboard && filteredLeaderboardData.length > 0 && ( - -
-
- - {filteredLeaderboardData.length} - - Contributors -
-
- - {getContributionCount( - filteredLeaderboardData[0], - filterPeriod - ) || 0} - - - Top{" "} - {filterPeriod.charAt(0).toUpperCase() + - filterPeriod.slice(1)} - -
-
- - {Math.round( - filteredLeaderboardData.reduce( - (acc, user) => - acc + getContributionCount(user, filterPeriod), - 0 - ) / filteredLeaderboardData.length - )} - - - Avg{" "} - {filterPeriod.charAt(0).toUpperCase() + - filterPeriod.slice(1)} - -
-
+ {/* ✅ REPLACED CONTRIBUTORS SECTION WITH THE NEW LEADERBOARD COMPONENT */} + {activeTab === "contributors" && ( + + + + )} -
- {filteredLeaderboardData.map((entry, index) => ( - - {/* Streak Display */} - {entry.streak && entry.streak > 1 && ( -
- {entry.streak} Day Streak -
- )} - -
-
- #{entry.rank} -
-
- -
- {entry.name} -
- -
-

{entry.name}

- {entry.username && entry.username !== entry.name && ( -

@{entry.username}

- )} - -
- - {getContributionCount(entry, filterPeriod)} - - - {filterPeriod === "weekly" - ? "this week" - : filterPeriod === "monthly" - ? "this month" - : "total"} - -
- -
-
- - {entry.contributions} - - Total PRs -
-
- - {entry.repositories} - - Repos -
-
- - {entry.achievements.length > 0 && ( -
- {entry.achievements.map((achievement, i) => ( - - {achievement} - - ))} -
- )} -
- - -
- ))} -
-
- )} - - {/* Empty State */} - {!isLoadingLeaderboard && - !leaderboardError && - filteredLeaderboardData.length === 0 && ( - -

No data available

-

No contributors found. Check back later!

-
- )} + {activeTab === "giveaway" && ( + +

Giveaways

+

+ Participate in our exciting giveaways for a chance to win awesome prizes! +

+
+

+ Stay tuned for our next giveaway. Follow our social media channels for updates! +

- ) : null} -
+ + )}
- ); +
+ ); }; const Dashboard: React.FC = () => { return ( - + - RecodeHive | Dashboard - + + + - Loading...
}> + Loading Dashboard...
}> {() => ( @@ -1817,4 +1277,4 @@ const Dashboard: React.FC = () => { ); }; -export default Dashboard; +export default Dashboard; \ No newline at end of file From b8508ff5141a95207c90a293f9f4b8fbef9acf28 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 14:05:09 +0530 Subject: [PATCH 03/11] some more fix --- src/pages/dashboard/dashboard.css | 4065 ++++------------------------- src/pages/dashboard/index.tsx | 729 +----- 2 files changed, 534 insertions(+), 4260 deletions(-) diff --git a/src/pages/dashboard/dashboard.css b/src/pages/dashboard/dashboard.css index 2c600b7c..f39009b6 100644 --- a/src/pages/dashboard/dashboard.css +++ b/src/pages/dashboard/dashboard.css @@ -1,3803 +1,660 @@ -/* Dashboard Layout */ +/* Dashboard Layout Styles */ .dashboard-layout { display: flex; min-height: 100vh; - background-color: var(--ifm-background-color); - position: relative; + background: var(--ifm-background-color); } -/* Sidebar Styles */ -.dashboard-sidebar { - width: 260px; - background: var(--ifm-card-background-color); - border-right: 1px solid var(--ifm-toc-border-color); - padding: 1.5rem 0; +/* Dashboard Menu Button - Mobile Only */ +.dashboard-menu-btn { + display: none; position: fixed; - height: 100vh; - overflow-y: auto; - transition: width 0.3s ease, transform 0.3s ease; - /* z-index: 100; */ - display: flex; - flex-direction: column; -} - -.sidebar-header { - padding: 0 1.5rem; - margin-bottom: 1.5rem; - position: relative; - display: flex; - justify-content: space-between; - align-items: center; -} - -.sidebar-logo { - padding: 0 0 1.5rem 0; - border-bottom: 1px solid var(--ifm-toc-border-color); - margin-bottom: 0; - flex-grow: 1; - transition: opacity 0.2s ease; -} - -.sidebar-logo h2 { - margin: 0; - color: var(--ifm-color-primary); -} - -.sidebar-nav { - list-style: none; - padding: 0; - margin: 0; - flex-grow: 1; -} - -.sidebar-footer { - padding: 1rem 1.5rem 0; - margin-top: auto; - border-top: 1px solid var(--ifm-toc-border-color); - padding-top: 1.5rem; - transition: opacity 0.2s ease; -} - -.nav-item { - display: flex; - align-items: center; - padding: 0.75rem 1.5rem; - cursor: pointer; - transition: all 0.2s ease; - color: var(--ifm-font-color-base); - white-space: nowrap; -} - -.nav-item:hover { - background: var(--ifm-menu-color-background-active); - color: var(--ifm-color-primary); -} - -.nav-item.active { - background: var(--ifm-menu-color-background-active); - border-left: 3px solid var(--ifm-color-primary); - color: var(--ifm-color-primary); - font-weight: 600; -} - -.nav-icon { - margin-right: 0.75rem; - font-size: 1.25rem; -} - -/* Toggle Button */ -.sidebar-toggle { + top: 70px; + left: 20px; + z-index: 1100; background: var(--ifm-color-primary); color: white; border: none; - border-radius: 50%; - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; + border-radius: 8px; + width: 44px; + height: 44px; cursor: pointer; - margin-left: 1rem; - flex-shrink: 0; - transition: background-color 0.2s ease; - margin-bottom: 20px; + font-size: 18px; + font-weight: bold; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; } -.sidebar-toggle:hover { +.dashboard-menu-btn:hover { background: var(--ifm-color-primary-dark); + transform: scale(1.05); } -.sidebar-toggle.bottom-toggle { - margin: 1.5rem auto 0; - display: block; -} - -/* Collapsed Sidebar */ -.dashboard-sidebar.collapsed { - width: 70px; - overflow: hidden; +.dashboard-menu-btn.open { + background: #dc3545; } -.dashboard-sidebar.collapsed .sidebar-logo h2, -.dashboard-sidebar.collapsed .nav-text, -.dashboard-sidebar.collapsed .sidebar-footer { - opacity: 0; +/* Mobile Menu */ +.dashboard-mobile-menu { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1050; pointer-events: none; - white-space: nowrap; -} - -.dashboard-sidebar.collapsed .sidebar-header { - justify-content: center; - padding: 0 1rem; -} - -.dashboard-sidebar.collapsed .sidebar-toggle { - position: absolute; - margin-right: 20px; - top: -1px; -} - -.dashboard-sidebar.collapsed .sidebar-toggle.bottom-toggle { - position: static; - margin: 1rem auto 0; -} - -.dashboard-sidebar.collapsed .nav-item { - padding: 0.75rem 1rem; -} - -.dashboard-sidebar.collapsed .nav-icon { - margin-right: 0; - font-size: 1.5rem; -} - -/* Main Content */ -.dashboard-main { - flex: 1; - margin-left: 300px; /* Match sidebar width */ - padding: 2rem; - max-width: calc(100% - 300px); - transition: margin-left 0.3s ease, max-width 0.3s ease; -} - -.dashboard-main.sidebar-collapsed { - margin-left: 70px; - max-width: calc(100% - 70px); -} - -.dashboard-main.discuss-view { - max-width: 100%; -} - -.dashboard-main.sidebar-collapsed.discuss-view { - margin-left: 0; - max-width: 100%; -} - -/* Discussion Section - Enhanced Modern UI */ -.discussion-container { - max-width: 1400px; - margin: 0 auto; - padding: 2rem 1rem; - color: var(--ifm-font-color-base); - position: relative; - overflow-x: hidden; - width: 100%; - box-sizing: border-box; } -/* Sidebar compatibility */ -.dashboard-main.discuss-view .discussion-container { - padding: 2rem 2rem; -} - -.dashboard-main.sidebar-collapsed.discuss-view .discussion-container { - padding: 2rem 1rem; +.dashboard-mobile-menu.show { + display: block; + pointer-events: all; } -/* Animated background particles */ -.discussion-container::before { - content: ""; +.dashboard-menu-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: radial-gradient( - circle at 20% 50%, - var(--ifm-color-primary-lightest) 0%, - transparent 50% - ), - radial-gradient( - circle at 80% 20%, - var(--ifm-color-secondary-lightest) 0%, - transparent 50% - ), - radial-gradient( - circle at 40% 80%, - var(--ifm-color-primary-lightest) 0%, - transparent 50% - ); - opacity: 0.3; - pointer-events: none; - z-index: -1; -} - -.discussion-header { - text-align: center; - margin-bottom: 3rem; - position: relative; - z-index: 1; -} - -.discussion-header h1 { - font-size: 3.5rem; - font-weight: 800; - margin-bottom: 1rem; - color: var(--ifm-color-emphasis-900); - position: relative; - display: inline-block; + background: rgba(0, 0, 0, 0.5); + z-index: 1051; } -.discussion-header h1::after { - content: ""; +.dashboard-mobile-menu > div:last-child { position: absolute; - bottom: -10px; - left: 50%; - transform: translateX(-50%); - width: 60px; - height: 4px; - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - border-radius: 2px; - animation: pulse-glow 2s ease-in-out infinite alternate; -} - -@keyframes pulse-glow { - 0% { - box-shadow: 0 0 5px var(--ifm-color-primary); - width: 60px; - } - 100% { - box-shadow: 0 0 20px var(--ifm-color-primary), - 0 0 30px var(--ifm-color-primary-light); - width: 80px; - } -} - -.discussion-header h1 .highlight { - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - #e74c3c, - var(--ifm-color-secondary) - ); - background-size: 200% 200%; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - animation: gradient-shift 3s ease-in-out infinite; -} - -@keyframes gradient-shift { - 0%, - 100% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } + top: 0; + left: 0; + width: 280px; + height: 100vh; + background: var(--ifm-background-color); + border-right: 1px solid var(--ifm-color-emphasis-200); + z-index: 1052; + overflow-y: auto; + transform: translateX(-100%); + transition: transform 0.3s ease; } -.discussion-header p { - font-size: 1.2rem; - color: var(--ifm-color-emphasis-700); - max-width: 700px; - margin: 0 auto; - line-height: 1.6; - opacity: 0.9; +.dashboard-mobile-menu.show > div:last-child { + transform: translateX(0); } -/* Enhanced Toolbar */ -.discussion-toolbar { +.dashboard-menu-header { + padding: 20px; + border-bottom: 1px solid var(--ifm-color-emphasis-200); display: flex; justify-content: space-between; align-items: center; - margin-bottom: 2rem; - padding: 1rem 1.5rem; - background: var(--ifm-card-background-color); - border-radius: 20px; - border: 1px solid var(--ifm-color-border); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); - backdrop-filter: blur(10px); - /* background-color: rgba(255, 255, 255, 0.95); */ - position: relative; - overflow: hidden; -} - -[data-theme="dark"] .discussion-toolbar { - background: rgba(var(--ifm-card-background-color-rgb), 0.8); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); -} - -.discussion-toolbar::before { - content: ""; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.1), - transparent - ); - transition: left 0.5s; -} - -.discussion-toolbar:hover::before { - left: 100%; } -.toolbar-left { - display: flex; - align-items: center; - gap: 0.75rem; +.dashboard-menu-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; } -/* Enhanced Tab Buttons */ -.tab-btn { - padding: 0.875rem 1.5rem; +.close-menu-btn { + background: none; border: none; - background: transparent; - color: var(--ifm-color-emphasis-700); - font-size: 0.95rem; - font-weight: 600; + font-size: 24px; cursor: pointer; - border-radius: 12px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; + color: var(--ifm-color-content); + padding: 0; + width: 30px; + height: 30px; display: flex; align-items: center; - gap: 0.5rem; - white-space: nowrap; - flex-shrink: 0; -} - -.tab-btn::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--ifm-color-primary); - opacity: 0; - transition: opacity 0.3s ease; - z-index: -1; -} - -.tab-btn:hover { - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.tab-btn:hover::before { - opacity: 0.1; -} - -.tab-btn.active { - background: var(--ifm-color-primary); - color: white; - box-shadow: 0 4px 20px rgba(var(--ifm-color-primary-rgb), 0.4); - transform: translateY(-1px); -} - -.tab-btn.active::before { - opacity: 1; + justify-content: center; } -/* Enhanced Sort Dropdown */ -.sort-dropdown { - position: relative; +.dashboard-menu-items { + padding: 20px 0; } -.sort-dropdown select { - padding: 0.875rem 3rem 0.875rem 1.5rem; - border: 2px solid var(--ifm-color-border); - border-radius: 12px; - background: var(--ifm-card-background-color); - color: var(--ifm-color-emphasis-700); - font-size: 0.95rem; - font-weight: 600; +.menu-item { + display: flex; + align-items: center; + padding: 12px 20px; cursor: pointer; - appearance: none; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.sort-dropdown select:hover { - border-color: var(--ifm-color-primary); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); + transition: all 0.2s ease; + color: var(--ifm-color-content); + font-weight: 500; } -.sort-dropdown select:focus { - outline: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest); +.menu-item:hover { + background: var(--ifm-color-emphasis-100); } -.sort-dropdown::after { - content: "▼"; - position: absolute; - right: 1rem; - top: 50%; - transform: translateY(-50%); +.menu-item.active { + background: var(--ifm-color-primary-lightest); color: var(--ifm-color-primary); - pointer-events: none; - transition: transform 0.3s ease; -} - -.sort-dropdown:hover::after { - transform: translateY(-50%) scale(1.1); + border-right: 3px solid var(--ifm-color-primary); } -/* Enhanced New Discussion Button */ -.new-discussion-btn { +.menu-icon { + margin-right: 12px; display: flex; align-items: center; - gap: 0.75rem; - padding: 0.875rem 2rem; - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-dark) - ); - color: white; - border: none; - border-radius: 25px; - font-size: 0.95rem; - font-weight: 700; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 4px 15px rgba(var(--ifm-color-primary-rgb), 0.3); - white-space: nowrap; -} - -.new-discussion-btn:hover { - transform: translateY(-3px); - box-shadow: 0 8px 25px rgba(var(--ifm-color-primary-rgb), 0.4); -} - -/* Enhanced Categories Bar */ -.categories-bar { - display: flex; - gap: 1rem; - margin-bottom: 2rem; - padding: 1rem 0; - overflow-x: auto; - scrollbar-width: none; - position: relative; -} - -.categories-bar::-webkit-scrollbar { - display: none; } -.category { - padding: 0.75rem 1.5rem; - border-radius: 25px; - background: var(--ifm-card-background-color); - border: 2px solid var(--ifm-color-border); - color: var(--ifm-color-emphasis-700); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - white-space: nowrap; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +/* Sidebar Styles */ +.dashboard-sidebar { + width: 280px; + background: var(--ifm-background-color); + border-right: 1px solid var(--ifm-color-emphasis-200); + padding: 20px; flex-shrink: 0; + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; } -.category:hover { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - border-color: var(--ifm-color-primary); - color: var(--ifm-color-primary); -} - -.category.active { - background: var(--ifm-color-primary); - color: white; - border-color: var(--ifm-color-primary); - font-weight: 700; - transform: translateY(-2px); - box-shadow: 0 4px 20px rgba(var(--ifm-color-primary-rgb), 0.3); +.sidebar-header { + margin-bottom: 30px; } -/* Enhanced Search Filters */ -.search-filters { +.back-button { display: flex; - gap: 1.5rem; - margin-bottom: 2.5rem; align-items: center; + padding: 10px; + background: var(--ifm-color-emphasis-100); + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--ifm-color-content); } -.search-bar { - flex: 1; - position: relative; - min-width: 0; -} - -.search-bar input { - width: 100%; - padding: 1rem 1.5rem 1rem 3.5rem; - border: 2px solid var(--ifm-color-border); - border-radius: 15px; - background: var(--ifm-card-background-color); - color: var(--ifm-font-color-base); - font-size: 1rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.search-bar input:focus { - outline: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest), - 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); -} - -.searchbar-icon { - position: absolute; - top: 50%; - left: 1.25rem; - transform: translateY(-50%); - color: var(--ifm-color-primary); - font-size: 1.1rem; - transition: all 0.3s ease; - z-index: 1; -} - -.searchbar-icon::before { - content: "🔍"; - font-size: 1rem; -} - -/* Responsive Design */ -@media (max-width: 1200px) { - .dashboard-main.discuss-view .discussion-container { - padding: 2rem 1.5rem; - } -} - -@media (max-width: 992px) { - .dashboard-main { - margin-left: 0; - max-width: 100%; - padding: 1rem; - } - - .discussion-container { - padding: 1rem; - } - - .discussion-header h1 { - font-size: 2.5rem; - } -} - -@media (max-width: 768px) { - .discussion-header h1 { - font-size: 2rem; - } - - .discussion-header p { - font-size: 1rem; - } - - .discussion-toolbar { - flex-direction: column; - gap: 1rem; - padding: 1rem; - } - - .toolbar-left { - flex-wrap: wrap; - justify-content: center; - gap: 0.5rem; - } - - .tab-btn { - padding: 0.75rem 1rem; - font-size: 0.85rem; - } - - .new-discussion-btn { - width: 100%; - justify-content: center; - padding: 1rem; - } - - .categories-bar { - padding: 0.5rem 0; - gap: 0.75rem; - } - - .category { - padding: 0.6rem 1rem; - font-size: 0.85rem; - } - - .search-filters { - flex-direction: column; - gap: 1rem; - } - - .search-bar input { - padding: 0.875rem 1rem 0.875rem 3rem; - font-size: 0.9rem; - } - - .sort-dropdown select { - width: 100%; - padding: 0.875rem 2.5rem 0.875rem 1rem; - } -} - -@media (max-width: 480px) { - .discussion-container { - padding: 0.5rem; - } - - .discussion-header { - margin-bottom: 2rem; - } - - .discussion-header h1 { - font-size: 1.75rem; - } - - .discussion-header p { - font-size: 0.9rem; - } - - .discussion-toolbar { - padding: 0.75rem; - } - - .tab-btn { - padding: 0.6rem 0.8rem; - font-size: 0.8rem; - } - - .category { - padding: 0.5rem 0.8rem; - font-size: 0.8rem; - } - - .search-bar input { - padding: 0.75rem 0.875rem 0.75rem 2.75rem; - font-size: 0.85rem; - } - - .searchbar-icon { - left: 1rem; - } - - .sort-dropdown select { - padding: 0.75rem 2rem 0.75rem 0.875rem; - font-size: 0.85rem; - } -} - -/* Dashboard Mobile Menu Button */ -.dashboard-menu-btn { - display: none; - position: fixed; - top: 4rem; - right: 1rem; - z-index: 1001; - background: var(--ifm-color-primary); - color: white; - border: none; - border-radius: 8px; - width: 44px; - height: 44px; - align-items: center; - justify-content: center; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - transition: all 0.3s ease; - font-size: 1.2rem; -} - -.dashboard-menu-btn:hover { - background: var(--ifm-color-primary-dark); - transform: scale(1.05); -} - -/* Dashboard Mobile Menu */ -.dashboard-mobile-menu { - position: fixed; - top: 0; - right: -100%; - width: 280px; - height: 100vh; - background: var(--ifm-card-background-color); - border-left: 1px solid var(--ifm-color-border); - z-index: 1002; - transition: right 0.3s ease; - box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1); - overflow-y: auto; -} - -.dashboard-mobile-menu.show { - right: 0; -} - -/* Mobile menu overlay */ -.dashboard-menu-overlay { - position: fixed; - top: 0; - left: 0; - right: 280px; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: -1; - cursor: pointer; -} - -.dashboard-menu-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.5rem; - border-bottom: 1px solid var(--ifm-color-border); - background: var(--ifm-color-primary); - color: white; -} - -.dashboard-menu-header h3 { - margin: 0; - font-size: 1.2rem; - font-weight: 600; - color: white; -} - -.close-menu-btn { - background: none; - border: none; - color: white; - font-size: 1.5rem; - cursor: pointer; - padding: 0.25rem; - border-radius: 4px; - transition: background-color 0.2s ease; -} - -.close-menu-btn:hover { - background: rgba(255, 255, 255, 0.1); -} - -.menu-divider { - height: 1px; - background: var(--ifm-color-border); - margin: 0.5rem 0; -} - -.dashboard-menu-items { - padding: 1rem 0; -} - -.menu-item { - display: flex; - align-items: center; - padding: 0.875rem 1.5rem; - cursor: pointer; - transition: all 0.2s ease; - color: var(--ifm-font-color-base); - border-left: 3px solid transparent; -} - -.menu-item:hover { - background: var(--ifm-menu-color-background-active); - color: var(--ifm-color-primary); -} - -.menu-item.active { - background: var(--ifm-menu-color-background-active); - border-left-color: var(--ifm-color-primary); - color: var(--ifm-color-primary); - font-weight: 600; -} - - - -.menu-icon { - margin-right: 0.75rem; - font-size: 1.1rem; - display: flex; - align-items: center; -} - -.menu-text { - font-size: 0.95rem; -} - -/* Mobile responsive styles */ -@media (max-width: 992px) { - .dashboard-menu-btn { - display: flex; - } - - .dashboard-sidebar { - display: none; - } - - .dashboard-layout::before { - content: ""; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: 999; - opacity: 0; - visibility: hidden; - transition: all 0.3s ease; - } - - .dashboard-mobile-menu.show ~ .dashboard-main::before { - content: ""; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: 999; - opacity: 1; - visibility: visible; - } - - .discussion-container { - padding-top: 4rem; - } - - .dashboard-main { - margin-left: 0; - max-width: 100%; - padding: 1rem; - } -} - -/* Sidebar collapsed states */ -@media (min-width: 993px) { - .dashboard-main.sidebar-collapsed.discuss-view .discussion-container { - padding: 2rem 1.5rem; - } -} - -/* Dark theme support for dashboard mobile menu */ -[data-theme="dark"] .dashboard-mobile-menu { - background: var(--ifm-background-surface-color); - border-left-color: var(--ifm-color-emphasis-300); - box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3); -} - -[data-theme="dark"] .dashboard-menu-header { - background: var(--ifm-color-primary); -} - - - -/* Responsive adjustments for mobile menu */ - - -@media (max-width: 320px) { - .dashboard-menu-header { - padding: 1rem; - } - - .menu-item { - padding: 0.75rem 1rem; - } - - -} - -/* Enhanced New Discussion Button */ -.new-discussion-btn { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.875rem 2rem; - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-dark) - ); - color: white; - border: none; - border-radius: 25px; - font-size: 0.95rem; - font-weight: 700; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 4px 15px rgba(var(--ifm-color-primary-rgb), 0.3); -} - -.new-discussion-btn::before { - content: ""; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.2), - transparent - ); - transition: left 0.5s; -} - -.new-discussion-btn:hover { - transform: translateY(-3px); - box-shadow: 0 8px 25px rgba(var(--ifm-color-primary-rgb), 0.4); -} - -.new-discussion-btn:hover::before { - left: 100%; -} - -.new-discussion-btn:active { - transform: translateY(-1px); -} - -/* Enhanced Categories Bar */ -.categories-bar { - display: flex; - gap: 1rem; - margin-bottom: 2rem; - padding: 1rem 0; - overflow-x: auto; - scrollbar-width: none; - position: relative; -} - -.categories-bar::-webkit-scrollbar { - display: none; -} - -.categories-bar::after { - content: ""; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient( - 90deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - border-radius: 1px; - opacity: 0.3; -} - -.category { - padding: 0.75rem 1.5rem; - border-radius: 25px; - background: var(--ifm-card-background-color); - border: 2px solid var(--ifm-color-border); - color: var(--ifm-color-emphasis-700); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - white-space: nowrap; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.category::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--ifm-color-primary); - opacity: 0; - transition: opacity 0.3s ease; - z-index: -1; -} - -.category:hover { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - border-color: var(--ifm-color-primary); - color: var(--ifm-color-primary); -} - -.category:hover::before { - opacity: 0.1; -} - -.category.active { - background: var(--ifm-color-primary); - color: white; - border-color: var(--ifm-color-primary); - font-weight: 700; - transform: translateY(-2px); - box-shadow: 0 4px 20px rgba(var(--ifm-color-primary-rgb), 0.3); -} - -.category.active::before { - opacity: 1; -} - -/* Enhanced Search Filters */ -.search-filters { - display: flex; - gap: 1.5rem; - margin-bottom: 2.5rem; - align-items: center; -} - -.search-bar { - flex: 1; - position: relative; -} - -.search-bar input { - width: 100%; - padding: 1rem 1.5rem 1rem 3.5rem; - border: 2px solid var(--ifm-color-border); - border-radius: 15px; - background: var(--ifm-card-background-color); - color: var(--ifm-font-color-base); - font-size: 1rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.search-bar input:hover { - border-color: var(--ifm-color-primary-light); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.search-bar input:focus { - outline: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest), - 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); -} - -.search-bar input::placeholder { - color: var(--ifm-color-emphasis-500); - font-style: italic; -} - -.searchbar-icon { - position: absolute; - top: 50%; - left: 1.25rem; - transform: translateY(-50%); - color: var(--ifm-color-primary); - font-size: 1.1rem; - transition: all 0.3s ease; - z-index: 1; -} - -.searchbar-icon::before { - content: "🔍"; - font-size: 1rem; -} - -.search-bar:hover .searchbar-icon { - color: var(--ifm-color-primary-dark); - transform: translateY(-50%) scale(1.1) lateY(-50%) scale(1.1); -} - -/* Enhanced New Discussion Button */ -.new-discussion-btn { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.875rem 2rem; - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-dark) - ); - color: white; - border: none; - border-radius: 25px; - font-size: 0.95rem; - font-weight: 700; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 4px 15px rgba(var(--ifm-color-primary-rgb), 0.3); -} - -.new-discussion-btn::before { - content: ""; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.2), - transparent - ); - transition: left 0.5s; -} - -.new-discussion-btn:hover { - transform: translateY(-3px); - box-shadow: 0 8px 25px rgba(var(--ifm-color-primary-rgb), 0.4); -} - -.new-discussion-btn:hover::before { - left: 100%; -} - -.new-discussion-btn:active { - transform: translateY(-1px); -} - -/* Enhanced Categories Bar */ -.categories-bar { - display: flex; - gap: 1rem; - margin-bottom: 2rem; - padding: 1rem 0; - overflow-x: auto; - scrollbar-width: none; - position: relative; -} - -.categories-bar::-webkit-scrollbar { - display: none; -} - -.categories-bar::after { - content: ""; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 2px; - background: linear-gradient( - 90deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - border-radius: 1px; - opacity: 0.3; -} - -.category { - padding: 0.75rem 1.5rem; - border-radius: 25px; - background: var(--ifm-card-background-color); - border: 2px solid var(--ifm-color-border); - color: var(--ifm-color-emphasis-700); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - white-space: nowrap; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.category::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--ifm-color-primary); - opacity: 0; - transition: opacity 0.3s ease; - z-index: -1; -} - -.category:hover { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - border-color: var(--ifm-color-primary); - color: var(--ifm-color-primary); -} - -.category:hover::before { - opacity: 0.1; -} - -.category.active { - background: var(--ifm-color-primary); - color: white; - border-color: var(--ifm-color-primary); - font-weight: 700; - transform: translateY(-2px); - box-shadow: 0 4px 20px rgba(var(--ifm-color-primary-rgb), 0.3); -} - -.category.active::before { - opacity: 1; -} - -/* Enhanced Search Filters */ -.search-filters { - display: flex; - gap: 1.5rem; - margin-bottom: 2.5rem; - align-items: center; -} - -.search-bar { - flex: 1; - position: relative; -} - -.search-bar input { - width: 100%; - padding: 1rem 1.5rem 1rem 3.5rem; - border: 2px solid var(--ifm-color-border); - border-radius: 15px; - background: var(--ifm-card-background-color); - color: var(--ifm-font-color-base); - font-size: 1rem; - font-weight: 500; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); -} - -.search-bar input:hover { - border-color: var(--ifm-color-primary-light); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.search-bar input:focus { - outline: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest), - 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); -} - -.search-bar input::placeholder { - color: var(--ifm-color-emphasis-500); - font-style: italic; -} - -.searchbar-icon { - position: absolute; - top: 50%; - left: 1.25rem; - transform: translateY(-50%); - color: var(--ifm-color-primary); - font-size: 1.1rem; - transition: all 0.3s ease; -} - -.search-bar:hover .searchbar-icon { - color: var(--ifm-color-primary-dark); - transform: translateY(-50%) scale(1.1); -} - -/* Enhanced Discussions List */ -.discussions-list { - display: grid; - grid-template-columns: 1fr; - gap: 2rem; -} - -/* Enhanced Discussion Items */ -.discussion-item { - display: flex; - flex-direction: column; - padding: 1.5rem; - background: #ffffff; - border-radius: 16px; - border: 1px solid #e2e8f0; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), - 0 2px 4px -1px rgba(0, 0, 0, 0.06); - cursor: pointer; -} - -[data-theme="dark"] .discussion-item { - background: #1e293b; - border-color: #334155; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), - 0 2px 4px -1px rgba(0, 0, 0, 0.2); -} - -[data-theme="dark"] .discussion-item { - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -} - -.discussion-item::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: linear-gradient( - 90deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - transform: scaleX(0); - transition: transform 0.3s ease; -} - -.discussion-item:hover { - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), - 0 10px 10px -5px rgba(0, 0, 0, 0.04); - border-color: #10b981; -} - -[data-theme="dark"] .discussion-item:hover { - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), - 0 10px 10px -5px rgba(0, 0, 0, 0.2); - border-color: #10b981; -} - -.discussion-item:hover::before { - transform: scaleX(1); -} - -/* Enhanced Avatar */ -.discussion-avatar { - width: 48px; - height: 48px; - border-radius: 50%; - overflow: hidden; - flex-shrink: 0; - position: relative; - border: 2px solid #e2e8f0; - transition: all 0.2s ease; - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); -} - -[data-theme="dark"] .discussion-avatar { - border-color: #475569; -} - -.discussion-avatar img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 50%; -} - -.discussion-avatar.verified::after { - content: "✓"; - position: absolute; - bottom: -2px; - right: -2px; - width: 16px; - height: 16px; - background: #2563eb; - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: bold; - border: 2px solid #ffffff; -} - -[data-theme="dark"] .discussion-avatar.verified::after { - border-color: #1e293b; -} - -.discussion-content { - flex: 1; - display: flex; - flex-direction: column; -} - -[data-theme="dark"] .discussion-content { - color: #f1f5f9; -} - -.discussion-content { - flex: 1; - display: flex; - flex-direction: column; -} - -.discussion-header { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 1rem; -} - -.discussion-pinned-badge { - display: inline-flex; - align-items: center; - gap: 0.5rem; - background: #059669; - color: white; - padding: 0.5rem 1rem; - border-radius: 20px; - font-size: 0.875rem; - font-weight: 600; -} - -.discussion-pinned-badge::before { - content: "📌"; -} - -.discussion-title { - font-size: 1.5rem; - font-weight: 700; - margin: 0 0 1rem 0; - color: #1e293b; - line-height: 1.375; - transition: color 0.2s ease; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.discussion-item:hover .discussion-title { - color: #2563eb; -} - -[data-theme="dark"] .discussion-title { - color: #f1f5f9; -} - -[data-theme="dark"] .discussion-item:hover .discussion-title { - color: #60a5fa; -} - -/* Enhanced Pinned Badge */ -.pinned-badge { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-dark) - ); - color: white; - border-radius: 25px; - font-size: 0.8rem; - font-weight: 700; - box-shadow: 0 2px 8px rgba(var(--ifm-color-primary-rgb), 0.3); - animation: glow-pulse 2s ease-in-out infinite alternate; -} - -@keyframes glow-pulse { - 0% { - box-shadow: 0 2px 8px rgba(var(--ifm-color-primary-rgb), 0.3); - } - 100% { - box-shadow: 0 4px 16px rgba(var(--ifm-color-primary-rgb), 0.5); - } -} - -.discussion-body p { - color: #475569; - font-size: 1rem; - line-height: 1.625; - margin: 0 0 1.5rem 0; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; -} - -[data-theme="dark"] .discussion-body p { - color: #cbd5e1; -} - -.discussion-meta { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid #e2e8f0; -} - -[data-theme="dark"] .discussion-meta { - border-top-color: #334155; -} - -.discussion-author { - color: #64748b; - font-size: 0.875rem; -} - -[data-theme="dark"] .discussion-author { - color: #94a3b8; -} - -.discussion-stats { - display: flex; - gap: 1rem; - align-items: center; - color: #64748b; - font-size: 0.875rem; -} - -[data-theme="dark"] .discussion-stats { - color: #94a3b8; -} - -.discussion-stats span { - display: flex; - align-items: center; - gap: 0.25rem; -} - -/* Enhanced Meta Section */ -.discussion-meta { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 1.5rem; - padding-top: 1rem; - border-top: 1px solid var(--ifm-color-border); -} - -/* Enhanced Tags */ -.tags { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; -} - -.tags { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; - margin-bottom: 1rem; -} - -.tags .tag { - padding: 0.375rem 0.75rem; - background: linear-gradient(135deg, #dbeafe 0%, #e0e7ff 100%); - border-radius: 9999px; - color: #1e40af; - font-size: 0.75rem; - font-weight: 500; - transition: all 0.2s ease; - border: 1px solid #bfdbfe; - text-decoration: none; -} - -[data-theme="dark"] .tags .tag { - background: linear-gradient(135deg, #1e3a8a 0%, #3730a3 100%); - color: #dbeafe; - border-color: #3b82f6; -} - -.tags .tag:hover { - background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); - color: white; - border-color: #2563eb; -} - -.tags .tag::before { - content: "#"; - opacity: 0.8; - margin-right: 0.125rem; -} - -/* Enhanced Meta Info */ -.meta-info { - display: flex; - align-items: center; - gap: 1.5rem; - color: var(--ifm-color-emphasis-600); - font-size: 0.9rem; - font-weight: 500; -} - -.meta-info span { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.75rem; - border-radius: 15px; - background: var(--ifm-color-emphasis-100); - transition: all 0.3s ease; -} - -.meta-info span:hover { - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary); - transform: translateY(-1px); -} - -/* Enhanced Giscus Wrapper */ -.giscus-wrapper { - margin-top: 4rem; - padding-top: 3rem; - border-top: 2px solid var(--ifm-color-border); - position: relative; -} - -.giscus-wrapper::before { - content: ""; - position: absolute; - top: -1px; - left: 50%; - transform: translateX(-50%); - width: 100px; - height: 2px; - background: linear-gradient( - 90deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - border-radius: 1px; -} - -/* Responsive Design */ -@media (max-width: 768px) { - .discussion-container { - padding: 1rem; - } - - .discussion-header h1 { - font-size: 2.5rem; - } - - .discussion-toolbar { - flex-direction: column; - gap: 1.5rem; - align-items: stretch; - padding: 1.5rem; - } - - .toolbar-left { - flex-wrap: wrap; - justify-content: center; - gap: 0.5rem; - } - - .tab-btn { - padding: 0.75rem 1rem; - font-size: 0.85rem; - } - - .new-discussion-btn { - justify-content: center; - padding: 1rem 1.5rem; - } - - .categories-bar { - gap: 0.75rem; - padding: 0.75rem 0; - } - - .category { - padding: 0.6rem 1rem; - font-size: 0.85rem; - } - - .search-filters { - flex-direction: column; - align-items: stretch; - gap: 1rem; - } - - .search-bar input { - padding: 0.875rem 1.25rem 0.875rem 3rem; - } - - .discussions-list { - gap: 1.5rem; - } - - .discussion-item { - flex-direction: column; - gap: 1rem; - padding: 1.5rem; - } - - .discussion-avatar { - width: 48px; - height: 48px; - align-self: flex-start; - } - - .discussion-meta { - flex-direction: column; - align-items: flex-start; - gap: 1rem; - } - - .meta-info { - gap: 1rem; - flex-wrap: wrap; - } -} - -@media (max-width: 480px) { - .discussion-header h1 { - font-size: 2rem; - } - - .discussion-header p { - font-size: 1rem; - } - - .discussion-item { - padding: 1rem; - } - - .discussion-header-content h3 { - font-size: 1.1rem; - } - - .tab-btn { - padding: 0.6rem 0.8rem; - font-size: 0.8rem; - } - - .category { - padding: 0.5rem 0.8rem; - font-size: 0.8rem; - } -} - -/* Dashboard Container */ -.dashboard-container { - max-width: 1200px; - margin: 0 auto; - padding: 0 1rem; -} - -/* Hero Section */ -.dashboard-hero { - text-align: center; - padding: 4rem 0 2rem; - margin-bottom: 3rem; -} - -.hero-content { - max-width: 800px; - margin: 0 auto; -} - -.dashboard-title { - font-size: 3.5rem; - font-weight: 700; - margin-bottom: 1.5rem; - color: var(--ifm-color-emphasis-900); - line-height: 1.1; -} - -.dashboard-title .highlight { - background: linear-gradient(135deg, var(--ifm-color-primary), #e74c3c); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.dashboard-subtitle { - font-size: 1.2rem; - color: var(--ifm-color-emphasis-700); - margin-bottom: 2rem; - line-height: 1.6; -} - -/* Stats Section */ -.dashboard-stats-section { - margin-bottom: 4rem; -} - -.dashboard-stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; - margin-bottom: 3rem; -} - -.dashboard-stat-card { - background: linear-gradient( - 135deg, - var(--ifm-card-background-color) 0%, - rgba(var(--ifm-color-primary-rgb), 0.05) 100% - ); - border-radius: 2rem; - padding: 2.5rem; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04); - border: 1px solid var(--ifm-color-border); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - display: flex; - align-items: center; - gap: 2rem; - position: relative; - overflow: hidden; - backdrop-filter: blur(10px); -} - -[data-theme="dark"] .dashboard-stat-card { - background: linear-gradient( - 135deg, - rgba(var(--ifm-card-background-color-rgb), 0.95) 0%, - rgba(var(--ifm-color-primary-rgb), 0.1) 100% - ); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.2); -} - -.dashboard-stat-card::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 135deg, - rgba(var(--ifm-color-primary-rgb), 0.1) 0%, - transparent 50%, - rgba(var(--ifm-color-secondary-rgb), 0.05) 100% - ); - opacity: 0; - transition: opacity 0.4s ease; - pointer-events: none; -} - -.dashboard-stat-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 8px 24px rgba(0, 0, 0, 0.08); - border-color: var(--ifm-color-primary-light); -} - -[data-theme="dark"] .dashboard-stat-card:hover { - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.3); -} - -.dashboard-stat-card:hover::before { - opacity: 1; -} - -.dashboard-stat-icon { - font-size: 3.5rem; - width: 5rem; - height: 5rem; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient( - 135deg, - var(--ifm-color-primary) 0%, - var(--ifm-color-primary-dark) 100% - ); - border-radius: 1.5rem; - flex-shrink: 0; - position: relative; - overflow: hidden; - box-shadow: 0 8px 24px rgba(var(--ifm-color-primary-rgb), 0.3); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.dashboard-stat-icon::before { - content: ""; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: linear-gradient( - 45deg, - transparent 30%, - rgba(255, 255, 255, 0.3) 50%, - transparent 70% - ); - transform: rotate(-45deg); - transition: transform 0.6s ease; -} - -.dashboard-stat-card:hover .dashboard-stat-icon { - transform: scale(1.1) rotate(5deg); - box-shadow: 0 12px 32px rgba(var(--ifm-color-primary-rgb), 0.4); -} - -.dashboard-stat-card:hover .dashboard-stat-icon::before { - transform: rotate(-45deg) translate(100%, 100%); -} - -.dashboard-stat-content { - flex: 1; -} - -.dashboard-stat-title { - font-size: 1.2rem; - font-weight: 700; - color: var(--ifm-color-emphasis-900); - margin-bottom: 0.75rem; - transition: all 0.3s ease; - position: relative; -} - -.dashboard-stat-title::after { - content: ""; - position: absolute; - bottom: -4px; - left: 0; - width: 0; - height: 2px; - background: linear-gradient( - 90deg, - var(--ifm-color-primary), - var(--ifm-color-secondary) - ); - transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); - border-radius: 1px; -} - -.dashboard-stat-card:hover .dashboard-stat-title { - color: var(--ifm-color-primary); - transform: translateX(4px); -} - -.dashboard-stat-card:hover .dashboard-stat-title::after { - width: 100%; -} - -.dashboard-stat-value { - /* font-size: 3rem; - font-weight: 800; - - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - - margin-bottom: 0.5rem; - text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; - position: relative; */ - /* background: linear-gradient( - 135deg, - var(--ifm-color-primary) 0%, - var(--ifm-color-primary-dark) 100% - ); */ - /* not removed the properties as this can be used later on */ - background-clip: text; - font-size: 3rem; - font-weight: 800; - color: var(--ifm-color-primary); - margin-bottom: 0.5rem; - text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transition: color 0.3s ease; - position: relative; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.dashboard-stat-card:hover .dashboard-stat-value { - transform: scale(1.05); - filter: drop-shadow(0 0 10px rgba(var(--ifm-color-primary-rgb), 0.3)); -} - -.dashboard-slot-counter { - font-size: inherit; - font-weight: inherit; - color: inherit; -} - -.dashboard-stat-description { - font-size: 0.95rem; - color: var(--ifm-color-emphasis-700); - margin: 0; - line-height: 1.5; - transition: all 0.3s ease; - opacity: 0.9; -} - -.dashboard-stat-card:hover .dashboard-stat-description { - color: var(--ifm-color-emphasis-800); - opacity: 1; - transform: translateY(-2px); -} - -.loading-spinner { - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -/* Leaderboard Section */ -.dashboard-leaderboard-section { - margin-bottom: 4rem; -} - -.leaderboard-header { - text-align: center; - margin-bottom: 3rem; -} - -.leaderboard-title { - font-size: 2.5rem; - font-weight: 700; - margin-bottom: 1rem; - color: var(--ifm-color-emphasis-900); -} - -.leaderboard-title .title-accent { - color: var(--ifm-color-primary); -} - -.leaderboard-description { - font-size: 1.1rem; - color: var(--ifm-color-emphasis-700); - max-width: 600px; - margin: 0 auto; - line-height: 1.6; -} - -.leaderboard-container { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.leaderboard-card { - background: var(--ifm-color-background); - border-radius: 1.5rem; - padding: 2rem; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - border: 1px solid var(--ifm-color-border); - transition: all 0.3s ease; - display: grid; - grid-template-columns: auto auto 1fr auto; - gap: 1.5rem; - align-items: center; -} - -/* Center images in leaderboard when sidebar is collapsed */ -.dashboard-main.sidebar-collapsed .leaderboard-card { - grid-template-columns: 1fr; - text-align: center; - padding: 1.5rem; -} - -.dashboard-main.sidebar-collapsed .leaderboard-avatar { - margin: 0 auto; -} - -.dashboard-main.sidebar-collapsed .leaderboard-info { - grid-column: 1 / -1; -} - -.dashboard-main.sidebar-collapsed .leaderboard-actions { - grid-column: 1 / -1; - justify-self: center; -} - -.leaderboard-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); -} - -.leaderboard-rank { - display: flex; - align-items: center; - justify-content: center; -} - -.rank-badge { - font-size: 1.2rem; - font-weight: 700; - padding: 0.5rem 1rem; - border-radius: 50px; - color: white; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); -} - -.rank-badge.rank-1 { - background: linear-gradient(135deg, #ffd700, #ffb347); - box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3); -} - -.rank-badge.rank-2 { - background: linear-gradient(135deg, #c0c0c0, #a8a8a8); - box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3); -} - -.rank-badge.rank-3 { - background: linear-gradient(135deg, #cd7f32, #b8860b); - box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3); -} - -.rank-badge.rank-4, -.rank-badge.rank-5 { - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-darker) - ); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.leaderboard-avatar { - width: 4rem; - height: 4rem; - border-radius: 50%; - overflow: hidden; - border: 3px solid var(--ifm-color-primary-lightest); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.leaderboard-avatar img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.leaderboard-info { - flex: 1; -} - -.leaderboard-name { - font-size: 1.4rem; - font-weight: 600; - color: var(--ifm-color-emphasis-900); - margin-bottom: 0.5rem; -} - -.leaderboard-stats { - display: flex; - gap: 1.5rem; - margin-bottom: 1rem; -} - -.stat-item { - font-size: 0.9rem; - color: var(--ifm-color-secondary-text); -} - -.stat-item strong { - color: var(--ifm-color-primary-text); - font-weight: 600; -} - -.leaderboard-achievements { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; -} - -.achievement-badge { - font-size: 0.8rem; - padding: 0.25rem 0.75rem; - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary-text); - border-radius: 50px; - font-weight: 500; - border: 1px solid var(--ifm-color-primary-light); -} - -.leaderboard-actions { - display: flex; - align-items: center; -} - -.github-profile-btn { - background: var(--ifm-color-primary); - color: white; - text-decoration: none; - padding: 0.75rem 1.5rem; - border-radius: 50px; - font-weight: 600; - transition: all 0.3s ease; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.github-profile-btn:hover { - background: var(--ifm-color-primary-darker); - transform: translateY(-1px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); - text-decoration: none; - color: white; -} - -/* Call to Action */ -.dashboard-cta { - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-darker) - ); - color: white; - padding: 4rem 2rem; - border-radius: 2rem; - text-align: center; - margin-bottom: 3rem; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); -} - -.cta-content h3 { - font-size: 2rem; - font-weight: 700; - margin-bottom: 1rem; - color: white; -} - -.cta-content p { - font-size: 1.1rem; - margin-bottom: 2rem; - opacity: 0.9; - line-height: 1.6; -} - -.cta-buttons { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; -} - -.cta-primary, -.cta-secondary { - padding: 0.75rem 2rem; - border-radius: 50px; - text-decoration: none; - font-weight: 600; - transition: all 0.3s ease; - display: inline-block; -} - -.cta-primary { - background: white; - color: var(--ifm-color-primary); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.cta-primary:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); - text-decoration: none; - color: var(--ifm-color-primary); -} - -.cta-secondary { - background: transparent; - color: white; - border: 2px solid white; -} - -.cta-secondary:hover { - background: white; - color: var(--ifm-color-primary); - transform: translateY(-2px); - text-decoration: none; -} - -/* Leaderboard Page Styles */ -.leaderboard-page-container { - max-width: 1200px; - margin: 0 auto; - padding: 2rem 0; -} - -.leaderboard-page-header { - text-align: center; - margin-bottom: 3rem; -} - -.leaderboard-page-title { - font-size: 3rem; - font-weight: 700; - margin-bottom: 1rem; - color: var(--ifm-color-emphasis-900); -} - -/* Light theme page title */ -[data-theme="light"] .leaderboard-page-title { - color: #1a202c !important; -} - -[data-theme="light"] .leaderboard-page-subtitle { - color: #4a5568 !important; -} - -.leaderboard-page-title .highlight { - background: linear-gradient(135deg, #ffd700, #ff8c00); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.leaderboard-page-subtitle { - font-size: 1.2rem; - color: var(--ifm-color-emphasis-700); - margin-bottom: 2rem; -} - -.refresh-section { - margin-top: 2rem; -} - -.refresh-button { - background: var(--ifm-color-primary); - color: white; - border: none; - padding: 0.75rem 2rem; - border-radius: 50px; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - font-size: 1rem; -} - -.refresh-button:hover:not(:disabled) { - background: var(--ifm-color-primary-darker); - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); -} - -.refresh-button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Loading and Error States */ -.loading-container { - text-align: center; - padding: 4rem 2rem; - background: var(--ifm-card-background-color); - border-radius: 1rem; - margin: 2rem 0; - border: 1px solid var(--ifm-color-border); -} - -.loading-spinner-large { - font-size: 3rem; - margin-bottom: 1rem; - animation: spin 2s linear infinite; -} - -.error-container { - text-align: center; - padding: 3rem 2rem; - background: var(--ifm-alert-background-color); - border: 1px solid var(--ifm-alert-border-color); - border-radius: 1rem; - margin: 2rem 0; -} - -.error-container h3 { - color: var(--ifm-color-danger); - margin-bottom: 1rem; -} - -.error-container p { - color: var(--ifm-font-color-base); - margin-bottom: 2rem; -} - -.error-help { - color: var(--ifm-font-color-base); -} - -/* Light theme loading and error states */ -[data-theme="light"] .loading-container { - background: #ffffff; - border: 1px solid #e2e8f0; - color: #1a202c; -} - -[data-theme="light"] .loading-text { - color: #4a5568 !important; -} - -[data-theme="light"] .error-container { - background: #fef2f2; - border: 1px solid #fecaca; - color: #1a202c; -} - -[data-theme="light"] .error-container h3 { - color: #dc2626 !important; -} - -[data-theme="light"] .error-container p, -[data-theme="light"] .error-help { - color: #374151 !important; -} - -.retry-button { - background: var(--ifm-color-danger); - color: white; - border: none; - padding: 0.75rem 2rem; - border-radius: 0.5rem; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - margin-top: 1rem; -} - -.retry-button:hover { - background: var(--ifm-color-danger-dark); - transform: translateY(-2px); -} - -/* Leaderboard Stats */ -.leaderboard-stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 2rem; - margin-bottom: 3rem; - padding: 2rem; - background: var(--ifm-card-background-color); - border-radius: 1rem; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); -} - -.leaderboard-stats .stat-item { - text-align: center; - padding: 1rem; -} - -.leaderboard-stats .stat-number { - display: block; - font-size: 2.5rem; - font-weight: 700; - color: var(--ifm-color-primary); - margin-bottom: 0.5rem; -} - -.leaderboard-stats .stat-label { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-600); - font-weight: 500; -} - -/* Light theme leaderboard stats */ -[data-theme="light"] .leaderboard-stats { - background: #ffffff; - border: 1px solid #e2e8f0; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .leaderboard-stats .stat-number { - color: var(--ifm-color-primary) !important; -} - -[data-theme="light"] .leaderboard-stats .stat-label { - color: #4a5568 !important; -} - -/* Leaderboard Grid */ -.leaderboard-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 2rem; -} - -.leaderboard-item { - background: var(--ifm-card-background-color); - border-radius: 1.5rem; - padding: 2rem; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - border: 1px solid var(--ifm-color-border); - transition: all 0.3s ease; - position: relative; - overflow: hidden; - color: var(--ifm-font-color-base); -} - -/* Light theme specific styles */ -[data-theme="light"] .leaderboard-item { - background: #ffffff; - border: 1px solid #e2e8f0; - color: #1a202c; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); -} - -[data-theme="light"] .leaderboard-item:hover { - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); - border-color: var(--ifm-color-primary); -} - -.leaderboard-item::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: var(--ifm-color-primary); -} - -.leaderboard-item.rank-1::before { - background: linear-gradient(135deg, #ffd700, #ffb347); -} - -.leaderboard-item.rank-2::before { - background: linear-gradient(135deg, #c0c0c0, #a8a8a8); -} - -.leaderboard-item.rank-3::before { - background: linear-gradient(135deg, #cd7f32, #b8860b); -} - -/* Rank Section */ -.rank-section { - text-align: center; - margin-bottom: 1.5rem; -} - -.rank-badge { - display: inline-block; - font-size: 1.2rem; - font-weight: 700; - padding: 0.75rem 1.5rem; - border-radius: 50px; - color: white; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - min-width: 80px; -} - -.rank-badge.rank-1 { - background: linear-gradient(135deg, #ffd700, #ffb347); - box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3); -} - -.rank-badge.rank-2 { - background: linear-gradient(135deg, #c0c0c0, #a8a8a8); - box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3); -} - -.rank-badge.rank-3 { - background: linear-gradient(135deg, #cd7f32, #b8860b); - box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3); -} - -.rank-badge.rank-other { - background: linear-gradient( - 135deg, - var(--ifm-color-primary), - var(--ifm-color-primary-darker) - ); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -/* Avatar Section */ -.avatar-section { - text-align: center; - margin-bottom: 1.5rem; -} - -.user-avatar { - width: 80px; - height: 80px; - border-radius: 50%; - border: 4px solid var(--ifm-color-primary-lightest); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - object-fit: cover; -} - -/* User Info */ -.user-info { - text-align: center; - margin-bottom: 1.5rem; -} - -.user-name { - font-size: 1.4rem; - font-weight: 600; - color: var(--ifm-color-emphasis-900); - margin-bottom: 0.5rem; -} - -.user-username { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-600); - margin-bottom: 1rem; - font-style: italic; -} - -/* Light theme text visibility fixes */ -[data-theme="light"] .user-name { - color: #1a202c !important; -} - -[data-theme="light"] .user-username { - color: #4a5568 !important; -} - -[data-theme="light"] .leaderboard-item .user-info { - color: #1a202c; -} - -/* Score Display */ -.score-display { - margin-bottom: 1.5rem; -} - -.score-number { - font-size: 2.5rem; - font-weight: 700; - color: var(--ifm-color-primary); - display: block; -} - -.score-label { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-600); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Light theme score visibility */ -[data-theme="light"] .score-number { - color: var(--ifm-color-primary) !important; -} - -[data-theme="light"] .score-label { - color: #4a5568 !important; -} - -/* User Stats */ -.user-stats { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; - margin-bottom: 1.5rem; -} - -.user-stats .stat { - text-align: center; - padding: 0.75rem; - background: var(--ifm-color-background); - border-radius: 0.5rem; - border: 1px solid var(--ifm-color-border); -} - -.stat-value { - display: block; - font-size: 1.5rem; - font-weight: 600; - color: var(--ifm-color-primary); - margin-bottom: 0.25rem; -} - -.stat-text { - font-size: 0.8rem; - color: var(--ifm-color-emphasis-600); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Light theme stats visibility */ -[data-theme="light"] .user-stats .stat { - background: #f7fafc; - border: 1px solid #e2e8f0; -} - -[data-theme="light"] .stat-value { - color: var(--ifm-color-primary) !important; -} - -[data-theme="light"] .stat-text { - color: #4a5568 !important; -} - -/* Achievements */ -.achievements { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - justify-content: center; - margin-bottom: 1.5rem; -} - -.achievement-tag { - font-size: 0.75rem; - padding: 0.25rem 0.75rem; - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary-darker); - border-radius: 50px; - font-weight: 500; - border: 1px solid var(--ifm-color-primary-light); - white-space: nowrap; -} - -/* Light theme achievement tags */ -[data-theme="light"] .achievement-tag { - background: #e6fffa; - color: #234e52; - border: 1px solid #81e6d9; -} - -/* Actions Section */ -.actions-section { - text-align: center; -} - -.github-link { - display: inline-flex; - align-items: center; - gap: 0.5rem; - background: var(--ifm-color-primary); - color: white; - text-decoration: none; - padding: 0.75rem 1.5rem; - border-radius: 50px; - font-weight: 600; - transition: all 0.3s ease; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.github-link:hover { - background: var(--ifm-color-primary-darker); - transform: translateY(-1px); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); - text-decoration: none; - color: white; -} - -/* Empty State */ -.empty-state { - text-align: center; - padding: 4rem 2rem; - background: var(--ifm-card-background-color); - border-radius: 1rem; - border: 1px solid var(--ifm-color-border); - color: var(--ifm-color-emphasis-700); -} - -.empty-state h3 { - margin-bottom: 1rem; - color: var(--ifm-color-emphasis-800); -} - -.empty-state p { - font-size: 1.1rem; - margin-bottom: 0; -} - -/* Light theme empty state */ -[data-theme="light"] .empty-state { - background: #ffffff; - border: 1px solid #e2e8f0; - color: #4a5568; -} - -[data-theme="light"] .empty-state h3 { - color: #1a202c !important; -} - -[data-theme="light"] .empty-state p { - color: #4a5568 !important; -} - -/* Responsive Design */ -@media (max-width: 768px) { - .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; - } -} - -@media (max-width: 480px) { - .leaderboard-page-container { - padding: 1rem 0; - } - - .leaderboard-stats { - grid-template-columns: 1fr; - } - - .user-stats { - grid-template-columns: 1fr; - } -} - -/* Error Message */ -.error-message { - text-align: center; - padding: 1rem; - background: var(--ifm-color-warning-lightest); - border: 1px solid var(--ifm-color-warning-light); - border-radius: 0.5rem; - margin-bottom: 2rem; - color: var(--ifm-color-warning-darker); -} - -/* Responsive Design */ -@media (max-width: 768px) { - .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%; - max-width: 200px; - } -} - -@media (max-width: 480px) { - .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; - } -} - -/* GSSoC-Style Enhancements */ -.leaderboard-page-container { - background: var(--ifm-background-color); - min-height: calc(100vh - 60px); -} - -.leaderboard-page-title { - 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; - text-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} - -/* Enhanced Leaderboard Item for GSSoC Style */ -.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); - 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), - 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); -} - -[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); -} - -/* Gradient rank badges like GSSoC */ -.rank-badge.rank-1 { - background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); - animation: pulse-gold 2s ease-in-out infinite alternate; - color: #fff; -} - -.rank-badge.rank-2 { - background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); - animation: pulse-silver 2s ease-in-out infinite alternate; - color: #333; -} - -.rank-badge.rank-3 { - background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%); - animation: pulse-bronze 2s ease-in-out infinite alternate; - color: #fff; -} - -@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); - } -} - -@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); - } -} - -@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); - } -} - -/* Special badges for GSSoC features */ -.achievement-tag { - font-size: 0.7rem; - padding: 0.3rem 0.8rem; - border-radius: 20px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - transition: all 0.3s ease; -} - -/* Postman badge styling */ -.achievement-tag:has-text("📮") { - background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); - color: white; - border: none; -} - -/* Web3 badge styling */ -.achievement-tag:has-text("🌐") { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; -} - -/* Enhanced score display */ -.score-display { - position: relative; - background: linear-gradient( - 135deg, - var(--ifm-color-primary) 0%, - var(--ifm-color-primary-darker) 100% - ); - color: white; - padding: 1.5rem; - border-radius: 1rem; - margin-bottom: 1.5rem; - text-align: center; -} - -.score-number { - font-size: 3rem; - font-weight: 800; - color: white; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.3)); -} - -.score-label { - color: rgba(255, 255, 255, 0.9); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; -} - -/* Enhanced user avatar with ranking glow */ -.user-avatar { - transition: all 0.3s ease; - filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15)); -} - -[data-theme="dark"] .user-avatar { - filter: drop-shadow(0 4px 12px rgba(255, 255, 255, 0.1)); -} - -.leaderboard-item:nth-child(1) .user-avatar { - border-color: #ffd700; - box-shadow: 0 0 20px rgba(255, 215, 0, 0.4); -} - -.leaderboard-item:nth-child(2) .user-avatar { - border-color: #c0c0c0; - box-shadow: 0 0 20px rgba(192, 192, 192, 0.4); -} - -.leaderboard-item:nth-child(3) .user-avatar { - border-color: #cd7f32; - box-shadow: 0 0 20px rgba(205, 127, 50, 0.4); -} - -/* Enhanced stats display */ -.user-stats .stat { - background: var(--ifm-color-background); - border: 1px solid var(--ifm-color-border); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06); - transition: all 0.3s ease; -} - -[data-theme="dark"] .user-stats .stat { - box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.03); -} - -.user-stats .stat:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -[data-theme="dark"] .user-stats .stat:hover { - box-shadow: 0 4px 12px rgba(255, 255, 255, 0.05); -} - -/* Enhanced GitHub link */ -.github-link { - background: var(--ifm-color-emphasis-800); - color: var(--ifm-font-color-base-inverse); - padding: 0.75rem 1.5rem; - border-radius: 50px; - text-decoration: none; - transition: all 0.3s ease; - font-weight: 600; - gap: 0.5rem; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -[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); -} - -.github-link:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); - color: var(--ifm-font-color-base-inverse); - text-decoration: none; -} - -[data-theme="dark"] .github-link:hover { - box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2); - color: var(--ifm-color-emphasis-900); -} - -/* Top 3 special styling */ -.leaderboard-item:nth-child(1) { - background: var(--ifm-card-background-color); - border: 2px solid #ffd700; - position: relative; - overflow: hidden; -} - -.leaderboard-item:nth-child(1)::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 135deg, - rgba(255, 215, 0, 0.1) 0%, - transparent 50% - ); - pointer-events: none; -} - -.leaderboard-item:nth-child(2) { - background: var(--ifm-card-background-color); - border: 2px solid #c0c0c0; - position: relative; - overflow: hidden; -} - -.leaderboard-item:nth-child(2)::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 135deg, - rgba(192, 192, 192, 0.1) 0%, - transparent 50% - ); - pointer-events: none; -} - -.leaderboard-item:nth-child(3) { - background: var(--ifm-card-background-color); - border: 2px solid #cd7f32; - position: relative; - overflow: hidden; -} - -.leaderboard-item:nth-child(3)::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - 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: "👑"; - position: absolute; - top: -10px; - right: 20px; - font-size: 2rem; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); -} - -.leaderboard-item:nth-child(2)::after { - content: "🥈"; - position: absolute; - top: -10px; - right: 20px; - font-size: 2rem; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); -} - -.leaderboard-item:nth-child(3)::after { - content: "🥉"; - position: absolute; - top: -10px; - right: 20px; - font-size: 2rem; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); -} - -/* Leaderboard stats enhancement */ -.leaderboard-stats { - 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 { - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); -} - -.leaderboard-stats .stat-number { - color: white; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); -} - -.leaderboard-stats .stat-label { - color: rgba(255, 255, 255, 0.9); - font-weight: 600; -} - -/* Loading animation enhancement */ -.loading-spinner-large { - 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; -} - -/* Empty state enhancement */ -.empty-state { - text-align: center; - padding: 4rem 2rem; - background: var(--ifm-card-background-color); - border-radius: 2rem; - margin: 2rem 0; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - border: 1px solid var(--ifm-color-border); -} - -[data-theme="dark"] .empty-state { - box-shadow: 0 8px 32px rgba(255, 255, 255, 0.05); -} - -/* Streak display */ -.streak-display { - position: absolute; - top: 1rem; - left: 1rem; - background: linear-gradient(135deg, #ff8a80, #ff5722); - color: white; - padding: 0.5rem 1rem; - border-radius: 50px; - font-size: 0.8rem; - font-weight: 600; - box-shadow: 0 4px 12px rgba(255, 87, 34, 0.3); - z-index: 10; -} - -.streak-display::before { - content: "🔥 "; -} - -/* Achievement tag enhancements for theme compatibility */ -.achievement-tag { - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary-darker); - border: 1px solid var(--ifm-color-primary-light); - transition: all 0.3s ease; -} - -[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); -} -.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; +.back-button:hover { + background: var(--ifm-color-emphasis-200); + transform: translateX(-2px); } -.refresh-section { + +.sidebar-nav { 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; + gap: 8px; } -.filter-button:hover { - background: var(--ifm-color-primary-lightest); - transform: translateY(-2px); +/* Main Content */ +.dashboard-main-content { + flex: 1; + padding: 40px; + overflow-x: hidden; } -.filter-button.active { - background: var(--ifm-color-primary); - color: white; - box-shadow: 0 4px 15px rgba(var(--ifm-color-primary-rgb), 0.3); +/* Home Dashboard Styles */ +.dashboard-home-container { + max-width: 1200px; + margin: 0 auto; } -.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; +.dashboard-main-title { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: 16px; + color: var(--ifm-color-content); + text-align: center; } -.filter-button:disabled { - opacity: 0.6; - cursor: not-allowed; + +.dashboard-description { + font-size: 1.1rem; + color: var(--ifm-color-content-secondary); + text-align: center; + margin-bottom: 60px; + max-width: 600px; + margin-left: auto; + margin-right: auto; } -.filter-button .filter-icon { - margin-right: 0.5rem; - font-size: 1rem; +.dashboard-stats-section { + margin-bottom: 60px; } -@keyframes shimmer { - 0% { - transform: translateX(-100%); - } - 100% { - transform: translateX(100%); - } +.section-title { + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 30px; + text-align: center; + color: var(--ifm-color-content); } -.filter-button .filter-icon { - margin-right: 0.5rem; +.stat-cards-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 24px; + margin-bottom: 40px; } -/* 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; +.dashboard-stat-card { + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 16px; + padding: 32px 24px; text-align: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + transition: all 0.3s ease; + position: relative; + overflow: hidden; } -.rate-limit-warning h4 { - color: #856404; - margin-bottom: 0.5rem; +.dashboard-stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--ifm-color-primary), var(--ifm-color-primary-light)); } -.rate-limit-warning p { - color: #856404; - margin-bottom: 1rem; +.dashboard-stat-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + border-color: var(--ifm-color-primary-lightest); } -.rate-limit-timer { - font-size: 1.5rem; - font-weight: bold; - color: #dc3545; - margin: 1rem 0; +.dashboard-stat-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + background: var(--ifm-color-primary-lightest); + color: var(--ifm-color-primary); + border-radius: 16px; + margin-bottom: 16px; + font-size: 24px; } -/* Light theme rate limit warning */ -[data-theme="light"] .rate-limit-warning { - background: #fef3c7; - border: 1px solid #f59e0b; +.dashboard-stat-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 8px; + color: var(--ifm-color-content-secondary); } -[data-theme="light"] .rate-limit-warning h4, -[data-theme="light"] .rate-limit-warning p { - color: #92400e !important; +.dashboard-stat-value { + font-size: 2.5rem; + font-weight: 800; + margin-bottom: 8px; + color: var(--ifm-color-content); + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; } -[data-theme="light"] .rate-limit-timer { - color: #dc2626 !important; +.dashboard-stat-description { + font-size: 0.9rem; + color: var(--ifm-color-content-secondary); + line-height: 1.4; + margin: 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; - } +.loading-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--ifm-color-emphasis-200); + border-top: 3px solid var(--ifm-color-primary); + border-radius: 50%; + animation: spin 1s linear infinite; } -/* Filter loading state */ -.filter-loading { - opacity: 0.6; - pointer-events: none; +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } -/* Light theme comprehensive fixes */ -[data-theme="light"] .leaderboard-page-container { - color: #1a202c; +/* Discussions Section */ +.dashboard-discussions { + max-width: 1200px; + margin: 0 auto; } -[data-theme="light"] .leaderboard-content { - color: #1a202c; +.discussions-header { + text-align: center; + margin-bottom: 40px; } -[data-theme="light"] .leaderboard-item * { - color: inherit; +.discussions-header h1 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 12px; + color: var(--ifm-color-content); } -[data-theme="light"] .leaderboard-item .user-info * { - color: inherit; +.discussions-header p { + font-size: 1.1rem; + color: var(--ifm-color-content-secondary); } -/* Ensure all text in leaderboard is visible in light theme */ -[data-theme="light"] .leaderboard-item, -[data-theme="light"] .leaderboard-item .user-info, -[data-theme="light"] .leaderboard-item .user-stats, -[data-theme="light"] .leaderboard-item .achievements { - color: #1a202c !important; +.discussions-controls { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 30px; } -[data-theme="light"] .leaderboard-item .user-name, -[data-theme="light"] .leaderboard-item .user-username, -[data-theme="light"] .leaderboard-item .stat-text { - color: inherit !important; +.discussion-tabs { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; } -/* Force visibility for all leaderboard text elements */ -[data-theme="light"] .leaderboard-page-container * { - color: inherit; +.tab-button { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 8px; + cursor: pointer; + font-weight: 500; + transition: all 0.2s ease; + color: var(--ifm-color-content); } -[data-theme="light"] .leaderboard-page-container h1, -[data-theme="light"] .leaderboard-page-container h2, -[data-theme="light"] .leaderboard-page-container h3, -[data-theme="light"] .leaderboard-page-container h4, -[data-theme="light"] .leaderboard-page-container p { - color: #1a202c !important; +.tab-button:hover { + background: var(--ifm-color-emphasis-100); + border-color: var(--ifm-color-primary-lightest); } -/* Keep giveaway page text white in light theme, but table text black */ -[data-theme="light"] - .min-h-screen.bg-gradient-to-br - *:not(table):not(th):not(td) { - color: white !important; +.tab-button.active { + background: var(--ifm-color-primary); + color: white; + border-color: var(--ifm-color-primary); } -[data-theme="light"] .min-h-screen.bg-gradient-to-br table th, -[data-theme="light"] .min-h-screen.bg-gradient-to-br table td { - color: black !important; + +.search-and-sort { + display: flex; + gap: 16px; + align-items: center; + justify-content: center; + flex-wrap: wrap; } -/* ===== CONTRIBUTORS PAGE STYLING ===== */ +.search-bar { + display: flex; + align-items: center; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 8px; + padding: 0 12px; + min-width: 250px; +} -.contributors-page-container { - max-width: 1200px; - margin: 0 auto; - padding: 2rem; +.search-bar svg { + color: var(--ifm-color-content-secondary); + margin-right: 8px; } -.contributors-hero .hero-content { - text-align: center; - margin-bottom: 3rem; +.search-bar input { + border: none; + outline: none; + background: transparent; + padding: 12px 0; + flex: 1; + color: var(--ifm-color-content); } -.contributors-content { - margin-top: 3rem; +.search-bar input::placeholder { + color: var(--ifm-color-content-secondary); } -.contributors-header { - text-align: center; - margin-bottom: 3rem; +.search-and-sort select { + padding: 12px 16px; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 8px; + background: var(--ifm-background-surface-color); + color: var(--ifm-color-content); + cursor: pointer; } -.contributors-header h2 { - font-size: 2.5rem; - font-weight: 700; - color: var(--ifm-color-emphasis-900); - margin-bottom: 1rem; +.new-discussion-btn { + padding: 12px 20px; + background: var(--ifm-color-primary); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 500; + transition: all 0.2s ease; } -.contributors-header p { - font-size: 1.2rem; - color: var(--ifm-color-emphasis-600); +.new-discussion-btn:hover { + background: var(--ifm-color-primary-dark); + transform: translateY(-1px); } -.contributors-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; - margin-top: 2rem; +.discussions-main-content { + display: flex; + gap: 30px; } -.contributor-card { - background: var(--ifm-card-background-color); +.category-sidebar { + min-width: 200px; + background: var(--ifm-background-surface-color); border: 1px solid var(--ifm-color-emphasis-200); border-radius: 12px; - padding: 2rem; - text-align: center; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + padding: 20px; + height: fit-content; + position: sticky; + top: 20px; } -.contributor-card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); - border-color: var(--ifm-color-primary); +.category-sidebar h3 { + margin-bottom: 16px; + font-size: 1.1rem; + font-weight: 600; + color: var(--ifm-color-content); } -.contributor-avatar { - width: 80px; - height: 80px; - border-radius: 50%; - margin-bottom: 1rem; - border: 3px solid var(--ifm-color-primary-light); +.category-sidebar ul { + list-style: none; + padding: 0; + margin: 0; } -.contributor-avatar.placeholder { - background: var(--ifm-color-primary-lightest); +.category-sidebar li { display: flex; align-items: center; - justify-content: center; - font-size: 2rem; - color: var(--ifm-color-primary); - margin: 0 auto 1rem; + gap: 8px; + padding: 10px 12px; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--ifm-color-content-secondary); + font-weight: 500; +} + +.category-sidebar li:hover { + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-content); } -.contributor-card h3 { - font-size: 1.25rem; +.category-sidebar li.active { + background: var(--ifm-color-primary-lightest); + color: var(--ifm-color-primary); font-weight: 600; - color: var(--ifm-color-emphasis-900); - margin-bottom: 0.5rem; } -.contributor-card p { - color: var(--ifm-color-emphasis-600); - margin-bottom: 1rem; - font-size: 0.9rem; +.discussion-list { + flex: 1; } -.contributor-stats { +.loading-spinner-container { display: flex; justify-content: center; - flex-wrap: wrap; - gap: 0.5rem; + align-items: center; + padding: 60px; } -.contributor-stats span { - background: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary-darker); - padding: 0.25rem 0.75rem; - border-radius: 20px; - font-size: 0.8rem; - font-weight: 500; +.discussions-error-message, +.no-discussions-found { + text-align: center; + padding: 60px 20px; + color: var(--ifm-color-content-secondary); } -/* Dark theme support for contributors */ -[data-theme="dark"] .contributor-card { - background: var(--ifm-background-surface-color); - border-color: var(--ifm-color-emphasis-300); +/* Contributors Section */ +.dashboard-contributors { + max-width: 1200px; + margin: 0 auto; } -[data-theme="dark"] .contributor-avatar.placeholder { - background: var(--ifm-color-primary-darkest); +/* Giveaway Section */ +.giveaway-section { + text-align: center; + max-width: 600px; + margin: 0 auto; + padding: 60px 20px; } -[data-theme="dark"] .contributor-stats span { - background: var(--ifm-color-primary-darkest); - color: var(--ifm-color-primary-lighter); +.giveaway-section h1 { + font-size: 2.2rem; + font-weight: 700; + margin-bottom: 16px; + color: var(--ifm-color-content); } -/* Responsive design for contributors */ -@media (max-width: 768px) { - .contributors-page-container { - padding: 1rem; +.giveaway-content { + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 12px; + padding: 40px; + margin-top: 30px; +} + +/* Mobile Responsive */ +@media (max-width: 996px) { + .dashboard-layout { + flex-direction: column; + } + + .dashboard-sidebar { + display: none; + } + + .dashboard-menu-btn { + display: block; + } + + .dashboard-main-content { + padding: 80px 20px 40px; + } + + .dashboard-main-title { + font-size: 2rem; + } + + .dashboard-description { + font-size: 1rem; + margin-bottom: 40px; } - .contributors-grid { + .stat-cards-container { grid-template-columns: 1fr; - gap: 1.5rem; + gap: 16px; + } + + .dashboard-stat-card { + padding: 24px 20px; } - .contributors-header h2 { + .dashboard-stat-value { font-size: 2rem; + min-height: 50px; } -} -/* Light theme github buttons */ -[data-theme="light"] .github-profile-btn, -[data-theme="light"] .github-link { - background: var(--ifm-color-primary); - color: white !important; -} + .discussions-main-content { + flex-direction: column; + gap: 20px; + } -[data-theme="light"] .github-profile-btn:hover, -[data-theme="light"] .github-link:hover { - background: var(--ifm-color-primary-darker); - color: white !important; - text-decoration: none; -} + .category-sidebar { + position: static; + min-width: auto; + } -/* Light theme refresh button */ -[data-theme="light"] .refresh-button { - background: var(--ifm-color-primary); - color: white !important; -} + .category-sidebar ul { + display: flex; + flex-wrap: wrap; + gap: 8px; + } -[data-theme="light"] .refresh-button:hover:not(:disabled) { - background: var(--ifm-color-primary-darker); - color: white !important; -} + .category-sidebar li { + white-space: nowrap; + } -/* Light theme rate limit status */ -[data-theme="light"] .rate-limit-status { - color: #4a5568 !important; -} + .search-and-sort { + flex-direction: column; + align-items: stretch; + } -/* ===== COMPREHENSIVE LIGHT THEME LEADERBOARD FIXES ===== */ + .search-bar { + min-width: auto; + } -/* Force all leaderboard text to be visible in light theme */ -[data-theme="light"] .leaderboard-page-container, -[data-theme="light"] .leaderboard-page-container *, -[data-theme="light"] .leaderboard-content, -[data-theme="light"] .leaderboard-content *, -[data-theme="light"] .leaderboard-grid, -[data-theme="light"] .leaderboard-grid *, -[data-theme="light"] .leaderboard-item, -[data-theme="light"] .leaderboard-item * { - color: #1a202c !important; -} + .discussion-tabs { + justify-content: flex-start; + overflow-x: auto; + padding-bottom: 8px; + } -/* Override specific elements that need different colors */ -[data-theme="light"] .score-display, -[data-theme="light"] .score-display * { - color: white !important; + .tab-button { + white-space: nowrap; + flex-shrink: 0; + } } -[data-theme="light"] .rank-badge, -[data-theme="light"] .rank-badge * { - color: white !important; -} +@media (max-width: 768px) { + .dashboard-main-content { + padding: 80px 16px 40px; + } -[data-theme="light"] .github-profile-btn, -[data-theme="light"] .github-profile-btn *, -[data-theme="light"] .github-link, -[data-theme="light"] .github-link * { - color: white !important; -} + .dashboard-stat-card { + padding: 20px 16px; + } -[data-theme="light"] .refresh-button, -[data-theme="light"] .refresh-button * { - color: white !important; -} + .dashboard-stat-icon { + width: 56px; + height: 56px; + font-size: 22px; + } -/* Ensure primary colored elements remain visible */ -[data-theme="light"] .stat-value, -[data-theme="light"] .score-number { - color: var(--ifm-color-primary) !important; -} + .dashboard-stat-value { + font-size: 1.8rem; + } -/* Fix any remaining invisible text */ -[data-theme="light"] .leaderboard-page-header *, -[data-theme="light"] .leaderboard-stats *, -[data-theme="light"] .user-info *, -[data-theme="light"] .user-stats *, -[data-theme="light"] .achievements * { - color: inherit !important; -} + .discussions-header h1 { + font-size: 1.8rem; + } -/* Ensure proper contrast for all interactive elements */ -[data-theme="light"] .filter-button { - color: var(--ifm-color-primary) !important; - border-color: var(--ifm-color-primary) !important; + .category-sidebar { + padding: 16px; + } } -[data-theme="light"] .filter-button.active { - background: var(--ifm-color-primary) !important; - color: white !important; +/* Dark mode enhancements */ +[data-theme="dark"] .dashboard-stat-card { + background: var(--ifm-color-emphasis-100); + border-color: var(--ifm-color-emphasis-300); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } -/* Final fallback for any missed elements */ -[data-theme="light"] .dashboard-main * { - color: inherit; +[data-theme="dark"] .dashboard-stat-card:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); } -[data-theme="light"] .dashboard-main { - color: #1a202c; +[data-theme="dark"] .dashboard-stat-icon { + background: rgba(99, 102, 241, 0.2); } + +[data-theme="dark"] .loading-spinner { + border-color: var(--ifm-color-emphasis-300); + border-top-color: var(--ifm-color-primary); +} \ No newline at end of file diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 0279882c..08dfac52 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { JSX, useEffect, useState } from "react"; import Layout from "@theme/Layout"; import Head from "@docusaurus/Head"; import BrowserOnly from "@docusaurus/BrowserOnly"; @@ -29,11 +29,12 @@ import { Calendar, BarChart3, ArrowLeft, + GitFork, } from "lucide-react"; import NavbarIcon from "@site/src/components/navbar/NavbarIcon"; import "@site/src/components/discussions/discussions.css"; import "./dashboard.css"; -import LeaderBoard from "./LeaderBoard/leaderboard"; // ✅ NEW IMPORT FOR LEADERBOARD COMPONENT +import LeaderBoard from "./LeaderBoard/leaderboard"; type DiscussionTab = "discussions" | "trending" | "unanswered"; type SortOption = "most_popular" | "latest" | "oldest"; @@ -45,38 +46,11 @@ type Category = | "show-and-tell" | "general"; -interface LeaderboardEntry { - rank: number; - name: string; - username?: string; - avatar: string; - contributions: number; - repositories: number; - achievements: string[]; - github_url: string; - score?: number; - streak?: number; - postManTag?: boolean; - web3hack?: boolean; - weeklyContributions?: number; - monthlyContributions?: number; -} - -type FilterPeriod = "weekly" | "monthly" | "overall"; - interface DashboardStats { totalContributors: number; totalRepositories: number; totalStars: number; totalForks: number; - topContributors: LeaderboardEntry[]; -} - -interface RateLimitInfo { - isLimited: boolean; - resetTime?: number; - remaining?: number; - limit?: number; } const categories: Category[] = [ @@ -104,8 +78,6 @@ const DashboardContent: React.FC = () => { const [discussions, setDiscussions] = useState([]); const [discussionsLoading, setDiscussionsLoading] = useState(true); const [discussionsError, setDiscussionsError] = useState(null); - const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); - const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); const [showDashboardMenu, setShowDashboardMenu] = useState(false); // Close dashboard menu when clicking outside @@ -127,24 +99,6 @@ const DashboardContent: React.FC = () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showDashboardMenu]); - - // ❌ REMOVE THE FOLLOWING STATE VARIABLES FOR THE LEADERBOARD - /* - 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 @@ -182,76 +136,6 @@ const DashboardContent: React.FC = () => { } }; - // ❌ REMOVE THE FOLLOWING useEffect HOOK FOR LEADERBOARD DATA - /* - // Fetch leaderboard data when contributors tab is active or on initial load - useEffect(() => { - if (activeTab === "contributors") { - fetchLeaderboardData(); - } - }, [activeTab]); - */ - - // ❌ REMOVE THE FOLLOWING useEffect HOOK FOR INITIAL DEMO DATA - /* - // Load initial demo data if no data exists - useEffect(() => { - if (leaderboardData.length === 0) { - const initialData: LeaderboardEntry[] = [ - { - rank: 1, - name: "sanjay-kv", - username: "sanjay-kv", - avatar: "https://avatars.githubusercontent.com/u/30715153?v=4", - contributions: 250, - repositories: 25, - score: 2500, - achievements: ["Top Contributor", "Founder", "Maintainer"], - github_url: "https://github.com/sanjay-kv", - streak: 15, - postManTag: false, - web3hack: false, - weeklyContributions: 35, - monthlyContributions: 120, - }, - { - rank: 2, - name: "vansh-codes", - username: "vansh-codes", - avatar: "https://avatars.githubusercontent.com/u/114163734?v=4", - contributions: 180, - repositories: 22, - score: 1800, - 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, - name: "Hemu21", - username: "Hemu21", - avatar: "https://avatars.githubusercontent.com/u/106808387?v=4", - contributions: 120, - repositories: 18, - score: 1200, - achievements: ["Power User", "Star Contributor", "Consistent"], - github_url: "https://github.com/Hemu21", - streak: 5, - postManTag: false, - web3hack: false, - weeklyContributions: 18, - monthlyContributions: 60, - }, - ]; - setLeaderboardData(initialData); - } - }, [leaderboardData.length]); - */ - // Discussion handlers const handleDiscussionTabChange = (tab: DiscussionTab) => { setActiveDiscussionTab(tab); @@ -307,7 +191,7 @@ const DashboardContent: React.FC = () => { // First apply tab filter switch (activeDiscussionTab) { case "trending": - return discussion.reactions.total_count > 5; // Show discussions with more than 5 reactions + return discussion.reactions.total_count > 5; case "unanswered": return discussion.comments === 0; default: @@ -380,7 +264,7 @@ const DashboardContent: React.FC = () => { new Date(b.created_at).getTime() ); default: - return b.reactions.total_count - a.reactions.total_count; // most_popular + return b.reactions.total_count - a.reactions.total_count; } }); }; @@ -390,367 +274,11 @@ const DashboardContent: React.FC = () => { [discussions, activeDiscussionTab, selectedCategory, searchQuery, sortBy] ); - // ❌ REMOVE THE FOLLOWING RATE LIMIT AND LEADERBOARD DATA FETCHING LOGIC - /* - // 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 - const reposResponse = await fetch('https://api.github.com/orgs/recodehive/repos?type=public&per_page=100'); - - if (!reposResponse.ok) { - if (reposResponse.status === 403) { - throw new Error('GitHub API rate limit exceeded'); - } - throw new Error(`GitHub API request failed: ${reposResponse.status}`); - } - - const repos = await reposResponse.json(); - - if (!Array.isArray(repos)) { - throw new Error("Invalid GitHub API response format"); - } - - // 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 - - // Fetch contributors for each repository with delay to avoid rate limits - for (let i = 0; i < topRepos.length; i++) { - const repo = topRepos[i]; - try { - // 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") { - 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, - avatar_url: contributor.avatar_url, - html_url: contributor.html_url, - contributions: contributor.contributions, - repositories: 1, - weeklyContributions: contributionData.weeklyContributions, - monthlyContributions: - contributionData.monthlyContributions, - }); - } - } - }); - } - } - } 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) - .map((contributor, index) => { - const score = contributor.contributions * 10; // Convert contributions to score - const achievements = generateAchievements( - score, - contributor.contributions - ); - - return { - rank: index + 1, - name: contributor.login, - username: contributor.login, - avatar: contributor.avatar_url, - contributions: contributor.contributions, - repositories: contributor.repositories, - score, - achievements, - github_url: contributor.html_url, - 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 - - setLeaderboardData(transformedData); - } catch (error) { - console.error("Error fetching RecodeHive contributors data:", error); - setLeaderboardError(error.message); - - // Load fallback demo data - console.warn("Using fallback leaderboard data due to GitHub API limitations"); - setLeaderboardError("GitHub API rate limit reached. Showing demo data."); - const demoData: LeaderboardEntry[] = [ - { - rank: 1, - name: "sanjay-kv", - username: "sanjay-kv", - avatar: "https://avatars.githubusercontent.com/u/30715153?v=4", - contributions: 250, - repositories: 25, - score: 2500, - achievements: ["Top Contributor", "Founder", "Maintainer"], - github_url: "https://github.com/sanjay-kv", - streak: 15, - postManTag: false, - web3hack: false, - weeklyContributions: 35, - monthlyContributions: 120, - }, - { - rank: 2, - name: "vansh-codes", - username: "vansh-codes", - avatar: "https://avatars.githubusercontent.com/u/114163734?v=4", - contributions: 180, - repositories: 22, - score: 1800, - 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, - name: "Hemu21", - username: "Hemu21", - avatar: "https://avatars.githubusercontent.com/u/106808387?v=4", - contributions: 120, - repositories: 18, - score: 1200, - 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 { - setIsLoadingLeaderboard(false); - } - }; - - const generateAchievements = ( - score: number, - contributions: number - ): string[] => { - const achievements: string[] = []; - 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"); - 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"); - if (score >= 7000) achievements.push("Legend"); - if (contributions >= 150) achievements.push("PR Master"); - return achievements.slice(0, 3); - }; - */ - const handleTabChange = ( tab: "home" | "discuss" | "giveaway" | "contributors" ) => { setActiveTab(tab); - setIsMobileSidebarOpen(false); // Close mobile sidebar - setShowDashboardMenu(false); // Close dashboard menu + setShowDashboardMenu(false); if (tab === "discuss") { history.push("#discuss"); window.scrollTo(0, 0); @@ -764,116 +292,6 @@ const DashboardContent: React.FC = () => { } }; - // ❌ REMOVE THE FOLLOWING FILTER FUNCTIONS FOR THE LEADERBOARD - /* - // 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 contributors page - will automatically refresh when the limit resets. -

- {retryTimer && ( -
- Retry in: {Math.floor(retryTimer / 60)}: - {(retryTimer % 60).toString().padStart(2, "0")} -
- )} - -
- ) : 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, githubContributorsCount, @@ -888,7 +306,6 @@ const DashboardContent: React.FC = () => { totalRepositories: 0, totalStars: 0, totalForks: 0, - topContributors: [], // You can keep this as an empty array or an initial state if needed }); useEffect(() => { @@ -897,7 +314,6 @@ const DashboardContent: React.FC = () => { totalRepositories: githubReposCount, totalStars: githubStarCount, totalForks: githubForksCount, - topContributors: [], // This will be handled by the new LeaderBoard component }); }, [ githubContributorsCount, @@ -907,7 +323,7 @@ const DashboardContent: React.FC = () => { ]); const StatCard: React.FC<{ - icon: string; + icon: React.ReactNode; title: string; value: number; valueText: string; @@ -926,7 +342,7 @@ const DashboardContent: React.FC = () => {

{title}

{loading ? ( -
Loading...
+
) : ( { onClick={() => setShowDashboardMenu(false)} /> )} -
-

Dashboard Menu

- -
- - - {/* Dashboard navigation items */} -
-
{ - handleTabChange("home"); - setShowDashboardMenu(false); - }} - > - Home -
-
{ - handleTabChange("discuss"); - setShowDashboardMenu(false); - }} - > - Discussions -
-
{ - handleTabChange("contributors"); - setShowDashboardMenu(false); - }} - > - Contributors +
+
+

Dashboard Menu

+
-
{ - handleTabChange("giveaway"); - setShowDashboardMenu(false); - }} - > - Giveaways + + {/* Dashboard navigation items */} +
+
{ + handleTabChange("home"); + setShowDashboardMenu(false); + }} + > + Home +
+
{ + handleTabChange("discuss"); + setShowDashboardMenu(false); + }} + > + Discussions +
+
{ + handleTabChange("contributors"); + setShowDashboardMenu(false); + }} + > + LeaderBoard +
+
{ + handleTabChange("giveaway"); + setShowDashboardMenu(false); + }} + > + Giveaways +
-
@@ -1216,7 +633,7 @@ const DashboardContent: React.FC = () => { )} - {/* ✅ REPLACED CONTRIBUTORS SECTION WITH THE NEW LEADERBOARD COMPONENT */} + {/* Contributors section with new LeaderBoard component */} {activeTab === "contributors" && ( Date: Sat, 13 Sep 2025 14:41:31 +0530 Subject: [PATCH 04/11] Some content updates --- .../dashboard/LeaderBoard/leaderboard.tsx | 276 +++++++----------- src/pages/dashboard/dashboard.css | 1 + 2 files changed, 113 insertions(+), 164 deletions(-) diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx index 53d151c5..b165f554 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -1,3 +1,4 @@ +// src/pages/dashboard/LeaderBoard/leaderboard.tsx import React, { JSX, useEffect, useState } from "react"; import { motion } from "framer-motion"; import { @@ -10,20 +11,11 @@ import { } from "react-icons/fa"; import { ChevronRight, ChevronLeft } from "lucide-react"; import { useColorMode } from "@docusaurus/theme-common"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; // ✅ New import +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -const GITHUB_REPO = "recodehive/recode-website"; // Change to your repo -// ❌ Remove this line, it will cause the "process is not defined" error -// const token = process.env.DOCUSAURUS_GIT_TOKEN; +const GITHUB_ORG = "recodehive"; // <- Set your organization here +const POINTS_PER_PR = 10; -// Points configuration for different PR levels -const POINTS: Record = { - "level-1": 3, // Easy - "level-2": 7, // Medium - "level-3": 10, // Hard/Feature -}; - -// Types interface Contributor { username: string; avatar: string; @@ -38,24 +30,18 @@ interface Stats { flooredTotalPoints: number; } -interface BadgeProps { - count: number; - label: string; - color: { background: string; color: string }; -} - interface User { login: string; avatar_url: string; html_url: string; } -interface PullRequest { +interface PullRequestItem { user: User; - labels: { name: string }[]; + merged_at?: string | null; } -function Badge({ count, label, color }: BadgeProps) { +function Badge({ count, label, color }: { count: number; label: string; color: { background: string; color: string } }) { return ( { + const fetchAllOrgRepos = async (headers: Record) => { + const repos: any[] = []; + let page = 1; + while (true) { + const resp = await fetch(`https://api.github.com/orgs/${GITHUB_ORG}/repos?type=public&per_page=100&page=${page}`, { + headers, + }); + if (!resp.ok) { + throw new Error(`Failed to fetch org repos: ${resp.status} ${resp.statusText}`); + } + const data = await resp.json(); + repos.push(...data); + if (!Array.isArray(data) || data.length < 100) break; + page++; + } + return repos; + }; + + const fetchMergedPRsForRepo = async (repoName: string, headers: Record) => { + const mergedPRs: PullRequestItem[] = []; + let page = 1; + while (true) { + // list PRs (closed) then filter merged + const resp = await fetch( + `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${page}`, + { headers } + ); + if (!resp.ok) { + // if a particular repo fails (permissions, archived, etc.) skip it + console.warn(`Failed to fetch PRs for ${repoName}: ${resp.status} ${resp.statusText}`); + break; + } + const prs: PullRequestItem[] = await resp.json(); + if (!Array.isArray(prs) || prs.length === 0) break; + + const merged = prs.filter((pr) => Boolean(pr.merged_at)); + mergedPRs.push(...merged); + + if (prs.length < 100) break; + page++; + } + return mergedPRs; + }; + const fetchLeaderboard = async () => { if (!token) { - setError("GitHub token not found. Please set DOCUSAURUS_GIT_TOKEN."); + setError("GitHub token not found. Please set customFields.gitToken or DOCUSAURUS_GIT_TOKEN."); setLoading(false); return; } @@ -102,65 +132,47 @@ export default function LeaderBoard(): JSX.Element { setError(null); try { - const headers = { + const headers: Record = { Authorization: `token ${token}`, + Accept: "application/vnd.github.v3+json", }; - const fetchPRs = async (state: "open" | "closed") => { - let allPRs: PullRequest[] = []; - let page = 1; - let hasNextPage = true; - - while (hasNextPage) { - const response = await fetch( - `https://api.github.com/repos/${GITHUB_REPO}/pulls?state=${state}&per_page=100&page=${page}`, - { headers } - ); - - if (!response.ok) { - throw new Error( - `Failed to fetch pull requests: ${response.statusText}` - ); - } - - const prs: PullRequest[] = await response.json(); - allPRs = allPRs.concat(prs); - hasNextPage = prs.length === 100; - page++; - } - return allPRs; - }; - - const [openPRs, closedPRs] = await Promise.all([ - fetchPRs("open"), - fetchPRs("closed"), - ]); - - const allPRs = [...openPRs, ...closedPRs]; + // 1) fetch all repos in the org (paged) + const repos = await fetchAllOrgRepos(headers); + // 2) for each repo, fetch merged PRs and aggregate const contributorMap = new Map(); - - for (const pr of allPRs) { - const username = pr.user.login; - if (!contributorMap.has(username)) { - contributorMap.set(username, { - username, - avatar: pr.user.avatar_url, - profile: pr.user.html_url, - points: 0, - prs: 0, - }); - } - - const contributor = contributorMap.get(username)!; - contributor.prs++; - - let prPoints = 0; - const label = pr.labels.find((l) => l.name in POINTS)?.name; - if (label) { - prPoints = POINTS[label]; + let totalMergedPRs = 0; + + // sequentially to be friendly to rate limits; if you expect small org, you can parallelize + for (const repo of repos) { + // skip forks or archived repos if desired + if (repo.archived) continue; + + const repoName = repo.name; + try { + const mergedPRs = await fetchMergedPRsForRepo(repoName, headers); + totalMergedPRs += mergedPRs.length; + + for (const pr of mergedPRs) { + const username = pr.user.login; + if (!contributorMap.has(username)) { + contributorMap.set(username, { + username, + avatar: pr.user.avatar_url, + profile: pr.user.html_url, + points: 0, + prs: 0, + }); + } + const contributor = contributorMap.get(username)!; + contributor.prs++; + contributor.points += POINTS_PER_PR; // fixed points per merged PR + } + } catch (repoErr) { + console.warn(`Skipping repo ${repoName} due to error:`, repoErr); + continue; } - contributor.points += prPoints; } const sortedContributors = Array.from(contributorMap.values()).sort( @@ -169,22 +181,19 @@ export default function LeaderBoard(): JSX.Element { setContributors(sortedContributors); setStats({ - flooredTotalPRs: allPRs.length, + flooredTotalPRs: totalMergedPRs, totalContributors: sortedContributors.length, - flooredTotalPoints: sortedContributors.reduce( - (sum, c) => sum + c.points, - 0 - ), + flooredTotalPoints: sortedContributors.reduce((sum, c) => sum + c.points, 0), }); setLoading(false); } catch (e: any) { - setError(e.message); + setError(e?.message || String(e)); setLoading(false); } }; fetchLeaderboard(); - }, [token]); // The token is now a dependency of the useEffect hook + }, [token]); const filteredContributors = contributors.filter((contributor) => contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) @@ -195,7 +204,7 @@ export default function LeaderBoard(): JSX.Element { const indexOfFirst = indexOfLast - itemsPerPage; const currentItems = filteredContributors.slice(indexOfFirst, indexOfLast); - const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + const paginate = (pageNumber: number) => setCurrentPage(Math.max(1, Math.min(pageNumber, totalPages))); const renderPaginationButtons = () => { const pages = []; @@ -234,9 +243,7 @@ export default function LeaderBoard(): JSX.Element { maxWidth: 960, margin: "0 auto", background: isDark ? "#1f2937" : "#f9fafb", - boxShadow: isDark - ? "0 4px 6px rgba(0,0,0,0.2)" - : "0 4px 6px rgba(0,0,0,0.1)", + boxShadow: isDark ? "0 4px 6px rgba(0,0,0,0.2)" : "0 4px 6px rgba(0,0,0,0.1)", borderRadius: 8, padding: "24px", }} @@ -256,9 +263,9 @@ export default function LeaderBoard(): JSX.Element { Recode Hive Leaderboard

- Top contributors to the open-source project + Top contributors across the {GITHUB_ORG} organization -

-
- Total PRs -
+
{stats.flooredTotalPRs}
+
Merged PRs
-
- {stats.flooredTotalPoints} -
-
- Total Points -
+
{stats.flooredTotalPoints}
+
Total Points
-
- {stats.totalContributors} -
-
- Total Contributors -
+
{stats.totalContributors}
+
Total Contributors
)} @@ -393,7 +373,7 @@ export default function LeaderBoard(): JSX.Element { animation: "spin 1s linear infinite", margin: "0 auto", }} - >
+ />

Loading leaderboard...

)} @@ -412,7 +392,6 @@ export default function LeaderBoard(): JSX.Element { {!loading && !error && filteredContributors.length > 0 && (
- {/* Table */}
-
- {indexOfFirst + index + 1} -
+
{indexOfFirst + index + 1}
{contributor.username}
@@ -496,16 +455,8 @@ export default function LeaderBoard(): JSX.Element {
)} - {/* Pagination Controls */} {totalPages > 1 && ( -
+
)}
+
); -} \ No newline at end of file +} diff --git a/src/pages/dashboard/dashboard.css b/src/pages/dashboard/dashboard.css index f39009b6..7b745d99 100644 --- a/src/pages/dashboard/dashboard.css +++ b/src/pages/dashboard/dashboard.css @@ -3,6 +3,7 @@ display: flex; min-height: 100vh; background: var(--ifm-background-color); + position:relative; } /* Dashboard Menu Button - Mobile Only */ From f88ab1fb4c44ddd2e516b1d687edb908ecbd6249 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 14:42:42 +0530 Subject: [PATCH 05/11] more updates --- src/pages/dashboard/LeaderBoard/leaderboard.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx index b165f554..49ade702 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -13,7 +13,7 @@ import { ChevronRight, ChevronLeft } from "lucide-react"; import { useColorMode } from "@docusaurus/theme-common"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -const GITHUB_ORG = "recodehive"; // <- Set your organization here +const GITHUB_ORG = "recodehive"; const POINTS_PER_PR = 10; interface Contributor { @@ -144,9 +144,8 @@ export default function LeaderBoard(): JSX.Element { const contributorMap = new Map(); let totalMergedPRs = 0; - // sequentially to be friendly to rate limits; if you expect small org, you can parallelize + for (const repo of repos) { - // skip forks or archived repos if desired if (repo.archived) continue; const repoName = repo.name; From 26c75325ea6031c14b29574daa6f0afb7d2efc01 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 19:15:06 +0530 Subject: [PATCH 06/11] Fixed the leaderboard --- .../dashboard/LeaderBoard/leaderboard.css | 652 +++++++++--------- .../dashboard/LeaderBoard/leaderboard.tsx | 454 ++++++------ 2 files changed, 537 insertions(+), 569 deletions(-) diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.css b/src/pages/dashboard/LeaderBoard/leaderboard.css index 2173d5da..580d8216 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.css +++ b/src/pages/dashboard/LeaderBoard/leaderboard.css @@ -48,280 +48,306 @@ color: #b3b3b3; } -/* Search */ -.search-container { - display: flex; - justify-content: center; +/* Top Performers Section */ +.top-performers-container { + text-align: center; + margin-bottom: 40px; +} + +.top-performers-title { + font-size: 24px; + font-weight: 700; margin-bottom: 24px; } -.search-wrapper { - position: relative; - width: 100%; - max-width: 320px; +.light .top-performers-title { + color: #333; } -.search-input { - width: 100%; - padding: 8px 16px 8px 36px; - border-radius: 8px; - font-size: 15px; - outline: none; - transition: border-color 0.3s ease, background-color 0.3s ease; +.dark .top-performers-title { + color: #f1f1f1; } -.light .search-input { - border: 1px solid #ddd; +.top-performers-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 24px; + justify-items: center; + align-items: end; +} + +.top-performer-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 24px; + border-radius: 12px; + transition: all 0.3s ease; +} + +.light .top-performer-card { background: #fff; - color: #222; + border: 1px solid #e2e8f0; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } -.dark .search-input { +.dark .top-performer-card { + background: #2b303b; border: 1px solid #444; - background: #23272f; - color: #fff; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } -.search-icon { +.top-performer-card:hover { + transform: translateY(-5px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); +} + +.top-performer-card .avatar.large { + width: 96px; + height: 96px; + border-radius: 50%; + margin-bottom: 12px; + border: 4px solid #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.dark .top-performer-card .avatar.large { + border: 4px solid #23272f; +} + +.rank-overlay { position: absolute; - left: 12px; - top: 50%; - transform: translateY(-50%); + top: 8px; + left: 8px; + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + border-radius: 50%; + font-weight: bold; + color: #fff; + border: 2px solid #fff; } -.light .search-icon { - color: #aaa; +.rank-overlay.top-1 { + background-color: #f59e0b; + border-color: #fde68a; + box-shadow: 0 0 10px #fde047; } -.dark .search-icon { - color: #b3b3b3; +.rank-overlay.top-2 { + background-color: #6b7280; + border-color: #d1d5db; +} + +.rank-overlay.top-3 { + background-color: #964b00; + border-color: #fcd34d; +} + +.performer-info { + margin-top: 8px; +} + +.performer-info .username-link { + font-weight: bold; + font-size: 1.1rem; + color: #6366f1; + text-decoration: none; } /* Stats Grid */ .stats-grid { - display: flex; - gap: 18px; - margin-bottom: 48px; - flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 40px; +} + +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: 1fr; + } } .stat-card { - flex: 1; - min-width: 220px; padding: 24px; - border-radius: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - transition: background 0.3s ease, border-color 0.3s ease; + border-radius: 12px; + transition: all 0.3s ease; } .light .stat-card { - background: linear-gradient(135deg, #e0e7ff, #f3f4f6); - border: 1px solid #eee; + background: #fff; + border: 1px solid #e2e8f0; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } .dark .stat-card { - background: linear-gradient(135deg, #23272f, #1a1d23); + background: #2b303b; border: 1px solid #444; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); +} + +.stat-card:hover { + transform: translateY(-3px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); } .stat-content { display: flex; align-items: center; + gap: 16px; } .stat-icon { - padding: 12px; - border-radius: 12px; - font-size: 22px; - margin-right: 16px; + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 48px; + border-radius: 50%; + color: #fff; + font-size: 20px; } .stat-icon.users { - background: rgba(59, 130, 246, 0.2); - color: #2563eb; -} - -.dark .stat-icon.users { - background: rgba(59, 130, 246, 0.2); - color: #60a5fa; + background: #3b82f6; } .stat-icon.prs { - background: rgba(16, 185, 129, 0.2); - color: #059669; -} - -.dark .stat-icon.prs { - background: rgba(16, 185, 129, 0.2); - color: #34d399; + background: #ef4444; } .stat-icon.points { - background: rgba(139, 92, 246, 0.2); - color: #7c3aed; -} - -.dark .stat-icon.points { - background: rgba(139, 92, 246, 0.2); - color: #a78bfa; -} - -.stat-label { - font-size: 14px; - margin: 0; -} - -.light .stat-label { - color: #555; -} - -.dark .stat-label { - color: #b3b3b3; + background: #10b981; } .stat-value { - font-size: 22px; - font-weight: 700; - margin: 0; + font-size: 2rem; + font-weight: 800; + margin-bottom: 4px; } .light .stat-value { - color: #222; + color: #333; } .dark .stat-value { - color: #fff; + color: #f1f1f1; } -/* Skeleton Loader */ -.skeleton-loader { - border-radius: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); - overflow: hidden; - margin-bottom: 24px; +.stat-label { + font-size: 14px; + text-transform: uppercase; + font-weight: bold; } -.skeleton-loader.light { - background: #fff; - border: 1px solid #eee; +.light .stat-label { + color: #666; } -.skeleton-loader.dark { - background: #23272f; - border: 1px solid #444; +.dark .stat-label { + color: #aaa; } -.skeleton-header { +/* Search */ +.search-container { display: flex; - justify-content: space-between; - padding: 16px; - font-size: 15px; - font-weight: 500; - background: #f6f6f6; - border-bottom: 1px solid #eee; + justify-content: center; + margin-bottom: 40px; } -.skeleton-row { - display: flex; - align-items: center; - padding: 16px; - gap: 16px; +.search-wrapper { + position: relative; + width: 100%; + max-width: 500px; } -.skeleton-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - background: #e5e7eb; - animation: pulse 1.5s infinite; +.search-icon { + position: absolute; + top: 50%; + left: 16px; + transform: translateY(-50%); + font-size: 18px; } -.skeleton-avatar.large { - width: 40px; - height: 40px; +.light .search-icon { + color: #666; } -.skeleton-info { - flex: 1; - display: flex; - flex-direction: column; - gap: 8px; +.dark .search-icon { + color: #aaa; } -.skeleton-bar { - height: 16px; - width: 96px; +.search-input { + width: 100%; + padding: 12px 12px 12px 48px; border-radius: 8px; - background: #e5e7eb; - animation: pulse 1.5s infinite; + font-size: 16px; } -.skeleton-badges { - display: flex; - gap: 8px; -} - -.skeleton-badge { - width: 48px; - height: 24px; - border-radius: 9999px; - background: #e5e7eb; - animation: pulse 1.5s infinite; +.light .search-input { + background: #fff; + border: 1px solid #e2e8f0; + color: #333; } -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.5; } - 100% { opacity: 1; } +.dark .search-input { + background: #2b303b; + border: 1px solid #444; + color: #f1f1f1; } -/* Contributors Container */ +/* Contributors List */ .contributors-container { - border-radius: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + border-radius: 12px; overflow: hidden; - margin: 0 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } .light .contributors-container { - border: 1px solid #eee; + background: #fff; + border: 1px solid #e2e8f0; } .dark .contributors-container { + background: #2b303b; border: 1px solid #444; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } -/* Contributors List */ -.contributors-list { - /* No additional styles needed */ +.contributors-header { + display: grid; + grid-template-columns: 0.5fr 0.5fr 2fr 1fr 1fr; + padding: 16px 24px; + font-weight: bold; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid #e2e8f0; } -.no-contributors { - text-align: center; - padding: 32px 0; +.light .contributors-header { + background: #f8fafc; + color: #666; + border-bottom: 1px solid #e2e8f0; } -.light .no-contributors { - color: #555; -} - -.dark .no-contributors { +.dark .contributors-header { + background: #323742; color: #b3b3b3; + border-bottom: 1px solid #444; } .contributor-row { - display: flex; + display: grid; + grid-template-columns: 0.5fr 0.5fr 2fr 1fr 1fr; align-items: center; padding: 16px 24px; - transition: background-color 0.3s ease; -} - -.light .contributor-row { - border-bottom: 1px solid #eee; -} - -.dark .contributor-row { - border-bottom: 1px solid #444; + transition: background-color 0.2s ease; } .light .contributor-row.even { @@ -329,147 +355,101 @@ } .light .contributor-row.odd { - background: #f6f6f6; + background: #f8fafc; } .dark .contributor-row.even { - background: #23272f; + background: #2b303b; } .dark .contributor-row.odd { - background: #1a1d23; -} - -/* Rank Badge */ -.rank-badge { - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - font-weight: 500; - margin-right: 16px; -} - -.rank-badge.top-1 { - background: rgba(253, 224, 71, 0.2); - color: #ca8a04; -} - -.dark .rank-badge.top-1 { - background: rgba(253, 224, 71, 0.2); - color: #fde047; -} - -.rank-badge.top-2 { - background: rgba(156, 163, 175, 0.2); - color: #6b7280; + background: #323742; } -.dark .rank-badge.top-2 { - background: rgba(156, 163, 175, 0.2); - color: #d1d5db; +.contributor-row:hover { + background-color: #f1f5f9; } -.rank-badge.top-3 { - background: rgba(251, 191, 36, 0.2); - color: #f59e42; +.dark .contributor-row:hover { + background-color: #3b424f; } -.dark .rank-badge.top-3 { - background: rgba(251, 191, 36, 0.2); - color: #fbbf24; -} - -.rank-badge.regular { - background: #e5e7eb; - color: #555; -} - -.dark .rank-badge.regular { - background: #23272f; - color: #b3b3b3; +.contributor-cell { + padding: 0 8px; } -/* Avatar */ .avatar { width: 40px; height: 40px; border-radius: 50%; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); - margin-right: 16px; -} - -.light .avatar { border: 2px solid #fff; } .dark .avatar { - border: 2px solid #444; -} - -/* User Info */ -.user-info { - flex: 1; + border-color: #2b303b; } .username-link { - font-weight: 500; - font-size: 15px; + font-weight: bold; + color: #6366f1; text-decoration: none; - margin-bottom: 4px; - display: block; - transition: color 0.3s ease; + font-size: 1.1rem; } .light .username-link { - color: #222; + color: #3b82f6; } .dark .username-link { - color: #fff; + color: #60a5fa; } -.username-link:hover { - text-decoration: underline; +/* Badges */ +.badges-container { + display: flex; + gap: 8px; + margin-top: 8px; } -.contributions-link { - font-size: 13px; - text-decoration: none; - margin-bottom: 4px; - display: block; - transition: color 0.3s ease; +.badge { + display: inline-block; + padding: 4px 10px; + border-radius: 9999px; + font-size: 12px; + font-weight: bold; } -.light .contributions-link { - color: #555; +.rank-badge { + display: flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + border-radius: 50%; + font-weight: bold; + color: #fff; } -.dark .contributions-link { - color: #b3b3b3; +.rank-badge.top-1 { + background-color: #f59e0b; } -.contributions-link:hover { - text-decoration: underline; +.rank-badge.top-2 { + background-color: #6b7280; } -.badges-container { - display: flex; - gap: 8px; - margin-top: 8px; +.rank-badge.top-3 { + background-color: #964b00; } -/* Badge */ -.badge { - display: flex; - align-items: center; - padding: 4px 12px; - border-radius: 9999px; - font-size: 13px; - font-weight: 500; +.rank-badge.regular { + background: #e5e7eb; + color: #666; +} + +.dark .rank-badge.regular { + background: #444; + color: #ccc; } /* Pagination */ @@ -478,48 +458,73 @@ justify-content: center; align-items: center; gap: 8px; - padding: 16px 0; + padding: 24px 0; } -.light .pagination { - border-top: 1px solid #eee; +.pagination-btn { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + cursor: pointer; + transition: all 0.2s ease; } -.dark .pagination { - border-top: 1px solid #444; +.light .pagination-btn { + background: #e5e7eb; + color: #6b7280; +} + +.dark .pagination-btn { + background: #374151; + color: #d1d5db; +} + +.pagination-btn:hover:not(.disabled) { + background: #d1d5db; +} + +.dark .pagination-btn:hover:not(.disabled) { + background: #4b5563; +} + +.pagination-btn.disabled { + opacity: 0.5; + cursor: not-allowed; } -.pagination-btn, .page-btn { - padding: 8px 16px; + display: flex; + justify-content: center; + align-items: center; + width: 36px; + height: 36px; border-radius: 8px; border: none; - font-weight: 500; cursor: pointer; - margin: 0 4px; - transition: all 0.3s ease; + font-weight: bold; + transition: all 0.2s ease; } -.light .pagination-btn, .light .page-btn { - background: #e0e7ff; - color: #3730a3; + background: transparent; + color: #666; } -.dark .pagination-btn, .dark .page-btn { - background: #23272f; - color: #a78bfa; + background: transparent; + color: #b3b3b3; } -.pagination-btn:hover:not(.disabled), -.page-btn:hover:not(.active) { - opacity: 0.8; +.page-btn:hover { + background: #e5e7eb; } -.pagination-btn.disabled { - opacity: 0.5; - cursor: not-allowed; +.dark .page-btn:hover { + background: #374151; } .page-btn.active { @@ -570,55 +575,72 @@ display: inline-flex; align-items: center; padding: 8px 16px; - background: #6366f1; - color: #fff; - font-size: 15px; - font-weight: 500; - border-radius: 8px; + border-radius: 9999px; + font-weight: bold; text-decoration: none; - transition: background-color 0.3s ease; + transition: all 0.3s ease; +} + +.light .cta-button { + background: #2563eb; + color: #fff; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .dark .cta-button { background: #3b82f6; + color: #fff; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } .cta-button:hover { - opacity: 0.9; + transform: translateY(-2px); + background: #1d4ed8; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.dark .cta-button:hover { + background: #1e40af; } -/* Responsive Design */ +/* Responsive design */ @media (max-width: 768px) { .leaderboard-container { - padding: 16px 4px; + padding: 24px 8px; } - - .title { - font-size: 24px; + .top-performers-grid { + grid-template-columns: 1fr; + gap: 20px; } - - .subtitle { - font-size: 15px; - } - .stats-grid { - flex-direction: column; - gap: 12px; + grid-template-columns: 1fr; } - - .stat-card { - min-width: auto; + .contributors-header { + display: none; } - .contributor-row { - padding: 12px 16px; + grid-template-columns: 1fr; + gap: 16px; + padding: 16px; + margin-bottom: 12px; + border-radius: 8px; + } + .contributor-cell { + padding: 0; + } + .rank-cell { + position: absolute; + top: 8px; + left: 8px; + } + .avatar-cell { + justify-self: center; } - - .username-link { - font-size: 14px; + .username-cell { + text-align: center; } - - .contributions-link { - font-size: 12px; + .prs-cell, + .points-cell { + justify-self: center; } } \ No newline at end of file diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx index 49ade702..57d2ba0f 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -12,8 +12,9 @@ import { import { ChevronRight, ChevronLeft } from "lucide-react"; import { useColorMode } from "@docusaurus/theme-common"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import "./leaderboard.css"; -const GITHUB_ORG = "recodehive"; +const GITHUB_ORG = "recodehive"; const POINTS_PER_PR = 10; interface Contributor { @@ -43,27 +44,41 @@ interface PullRequestItem { function Badge({ count, label, color }: { count: number; label: string; color: { background: string; color: string } }) { return ( - + {count} {label} ); } +function TopPerformerCard({ contributor, rank }: { contributor: Contributor; rank: number }) { + const { colorMode } = useColorMode(); + const isDark = colorMode === "dark"; + const rankClass = rank === 1 ? "top-1" : rank === 2 ? "top-2" : "top-3"; + + return ( +
+ {contributor.username} +
+ {rank} +
+ +
+ ); +} + export default function LeaderBoard(): JSX.Element { const { siteConfig: { customFields }, } = useDocusaurusContext(); - // token fallback: prefer customFields.gitToken, otherwise environment var - const token = customFields?.gitToken || (process.env.DOCUSAURUS_GIT_TOKEN as string) || ""; + const token = customFields?.gitToken || ""; const { colorMode } = useColorMode(); const isDark = colorMode === "dark"; @@ -99,13 +114,11 @@ export default function LeaderBoard(): JSX.Element { const mergedPRs: PullRequestItem[] = []; let page = 1; while (true) { - // list PRs (closed) then filter merged const resp = await fetch( `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${page}`, { headers } ); if (!resp.ok) { - // if a particular repo fails (permissions, archived, etc.) skip it console.warn(`Failed to fetch PRs for ${repoName}: ${resp.status} ${resp.statusText}`); break; } @@ -123,7 +136,7 @@ export default function LeaderBoard(): JSX.Element { const fetchLeaderboard = async () => { if (!token) { - setError("GitHub token not found. Please set customFields.gitToken or DOCUSAURUS_GIT_TOKEN."); + setError("GitHub token not found. Please set customFields.gitToken in docusaurus.config.js."); setLoading(false); return; } @@ -137,14 +150,11 @@ export default function LeaderBoard(): JSX.Element { Accept: "application/vnd.github.v3+json", }; - // 1) fetch all repos in the org (paged) const repos = await fetchAllOrgRepos(headers); - // 2) for each repo, fetch merged PRs and aggregate const contributorMap = new Map(); let totalMergedPRs = 0; - for (const repo of repos) { if (repo.archived) continue; @@ -166,7 +176,7 @@ export default function LeaderBoard(): JSX.Element { } const contributor = contributorMap.get(username)!; contributor.prs++; - contributor.points += POINTS_PER_PR; // fixed points per merged PR + contributor.points += POINTS_PER_PR; } } catch (repoErr) { console.warn(`Skipping repo ${repoName} due to error:`, repoErr); @@ -212,15 +222,7 @@ export default function LeaderBoard(): JSX.Element { @@ -229,269 +231,213 @@ export default function LeaderBoard(): JSX.Element { return pages; }; + const getRankClass = (index: number) => { + if (index === 0) return "top-1"; + if (index === 1) return "top-2"; + if (index === 2) return "top-3"; + return "regular"; + }; + return ( -
-
-
-

- - Recode Hive Leaderboard -

-

+

+
+ {/* Header */} + +

Recode Hive Leaderboard

+

Top contributors across the {GITHUB_ORG} organization - - - +

-
+ + + {/* Top 3 Performers Section */} + {!loading && !error && contributors.length > 2 && ( +
+

recodehive Top Performers

+
+ + + +
+
+ )} + {/* Stats */} {stats && ( -
-
- -
{stats.flooredTotalPRs}
-
Merged PRs
+
+
+
+
+ +
+
+
{stats.totalContributors}
+
Total Contributors
+
+
-
- -
{stats.flooredTotalPoints}
-
Total Points
+
+
+
+ +
+
+
{stats.flooredTotalPRs}
+
Merged PRs
+
+
-
- -
{stats.totalContributors}
-
Total Contributors
+
+
+
+ +
+
+
{stats.flooredTotalPoints}
+
Total Points
+
+
)} -
- - { - setSearchQuery(e.target.value); - setCurrentPage(1); - }} - style={{ - width: "100%", - padding: "12px 16px 12px 48px", - borderRadius: 9999, - border: isDark ? "1px solid #4b5563" : "1px solid #d1d5db", - background: isDark ? "#374151" : "#fff", - color: isDark ? "#f3f4f6" : "#1f2937", - fontSize: 16, - outline: "none", - }} - /> + {/* Search */} +
+
+ + { + setSearchQuery(e.target.value); + setCurrentPage(1); + }} + className={`search-input ${isDark ? "dark" : "light"}`} + /> +
{loading && ( -
-
-

Loading leaderboard...

+
+
+
#
+
Contributor
+
Contributions
+
+ {[...Array(itemsPerPage)].map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+
+ ))}
)} {error && ( -
+

Error: {error}

)} {!loading && !error && filteredContributors.length === 0 && ( -
+

No contributors found.

)} {!loading && !error && filteredContributors.length > 0 && ( -
-
-
+
+
Rank
+
Avatar
+
User
+
PRs
+
Points
+
+ {currentItems.map((contributor, index) => ( + -
#
-
User
-
Points
-
- - {currentItems.map((contributor, index) => ( - -
{indexOfFirst + index + 1}
+
+
+ {indexOfFirst + index + 1} +
+
+ +
+ +
+
+ +
+
+ ))} + + {/* Pagination */} + {totalPages > 1 && ( +
+ +
{renderPaginationButtons()}
+ +
+ )} + + {/* CTA Footer */} +
+

Want to get on this leaderboard?

+ + + Contribute on GitHub +
)} - - {totalPages > 1 && ( -
- -
{renderPaginationButtons()}
- -
- )}
- -
); -} +} \ No newline at end of file From c87f9e1874e7144cc2426a52d125f52a4de93da1 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sat, 13 Sep 2025 19:39:43 +0530 Subject: [PATCH 07/11] Caching added using stateprovider --- src/lib/statsProvider.tsx | 407 ++++++++++++------ .../dashboard/LeaderBoard/leaderboard.tsx | 144 +------ 2 files changed, 270 insertions(+), 281 deletions(-) diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index e5b8bfa9..0bb76188 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -1,64 +1,201 @@ +// src/lib/statsProvider.tsx + /** @jsxImportSource react */ import React, { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useState, - } from "react"; + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + ReactNode, +} from "react"; import { githubService, type GitHubOrgStats } from "../services/githubService"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; + +interface ICommunityStatsContext { + githubStarCount: number; + githubStarCountText: string; + githubContributorsCount: number; + githubContributorsCountText: string; + githubForksCount: number; + githubForksCountText: string; + githubReposCount: number; + githubReposCountText: string; + githubDiscussionsCount: number; + githubDiscussionsCountText: string; + loading: boolean; + error: string | null; + lastUpdated: Date | null; + refetch: (signal: AbortSignal) => Promise; + clearCache: () => void; + // New properties for leaderboard + contributors: Contributor[]; + stats: Stats | null; +} + +// Define types for leaderboard data +interface Contributor { + username: string; + avatar: string; + profile: string; + points: number; + prs: number; +} + +interface Stats { + flooredTotalPRs: number; + totalContributors: number; + flooredTotalPoints: number; +} + +interface PullRequestItem { + user: { + login: string; + avatar_url: string; + html_url: string; + }; + merged_at?: string | null; +} + +export const CommunityStatsContext = createContext(undefined); + +interface CommunityStatsProviderProps { + children: ReactNode; +} + +const GITHUB_ORG = "recodehive"; +const POINTS_PER_PR = 10; + +export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) { + const { + siteConfig: { customFields }, + } = useDocusaurusContext(); + const token = customFields?.gitToken || ""; + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [githubStarCount, setGithubStarCount] = useState(0); + const [githubContributorsCount, setGithubContributorsCount] = useState(0); + const [githubForksCount, setGithubForksCount] = useState(0); + const [githubReposCount, setGithubReposCount] = useState(0); + const [githubDiscussionsCount, setGithubDiscussionsCount] = useState(0); + const [lastUpdated, setLastUpdated] = useState(null); - interface ICommunityStatsContext { - githubStarCount: number; - githubStarCountText: string; - githubContributorsCount: number; - githubContributorsCountText: string; - githubForksCount: number; - githubForksCountText: string; - githubReposCount: number; - githubReposCountText: string; - githubDiscussionsCount: number; - githubDiscussionsCountText: string; - loading: boolean; - error: string | null; - lastUpdated: Date | null; - refetch: (signal: AbortSignal) => Promise; - clearCache: () => void; - } - - export const CommunityStatsContext = createContext(undefined); - - interface CommunityStatsProviderProps { - children: React.ReactNode; - } - - export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) { - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [githubStarCount, setGithubStarCount] = useState(0); - const [githubContributorsCount, setGithubContributorsCount] = useState(0); - const [githubForksCount, setGithubForksCount] = useState(0); - const [githubReposCount, setGithubReposCount] = useState(0); - const [githubDiscussionsCount, setGithubDiscussionsCount] = useState(0); - const [lastUpdated, setLastUpdated] = useState(null); - - const fetchGithubStats = useCallback(async (signal: AbortSignal) => { - try { - setLoading(true); - setError(null); - - const stats: GitHubOrgStats = await githubService.fetchOrganizationStats(signal); - - setGithubStarCount(stats.totalStars); - setGithubContributorsCount(stats.totalContributors); - setGithubForksCount(stats.totalForks); - setGithubReposCount(stats.publicRepositories); - setGithubDiscussionsCount(stats.discussionsCount); - setLastUpdated(new Date(stats.lastUpdated)); - - console.log("GitHub organization stats fetched successfully:", stats); - } catch (err) { + // New state for leaderboard data + const [contributors, setContributors] = useState([]); + const [stats, setStats] = useState(null); + + const fetchAllOrgRepos = useCallback(async (headers: Record) => { + const repos: any[] = []; + let page = 1; + while (true) { + const resp = await fetch(`https://api.github.com/orgs/${GITHUB_ORG}/repos?type=public&per_page=100&page=${page}`, { + headers, + }); + if (!resp.ok) { + throw new Error(`Failed to fetch org repos: ${resp.status} ${resp.statusText}`); + } + const data = await resp.json(); + repos.push(...data); + if (!Array.isArray(data) || data.length < 100) break; + page++; + } + return repos; + }, []); + + const fetchMergedPRsForRepo = useCallback(async (repoName: string, headers: Record) => { + const mergedPRs: PullRequestItem[] = []; + let page = 1; + while (true) { + const resp = await fetch( + `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${page}`, + { headers } + ); + if (!resp.ok) { + console.warn(`Failed to fetch PRs for ${repoName}: ${resp.status} ${resp.statusText}`); + break; + } + const prs: PullRequestItem[] = await resp.json(); + if (!Array.isArray(prs) || prs.length === 0) break; + + const merged = prs.filter((pr) => Boolean(pr.merged_at)); + mergedPRs.push(...merged); + + if (prs.length < 100) break; + page++; + } + return mergedPRs; + }, []); + + const fetchAllStats = useCallback(async (signal: AbortSignal) => { + setLoading(true); + setError(null); + if (!token) { + setError("GitHub token not found. Please set customFields.gitToken in docusaurus.config.js."); + setLoading(false); + return; + } + + try { + const headers: Record = { + Authorization: `token ${token}`, + Accept: "application/vnd.github.v3+json", + }; + + // Fetch general organization stats + const orgStats: GitHubOrgStats = await githubService.fetchOrganizationStats(signal); + setGithubStarCount(orgStats.totalStars); + setGithubContributorsCount(orgStats.totalContributors); + setGithubForksCount(orgStats.totalForks); + setGithubReposCount(orgStats.publicRepositories); + setGithubDiscussionsCount(orgStats.discussionsCount); + setLastUpdated(new Date(orgStats.lastUpdated)); + + // Fetch leaderboard data + const repos = await fetchAllOrgRepos(headers); + const contributorMap = new Map(); + let totalMergedPRs = 0; + + for (const repo of repos) { + if (repo.archived) continue; + const repoName = repo.name; + try { + const mergedPRs = await fetchMergedPRsForRepo(repoName, headers); + totalMergedPRs += mergedPRs.length; + + for (const pr of mergedPRs) { + const username = pr.user.login; + if (!contributorMap.has(username)) { + contributorMap.set(username, { + username, + avatar: pr.user.avatar_url, + profile: pr.user.html_url, + points: 0, + prs: 0, + }); + } + const contributor = contributorMap.get(username)!; + contributor.prs++; + contributor.points += POINTS_PER_PR; + } + } catch (repoErr) { + console.warn(`Skipping repo ${repoName} due to error:`, repoErr); + continue; + } + } + + const sortedContributors = Array.from(contributorMap.values()).sort( + (a, b) => b.points - a.points || b.prs - a.prs + ); + setContributors(sortedContributors); + setStats({ + flooredTotalPRs: totalMergedPRs, + totalContributors: sortedContributors.length, + flooredTotalPoints: sortedContributors.reduce((sum, c) => sum + c.points, 0), + }); + + } catch (err: any) { if (err.name !== 'AbortError') { console.error("Error fetching GitHub organization stats:", err); setError(err instanceof Error ? err.message : 'Failed to fetch GitHub stats'); @@ -73,90 +210,76 @@ import { githubService, type GitHubOrgStats } from "../services/githubService"; } finally { setLoading(false); } - }, []); - - const clearCache = useCallback(() => { - githubService.clearCache(); - // Optionally refetch after clearing cache - const abortController = new AbortController(); - fetchGithubStats(abortController.signal); - }, [fetchGithubStats]); - - useEffect(() => { - const abortController = new AbortController(); - fetchGithubStats(abortController.signal); - - return () => { - abortController.abort(); - }; - }, [fetchGithubStats]); - - const githubStarCountText = useMemo(() => { - return convertStatToText(githubStarCount); - }, [githubStarCount]); - - const githubContributorsCountText = useMemo(() => { - return convertStatToText(githubContributorsCount); - }, [githubContributorsCount]); - - const githubForksCountText = useMemo(() => { - return convertStatToText(githubForksCount); - }, [githubForksCount]); - - const githubReposCountText = useMemo(() => { - return convertStatToText(githubReposCount); - }, [githubReposCount]); - - const githubDiscussionsCountText = useMemo(() => { - return convertStatToText(githubDiscussionsCount); - }, [githubDiscussionsCount]); - - const value: ICommunityStatsContext = { - githubStarCount, - githubStarCountText, - githubContributorsCount, - githubContributorsCountText, - githubForksCount, - githubForksCountText, - githubReposCount, - githubReposCountText, - githubDiscussionsCount, - githubDiscussionsCountText, - loading, - error, - lastUpdated, - refetch: fetchGithubStats, - clearCache, + }, [token, fetchAllOrgRepos, fetchMergedPRsForRepo]); + + const clearCache = useCallback(() => { + githubService.clearCache(); + const abortController = new AbortController(); + fetchAllStats(abortController.signal); + }, [fetchAllStats]); + + useEffect(() => { + const abortController = new AbortController(); + fetchAllStats(abortController.signal); + + return () => { + abortController.abort(); }; - - return ( - - {children} - - ); - } - - export const useCommunityStatsContext = (): ICommunityStatsContext => { - const context = useContext(CommunityStatsContext); - if (context === undefined) { - throw new Error("useCommunityStatsContext must be used within a CommunityStatsProvider"); - } - return context; - }; - - export const convertStatToText = (num: number): string => { - const hasIntlSupport = - typeof Intl === "object" && Intl && typeof Intl.NumberFormat === "function"; - - if (!hasIntlSupport) { - return `${(num / 1000).toFixed(1)}k`; - } - - const formatter = new Intl.NumberFormat("en-US", { - notation: "compact", - compactDisplay: "short", - maximumSignificantDigits: 3, - }); - return formatter.format(num); + }, [fetchAllStats]); + + const githubStarCountText = useMemo(() => convertStatToText(githubStarCount), [githubStarCount]); + const githubContributorsCountText = useMemo(() => convertStatToText(githubContributorsCount), [githubContributorsCount]); + const githubForksCountText = useMemo(() => convertStatToText(githubForksCount), [githubForksCount]); + const githubReposCountText = useMemo(() => convertStatToText(githubReposCount), [githubReposCount]); + const githubDiscussionsCountText = useMemo(() => convertStatToText(githubDiscussionsCount), [githubDiscussionsCount]); + + const value: ICommunityStatsContext = { + githubStarCount, + githubStarCountText, + githubContributorsCount, + githubContributorsCountText, + githubForksCount, + githubForksCountText, + githubReposCount, + githubReposCountText, + githubDiscussionsCount, + githubDiscussionsCountText, + loading, + error, + lastUpdated, + refetch: fetchAllStats, + clearCache, + contributors, + stats, }; - \ No newline at end of file + + return ( + + {children} + + ); +} + +export const useCommunityStatsContext = (): ICommunityStatsContext => { + const context = useContext(CommunityStatsContext); + if (context === undefined) { + throw new Error("useCommunityStatsContext must be used within a CommunityStatsProvider"); + } + return context; +}; + +export const convertStatToText = (num: number): string => { + const hasIntlSupport = + typeof Intl === "object" && Intl && typeof Intl.NumberFormat === "function"; + + if (!hasIntlSupport) { + return `${(num / 1000).toFixed(1)}k`; + } + + const formatter = new Intl.NumberFormat("en-US", { + notation: "compact", + compactDisplay: "short", + maximumSignificantDigits: 3, + }); + return formatter.format(num); +}; \ No newline at end of file diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/pages/dashboard/LeaderBoard/leaderboard.tsx index 57d2ba0f..482e6314 100644 --- a/src/pages/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/pages/dashboard/LeaderBoard/leaderboard.tsx @@ -1,5 +1,5 @@ // src/pages/dashboard/LeaderBoard/leaderboard.tsx -import React, { JSX, useEffect, useState } from "react"; +import React, { JSX, useState } from "react"; import { motion } from "framer-motion"; import { FaTrophy, @@ -11,11 +11,10 @@ import { } from "react-icons/fa"; import { ChevronRight, ChevronLeft } from "lucide-react"; import { useColorMode } from "@docusaurus/theme-common"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { useCommunityStatsContext } from "@site/src/lib/statsProvider"; import "./leaderboard.css"; -const GITHUB_ORG = "recodehive"; -const POINTS_PER_PR = 10; +const GITHUB_ORG = "recodehive"; interface Contributor { username: string; @@ -31,17 +30,6 @@ interface Stats { flooredTotalPoints: number; } -interface User { - login: string; - avatar_url: string; - html_url: string; -} - -interface PullRequestItem { - user: User; - merged_at?: string | null; -} - function Badge({ count, label, color }: { count: number; label: string; color: { background: string; color: string } }) { return ( @@ -75,135 +63,14 @@ function TopPerformerCard({ contributor, rank }: { contributor: Contributor; ran } export default function LeaderBoard(): JSX.Element { - const { - siteConfig: { customFields }, - } = useDocusaurusContext(); - const token = customFields?.gitToken || ""; - + const { contributors, stats, loading, error } = useCommunityStatsContext(); const { colorMode } = useColorMode(); const isDark = colorMode === "dark"; - const [contributors, setContributors] = useState([]); - const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; - useEffect(() => { - const fetchAllOrgRepos = async (headers: Record) => { - const repos: any[] = []; - let page = 1; - while (true) { - const resp = await fetch(`https://api.github.com/orgs/${GITHUB_ORG}/repos?type=public&per_page=100&page=${page}`, { - headers, - }); - if (!resp.ok) { - throw new Error(`Failed to fetch org repos: ${resp.status} ${resp.statusText}`); - } - const data = await resp.json(); - repos.push(...data); - if (!Array.isArray(data) || data.length < 100) break; - page++; - } - return repos; - }; - - const fetchMergedPRsForRepo = async (repoName: string, headers: Record) => { - const mergedPRs: PullRequestItem[] = []; - let page = 1; - while (true) { - const resp = await fetch( - `https://api.github.com/repos/${GITHUB_ORG}/${repoName}/pulls?state=closed&per_page=100&page=${page}`, - { headers } - ); - if (!resp.ok) { - console.warn(`Failed to fetch PRs for ${repoName}: ${resp.status} ${resp.statusText}`); - break; - } - const prs: PullRequestItem[] = await resp.json(); - if (!Array.isArray(prs) || prs.length === 0) break; - - const merged = prs.filter((pr) => Boolean(pr.merged_at)); - mergedPRs.push(...merged); - - if (prs.length < 100) break; - page++; - } - return mergedPRs; - }; - - const fetchLeaderboard = async () => { - if (!token) { - setError("GitHub token not found. Please set customFields.gitToken in docusaurus.config.js."); - setLoading(false); - return; - } - - setLoading(true); - setError(null); - - try { - const headers: Record = { - Authorization: `token ${token}`, - Accept: "application/vnd.github.v3+json", - }; - - const repos = await fetchAllOrgRepos(headers); - - const contributorMap = new Map(); - let totalMergedPRs = 0; - - for (const repo of repos) { - if (repo.archived) continue; - - const repoName = repo.name; - try { - const mergedPRs = await fetchMergedPRsForRepo(repoName, headers); - totalMergedPRs += mergedPRs.length; - - for (const pr of mergedPRs) { - const username = pr.user.login; - if (!contributorMap.has(username)) { - contributorMap.set(username, { - username, - avatar: pr.user.avatar_url, - profile: pr.user.html_url, - points: 0, - prs: 0, - }); - } - const contributor = contributorMap.get(username)!; - contributor.prs++; - contributor.points += POINTS_PER_PR; - } - } catch (repoErr) { - console.warn(`Skipping repo ${repoName} due to error:`, repoErr); - continue; - } - } - - const sortedContributors = Array.from(contributorMap.values()).sort( - (a, b) => b.points - a.points || b.prs - a.prs - ); - - setContributors(sortedContributors); - setStats({ - flooredTotalPRs: totalMergedPRs, - totalContributors: sortedContributors.length, - flooredTotalPoints: sortedContributors.reduce((sum, c) => sum + c.points, 0), - }); - setLoading(false); - } catch (e: any) { - setError(e?.message || String(e)); - setLoading(false); - } - }; - - fetchLeaderboard(); - }, [token]); - const filteredContributors = contributors.filter((contributor) => contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) ); @@ -251,14 +118,13 @@ export default function LeaderBoard(): JSX.Element {

Recode Hive Leaderboard

Top contributors across the {GITHUB_ORG} organization -

{/* Top 3 Performers Section */} {!loading && !error && contributors.length > 2 && (
-

recodehive Top Performers

+

RecodeHive Top Performers

From a498f27ea1af15f45e15f99d2eff9f8b4d524eea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:32:45 +0000 Subject: [PATCH 08/11] Initial plan From be83fa424f9a39e96402c143081b69e98c6ae3dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:45:51 +0000 Subject: [PATCH 09/11] Fix dashboard sidebar navigation and integrate LeaderBoard properly Co-authored-by: Adez017 <142787780+Adez017@users.noreply.github.com> --- .../dashboard/LeaderBoard/leaderboard.css | 0 .../dashboard/LeaderBoard/leaderboard.tsx | 0 src/components/navbar/NavbarIcon.tsx | 45 ++++++++++++++++- src/pages/dashboard/dashboard.css | 48 +++++++++++++++++++ src/pages/dashboard/index.tsx | 2 +- 5 files changed, 92 insertions(+), 3 deletions(-) rename src/{pages => components}/dashboard/LeaderBoard/leaderboard.css (100%) rename src/{pages => components}/dashboard/LeaderBoard/leaderboard.tsx (100%) diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.css b/src/components/dashboard/LeaderBoard/leaderboard.css similarity index 100% rename from src/pages/dashboard/LeaderBoard/leaderboard.css rename to src/components/dashboard/LeaderBoard/leaderboard.css diff --git a/src/pages/dashboard/LeaderBoard/leaderboard.tsx b/src/components/dashboard/LeaderBoard/leaderboard.tsx similarity index 100% rename from src/pages/dashboard/LeaderBoard/leaderboard.tsx rename to src/components/dashboard/LeaderBoard/leaderboard.tsx diff --git a/src/components/navbar/NavbarIcon.tsx b/src/components/navbar/NavbarIcon.tsx index 1a06566f..df6cbd8e 100644 --- a/src/components/navbar/NavbarIcon.tsx +++ b/src/components/navbar/NavbarIcon.tsx @@ -1,11 +1,52 @@ import React from "react"; import { NAVBAR_CONFIG, type NavbarIconName } from "../../constants/navbarConfig"; -interface NavbarIconProps { +// Legacy interface for dashboard usage +interface DashboardNavbarIconProps { + icon: React.ReactNode; + text: string; + active: boolean; + onClick: () => void; +} + +// New interface for navbar usage +interface ConfigNavbarIconProps { name: NavbarIconName; } -export default function NavbarIcon({ name }: NavbarIconProps) { +type NavbarIconProps = DashboardNavbarIconProps | ConfigNavbarIconProps; + +// Type guard to check if props are for dashboard usage +function isDashboardProps(props: NavbarIconProps): props is DashboardNavbarIconProps { + return 'icon' in props && 'text' in props && 'active' in props && 'onClick' in props; +} + +export default function NavbarIcon(props: NavbarIconProps) { + // Handle dashboard usage + if (isDashboardProps(props)) { + const { icon, text, active, onClick } = props; + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + onClick(); + } + }} + > + + {icon} + + {text} +
+ ); + } + + // Handle navbar config usage + const { name } = props; const IconComponent = NAVBAR_CONFIG[name]; if (!IconComponent) { diff --git a/src/pages/dashboard/dashboard.css b/src/pages/dashboard/dashboard.css index 7b745d99..866f0e74 100644 --- a/src/pages/dashboard/dashboard.css +++ b/src/pages/dashboard/dashboard.css @@ -178,6 +178,54 @@ gap: 8px; } +/* Navbar Icon Items for Dashboard */ +.navbar-icon-item { + display: flex; + align-items: center; + padding: 12px 16px; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--ifm-color-content); + font-weight: 500; + text-decoration: none; + position: relative; +} + +.navbar-icon-item:hover { + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-content); +} + +.navbar-icon-item.active { + background: var(--ifm-color-primary-lightest); + color: var(--ifm-color-primary); + font-weight: 600; +} + +.navbar-icon-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: var(--ifm-color-primary); + border-radius: 0 2px 2px 0; +} + +.navbar-icon { + margin-right: 12px; + display: flex; + align-items: center; + font-size: 18px; +} + +.navbar-text { + font-size: 14px; + line-height: 1.4; +} + /* Main Content */ .dashboard-main-content { flex: 1; diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 08dfac52..ab2e2a9e 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -34,7 +34,7 @@ import { import NavbarIcon from "@site/src/components/navbar/NavbarIcon"; import "@site/src/components/discussions/discussions.css"; import "./dashboard.css"; -import LeaderBoard from "./LeaderBoard/leaderboard"; +import LeaderBoard from "@site/src/components/dashboard/LeaderBoard/leaderboard"; type DiscussionTab = "discussions" | "trending" | "unanswered"; type SortOption = "most_popular" | "latest" | "oldest"; From eafb65b40e5d5fee975b79f521928cf8123ffb5e Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Sun, 14 Sep 2025 21:36:33 +0530 Subject: [PATCH 10/11] filtered contributors --- .../dashboard/LeaderBoard/leaderboard.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/LeaderBoard/leaderboard.tsx b/src/components/dashboard/LeaderBoard/leaderboard.tsx index 482e6314..a58ddc96 100644 --- a/src/components/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/components/dashboard/LeaderBoard/leaderboard.tsx @@ -16,6 +16,9 @@ import "./leaderboard.css"; const GITHUB_ORG = "recodehive"; +// Users to exclude from the leaderboard +const EXCLUDED_USERS = ["sanjay-kv", "allcontributors", "allcontributors[bot]"]; + interface Contributor { username: string; avatar: string; @@ -71,9 +74,16 @@ export default function LeaderBoard(): JSX.Element { const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; - const filteredContributors = contributors.filter((contributor) => - contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) - ); + // Filter out excluded users and then apply search filter + const filteredContributors = contributors + .filter((contributor) => + !EXCLUDED_USERS.some(excludedUser => + contributor.username.toLowerCase() === excludedUser.toLowerCase() + ) + ) + .filter((contributor) => + contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) + ); const totalPages = Math.ceil(filteredContributors.length / itemsPerPage); const indexOfLast = currentPage * itemsPerPage; @@ -122,13 +132,13 @@ export default function LeaderBoard(): JSX.Element { {/* Top 3 Performers Section */} - {!loading && !error && contributors.length > 2 && ( + {!loading && !error && filteredContributors.length > 2 && (

RecodeHive Top Performers

- - - + + +
)} From 2c758682f298354e72982eb574ee5f61839897f3 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Tue, 16 Sep 2025 11:13:09 +0530 Subject: [PATCH 11/11] Added Concurrent processing for faster Execution --- src/lib/statsProvider.tsx | 92 +++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index 0bb76188..7d23451d 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -66,6 +66,7 @@ interface CommunityStatsProviderProps { const GITHUB_ORG = "recodehive"; const POINTS_PER_PR = 10; +const MAX_CONCURRENT_REQUESTS = 5; // Limit concurrent requests to avoid rate limiting export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) { const { @@ -128,6 +129,58 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps return mergedPRs; }, []); + // NEW: Concurrent processing function with controlled concurrency + const processBatch = useCallback(async ( + repos: any[], + headers: Record + ): Promise<{ contributorMap: Map; totalMergedPRs: number }> => { + const contributorMap = new Map(); + let totalMergedPRs = 0; + + // Process repos in batches to control concurrency + for (let i = 0; i < repos.length; i += MAX_CONCURRENT_REQUESTS) { + const batch = repos.slice(i, i + MAX_CONCURRENT_REQUESTS); + + const promises = batch.map(async (repo) => { + if (repo.archived) return { mergedPRs: [], repoName: repo.name }; + + try { + const mergedPRs = await fetchMergedPRsForRepo(repo.name, headers); + return { mergedPRs, repoName: repo.name }; + } catch (error) { + console.warn(`Skipping repo ${repo.name} due to error:`, error); + return { mergedPRs: [], repoName: repo.name }; + } + }); + + // Wait for current batch to complete + const results = await Promise.all(promises); + + // Process results from this batch + results.forEach(({ mergedPRs }) => { + totalMergedPRs += mergedPRs.length; + + mergedPRs.forEach((pr) => { + const username = pr.user.login; + if (!contributorMap.has(username)) { + contributorMap.set(username, { + username, + avatar: pr.user.avatar_url, + profile: pr.user.html_url, + points: 0, + prs: 0, + }); + } + const contributor = contributorMap.get(username)!; + contributor.prs++; + contributor.points += POINTS_PER_PR; + }); + }); + } + + return { contributorMap, totalMergedPRs }; + }, [fetchMergedPRsForRepo]); + const fetchAllStats = useCallback(async (signal: AbortSignal) => { setLoading(true); setError(null); @@ -143,7 +196,7 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps Accept: "application/vnd.github.v3+json", }; - // Fetch general organization stats + // Fetch general organization stats (unchanged) const orgStats: GitHubOrgStats = await githubService.fetchOrganizationStats(signal); setGithubStarCount(orgStats.totalStars); setGithubContributorsCount(orgStats.totalContributors); @@ -152,38 +205,11 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps setGithubDiscussionsCount(orgStats.discussionsCount); setLastUpdated(new Date(orgStats.lastUpdated)); - // Fetch leaderboard data + // Fetch leaderboard data with concurrent processing const repos = await fetchAllOrgRepos(headers); - const contributorMap = new Map(); - let totalMergedPRs = 0; - - for (const repo of repos) { - if (repo.archived) continue; - const repoName = repo.name; - try { - const mergedPRs = await fetchMergedPRsForRepo(repoName, headers); - totalMergedPRs += mergedPRs.length; - - for (const pr of mergedPRs) { - const username = pr.user.login; - if (!contributorMap.has(username)) { - contributorMap.set(username, { - username, - avatar: pr.user.avatar_url, - profile: pr.user.html_url, - points: 0, - prs: 0, - }); - } - const contributor = contributorMap.get(username)!; - contributor.prs++; - contributor.points += POINTS_PER_PR; - } - } catch (repoErr) { - console.warn(`Skipping repo ${repoName} due to error:`, repoErr); - continue; - } - } + + // NEW: Use concurrent processing instead of sequential + const { contributorMap, totalMergedPRs } = await processBatch(repos, headers); const sortedContributors = Array.from(contributorMap.values()).sort( (a, b) => b.points - a.points || b.prs - a.prs @@ -210,7 +236,7 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps } finally { setLoading(false); } - }, [token, fetchAllOrgRepos, fetchMergedPRsForRepo]); + }, [token, fetchAllOrgRepos, processBatch]); const clearCache = useCallback(() => { githubService.clearCache();