Skip to content

Commit edb6395

Browse files
authored
Merge pull request #1678 from akshara200829-lgtm/feat/contributor-achievement-card
feat: add Contributor Achievement Card Generator to leaderboard
2 parents cb41554 + 00f9a2c commit edb6395

6 files changed

Lines changed: 392 additions & 235 deletions

File tree

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"embla-carousel-autoplay": "^8.6.0",
5858
"embla-carousel-react": "^8.6.0",
5959
"framer-motion": "^12.40.0",
60+
"html-to-image": "^1.11.13",
6061
"lucide-react": "^0.503.0",
6162
"prism-react-renderer": "^2.3.0",
6263
"react": "^18.3.1",
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// src/components/dashboard/LeaderBoard/ContributorCard.tsx
2+
import React, { useRef, useState } from "react";
3+
import { toPng } from "html-to-image";
4+
import { FaGithub, FaDownload, FaTrophy } from "react-icons/fa";
5+
6+
interface ContributorCardProps {
7+
username: string;
8+
avatar: string;
9+
prs: number;
10+
points: number;
11+
rank: number;
12+
badges: string[];
13+
isDark: boolean;
14+
}
15+
16+
export default function ContributorCard({
17+
username,
18+
avatar,
19+
prs,
20+
points,
21+
rank,
22+
badges,
23+
isDark,
24+
}: ContributorCardProps) {
25+
const [open, setOpen] = useState(false);
26+
const cardRef = useRef<HTMLDivElement>(null);
27+
28+
const handleDownload = async () => {
29+
if (!cardRef.current) return;
30+
const dataUrl = await toPng(cardRef.current, { cacheBust: true });
31+
const link = document.createElement("a");
32+
link.download = `${username}-recodehive-card.png`;
33+
link.href = dataUrl;
34+
link.click();
35+
};
36+
37+
return (
38+
<>
39+
{/* Trigger button — sits right of "Contribute on GitHub" */}
40+
<button
41+
className="cta-button"
42+
onClick={() => setOpen(true)}
43+
style={{ marginLeft: 12 }}
44+
aria-label="Generate your achievement card"
45+
>
46+
<FaTrophy style={{ marginRight: 8 }} />
47+
My Achievement Card
48+
</button>
49+
50+
{/* Modal overlay */}
51+
{open && (
52+
<div
53+
className="card-modal-overlay"
54+
onClick={() => setOpen(false)}
55+
>
56+
<div
57+
className="card-modal-inner"
58+
onClick={(e) => e.stopPropagation()}
59+
>
60+
{/* The actual downloadable card */}
61+
<div
62+
ref={cardRef}
63+
className={`achievement-card ${isDark ? "dark" : "light"}`}
64+
>
65+
<div className="achievement-card-header">
66+
<img src="/img/logo.png" alt="RecodeHive" className="card-logo" />
67+
<span className="card-brand">recodehive</span>
68+
</div>
69+
<img src={avatar} alt={username} className="card-avatar" />
70+
<h2 className="card-username">{username}</h2>
71+
<div className="card-rank">#{rank} on Leaderboard</div>
72+
<div className="card-stats">
73+
<div className="card-stat">
74+
<span className="card-stat-value">{prs}</span>
75+
<span className="card-stat-label">PRs Merged</span>
76+
</div>
77+
<div className="card-stat">
78+
<span className="card-stat-value">{points}</span>
79+
<span className="card-stat-label">Points</span>
80+
</div>
81+
</div>
82+
{badges.length > 0 && (
83+
<div className="card-badges">
84+
{badges.slice(0, 5).map((badge, i) => (
85+
<img key={i} src={badge} alt={`badge-${i}`} className="card-badge-icon" />
86+
))}
87+
</div>
88+
)}
89+
<div className="card-footer">
90+
<FaGithub /> github.com/recodehive
91+
</div>
92+
</div>
93+
94+
{/* Action buttons */}
95+
<div className="card-modal-actions">
96+
<button className="cta-button" onClick={handleDownload}>
97+
<FaDownload style={{ marginRight: 8 }} />
98+
Download PNG
99+
</button>
100+
<button
101+
className="cta-button"
102+
style={{ background: "#6b7280", marginLeft: 8 }}
103+
onClick={() => setOpen(false)}
104+
>
105+
Close
106+
</button>
107+
</div>
108+
</div>
109+
</div>
110+
)}
111+
</>
112+
);
113+
}

0 commit comments

Comments
 (0)