From 7fe9c9b6ec44ead56effb97379dc916266409c96 Mon Sep 17 00:00:00 2001 From: Muragesh-24 Date: Thu, 21 May 2026 12:33:38 +0530 Subject: [PATCH] feat : added a top laung feature for top repos --- src/app/api/metrics/repos/route.ts | 73 ++++++++++++++++++++++++--- src/components/TopRepos.tsx | 80 ++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 8 deletions(-) diff --git a/src/app/api/metrics/repos/route.ts b/src/app/api/metrics/repos/route.ts index 399a20b7..ee3fd1cb 100644 --- a/src/app/api/metrics/repos/route.ts +++ b/src/app/api/metrics/repos/route.ts @@ -22,6 +22,14 @@ interface RepoSummary { name: string; commits: number; description: string | null; + url: string; + languages?: RepoLanguage[]; +} + +interface RepoLanguage { + name: string; + bytes: number; + percentage: number; } interface RepoResponse { @@ -30,22 +38,63 @@ interface RepoResponse { } function mergeRepoCommits( - a: Array<{ name: string; commits: number; description: string | null }>, - b: Array<{ name: string; commits: number; description: string | null }> -): Array<{ name: string; commits: number; description: string | null }> { - const map = new Map(); + a: Array, + b: Array +): Array { + const map = new Map(); for (const repo of [...a, ...b]) { const existing = map.get(repo.name); map.set(repo.name, { commits: (existing?.commits ?? 0) + repo.commits, description: existing?.description ?? repo.description, + url: existing?.url ?? repo.url, + languages: existing?.languages ?? repo.languages, }); } return Array.from(map.entries()) - .map(([name, { commits, description }]) => ({ name, commits, description })) + .map(([name, { commits, description, url, languages }]) => ({ + name, + commits, + description, + url, + languages, + })) .sort((x, y) => y.commits - x.commits); } +async function fetchRepoLanguages( + token: string, + repoName: string +): Promise { + const res = await fetch(`${GITHUB_API}/repos/${repoName}/languages`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github+json", + }, + cache: "no-store", + }); + + if (!res.ok) { + return []; + } + + const langs = (await res.json()) as Record; + const totalBytes = Object.values(langs).reduce((sum, bytes) => sum + bytes, 0); + + if (totalBytes <= 0) { + return []; + } + + return Object.entries(langs) + .map(([name, bytes]) => ({ + name, + bytes, + percentage: Math.round((bytes / totalBytes) * 1000) / 10, + })) + .sort((a, b) => b.percentage - a.percentage) + .slice(0, 6); +} + async function fetchReposForAccount( token: string, githubLogin: string, @@ -90,21 +139,29 @@ async function fetchReposForAccount( }>; }; - const repoMap: Record = {}; + const repoMap: Record = {}; for (const item of data.items) { const name = item.repository.full_name; repoMap[name] = { commits: (repoMap[name]?.commits ?? 0) + 1, description: item.repository.description, + url: item.repository.html_url, }; } const repos = Object.entries(repoMap) - .map(([name, { commits, description }]) => ({ name, commits, description })) + .map(([name, { commits, description, url }]) => ({ name, commits, description, url })) .sort((a, b) => b.commits - a.commits) .slice(0, 6); - return { repos, days }; + const reposWithLanguages = await Promise.all( + repos.map(async (repo) => { + const languages = await fetchRepoLanguages(token, repo.name); + return languages.length > 0 ? { ...repo, languages } : repo; + }) + ); + + return { repos: reposWithLanguages, days }; } ); } diff --git a/src/components/TopRepos.tsx b/src/components/TopRepos.tsx index 3dc9a4be..0c7dd488 100644 --- a/src/components/TopRepos.tsx +++ b/src/components/TopRepos.tsx @@ -4,11 +4,70 @@ import { useCallback, useEffect, useState } from "react"; import { useAccount } from "@/components/AccountContext"; import type { RepoHealthScore } from "@/types/repo-health"; +interface RepoLanguage { + name: string; + bytes: number; + percentage: number; +} + interface Repo { name: string; commits: number; url: string; description: string | null; + languages?: RepoLanguage[]; +} + +const LANGUAGE_COLORS: Record = { + TypeScript: "#3178c6", + JavaScript: "#f7df1e", + Python: "#3572A5", + Go: "#00ADD8", + Rust: "#dea584", + Java: "#b07219", + CSS: "#563d7c", + HTML: "#e34c26", + Ruby: "#701516", + Shell: "#89e051", +}; + +const FALLBACK_LANGUAGE_COLOR = "#6b7280"; + +function getLanguageColor(name: string): string { + return LANGUAGE_COLORS[name] ?? FALLBACK_LANGUAGE_COLOR; +} + +function getVisibleLanguages(languages: RepoLanguage[]): RepoLanguage[] { + const sorted = [...languages].sort((a, b) => b.percentage - a.percentage); + + if (sorted.length <= 3) { + const total = sorted.reduce((sum, lang) => sum + lang.percentage, 0); + if (total < 100 && sorted.length > 0) { + return [ + ...sorted, + { + name: "Other", + bytes: 0, + percentage: Math.round((100 - total) * 10) / 10, + }, + ]; + } + return sorted; + } + + const topLanguages = sorted.slice(0, 2); + const otherPercentage = Math.round( + sorted.slice(2).reduce((sum, lang) => sum + lang.percentage, 0) * 10 + ) / 10; + + return [ + ...topLanguages, + { + name: "Other", + bytes: 0, + percentage: otherPercentage, + }, + ]; } export default function TopRepos() { @@ -191,6 +250,7 @@ export default function TopRepos() { : health?.grade === "yellow" ? "bg-yellow-500/15 text-yellow-300 border border-yellow-500/25" : "bg-red-500/15 text-red-300 border border-red-500/25"; + const visibleLanguages = repo.languages ? getVisibleLanguages(repo.languages) : []; return (
  • @@ -233,6 +293,26 @@ export default function TopRepos() { style={{ width: `${barWidth}%` }} />
    +
    + {visibleLanguages.length > 0 && ( +
    + {visibleLanguages.map((language) => ( + + + {language.name} + {language.percentage}% + + ))} +
    + )} +
  • ); })}