Skip to content

Commit 5ef8fe3

Browse files
authored
Merge branch 'main' into fix/landing-page-ui-improvements
2 parents 0a1bd16 + 20107a0 commit 5ef8fe3

2 files changed

Lines changed: 132 additions & 71 deletions

File tree

src/app/dashboard/page.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import RepoAnalyticsExplorer from "@/components/repo-analytics/RepoAnalyticsExpl
1616
import dynamic from "next/dynamic";
1717
import WeeklySummaryCard from "@/components/WeeklySummaryCard";
1818
import { AIMentorWidget } from "@/components/AIMentorWidget";
19+
import ExportButton from "@/components/ExportButton";
20+
import Link from "next/link";
1921
import PersonalRecords from "@/components/PersonalRecords";
2022
import LocalCodingTime from "@/components/LocalCodingTime";
2123
import CodingTimeWidget from "@/components/CodingTimeWidget";
@@ -105,7 +107,28 @@ export default async function DashboardPage() {
105107
<div className="min-h-screen bg-[var(--background)] p-4 text-[var(--foreground)] transition-colors md:p-8">
106108
<DashboardHeader />
107109

108-
<StreakAtRiskBanner />
110+
{/* Quick actions */}
111+
<div className="mt-4 flex flex-wrap items-center gap-2 sm:gap-3">
112+
<Link
113+
href="/wrapped"
114+
className="inline-flex items-center gap-2 rounded-lg border border-[var(--accent)] bg-[var(--accent-soft)] px-4 py-2 text-sm font-semibold text-[var(--accent)] transition-opacity hover:opacity-90"
115+
>
116+
✨ Year in Code
117+
</Link>
118+
<Link
119+
href="/dashboard/settings"
120+
className="secondary-button inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium"
121+
>
122+
Settings
123+
</Link>
124+
<div className="sm:ml-auto">
125+
<ExportButton />
126+
</div>
127+
</div>
128+
129+
<div className="mt-4">
130+
<StreakAtRiskBanner />
131+
</div>
109132

110133
<div className="mt-6 mb-6">
111134
<TodayFocusHero userName={session.user?.name ?? null} />

src/components/AppNavbar.tsx

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
1-
"use client";
1+
"use client";
22

33
import Link from "next/link";
44
import { Menu, X } from "lucide-react";
55
import { signOut, useSession } from "next-auth/react";
66
import { usePathname } from "next/navigation";
77
import { useEffect, useMemo, useState } from "react";
88

9-
type NavItem = {
10-
href: string;
11-
label: string;
12-
};
9+
type NavItem = { href: string; label: string };
1310

1411
function isActivePath(pathname: string, href: string) {
15-
if (href === "/") {
16-
return pathname === "/";
17-
}
18-
12+
if (href === "/") return pathname === "/";
1913
if (href.includes("#")) {
2014
const [base] = href.split("#");
2115
return pathname === base;
2216
}
23-
2417
return pathname === href || pathname.startsWith(`${href}/`);
2518
}
19+
20+
const MONO = "var(--font-jetbrains, ui-monospace, monospace)";
21+
2622
export default function AppNavbar() {
2723
const pathname = usePathname();
2824
const { data: session, status } = useSession();
2925
const [mobileOpen, setMobileOpen] = useState(false);
26+
const [scrolled, setScrolled] = useState(false);
27+
28+
useEffect(() => { setMobileOpen(false); }, [pathname]);
3029

3130
useEffect(() => {
32-
setMobileOpen(false);
33-
}, [pathname]);
31+
const fn = () => setScrolled(window.scrollY > 20);
32+
window.addEventListener("scroll", fn, { passive: true });
33+
return () => window.removeEventListener("scroll", fn);
34+
}, []);
3435

3536
const isAuthenticated = status === "authenticated" && Boolean(session);
3637
const isPublicProfileRoute = pathname.startsWith("/u/");
3738
const identityLabel =
38-
session?.user?.name ?? session?.githubLogin ?? session?.user?.email ?? "GitHub user";
39+
session?.githubLogin ?? session?.user?.name ?? session?.user?.email ?? "user";
3940

