From fda23dd67aa3421a462df35d3940dc0cd759097d Mon Sep 17 00:00:00 2001 From: Sanjay Viswanathan Date: Sun, 24 May 2026 21:14:29 +1000 Subject: [PATCH] Revert "feat: add Contributor Achievement Card Generator to leaderboard" --- package-lock.json | 7 - package.json | 1 - .../dashboard/LeaderBoard/ContributorCard.tsx | 113 -------- .../dashboard/LeaderBoard/leaderboard.css | 273 +++++------------- .../dashboard/LeaderBoard/leaderboard.tsx | 218 +++++++++----- src/pages/dashboard/index.tsx | 15 +- 6 files changed, 235 insertions(+), 392 deletions(-) delete mode 100644 src/components/dashboard/LeaderBoard/ContributorCard.tsx diff --git a/package-lock.json b/package-lock.json index 51d8badb..d3372739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,6 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.40.0", - "html-to-image": "^1.11.13", "lucide-react": "^0.503.0", "prism-react-renderer": "^2.3.0", "react": "^18.3.1", @@ -14060,12 +14059,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/html-to-image": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", - "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", - "license": "MIT" - }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", diff --git a/package.json b/package.json index 939de2b6..3bb97283 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "framer-motion": "^12.40.0", - "html-to-image": "^1.11.13", "lucide-react": "^0.503.0", "prism-react-renderer": "^2.3.0", "react": "^18.3.1", diff --git a/src/components/dashboard/LeaderBoard/ContributorCard.tsx b/src/components/dashboard/LeaderBoard/ContributorCard.tsx deleted file mode 100644 index 14c5c652..00000000 --- a/src/components/dashboard/LeaderBoard/ContributorCard.tsx +++ /dev/null @@ -1,113 +0,0 @@ -// src/components/dashboard/LeaderBoard/ContributorCard.tsx -import React, { useRef, useState } from "react"; -import { toPng } from "html-to-image"; -import { FaGithub, FaDownload, FaTrophy } from "react-icons/fa"; - -interface ContributorCardProps { - username: string; - avatar: string; - prs: number; - points: number; - rank: number; - badges: string[]; - isDark: boolean; -} - -export default function ContributorCard({ - username, - avatar, - prs, - points, - rank, - badges, - isDark, -}: ContributorCardProps) { - const [open, setOpen] = useState(false); - const cardRef = useRef(null); - - const handleDownload = async () => { - if (!cardRef.current) return; - const dataUrl = await toPng(cardRef.current, { cacheBust: true }); - const link = document.createElement("a"); - link.download = `${username}-recodehive-card.png`; - link.href = dataUrl; - link.click(); - }; - - return ( - <> - {/* Trigger button — sits right of "Contribute on GitHub" */} - - - {/* Modal overlay */} - {open && ( -
setOpen(false)} - > -
e.stopPropagation()} - > - {/* The actual downloadable card */} -
-
- RecodeHive - recodehive -
- {username} -

{username}

