diff --git a/e2e/dashboard-widgets.spec.js b/e2e/dashboard-widgets.spec.js index 0ed5886a..3cd8e9fa 100644 --- a/e2e/dashboard-widgets.spec.js +++ b/e2e/dashboard-widgets.spec.js @@ -1,41 +1,36 @@ import { expect, test } from "@playwright/test"; import { encode } from "next-auth/jwt"; - -const authSecret = "playwright-placeholder-secret-that-is-long-enough"; - -test.beforeEach(async ({ page }) => { - const sessionToken = await encode({ - secret: authSecret, - token: { - name: "Playwright User", - email: "playwright@example.com", - sub: "12345", - githubLogin: "playwright-user", - githubId: "12345", - accessToken: "test-token", - }, - maxAge: 60 * 60, - cookieName: "next-auth.session-token", + await page.route("**/api/ai-insights**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + data: { + insights: [ + { + id: "insight-1", + type: "productivity", + title: "High Consistency", + description: "You have coded 5 days this week!", + severity: "positive", + }, + ], + trend: { direction: "up", percentage: 15 }, + aiSummary: "Great job shipping features this week. Keep up the high standard!", + generatedAt: "2026-05-18T12:00:00.000Z", + }, + }), + }); }); - await page.context().addCookies([ - { - 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, - }, - ]); - - await page.route("**/api/auth/session", async (route) => { + await page.route("**/api/notifications**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ - user: { name: "Playwright User", email: "playwright@example.com" }, + notifications: [], + unreadCount: 0, + }), + }); + }); githubLogin: "playwright-user", githubId: "12345", accessToken: "test-token", @@ -88,6 +83,7 @@ test.beforeEach(async ({ page }) => { unit: "commits", recurrence: "weekly", period_start: "2026-05-18", + last_synced_at: new Date().toISOString(), }, ], }), @@ -101,6 +97,7 @@ test.beforeEach(async ({ page }) => { }); }); +<<<<<<< HEAD await page.route("**/api/ai-insights**", async (route) => { await route.fulfill({ contentType: "application/json", @@ -133,6 +130,8 @@ test.beforeEach(async ({ page }) => { }); }); +======= +>>>>>>> d4b3909 (test(e2e): mock /api/goals/sync and mark mock goal as synced) const metricRoutes = [ "**/api/metrics/prs**", "**/api/metrics/pr-breakdown**", diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index 62f39d22..f3dc2295 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -37,21 +37,9 @@ function MouseSpotlight() { export default function SignInPage() { return ( -
- - - {/* Subtle grid */} +
+
+
signIn("github", { callbackUrl: "/dashboard" })} - style={{ - width: "100%", - display: "inline-flex", alignItems: "center", justifyContent: "center", - gap: 10, - background: A, color: "#000", - fontFamily: MONO, fontWeight: 600, fontSize: 14, - padding: "14px 24px", borderRadius: 6, - border: "none", cursor: "pointer", - transition: "background 0.2s, transform 0.1s", - marginBottom: 20, - }} - onMouseEnter={(e) => { - (e.currentTarget as HTMLButtonElement).style.background = "#fff"; - }} - onMouseLeave={(e) => { - (e.currentTarget as HTMLButtonElement).style.background = A; - }} - onMouseDown={(e) => { - (e.currentTarget as HTMLButtonElement).style.transform = "scale(0.97)"; - }} - onMouseUp={(e) => { - (e.currentTarget as HTMLButtonElement).style.transform = "scale(1)"; - }} + className="primary-button relative w-full inline-flex items-center justify-center gap-3 rounded-xl py-3 font-semibold" > diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 159cf9e2..e0671e04 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -39,12 +39,12 @@ export default async function DashboardPage() { if (session.error === "TokenRevoked") redirect("/"); return ( -
+
Settings diff --git a/src/app/globals.css b/src/app/globals.css index cff55606..2d1d27d4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,58 +4,98 @@ :root { color-scheme: light; - --background: #f8fafc; - --foreground: #0f172a; - --muted-foreground: #475569; - --card: #ffffff; - --card-foreground: #0f172a; - --card-muted: #e2e8f0; - --border: #cbd5e1; - --accent: #6366f1; + --background: #ffffff; + --foreground: #111827; + --muted-foreground: #4b5563; + --card: #f8fafc; + --card-foreground: #111827; + --card-muted: #eff6ff; + --border: #e5e7eb; + --accent: #3b82f6; + --accent-secondary: #60a5fa; --success: #10b981; --warning: #f59e0b; --destructive: #ef4444; - --accent-soft: rgba(99, 102, 241, 0.15); + --accent-soft: rgba(59, 130, 246, 0.14); --accent-foreground: #ffffff; - --control: #e2e8f0; - --control-hover: #cbd5e1; + --control: #f1f5f9; + --control-hover: #e2e8f0; --tooltip: #ffffff; - --tooltip-foreground: #0f172a; - --destructive: #ef4444; + --tooltip-foreground: #111827; --destructive-muted: rgba(239, 68, 68, 0.1); --destructive-muted-border: rgba(239, 68, 68, 0.3); --destructive-foreground: #ffffff; + --shadow-soft: 0 12px 30px -20px rgba(37, 99, 235, 0.35); + --shadow-medium: 0 18px 35px -24px rgba(37, 99, 235, 0.4); } .dark { color-scheme: dark; - --background: #080808; - --foreground: #e8e8e8; - --muted-foreground: #666666; - --card: #0e0e0e; - --card-foreground: #e8e8e8; - --card-muted: #161616; - --border: #222222; - --accent: #818cf8; + --background: #0f172a; + --foreground: #f8fafc; + --muted-foreground: #94a3b8; + --card: #1a2538; + --card-foreground: #f8fafc; + --card-muted: #223248; + --border: #334155; + --accent: #60a5fa; + --accent-secondary: #3b82f6; --success: #10b981; --warning: #fbbf24; --destructive: #f87171; - --accent-soft: rgba(129, 140, 248, 0.12); - --accent-foreground: #000000; - --control: #161616; - --control-hover: #252525; - --tooltip: #0e0e0e; - --tooltip-foreground: #e8e8e8; + --accent-soft: rgba(96, 165, 250, 0.2); + --accent-foreground: #ffffff; + --control: #2a3b53; + --control-hover: #344a67; + --tooltip: #1a2538; + --tooltip-foreground: #f8fafc; --destructive-muted: rgba(248, 113, 113, 0.1); --destructive-muted-border: rgba(248, 113, 113, 0.3); --destructive-foreground: #ffffff; + --shadow-soft: 0 16px 34px -24px rgba(2, 6, 23, 0.8); + --shadow-medium: 0 24px 45px -28px rgba(2, 6, 23, 0.85); } body { - background: var(--background); + background: + radial-gradient(circle at 0% 0%, rgba(96, 165, 250, 0.16), transparent 36%), + radial-gradient(circle at 100% 18%, rgba(59, 130, 246, 0.1), transparent 32%), + var(--background); color: var(--foreground); transition: background-color 200ms ease, color 200ms ease; } + +.surface-card { + border: 1px solid var(--border); + background: linear-gradient(180deg, color-mix(in srgb, var(--card) 92%, #ffffff 8%), var(--card)); + box-shadow: var(--shadow-soft); +} + +.primary-button { + border: 1px solid color-mix(in srgb, var(--accent) 86%, white 14%); + background: linear-gradient(140deg, var(--accent), var(--accent-secondary)); + color: var(--accent-foreground); + transition: transform 180ms ease, box-shadow 180ms ease, filter 180ms ease; +} + +.primary-button:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-medium); + filter: saturate(1.05); +} + +.secondary-button { + border: 1px solid var(--border); + background: color-mix(in srgb, var(--card) 88%, white 12%); + color: var(--card-foreground); + transition: transform 180ms ease, border-color 180ms ease, background-color 180ms ease; +} + +.secondary-button:hover { + transform: translateY(-1px); + border-color: color-mix(in srgb, var(--accent) 50%, var(--border) 50%); + background: color-mix(in srgb, var(--control) 90%, white 10%); +} /* Custom slim scrollbar for dashboard widgets */ .scrollbar-thin { scrollbar-width: thin; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2ebaa443..a269c307 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,8 +5,8 @@ import Providers from "./providers"; import PWARegister from "@/components/pwa-register"; import "./globals.css"; import { Toaster } from "sonner"; -import { Analytics } from "@vercel/analytics/next"; -import { SpeedInsights } from "@vercel/speed-insights/next"; +// Load Vercel integrations dynamically so build doesn't fail when packages +// aren't installed in CI/environments where they're optional. const inter = Inter({ subsets: ["latin"] }); const syne = Syne({ @@ -49,11 +49,23 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + let Analytics: any = null; + let SpeedInsights: any = null; + + try { + const a = await import("@vercel/analytics/next"); + Analytics = a?.Analytics ?? a?.default ?? null; + } catch (e) {} + + try { + const s = await import("@vercel/speed-insights/next"); + SpeedInsights = s?.SpeedInsights ?? s?.default ?? null; + } catch (e) {} return ( @@ -63,12 +75,18 @@ export default function RootLayout({ (function() { try { const stored = localStorage.getItem('theme'); - if (stored === 'light') { + if (stored === 'dark') { + document.documentElement.classList.add('dark'); + document.documentElement.style.colorScheme = 'dark'; + } else if (stored === 'light') { document.documentElement.classList.remove('dark'); document.documentElement.style.colorScheme = 'light'; - } else { + } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); document.documentElement.style.colorScheme = 'dark'; + } else { + document.documentElement.classList.remove('dark'); + document.documentElement.style.colorScheme = 'light'; } } catch (e) {} })(); @@ -91,8 +109,8 @@ export default function RootLayout({
- - + {Analytics ? : null} + {SpeedInsights ? : null} ); diff --git a/src/app/leaderboard/page.tsx b/src/app/leaderboard/page.tsx index 3a12bd9e..7945bd75 100644 --- a/src/app/leaderboard/page.tsx +++ b/src/app/leaderboard/page.tsx @@ -92,7 +92,7 @@ export default async function LeaderboardPage({ )}
-
+
{tabs.map((tab) => { const active = tab.id === activeTab; return ( @@ -101,7 +101,7 @@ export default async function LeaderboardPage({ href={`/leaderboard?tab=${tab.id}`} className={`rounded-lg border px-4 py-2 text-sm font-semibold transition-colors ${ active - ? "border-[var(--accent)] bg-[var(--accent)] text-[var(--accent-foreground)]" + ? "border-[var(--accent)] bg-[var(--accent)] text-[var(--accent-foreground)] shadow-sm" : "border-[var(--border)] bg-[var(--card)] text-[var(--card-foreground)] hover:bg-[var(--control)]" }`} > @@ -111,7 +111,7 @@ export default async function LeaderboardPage({ })}
-
+
Rank
Contributor
@@ -168,7 +168,7 @@ export default async function LeaderboardPage({
View diff --git a/src/app/page.tsx b/src/app/page.tsx index b4ca229a..e5b50df3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,71 +1,7 @@ -import { Syne, DM_Sans, JetBrains_Mono } from 'next/font/google'; +import Link from "next/link"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { redirect } from "next/navigation"; -import LandingPage, { type RepoStats } from "@/components/landing/LandingPage"; - -const syne = Syne({ - subsets: ['latin'], - variable: '--font-syne', - weight: ['700', '800'], - display: 'swap', -}); -const dmSans = DM_Sans({ - subsets: ['latin'], - variable: '--font-dm-sans', - weight: ['400', '500', '600'], - display: 'swap', -}); -const jetbrains = JetBrains_Mono({ - subsets: ['latin'], - variable: '--font-jetbrains', - weight: ['400', '500', '600', '700'], - display: 'swap', -}); - -async function fetchRepoStats(): Promise { - const GH_HEADERS = { Accept: 'application/vnd.github.v3+json' }; - const OPTS = (ttl: number) => ({ next: { revalidate: ttl }, headers: GH_HEADERS }); - - try { - const [repoRes, contribRes, gfiRes] = await Promise.all([ - fetch('https://api.github.com/repos/Priyanshu-byte-coder/devtrack', OPTS(3600)), - fetch('https://api.github.com/repos/Priyanshu-byte-coder/devtrack/contributors?per_page=30', OPTS(3600)), - fetch('https://api.github.com/repos/Priyanshu-byte-coder/devtrack/issues?labels=good+first+issue&state=open&per_page=100', OPTS(1800)), - ]); - - if (!repoRes.ok) throw new Error('repo fetch failed'); - - const repo = await repoRes.json() as Record; - const contributors = contribRes.ok ? (await contribRes.json() as Array>) : []; - const gfiIssues = gfiRes.ok ? (await gfiRes.json() as unknown[]) : []; - - return { - stars: typeof repo.stargazers_count === 'number' ? repo.stargazers_count : 0, - forks: typeof repo.forks_count === 'number' ? repo.forks_count : 0, - openIssues: typeof repo.open_issues_count === 'number' ? repo.open_issues_count : 0, - contributorCount: Array.isArray(contributors) ? contributors.length : 0, - goodFirstIssues: Array.isArray(gfiIssues) ? gfiIssues.length : 0, - contributors: Array.isArray(contributors) - ? contributors.slice(0, 20).map(c => ({ - login: String(c.login ?? ''), - avatar_url: String(c.avatar_url ?? ''), - html_url: String(c.html_url ?? ''), - })) - : [], - }; - } catch { - // Graceful fallback โ€” page still renders without live stats - return { - stars: 40, - forks: 160, - openIssues: 307, - contributorCount: 30, - goodFirstIssues: 36, - contributors: [], - }; - } -} export default async function HomePage() { const session = await getServerSession(authOptions); @@ -74,11 +10,95 @@ export default async function HomePage() { redirect("/dashboard"); } - const repoStats = await fetchRepoStats(); + const features = [ + { + icon: "๐Ÿ”ฅ", + title: "Streak Tracking", + description: "Never lose your streak and stay consistent every day.", + }, + { + icon: "๐Ÿ“Š", + title: "PR Analytics", + description: "Understand your pull request activity and review velocity.", + }, + { + icon: "๐Ÿ†", + title: "Goals", + description: "Set coding goals and automatically track your progress.", + }, + { + icon: "๐ŸŒ", + title: "Public Profile", + description: + "Share your developer stats and achievements with the world.", + }, + ]; return ( -
- -
+
+
+
+
+
+ +
+
+ + Open-source dev productivity + +

+ DevTrack +

+

+ Open-source developer productivity dashboard. Track coding habits, + visualize GitHub contributions, and hit your goals. +

+
+ + Sign in with GitHub + + + View on GitHub + +
+
+ +
+

+ Everything you need to track your coding growth +

+ +
+ {features.map((feature) => ( +
+
+ {feature.icon} +
+ +

+ {feature.title} +

+ +

+ {feature.description} +

+
+ ))} +
+
+
+
+>>>>>>> 375a1b5 (feat(ui): modernize interface with light blue and white theme (#924)) ); } diff --git a/src/app/u/[username]/page.tsx b/src/app/u/[username]/page.tsx index 4ad8b075..025827a6 100644 --- a/src/app/u/[username]/page.tsx +++ b/src/app/u/[username]/page.tsx @@ -79,7 +79,7 @@ export default async function PublicProfilePage({ if (!profile) { return (
-
+

Profile Not Found

@@ -98,7 +98,7 @@ export default async function PublicProfilePage({

Back to Home @@ -111,7 +111,7 @@ export default async function PublicProfilePage({ const topRepo = profile.repos[0]?.name ?? ""; return ( -
+
@@ -176,7 +176,7 @@ function PublicContributionGraph({ .map(([day, commits]) => ({ day, commits })); return ( -
+

@@ -263,7 +263,7 @@ function PublicStreakTracker({ streak }: { streak: any }) { ]; return ( -
+

Commit Streaks

@@ -307,7 +307,7 @@ function PublicTopRepos({ const maxCommits = repos[0]?.commits ?? 1; return ( -
+

Top Repositories

diff --git a/src/components/DashboardHeader.tsx b/src/components/DashboardHeader.tsx index f830287f..157a21b8 100644 --- a/src/components/DashboardHeader.tsx +++ b/src/components/DashboardHeader.tsx @@ -39,22 +39,13 @@ export default function DashboardHeader() { }, [session]); return ( -
+
{/* Left Section */}
-

- โ–ฒ DEVTRACK -

-

- DASHBOARD +

+ Dashboard

Share Profile )} -

+
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index a9162479..9215409e 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -4,11 +4,11 @@ const year = new Date().getFullYear(); export default function Footer() { return ( -