4041
const navItems = useMemo<NavItem[]>(() => {
4142
if (isAuthenticated) {
4243
return [
4344
{ href: "/dashboard", label: "Dashboard" },
4445
{ href: "/dashboard#streaks", label: "Streaks" },
45-
{ href: "/dashboard#pull-requests", label: "Pull Requests" },
46+
{ href: "/dashboard#pull-requests", label: "PRs" },
4647
{ href: "/dashboard#goals", label: "Goals" },
4748
{ href: "/leaderboard", label: "Leaderboard" },
4849
{ href: "/dashboard/settings", label: "Settings" },
4950
];
5051
}
51-
5252
return [
5353
{ href: "/", label: "Home" },
5454
{ href: "/#features", label: "Features" },
@@ -57,126 +57,164 @@ export default function AppNavbar() {
5757
];
5858
}, [isAuthenticated]);
5959

60+
const headerStyle: React.CSSProperties = {
61+
position: "sticky",
62+
top: 0,
63+
zIndex: 50,
64+
background: scrolled ? "rgba(6,11,24,0.92)" : "var(--background)",
65+
backdropFilter: scrolled ? "blur(16px)" : "none",
66+
WebkitBackdropFilter: scrolled ? "blur(16px)" : "none",
67+
borderBottom: "1px solid var(--border)",
68+
transition: "background 0.3s ease",
69+
};
70+
6071
return (
61-
<header className="sticky top-0 z-50 border-b border-[var(--border)] bg-[var(--background)] backdrop-blur-md">
72+
<header style={headerStyle}>
6273
<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">
74+
75+
{/* Logo */}
6376
<Link
6477
href={isAuthenticated ? "/dashboard" : "/"}
65-
className="inline-flex items-center gap-2 text-sm font-semibold tracking-[0.16em] text-[var(--foreground)]"
66-
style={{ fontFamily: "var(--font-jetbrains, ui-monospace, monospace)" }}
78+
className="inline-flex items-center gap-2 select-none"
79+
style={{ fontFamily: MONO }}
6780
>
68-
<span className="text-[var(--accent)]">{">"}</span>
69-
<span>DEVTRACK</span>
81+
<span className="text-base font-bold" style={{ color: "var(--accent)" }}></span>
82+
<span className="text-sm font-bold tracking-[0.18em] text-[var(--foreground)]">DEVTRACK</span>
7083
</Link>
7184

72-
<nav className="hidden items-center gap-2 lg:flex">
85+
{/* Desktop nav */}
86+
<nav className="hidden items-center lg:flex" aria-label="Main navigation">
7387
{navItems.map((item) => {
7488
const active = isActivePath(pathname, item.href);
75-
7689
return (
7790
<Link
7891
key={item.href}
7992
href={item.href}
80-
className={`rounded-full px-4 py-2 text-sm font-medium transition-colors ${
81-
active
82-
? "bg-[var(--accent-soft)] text-[var(--accent)]"
83-
: "text-[var(--muted-foreground)] hover:bg-[var(--card)] hover:text-[var(--foreground)]"
84-
}`}
93+
className="relative px-3 py-2 text-[12px] font-medium transition-colors duration-150"
94+
style={{
95+
fontFamily: MONO,
96+
color: active ? "var(--accent)" : "var(--muted-foreground)",
97+
}}
98+
onMouseEnter={(e) => {
99+
if (!active) (e.currentTarget as HTMLAnchorElement).style.color = "var(--foreground)";
100+
}}
101+
onMouseLeave={(e) => {
102+
if (!active) (e.currentTarget as HTMLAnchorElement).style.color = "var(--muted-foreground)";
103+
}}
85104
>
86105
{item.label}
106+
{active && (
107+
<span
108+
className="absolute inset-x-2 bottom-0 h-px"
109+
style={{ background: "var(--accent)" }}
110+
/>
111+
)}
87112
</Link>
88113
);
89114
})}
90115
</nav>
91116

117+
{/* Desktop right */}
92118
<div className="hidden items-center gap-3 lg:flex">
93119
{isAuthenticated ? (
94120
<>
95-
<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">
96-
{identityLabel}
97-
</div>
121+
<span
122+
className="hidden max-w-44 truncate text-[11px] text-[var(--muted-foreground)] xl:block"
123+
style={{ fontFamily: MONO }}
124+
>
125+
@{identityLabel}
126+
</span>
98127
<button
99128
type="button"
100129
onClick={() => signOut({ callbackUrl: "/" })}
101-
className="rounded-full bg-[var(--destructive)] px-4 py-2 text-sm font-semibold text-[var(--destructive-foreground)] transition-opacity hover:opacity-90"
130+
className="rounded-md border border-[var(--border)] px-3 py-1.5 text-[11px] font-medium text-[var(--muted-foreground)] transition-colors hover:border-red-500/60 hover:text-red-400"
131+
style={{ fontFamily: MONO }}
102132
>
103-
Sign out
133+
sign out
104134
</button>
105135
</>
106136
) : (
107137
!isPublicProfileRoute && (
108138
<Link
109139
href="/api/auth/signin/github?callbackUrl=/dashboard"
110-
className="rounded-full bg-[var(--accent)] px-4 py-2 text-sm font-semibold text-[var(--accent-foreground)] transition-opacity hover:opacity-90"
140+
className="rounded-md px-4 py-2 text-[12px] font-semibold text-[var(--accent-foreground)] transition-opacity hover:opacity-90"
141+
style={{ fontFamily: MONO, background: "var(--accent)" }}
111142
>
112-
Sign in with GitHub
143+
SIGN IN →
113144
</Link>
114145
)
115146
)}
116147
</div>
117148

149+
{/* Mobile hamburger */}
118150
<button
119151
type="button"
120-
onClick={() => setMobileOpen((open) => !open)}
121-
className="inline-flex items-center justify-center rounded-full border border-[var(--border)] bg-[var(--card)] p-2 text-[var(--foreground)] lg:hidden"
152+
onClick={() => setMobileOpen((o) => !o)}
153+
className="inline-flex items-center justify-center rounded-md border border-[var(--border)] p-2 text-[var(--foreground)] lg:hidden"
122154
aria-expanded={mobileOpen}
123155
aria-controls="app-mobile-nav"
124156
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
125157
>
126-
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
158+
{mobileOpen ? <X className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
127159
</button>
128160
</div>
129161

130-
{mobileOpen ? (
162+
{/* Mobile menu */}
163+
{mobileOpen && (
131164
<div
132165
id="app-mobile-nav"
133-
className="border-t border-[var(--border)] bg-[var(--background)] lg:hidden"
166+
className="border-t border-[var(--border)] lg:hidden"
167+
style={{ background: "rgba(6,11,24,0.97)", backdropFilter: "blur(16px)" }}
134168
>
135-
<div className="mx-auto flex w-full max-w-7xl flex-col gap-2 px-4 py-4 sm:px-6 lg:px-8">
169+
<div className="mx-auto flex w-full max-w-7xl flex-col gap-1 px-4 py-4 sm:px-6">
136170
{navItems.map((item) => {
137171
const active = isActivePath(pathname, item.href);
138-
139172
return (
140173
<Link
141174
key={item.href}
142175
href={item.href}
143-
className={`rounded-2xl px-4 py-3 text-sm font-medium transition-colors ${
144-
active
145-
? "bg-[var(--accent-soft)] text-[var(--accent)]"
146-
: "bg-[var(--card)] text-[var(--card-foreground)] hover:bg-[var(--control)]"
147-
}`}
176+
className="rounded-lg px-4 py-3 text-sm font-medium transition-colors"
177+
style={{
178+
fontFamily: MONO,
179+
color: active ? "var(--accent)" : "var(--muted-foreground)",
180+
background: active ? "var(--accent-soft)" : "transparent",
181+
}}
148182
>
149183
{item.label}
150184
</Link>
151185
);
152186
})}
153187

154-
{isAuthenticated ? (
155-
<>
156-
<div className="rounded-2xl border border-[var(--border)] bg-[var(--card)] px-4 py-3 text-sm text-[var(--card-foreground)]">
157-
{identityLabel}
158-
</div>
159-
<button
160-
type="button"
161-
onClick={() => signOut({ callbackUrl: "/" })}
162-
className="rounded-2xl bg-[var(--destructive)] px-4 py-3 text-left text-sm font-semibold text-[var(--destructive-foreground)]"
163-
>
164-
Sign out
165-
</button>
166-
</>
167-
) : (
168-
!isPublicProfileRoute && (
169-
<Link
170-
href="/api/auth/signin/github?callbackUrl=/dashboard"
171-
className="rounded-2xl bg-[var(--accent)] px-4 py-3 text-sm font-semibold text-[var(--accent-foreground)]"
172-
>
173-
Sign in with GitHub
174-
</Link>
175-
)
176-
)}
188+
<div className="mt-3 border-t border-[var(--border)] pt-3">
189+
{isAuthenticated ? (
190+
<>
191+
<p className="px-4 py-1.5 text-[11px] text-[var(--muted-foreground)]" style={{ fontFamily: MONO }}>
192+
@{identityLabel}
193+
</p>
194+
<button
195+
type="button"
196+
onClick={() => signOut({ callbackUrl: "/" })}
197+
className="w-full rounded-lg px-4 py-3 text-left text-sm font-medium text-red-400 transition-colors hover:bg-red-500/10"
198+
style={{ fontFamily: MONO }}
199+
>
200+
sign out →
201+
</button>
202+
</>
203+
) : (
204+
!isPublicProfileRoute && (
205+
<Link
206+
href="/api/auth/signin/github?callbackUrl=/dashboard"
207+
className="block rounded-lg px-4 py-3 text-center text-sm font-semibold text-[var(--accent-foreground)]"
208+
style={{ background: "var(--accent)" }}
209+
>
210+
SIGN IN →
211+
</Link>
212+
)
213+
)}
214+
</div>
177215
</div>
178216
</div>
179-
) : null}
217+
)}
180218
</header>
181219
);
182220
}

0 commit comments

Comments
 (0)