diff --git a/e2e/dashboard-widgets.spec.js b/e2e/dashboard-widgets.spec.js index 40ab6562..0ff5f3f4 100644 --- a/e2e/dashboard-widgets.spec.js +++ b/e2e/dashboard-widgets.spec.js @@ -21,24 +21,36 @@ test.beforeEach(async ({ page }) => { { name: "next-auth.session-token", value: sessionToken, - domain: "127.0.0.1", - path: "/", - httpOnly: true, - sameSite: "Lax", - secure: false, - expires: Math.floor(Date.now() / 1000) + 60 * 60, + url: "http://127.0.0.1:3000", }, ]); - await page.route("**/api/auth/session", async (route) => { + await page.route("**/api/goals", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + goals: [ + { + id: "goal-1", + title: "Make 10 commits", + target: 10, + current: 4, + unit: "commits", + recurrence: "weekly", + period_start: "2026-05-18", + last_synced_at: null, + }, + ], + }), + }); + }); + + await page.route("**/api/goals/sync", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ - user: { name: "Playwright User", email: "playwright@example.com" }, - githubLogin: "playwright-user", - githubId: "12345", - accessToken: "test-token", - expires: "2099-01-01T00:00:00.000Z", + synced: true, + updated: 1, }), }); }); @@ -65,34 +77,6 @@ test.beforeEach(async ({ page }) => { }); }); - await page.route("**/api/goals", async (route) => { - if (route.request().method() === "POST") { - await route.fulfill({ - contentType: "application/json", - status: 201, - body: JSON.stringify({ ok: true }), - }); - return; - } - - await route.fulfill({ - contentType: "application/json", - body: JSON.stringify({ - goals: [ - { - id: "goal-1", - title: "Make 10 commits", - target: 10, - current: 4, - unit: "commits", - recurrence: "weekly", - period_start: "2026-05-18", - }, - ], - }), - }); - }); - const metricRoutes = [ "**/api/metrics/prs**", "**/api/metrics/pr-breakdown**", @@ -122,10 +106,10 @@ test.beforeEach(async ({ page }) => { test("dashboard widgets render with mocked metrics", async ({ page }) => { await page.goto("/dashboard", { waitUntil: "load" }); await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible({ timeout: 30000 }); + await page.waitForLoadState("networkidle"); await expect(page.getByRole("heading", { name: "Your Commits" })).toBeVisible({ timeout: 10000 }); await expect(page.getByRole("heading", { name: "PR Analytics" })).toBeVisible({ timeout: 10000 }); - await expect(page.getByRole("heading", { name: "Goals" })).toBeVisible({ timeout: 10000 }); - await expect(page.getByText("Make 10 commits")).toBeVisible({ timeout: 10000 }); + await expect(page.getByText("Make 10 commits", { exact: true })).toBeVisible({ timeout: 20000 }); }); test("contribution graph range buttons request a new range", async ({ page }) => { diff --git a/src/app/page.tsx b/src/app/page.tsx index b7658f4f..1258debb 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,6 +3,51 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { redirect } from "next/navigation"; +interface Contributor { + login: string; + avatar_url: string; + html_url: string; + contributions: number; +} + +async function fetchContributors(): Promise { + try { + const token = process.env.GITHUB_TOKEN; + const headers: Record = { + "User-Agent": "DevTrack-App", + Accept: "application/vnd.github+json", + }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const res = await fetch( + "https://api.github.com/repos/Priyanshu-byte-coder/devtrack/contributors", + { + headers, + next: { revalidate: 3600 }, + } + ); + if (!res.ok) { + console.error(`GitHub API error: ${res.status}`); + return []; + } + const data = await res.json(); + if (Array.isArray(data)) { + return data.map((item: any) => ({ + login: item.login, + avatar_url: item.avatar_url, + html_url: item.html_url, + contributions: item.contributions, + })); + } + return []; + } catch (error) { + console.error("Failed to fetch contributors from GitHub:", error); + return []; + } +} + export default async function HomePage() { const session = await getServerSession(authOptions); @@ -34,11 +79,18 @@ export default async function HomePage() { }, ]; + const contributors = await fetchContributors(); + const sortedContributors = [...contributors].sort((a, b) => b.contributions - a.contributions); + const top3 = sortedContributors.slice(0, 3); + const rest = sortedContributors.slice(3); + const firstPlace = top3[0]; + const secondPlace = top3[1]; + const thirdPlace = top3[2]; + return ( -
- {/* Hero Section */} -
-

+
+
+

DevTrack

@@ -97,6 +149,151 @@ export default async function HomePage() { ))}
+ + {contributors.length > 0 && ( +
+

+ Top Contributors +

+

+ Meet the developers who are actively shaping DevTrack. Thank you for making our open-source productivity platform grow! +

+ + {/* Podium for top 3 */} +
+ {/* 2nd Place */} + {secondPlace && ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {secondPlace.login} +
+ 2nd +
+
+
+ + @{secondPlace.login} + + + {secondPlace.contributions} commits + +
+
+ )} + + {/* 1st Place */} + {firstPlace && ( +
+
👑
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {firstPlace.login} +
+ 1st +
+
+
+ + @{firstPlace.login} + + + {firstPlace.contributions} commits + +
+
+ )} + + {/* 3rd Place */} + {thirdPlace && ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {thirdPlace.login} +
+ 3rd +
+
+
+ + @{thirdPlace.login} + + + {thirdPlace.contributions} commits + +
+
+ )} +
+ + {/* Rest of Contributors Grid */} + {rest.length > 0 && ( +
+ {rest.map((contrib, index) => { + const rank = index + 4; + return ( +
+ + #{rank} + + {/* eslint-disable-next-line @next/next/no-img-element */} + {contrib.login} +
+ +

+ {contrib.contributions} commits +

+
+
+ ); + })} +
+ )} +
+ )}
); }