diff --git a/src/app/contributors/page.tsx b/src/app/contributors/page.tsx index d1ccddfe..29467f45 100644 --- a/src/app/contributors/page.tsx +++ b/src/app/contributors/page.tsx @@ -1,18 +1,124 @@ "use client"; -import { Github, Code, FileText, MessageSquare, ExternalLink } from 'lucide-react'; +import { useState, useEffect } from 'react'; +import { Github, Code, FileText, MessageSquare, ExternalLink, Loader2 } from 'lucide-react'; import Button from '@/components/ui/Button'; import styles from './Contributors.module.css'; -const contributors = [ - { name: "Aditya Patil", handle: "@Aditya948351", contributions: 500, types: ["code", "design", "community"], avatar: "AP" }, +// Type definitions +interface Contributor { + name: string; + handle: string; + contributions: number; + types: string[]; + avatar: string | null; +} + +interface TopContributor { + name: string; + handle: string; + contributions: number; + rank: number; + avatar: string | null; +} + +// Fallback lists if API fails or rate-limit is hit +const FALLBACK_TOP: TopContributor[] = [ + { name: "Aditya Patil", handle: "@Aditya948351", contributions: 500, rank: 1, avatar: null }, + { name: "schrodingerspet", handle: "@schrodingerspet", contributions: 300, rank: 2, avatar: null }, + { name: "Niteshagarwal01", handle: "@Niteshagarwal01", contributions: 150, rank: 3, avatar: null }, ]; -const topContributors = [ - { name: "Aditya Patil", handle: "@Aditya948351", contributions: 500, rank: 1, avatar: "AP" }, +const contributors: Contributor[] = [ + { name: "Aditya Patil", handle: "@Aditya948351", contributions: 500, types: ["code", "design", "community"], avatar: "AP" }, ]; export default function ContributorsPage() { + const [topContributors, setTopContributors] = useState([]); + const [stats, setStats] = useState({ + totalContributors: 1240, + totalContributions: 15400, + activeThisMonth: 128, + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchContributorsData() { + try { + const res = await fetch('https://api.github.com/repos/devpathindcommunity-india/DevPath-Web/contributors'); + if (!res.ok) { + throw new Error(`Failed to fetch: ${res.statusText}`); + } + const data = await res.json(); + + // Sort by contributions descending + const sorted = [...data].sort((a: any, b: any) => b.contributions - a.contributions); + + // Extract top 3 + const mappedTop = sorted.slice(0, 3).map((c: any, index: number) => ({ + name: c.login, + handle: `@${c.login}`, + contributions: c.contributions, + rank: index + 1, + avatar: c.avatar_url, + })); + + setTopContributors(mappedTop); + + // Calculate stats + const totalContributorsCount = sorted.length; + const totalContributionsCount = sorted.reduce((sum: number, c: any) => sum + c.contributions, 0); + + let activeCount = Math.ceil(totalContributorsCount * 0.4); // Fallback: ~40% active + + try { + // Fetch recent commits (last 30 days) to calculate active contributors count + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + const commitsRes = await fetch(`https://api.github.com/repos/devpathindcommunity-india/DevPath-Web/commits?since=${thirtyDaysAgo.toISOString()}`); + if (commitsRes.ok) { + const commits = await commitsRes.json(); + const uniqueAuthors = new Set( + commits.map((commit: any) => commit.author?.login || commit.commit?.author?.name).filter(Boolean) + ); + activeCount = uniqueAuthors.size; + } + } catch (commitsErr) { + console.error("Error fetching commits for active stats:", commitsErr); + } + + setStats({ + totalContributors: totalContributorsCount, + totalContributions: totalContributionsCount, + activeThisMonth: activeCount || 1, + }); + } catch (err: any) { + console.error("Error fetching contributors data:", err); + setError(err.message || "Failed to load contributors data"); + setTopContributors(FALLBACK_TOP); + } finally { + setLoading(false); + } + } + + fetchContributorsData(); + }, []); + + // Standard physical podium arrangement: [2nd, 1st, 3rd] + const arrangePodium = (list: TopContributor[]) => { + if (list.length < 3) return list; + return [list[1], list[0], list[2]]; + }; + + // Helper to format large numbers (e.g. 15400 -> 15.4k) + const formatNumber = (num: number): string => { + if (num >= 1000) { + return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } + return num.toString(); + }; + return (
@@ -22,36 +128,59 @@ export default function ContributorsPage() {

- 1,240 + + {loading ? "..." : stats.totalContributors.toLocaleString()} + Total Contributors
- 15.4k + + {loading ? "..." : formatNumber(stats.totalContributions)} + Contributions
- 128 + + {loading ? "..." : stats.activeThisMonth.toLocaleString()} + Active This Month
-
- {topContributors.map((contributor) => ( -
-
- {contributor.rank === 1 &&
👑
} - {contributor.rank === 2 &&
🥈
} - {contributor.rank === 3 &&
🥉
} -
- {contributor.avatar} + {loading ? ( +
+ +

Loading contributors podium...

+
+ ) : ( +
+ {arrangePodium(topContributors).map((contributor) => ( +
+
+ {contributor.rank === 1 &&
👑
} + {contributor.rank === 2 &&
🥈
} + {contributor.rank === 3 &&
🥉
} +
+ {contributor.avatar ? ( + {contributor.name} + ) : ( +
+ {contributor.name.slice(0, 2).toUpperCase()} +
+ )} +
+
{contributor.name}
+
{contributor.contributions} commits
-
{contributor.name}
-
{contributor.contributions} commits
-
- ))} -
+ ))} +
+ )}
{contributors.map((contributor, index) => (