Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default async function DashboardPage() {
<DashboardSSEProvider>
<div className="min-h-screen bg-[var(--background)] p-4 text-[var(--foreground)] transition-colors md:p-8">
<DashboardHeader />
<div className="mb-6 flex justify-end items-center gap-2">
<div id="overview" className="mb-6 flex justify-end items-center gap-2">
<Link
href="/wrapped"
className="rounded-lg border border-[var(--accent)] bg-[var(--accent-soft)] px-4 py-2 text-sm font-semibold text-[var(--accent)] hover:opacity-90 transition-opacity min-w-[140px] flex items-center justify-center"
Expand Down Expand Up @@ -155,7 +155,7 @@ export default async function DashboardPage() {
</div>

{/* Row 1: Contribution graph + Streak + Local Coding Time */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div id="streaks" className="grid grid-cols-1 gap-6 scroll-mt-24 lg:grid-cols-3">
<div className="lg:col-span-2">
<ContributionGraph />
<div className="mt-6">
Expand All @@ -179,7 +179,7 @@ export default async function DashboardPage() {
</div>

{/* Row 2: PR metrics, community metrics, PR breakdown & Time Chart */}
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
<div id="pull-requests" className="mt-6 grid grid-cols-1 gap-6 scroll-mt-24 md:grid-cols-2 xl:grid-cols-4">
<PRMetrics />
<CommunityMetrics />
<PRBreakdownChart />
Expand All @@ -199,7 +199,7 @@ export default async function DashboardPage() {
</div>

{/* Row 3: Issue metrics + CI analytics */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<div id="goals" className="mt-6 grid grid-cols-1 gap-6 scroll-mt-24 lg:grid-cols-3">
<div className="lg:col-span-2">
<IssueMetrics />
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata, Viewport } from "next";
import { Inter, Syne, JetBrains_Mono } from "next/font/google";
import AppNavbar from "@/components/AppNavbar";
import Footer from "@/components/Footer";
import DeferredVercelMetrics from "@/components/DeferredVercelMetrics";
import Providers from "./providers";
Expand Down Expand Up @@ -89,7 +90,10 @@ export default async function RootLayout({

<div className="flex min-h-screen flex-col">
<div className="flex-1">
<Providers>{children}</Providers>
<Providers>
<AppNavbar />
{children}
</Providers>
</div>

<Footer />
Expand Down
178 changes: 178 additions & 0 deletions src/components/AppNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"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<NavItem[]>(() => {
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 (
<header className="sticky top-0 z-50 border-b border-[var(--border)] bg-[color-mix(in_srgb,var(--background)_82%,transparent)] backdrop-blur-xl">
<div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-4 py-3 sm:px-6 lg:px-8">
<Link
href={isAuthenticated ? "/dashboard" : "/"}
className="inline-flex items-center gap-2 text-sm font-semibold tracking-[0.16em] text-[var(--foreground)]"
style={{ fontFamily: "var(--font-jetbrains, ui-monospace, monospace)" }}
>
<span className="text-[var(--accent)]">{">"}</span>
<span>DEVTRACK</span>
</Link>

<nav className="hidden items-center gap-2 lg:flex">
{navItems.map((item) => {
const active = isActivePath(pathname, item.href);

return (
<Link
key={item.href}
href={item.href}
className={`rounded-full px-4 py-2 text-sm font-medium transition-colors ${
active
? "bg-[var(--accent-soft)] text-[var(--accent)]"
: "text-[var(--muted-foreground)] hover:bg-[var(--card)] hover:text-[var(--foreground)]"
}`}
>
{item.label}
</Link>
);
})}
</nav>

<div className="hidden items-center gap-3 lg:flex">
{isAuthenticated ? (
<>
<div className="hidden max-w-48 truncate rounded-full border border-[var(--border)] bg-[var(--card)] px-4 py-2 text-sm text-[var(--card-foreground)] xl:block">
{identityLabel}
</div>
<button
type="button"
onClick={() => signOut({ callbackUrl: "/" })}
className="rounded-full bg-[var(--destructive)] px-4 py-2 text-sm font-semibold text-[var(--destructive-foreground)] transition-opacity hover:opacity-90"
>
Sign out
</button>
</>
) : (
<Link
href="/api/auth/signin/github?callbackUrl=/dashboard"
className="rounded-full bg-[var(--accent)] px-4 py-2 text-sm font-semibold text-[var(--accent-foreground)] transition-opacity hover:opacity-90"
>
Sign in with GitHub
</Link>
)}
</div>

<button
type="button"
onClick={() => setMobileOpen((open) => !open)}
className="inline-flex items-center justify-center rounded-full border border-[var(--border)] bg-[var(--card)] p-2 text-[var(--foreground)] lg:hidden"
aria-expanded={mobileOpen}
aria-controls="app-mobile-nav"
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
>
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
</div>

{mobileOpen ? (
<div
id="app-mobile-nav"
className="border-t border-[var(--border)] bg-[var(--background)] lg:hidden"
>
<div className="mx-auto flex w-full max-w-7xl flex-col gap-2 px-4 py-4 sm:px-6 lg:px-8">
{navItems.map((item) => {
const active = isActivePath(pathname, item.href);

return (
<Link
key={item.href}
href={item.href}
className={`rounded-2xl px-4 py-3 text-sm font-medium transition-colors ${
active
? "bg-[var(--accent-soft)] text-[var(--accent)]"
: "bg-[var(--card)] text-[var(--card-foreground)] hover:bg-[var(--control)]"
}`}
>
{item.label}
</Link>
);
})}

{isAuthenticated ? (
<>
<div className="rounded-2xl border border-[var(--border)] bg-[var(--card)] px-4 py-3 text-sm text-[var(--card-foreground)]">
{identityLabel}
</div>
<button
type="button"
onClick={() => signOut({ callbackUrl: "/" })}
className="rounded-2xl bg-[var(--destructive)] px-4 py-3 text-left text-sm font-semibold text-[var(--destructive-foreground)]"
>
Sign out
</button>
</>
) : (
<Link
href="/api/auth/signin/github?callbackUrl=/dashboard"
className="rounded-2xl bg-[var(--accent)] px-4 py-3 text-sm font-semibold text-[var(--accent-foreground)]"
>
Sign in with GitHub
</Link>
)}
</div>
</div>
) : null}
</header>
);
}
4 changes: 2 additions & 2 deletions src/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ function StatItem({ value, label, delay }: { value: number; label: string; delay

function StatsSection() {
return (
<section style={{
<section id="features" style={{
padding: '64px clamp(20px,4vw,48px)',
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px,1fr))',
gap: 24, borderTop: '1px solid #111',
Expand Down Expand Up @@ -633,6 +633,7 @@ function SetupSection() {
const [ref, vis] = useScrollReveal(0.2);
return (
<section
id="open-source"
ref={ref}
style={{
padding: '80px clamp(20px,4vw,48px)',
Expand Down Expand Up @@ -888,7 +889,6 @@ export default function LandingPage({ repoStats }: { repoStats: RepoStats }) {
style={{ background: BG, color: TEXT, minHeight: '100vh', position: 'relative', overflowX: 'hidden' }}
>
<MouseSpotlight />
<LandingNav />
<HeroSection />
<CommitTicker />
<HeatmapSection />
Expand Down
Loading