-
#{rank} on Leaderboard
-
-
- {prs} - PRs Merged -
-
- {points} - Points -
-
- {badges.length > 0 && ( -
- {badges.slice(0, 5).map((badge, i) => ( - {`badge-${i}`} - ))} -
- )} -
- github.com/recodehive -
-
- - {/* Action buttons */} -
- - -
-
-
- )} - - ); -} \ No newline at end of file diff --git a/src/components/dashboard/LeaderBoard/leaderboard.css b/src/components/dashboard/LeaderBoard/leaderboard.css index a69dae19..44812754 100644 --- a/src/components/dashboard/LeaderBoard/leaderboard.css +++ b/src/components/dashboard/LeaderBoard/leaderboard.css @@ -37,11 +37,11 @@ .header { margin-bottom: 32px; } - + .title { font-size: 24px; } - + .subtitle { font-size: 15px; } @@ -51,7 +51,7 @@ .title { font-size: 20px; } - + .subtitle { font-size: 14px; } @@ -92,7 +92,7 @@ flex-direction: column; gap: 12px; } - + .top-performers-title { font-size: 20px; } @@ -136,19 +136,19 @@ gap: 20px; padding: 20px 10px; } - + .podium-card { width: 100%; max-width: 280px; transform: none !important; } - + .first-place, .second-place, .third-place { transform: none !important; } - + .first-place:hover, .second-place:hover, .third-place:hover { @@ -186,17 +186,9 @@ } /* Staircase Height Adjustment */ -.first-place { - transform: translateY(-30px); -} - -.second-place { - transform: translateY(0px); -} - -.third-place { - transform: translateY(0px); -} +.first-place { transform: translateY(-30px); } +.second-place { transform: translateY(0px); } +.third-place { transform: translateY(0px); } /* Reflection Effect */ .podium-card::after { @@ -207,33 +199,35 @@ height: 25%; width: 100%; border-radius: 0 0 16px 16px; - background: linear-gradient(to bottom, - rgba(255, 255, 255, 0.4) 0%, - rgba(255, 255, 255, 0.3) 25%, - rgba(255, 255, 255, 0.1) 75%, - rgba(255, 255, 255, 0) 100%); + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0.4) 0%, + rgba(255, 255, 255, 0.3) 25%, + rgba(255, 255, 255, 0.1) 75%, + rgba(255, 255, 255, 0) 100% + ); transform: scaleY(-1); z-index: 1; pointer-events: none; } .dark .podium-card::after { - background: linear-gradient(to bottom, - rgba(43, 48, 59, 0.4) 0%, - rgba(43, 48, 59, 0.3) 25%, - rgba(43, 48, 59, 0.1) 75%, - rgba(43, 48, 59, 0) 100%); + background: linear-gradient( + to bottom, + rgba(43, 48, 59, 0.4) 0%, + rgba(43, 48, 59, 0.3) 25%, + rgba(43, 48, 59, 0.1) 75%, + rgba(43, 48, 59, 0) 100% + ); } /* Reflection box-shadows */ .first-place::after { box-shadow: 0 2px 6px rgba(255, 215, 0, 0.1); } - .second-place::after { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } - .third-place::after { box-shadow: 0 2px 6px rgba(205, 127, 50, 0.1); } @@ -295,11 +289,9 @@ .first-place .rank-badge { background-color: #ffd700; } - .second-place .rank-badge { background-color: #555; } - .third-place .rank-badge { background-color: #cd7f32; } @@ -332,7 +324,7 @@ .podium-card .stats { gap: 6px; } - + .podium-card .prs, .podium-card .points { font-size: 0.75em; @@ -612,9 +604,11 @@ font-weight: 600; letter-spacing: 0.3px; appearance: none; - background: linear-gradient(to bottom, - rgba(255, 255, 255, 0.05) 0%, - rgba(0, 0, 0, 0.05) 100%); + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0.05) 0%, + rgba(0, 0, 0, 0.05) 100% + ); cursor: pointer; transition: all 0.25s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); @@ -798,7 +792,7 @@ width: 36px; height: 36px; } - + .contributor-badges { gap: 6px; justify-content: center; @@ -810,7 +804,7 @@ width: 32px; height: 32px; } - + .contributor-badges { gap: 4px; } @@ -859,7 +853,7 @@ width: 32px; height: 32px; } - + .top-performer-card .contributor-badges { gap: 6px; } @@ -898,7 +892,7 @@ width: 28px; height: 28px; } - + .podium-card .contributor-badges { gap: 6px; flex-wrap: wrap; @@ -1309,7 +1303,7 @@ justify-self: center; margin: 4px 0; } - + .badges-cell { justify-self: center; width: 100%; @@ -1319,14 +1313,14 @@ margin-top: 4px; overflow: visible; } - + .contributor-badges { justify-content: center; max-width: 100%; flex-wrap: wrap; gap: 6px; } - + .contributor-badge-icon { width: 36px; height: 36px; @@ -1338,16 +1332,16 @@ padding: 12px; gap: 10px; } - + .contributor-badge-icon { width: 32px; height: 32px; } - + .contributor-badges { gap: 4px; } - + .podium-card { max-width: 100%; padding: 16px; @@ -1385,7 +1379,6 @@ } @media (max-width: 475px) { - .contributors-container .page-numbers .page-btn, .contributors-container .pagination .pagination-btn, .contributors-container .pagination-ellipsis { @@ -1402,23 +1395,22 @@ } @media (max-width: 320px) { - .contributors-container .page-numbers .page-btn, .contributors-container .pagination .pagination-btn, .contributors-container .pagination-ellipsis { margin: 0 2px; } - + .contributor-badge-icon { width: 28px; height: 28px; } - + .podium-card .contributor-badge-icon { width: 20px; height: 20px; } - + .contributor-badges { gap: 3px; } @@ -1484,19 +1476,23 @@ width: 32px; height: 32px; border-radius: 50%; - background: linear-gradient(90deg, - rgba(0, 0, 0, 0.06) 25%, - rgba(0, 0, 0, 0.12) 50%, - rgba(0, 0, 0, 0.06) 75%); + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.06) 25%, + rgba(0, 0, 0, 0.12) 50%, + rgba(0, 0, 0, 0.06) 75% + ); background-size: 200% 100%; animation: skeleton-shimmer 2s infinite; } .skeleton-loader.dark .skeleton-avatar { - background: linear-gradient(90deg, - rgba(255, 255, 255, 0.05) 25%, - rgba(255, 255, 255, 0.1) 50%, - rgba(255, 255, 255, 0.05) 75%); + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.05) 25%, + rgba(255, 255, 255, 0.1) 50%, + rgba(255, 255, 255, 0.05) 75% + ); background-size: 200% 100%; } @@ -1516,19 +1512,23 @@ height: 16px; width: 60%; border-radius: 4px; - background: linear-gradient(90deg, - rgba(0, 0, 0, 0.06) 25%, - rgba(0, 0, 0, 0.12) 50%, - rgba(0, 0, 0, 0.06) 75%); + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.06) 25%, + rgba(0, 0, 0, 0.12) 50%, + rgba(0, 0, 0, 0.06) 75% + ); background-size: 200% 100%; animation: skeleton-shimmer 2s infinite; } .skeleton-loader.dark .skeleton-bar { - background: linear-gradient(90deg, - rgba(255, 255, 255, 0.05) 25%, - rgba(255, 255, 255, 0.1) 50%, - rgba(255, 255, 255, 0.05) 75%); + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.05) 25%, + rgba(255, 255, 255, 0.1) 50%, + rgba(255, 255, 255, 0.05) 75% + ); background-size: 200% 100%; } @@ -1541,19 +1541,23 @@ height: 20px; width: 60px; border-radius: 12px; - background: linear-gradient(90deg, - rgba(0, 0, 0, 0.06) 25%, - rgba(0, 0, 0, 0.12) 50%, - rgba(0, 0, 0, 0.06) 75%); + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.06) 25%, + rgba(0, 0, 0, 0.12) 50%, + rgba(0, 0, 0, 0.06) 75% + ); background-size: 200% 100%; animation: skeleton-shimmer 2s infinite; } .skeleton-loader.dark .skeleton-badge { - background: linear-gradient(90deg, - rgba(255, 255, 255, 0.05) 25%, - rgba(255, 255, 255, 0.1) 50%, - rgba(255, 255, 255, 0.05) 75%); + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.05) 25%, + rgba(255, 255, 255, 0.1) 50%, + rgba(255, 255, 255, 0.05) 75% + ); background-size: 200% 100%; } @@ -2264,13 +2268,10 @@ } @keyframes pulse { - - 0%, - 100% { + 0%, 100% { transform: scale(1); box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4); } - 50% { transform: scale(1.1); box-shadow: 0 4px 12px rgba(16, 185, 129, 0.6); @@ -2373,117 +2374,3 @@ } } -/* Achievement Card */ -.card-modal-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.card-modal-inner { - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; -} - -.achievement-card { - width: 320px; - border-radius: 16px; - padding: 24px; - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - background: linear-gradient(135deg, #1e3a5f, #0f172a); - color: white; - font-family: 'Inter', sans-serif; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); -} - -.achievement-card.light { - background: linear-gradient(135deg, #dbeafe, #ede9fe); - color: #1e1e2e; -} - -.achievement-card-header { - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; - opacity: 0.8; -} - -.card-logo { - width: 24px; - height: 24px; - border-radius: 50%; -} - -.card-avatar { - width: 80px; - height: 80px; - border-radius: 50%; - border: 3px solid #7c3aed; -} - -.card-username { - font-size: 20px; - font-weight: 700; - margin: 0; -} - -.card-rank { - font-size: 13px; - opacity: 0.7; -} - -.card-stats { - display: flex; - gap: 24px; - margin: 8px 0; -} - -.card-stat { - display: flex; - flex-direction: column; - align-items: center; -} - -.card-stat-value { - font-size: 22px; - font-weight: 800; - color: #a78bfa; -} - -.card-stat-label { - font-size: 11px; - opacity: 0.7; -} - -.card-badges { - display: flex; - gap: 6px; -} - -.card-badge-icon { - width: 28px; - height: 28px; -} - -.card-footer { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - opacity: 0.6; - margin-top: 4px; -} - -.card-modal-actions { - display: flex; -} \ No newline at end of file diff --git a/src/components/dashboard/LeaderBoard/leaderboard.tsx b/src/components/dashboard/LeaderBoard/leaderboard.tsx index 0258936a..c4738056 100644 --- a/src/components/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/components/dashboard/LeaderBoard/leaderboard.tsx @@ -9,7 +9,6 @@ import PRListModal from "./PRListModal"; import BadgeModal from "./BadgeModal"; import { mockContributors } from "./mockData"; import "./leaderboard.css"; -import ContributorCard from "./ContributorCard"; const GITHUB_ORG = "recodehive"; @@ -42,7 +41,7 @@ interface Contributor { points: number; prs: number; prDetails?: PRDetails[]; - badges?: string[]; + badges?: string[]; // Array of badge image paths } interface Stats { @@ -105,19 +104,28 @@ const BADGE_CONFIG = [ }, ]; -function getContributorBadges(contributor: Contributor, rank: number): string[] { +/** + * Determines which badges a contributor should have based on their stats + */ +function getContributorBadges( + contributor: Contributor, + rank: number, +): string[] { const badges: string[] = []; + // Special rank-based badges if (rank === 1) { - badges.push("/badges/10.png"); + badges.push("/badges/10.png"); // Hall of Fame for #1 } else if (rank === 2) { - badges.push("/badges/9.png"); + badges.push("/badges/9.png"); // Legendary for #2 } else if (rank === 3) { - badges.push("/badges/8.png"); + badges.push("/badges/8.png"); // Elite for #3 } + // Achievement-based badges BADGE_CONFIG.forEach((badge) => { if (badge.criteria(contributor.prs, contributor.points)) { + // Avoid duplicates if (!badges.includes(badge.image)) { badges.push(badge.image); } @@ -127,6 +135,9 @@ function getContributorBadges(contributor: Contributor, rank: number): string[] return badges; } +/** + * Badge display component + */ function ContributorBadges({ badges, onClick, @@ -259,6 +270,7 @@ function TopPerformerCard({ } export default function LeaderBoard(): JSX.Element { + // Get time filter functions from context const { contributors, stats, @@ -272,13 +284,16 @@ export default function LeaderBoard(): JSX.Element { const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); - const [selectedContributor, setSelectedContributor] = useState(null); + const [selectedContributor, setSelectedContributor] = + useState(null); const [isModalOpen, setIsModalOpen] = useState(false); - const [badgeModalContributor, setBadgeModalContributor] = useState(null); + const [badgeModalContributor, setBadgeModalContributor] = + useState(null); const [isBadgeModalOpen, setIsBadgeModalOpen] = useState(false); const [isSelectChanged, setIsSelectChanged] = useState(false); const itemsPerPage = 20; + // Modal handlers const handlePRClick = (contributor: Contributor) => { setSelectedContributor(contributor); setIsModalOpen(true); @@ -289,16 +304,21 @@ export default function LeaderBoard(): JSX.Element { setSelectedContributor(null); }, []); - const handleBadgeClick = useCallback((contributor: Contributor) => { - setBadgeModalContributor(contributor); - setIsBadgeModalOpen(true); - }, []); + // Badge modal handlers + const handleBadgeClick = useCallback( + (contributor: Contributor) => { + setBadgeModalContributor(contributor); + setIsBadgeModalOpen(true); + }, + [], + ); const handleCloseBadgeModal = useCallback(() => { setIsBadgeModalOpen(false); setBadgeModalContributor(null); }, []); + // Use mock data only in development mode when there's an error or no contributors const displayContributors = error || contributors.length === 0 ? typeof process !== "undefined" && process.env.NODE_ENV === "development" @@ -306,6 +326,7 @@ export default function LeaderBoard(): JSX.Element { : [] : contributors; + // Filter out excluded users and apply search filter const filteredContributors = contributors .filter( (contributor) => @@ -328,7 +349,9 @@ export default function LeaderBoard(): JSX.Element { const renderPaginationButtons = () => { const pages = []; + const maxVisibleButtons = 5; // Maximum number of page buttons to show directly + // Special case: if we have 7 or fewer pages, show all of them without ellipsis if (totalPages <= 7) { for (let i = 1; i <= totalPages; i++) { pages.push( @@ -344,6 +367,9 @@ export default function LeaderBoard(): JSX.Element { return pages; } + // For more than 7 pages, use the ellipsis approach + + // Always show first page pages.push(