diff --git a/src/app/api/metrics/profile-stats/route.ts b/src/app/api/metrics/profile-stats/route.ts
new file mode 100644
index 0000000..a8ecfac
--- /dev/null
+++ b/src/app/api/metrics/profile-stats/route.ts
@@ -0,0 +1,74 @@
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/lib/auth";
+
+export const dynamic = "force-dynamic";
+
+const GITHUB_API = "https://api.github.com";
+
+type GithubRepo = {
+ stargazers_count: number;
+ forks_count: number;
+};
+
+export async function GET() {
+ const session = await getServerSession(authOptions);
+
+ if (!session?.accessToken || !session.githubLogin) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const userRes = await fetch(`${GITHUB_API}/user`, {
+ headers: {
+ Authorization: `Bearer ${session.accessToken}`,
+ Accept: "application/vnd.github+json",
+ },
+ cache: "no-store",
+ });
+
+ if (!userRes.ok) {
+ return Response.json(
+ { error: "Failed to fetch profile stats" },
+ { status: 502 }
+ );
+ }
+
+ const user = await userRes.json();
+
+ const reposRes = await fetch(
+ `${GITHUB_API}/user/repos?per_page=100&type=owner&sort=updated`,
+ {
+ headers: {
+ Authorization: `Bearer ${session.accessToken}`,
+ Accept: "application/vnd.github+json",
+ },
+ cache: "no-store",
+ }
+ );
+
+ if (!reposRes.ok) {
+ return Response.json(
+ { error: "Failed to fetch repo stats" },
+ { status: 502 }
+ );
+ }
+
+ const repos = (await reposRes.json()) as GithubRepo[];
+
+ const totalStars = repos.reduce(
+ (sum, repo) => sum + (repo.stargazers_count ?? 0),
+ 0
+ );
+
+ const totalForks = repos.reduce(
+ (sum, repo) => sum + (repo.forks_count ?? 0),
+ 0
+ );
+
+ return Response.json({
+ memberSince: user.created_at,
+ publicRepos: user.public_repos,
+ totalStars,
+ totalForks,
+ followers: user.followers,
+ });
+}
\ No newline at end of file
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 23f9881..2fbe9cd 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -17,6 +17,7 @@ import WeeklySummaryCard from "@/components/WeeklySummaryCard";
import ExportButton from "@/components/ExportButton";
import Link from "next/link";
import PersonalRecords from "@/components/PersonalRecords";
+import ProfileStats from "@/components/ProfileStats";
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
@@ -31,27 +32,32 @@ export default async function DashboardPage() {
return (
-
+
+
Settings
+
+
+
- {/* Row 1: Contribution graph + Streak + Friend Comparison */}
-
+
@@ -65,32 +71,28 @@ export default async function DashboardPage() {
- {/* Row 2: PR metrics, PR breakdown & Time Chart */}
-
+
- {/* Row 3: Issue metrics + CI analytics */}
-
+
- {/* Row 4: Pinned repositories */}
- {/* Row 5: Top repos + Language breakdown + Goal tracker */}
-
);
-}
+}
\ No newline at end of file
diff --git a/src/components/ProfileStats.tsx b/src/components/ProfileStats.tsx
new file mode 100644
index 0000000..6031804
--- /dev/null
+++ b/src/components/ProfileStats.tsx
@@ -0,0 +1,127 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+
+type ProfileStatsData = {
+ memberSince: string;
+ publicRepos: number;
+ totalStars: number;
+ totalForks: number;
+ followers: number;
+};
+
+type StatCard = {
+ label: string;
+ value: string;
+ icon: string;
+};
+
+function formatCompactNumber(value: number) {
+ return new Intl.NumberFormat("en", {
+ notation: "compact",
+ maximumFractionDigits: 1,
+ }).format(value);
+}
+
+function formatMemberSince(dateString: string) {
+ const date = new Date(dateString);
+
+ return new Intl.DateTimeFormat("en", {
+ month: "short",
+ year: "numeric",
+ }).format(date);
+}
+
+export default function ProfileStats() {
+ const [data, setData] = useState
(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ fetch("/api/metrics/profile-stats")
+ .then(async (r) => {
+ const res = await r.json();
+
+ if (!r.ok) {
+ throw new Error(res.error || "Failed to load profile stats");
+ }
+
+ return res as ProfileStatsData;
+ })
+ .then((res) => setData(res))
+ .catch((error) => {
+ console.error(error);
+ setData(null);
+ })
+ .finally(() => setLoading(false));
+ }, []);
+
+ const stats: StatCard[] = useMemo(() => {
+ if (!data) return [];
+
+ return [
+ {
+ label: "Member Since",
+ value: formatMemberSince(data.memberSince),
+ icon: "📅",
+ },
+ {
+ label: "Public Repos",
+ value: formatCompactNumber(data.publicRepos),
+ icon: "📦",
+ },
+ {
+ label: "Total Stars",
+ value: formatCompactNumber(data.totalStars),
+ icon: "⭐",
+ },
+ {
+ label: "Total Forks",
+ value: formatCompactNumber(data.totalForks),
+ icon: "🍴",
+ },
+ {
+ label: "Followers",
+ value: formatCompactNumber(data.followers),
+ icon: "👥",
+ },
+ ];
+ }, [data]);
+
+ return (
+
+
+
+ GitHub Profile Stats
+
+
+
+
+ {loading
+ ? Array.from({ length: 5 }).map((_, i) => (
+
+ ))
+ : stats.map((stat) => (
+
+
+ {stat.icon}
+ {stat.label}
+
+
+
+ {stat.value}
+
+
+ ))}
+
+
+ );
+}