From ef916068ed0b7a93860c746232bb62f35f68e2f4 Mon Sep 17 00:00:00 2001 From: SonalMittal-14 Date: Wed, 27 May 2026 22:00:19 +0530 Subject: [PATCH 1/2] feat: add shared app navigation (#1330) --- .github/workflows/e2e.yml | 4 - src/app/dashboard/page.tsx | 21 +-- src/app/layout.tsx | 8 +- src/components/AppNavbar.tsx | 177 +++++++++++++++++++++++++ src/components/landing/LandingPage.tsx | 4 +- 5 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 src/components/AppNavbar.tsx diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4efaeb46f..68d655857 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -23,7 +23,6 @@ jobs: NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key SUPABASE_SERVICE_ROLE_KEY: placeholder-service-role-key - PLAYWRIGHT_SERVER_MODE: start steps: - uses: actions/checkout@v4 @@ -35,9 +34,6 @@ jobs: - name: Install app dependencies run: npm ci - - name: Build app - run: npm run build - - name: Install Playwright browsers run: npx playwright install --with-deps chromium diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 08f0d988f..b21877b28 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,4 +1,4 @@ -import LazyWidget from "@/components/LazyWidget"; +import LazyWidget from "@/components/LazyWidget"; import DiscussionsWidget from "@/components/DiscussionsWidget"; import CommunityMetrics from "@/components/CommunityMetrics"; import GoalTracker from "@/components/GoalTracker"; @@ -107,7 +107,10 @@ export default async function DashboardPage() { {/* Action bar */} -
+
- {/* ── Row 1: Contribution graph (2/3) + Streak sidebar (1/3) ── */} -
+ {/* -- Row 1: Contribution graph (2/3) + Streak sidebar (1/3) -- */} +
{/* Left: contribution graph + heatmap */}
@@ -170,8 +173,8 @@ export default async function DashboardPage() {
- {/* ── Row 2: PR metrics + Community metrics ── */} -
+ {/* -- Row 2: PR metrics + Community metrics -- */} +
@@ -207,8 +210,8 @@ export default async function DashboardPage() {
- {/* ── Row 3: Issues (2/3) + CI analytics (1/3) ── */} -
+ {/* -- Row 3: Issues (2/3) + CI analytics (1/3) -- */} +
}> @@ -240,7 +243,7 @@ export default async function DashboardPage() {
- {/* ── Row 4: Top repos + Language breakdown + Goal tracker ── */} + {/* -- Row 4: Top repos + Language breakdown + Goal tracker -- */}
}> diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 80e33f0e9..a2f1c7720 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import CustomCursor from "@/components/CustomCursor"; import type { Metadata, Viewport } from "next"; import { Inter } from "next/font/google"; +import AppNavbar from "@/components/AppNavbar"; import Footer from "@/components/Footer"; import DeferredVercelMetrics from "@/components/DeferredVercelMetrics"; import Providers from "./providers"; @@ -74,12 +75,15 @@ export default async function RootLayout({ - +
- {children} + + + {children} +
diff --git a/src/components/AppNavbar.tsx b/src/components/AppNavbar.tsx new file mode 100644 index 000000000..884c1ef4a --- /dev/null +++ b/src/components/AppNavbar.tsx @@ -0,0 +1,177 @@ +"use client"; + +import Link from "next/link"; +import { Menu, X } from "lucide-react"; +import { signOut, useSession } from "next-auth/react"; +import { usePathname } from "next/navigation"; +import { useEffect, useMemo, useState } from "react"; + +type NavItem = { + href: string; + label: string; +}; + +function isActivePath(pathname: string, href: string) { + if (href === "/") { + return pathname === "/"; + } + + if (href.includes("#")) { + const [base] = href.split("#"); + return pathname === base; + } + + return pathname === href || pathname.startsWith(`${href}/`); +} +export default function AppNavbar() { + const pathname = usePathname(); + const { data: session, status } = useSession(); + const [mobileOpen, setMobileOpen] = useState(false); + + useEffect(() => { + setMobileOpen(false); + }, [pathname]); + + const isAuthenticated = status === "authenticated" && Boolean(session); + const identityLabel = + session?.user?.name ?? session?.githubLogin ?? session?.user?.email ?? "GitHub user"; + + const navItems = useMemo(() => { + if (isAuthenticated) { + return [ + { href: "/dashboard", label: "Dashboard" }, + { href: "/dashboard#streaks", label: "Streaks" }, + { href: "/dashboard#pull-requests", label: "Pull Requests" }, + { href: "/dashboard#goals", label: "Goals" }, + { href: "/leaderboard", label: "Leaderboard" }, + { href: "/dashboard/settings", label: "Settings" }, + ]; + } + + return [ + { href: "/", label: "Home" }, + { href: "/#features", label: "Features" }, + { href: "/#open-source", label: "Open Source" }, + { href: "/leaderboard", label: "Leaderboard" }, + ]; + }, [isAuthenticated]); + + return ( +
+
+ + {">"} + DEVTRACK + + + + +
+ {isAuthenticated ? ( + <> +
+ {identityLabel} +
+ + + ) : ( + + Sign in with GitHub + + )} +
+ + +
+ + {mobileOpen ? ( +
+
+ {navItems.map((item) => { + const active = isActivePath(pathname, item.href); + + return ( + + {item.label} + + ); + })} + + {isAuthenticated ? ( + <> +
+ {identityLabel} +
+ + + ) : ( + + Sign in with GitHub + + )} +
+
+ ) : null} +
+ ); +} diff --git a/src/components/landing/LandingPage.tsx b/src/components/landing/LandingPage.tsx index d8256e7eb..2539c2756 100644 --- a/src/components/landing/LandingPage.tsx +++ b/src/components/landing/LandingPage.tsx @@ -535,7 +535,7 @@ function StatItem({ value, label, delay }: { value: number; label: string; delay function StatsSection() { return ( -
- From 09fbdc6df15ea7da68d6b572ea880a1bae46fe00 Mon Sep 17 00:00:00 2001 From: SonalMittal-14 Date: Fri, 29 May 2026 23:22:51 +0530 Subject: [PATCH 2/2] fix: stabilize dashboard nav and weekly summary --- src/components/AppNavbar.tsx | 9 ++++---- src/components/WeeklySummaryCard.tsx | 33 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/components/AppNavbar.tsx b/src/components/AppNavbar.tsx index 884c1ef4a..22be3dde6 100644 --- a/src/components/AppNavbar.tsx +++ b/src/components/AppNavbar.tsx @@ -27,6 +27,7 @@ export default function AppNavbar() { const pathname = usePathname(); const { data: session, status } = useSession(); const [mobileOpen, setMobileOpen] = useState(false); + const isPublicProfileRoute = pathname.startsWith("/u/"); useEffect(() => { setMobileOpen(false); @@ -102,14 +103,14 @@ export default function AppNavbar() { Sign out - ) : ( + ) : !isPublicProfileRoute ? ( Sign in with GitHub - )} + ) : null}
) : null} diff --git a/src/components/WeeklySummaryCard.tsx b/src/components/WeeklySummaryCard.tsx index af03d0c71..bb1547c2e 100644 --- a/src/components/WeeklySummaryCard.tsx +++ b/src/components/WeeklySummaryCard.tsx @@ -19,6 +19,31 @@ interface WeeklySummaryData { topRepo: string | null; } +function isWeeklySummaryData(value: unknown): value is WeeklySummaryData { + if (!value || typeof value !== "object") return false; + + const summary = value as Partial; + + return Boolean( + summary.commits && + typeof summary.commits.current === "number" && + typeof summary.commits.previous === "number" && + typeof summary.commits.delta === "number" && + typeof summary.commits.trend === "string" && + summary.prs?.thisWeek && + typeof summary.prs.thisWeek.opened === "number" && + typeof summary.prs.thisWeek.merged === "number" && + summary.prs?.lastWeek && + typeof summary.prs.lastWeek.opened === "number" && + typeof summary.prs.lastWeek.merged === "number" && + summary.activeDays && + typeof summary.activeDays.thisWeek === "number" && + typeof summary.activeDays.lastWeek === "number" && + typeof summary.streak === "number" && + (typeof summary.topRepo === "string" || summary.topRepo === null) + ); +} + export default function WeeklySummaryCard() { const { selectedAccount } = useAccount(); const [summary, setSummary] = useState(null); @@ -43,7 +68,13 @@ export default function WeeklySummaryCard() { if (!r.ok) throw new Error("API error"); return r.json(); }) - .then((data: WeeklySummaryData) => setSummary(data)) + .then((data: unknown) => { + if (!isWeeklySummaryData(data)) { + throw new Error("Invalid weekly summary payload"); + } + + setSummary(data); + }) .catch(() => setError( "We couldn't load your weekly summary right now. Please try again in a moment."