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
25 changes: 7 additions & 18 deletions e2e/landing.spec.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { expect, test } from "@playwright/test";

test("landing page renders GitHub sign-in entrypoint", async ({ page }) => {
test("[Landing E2E] page renders GitHub sign-in entrypoint", async ({ page }) => {
await page.goto("/");

// The hero h1 is "YOUR CODE HAS A PULSE" — verify the page loaded
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();

// Two "Sign in with GitHub" links exist (hero + setup section) — check first one
await expect(
page.getByRole("link", { name: "Sign in with GitHub" }).first(),
).toHaveAttribute("href", /\/auth\/signin/);

// Verify at least one link to the upstream GitHub repo is present
).toHaveAttribute("href", /\/api\/auth\/signin\/github\?callbackUrl=\/dashboard/);
await expect(
page.getByRole("link", { name: /star on github/i }).first(),
).toHaveAttribute("href", "https://github.com/Priyanshu-byte-coder/devtrack");
});

test("dashboard stays protected for unauthenticated users", async ({ page }) => {
test("[Landing E2E] dashboard stays protected for unauthenticated users", async ({ page }) => {
await page.goto("/dashboard");

await expect(page).toHaveURL(/\/$/);
await expect(page.getByRole("link", { name: "Sign in with GitHub" }).first()).toBeVisible();
});

test("landing page shows dashboard link", async ({ page }) => {
test("[Landing E2E] landing has dashboard link", async ({ page }) => {
await page.goto("/");

await expect(page.getByRole("link", { name: "Dashboard" })).toBeVisible();
});

test("landing shows footer", async ({ page }) => {
test("[Landing E2E] landing shows footer via test-id", async ({ page }) => {
await page.goto("/");

await expect(
page.locator('[data-testid="landing-footer"]'),
).toBeVisible();
});
await expect(page.locator('[data-testid="landing-footer"]')).toBeVisible();
});
74 changes: 37 additions & 37 deletions src/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type RepoStats = {
openIssues: number;
contributorCount: number;
goodFirstIssues: number;
contributors: Array<{ login: string; avatar_url: string; html_url: string; isSponsor?: boolean }>;
contributors: Array<{ login: string; avatar_url: string; html_url: string }>;
};

/* ═══════════════════════════════════════════
Expand Down Expand Up @@ -108,7 +108,8 @@ function MouseSpotlight() {
useEffect(() => {
const fn = (e: MouseEvent) => {
if (ref.current) {
ref.current.style.transform = `translate3d(calc(${e.clientX}px - 50%), calc(${e.clientY}px - 50%), 0)`;
ref.current.style.left = e.clientX + 'px';
ref.current.style.top = e.clientY + 'px';
}
};
window.addEventListener('mousemove', fn, { passive: true });
Expand All @@ -120,11 +121,10 @@ function MouseSpotlight() {
aria-hidden
style={{
position: 'fixed', pointerEvents: 'none', zIndex: 0,
left: 0, top: 0,
width: 700, height: 700,
background: 'radial-gradient(circle, rgba(129,140,248,0.05) 0%, transparent 70%)',
transform: 'translate3d(-50%, -50%, 0)',
willChange: 'transform',
transform: 'translate(-50%,-50%)',
transition: 'left 0.15s ease-out, top 0.15s ease-out',
}}
/>
);
Expand Down Expand Up @@ -156,7 +156,7 @@ function LandingNav() {
<span style={{ fontFamily: MONO, fontWeight: 700, fontSize: 14, color: TEXT, letterSpacing: '-0.02em' }}>
<span style={{ color: A }}>▲</span> DEVTRACK
</span>
<a href="/auth/signin" className="lnd-nav-link">
<a href="/api/auth/signin/github?callbackUrl=/dashboard" className="lnd-nav-link">
SIGN IN →
</a>
</nav>
Expand All @@ -168,7 +168,7 @@ function LandingNav() {
═══════════════════════════════════════════ */
const wLabel: React.CSSProperties = {
fontFamily: MONO, fontSize: 10, fontWeight: 500,
color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.1em',
color: '#444', textTransform: 'uppercase', letterSpacing: '0.1em',
};
const wValue: React.CSSProperties = {
fontFamily: MONO, fontWeight: 600, color: TEXT,
Expand Down Expand Up @@ -273,7 +273,7 @@ function MergeWidget() {
<div ref={ref} style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', height: '100%' }}>
<span style={wLabel}>merge rate</span>
<span style={{ ...wValue, fontSize: 26, marginTop: 4, color: A }}>
87<span style={{ color: '#9ca3af', fontSize: 14 }}>%</span>
87<span style={{ color: '#333', fontSize: 14 }}>%</span>
</span>
<div style={{ marginTop: 8, height: 3, borderRadius: 2, background: '#1a1a1a', overflow: 'hidden' }}>
<div style={{
Expand All @@ -294,7 +294,7 @@ function GoalWidget() {
<div ref={ref} style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', height: '100%' }}>
<span style={wLabel}>weekly goal</span>
<span style={{ ...wValue, fontSize: 26, marginTop: 4 }}>
84<span style={{ color: '#9ca3af', fontSize: 14 }}>%</span>
84<span style={{ color: '#333', fontSize: 14 }}>%</span>
</span>
<div style={{ marginTop: 8, height: 3, borderRadius: 2, background: '#1a1a1a', overflow: 'hidden' }}>
<div style={{
Expand Down Expand Up @@ -388,7 +388,7 @@ function HeroSection() {
>
YOUR<br />CODE<br />HAS A<br />
<span style={{ color: A }}>PULSE</span>
<span style={{ color: '#9ca3af' }}>.</span>
<span style={{ color: '#222' }}>.</span>
</h1>

{/* Tagline */}
Expand All @@ -402,7 +402,7 @@ function HeroSection() {

{/* CTAs */}
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<a href="/auth/signin" className="lnd-cta-primary">
<a href="/api/auth/signin/github?callbackUrl=/dashboard" className="lnd-cta-primary">
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
Expand Down Expand Up @@ -442,7 +442,7 @@ function CommitTicker() {
<span
key={i}
style={{
fontFamily: MONO, fontSize: 12, color: '#9ca3af',
fontFamily: MONO, fontSize: 12, color: '#333',
display: 'inline-flex', alignItems: 'center', gap: 10,
}}
>
Expand All @@ -463,15 +463,15 @@ function HeatmapSection() {
return (
<section ref={ref} style={{ padding: '64px clamp(20px,4vw,48px)', overflow: 'hidden' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 20 }}>
<span style={{ fontFamily: MONO, fontSize: 11, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
<span style={{ fontFamily: MONO, fontSize: 11, color: '#333', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
52 weeks of contributions
</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<span style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af' }}>less</span>
<span style={{ fontFamily: MONO, fontSize: 10, color: '#333' }}>less</span>
{HC.map((c, i) => (
<div key={i} style={{ width: 10, height: 10, borderRadius: 2, background: c, border: `1px solid ${BORDER}` }} />
))}
<span style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af' }}>more</span>
<span style={{ fontFamily: MONO, fontSize: 10, color: '#333' }}>more</span>
</div>
</div>
<div style={{
Expand Down Expand Up @@ -502,9 +502,9 @@ function HeatmapSection() {
═══════════════════════════════════════════ */
const STATS = [
{ value: 847, label: 'COMMITS TRACKED' },
{ value: 43, label: 'PRS MERGED' },
{ value: 89, label: 'DAY BEST STREAK' },
{ value: 67, label: 'REVIEWS GIVEN' },
{ value: 43, label: 'PRS MERGED' },
{ value: 89, label: 'DAY BEST STREAK' },
{ value: 67, label: 'REVIEWS GIVEN' },
];

function StatItem({ value, label, delay }: { value: number; label: string; delay: number }) {
Expand All @@ -524,9 +524,9 @@ function StatItem({ value, label, delay }: { value: number; label: string; delay
lineHeight: 1, letterSpacing: '-0.03em',
}}>
<Counter end={value} active={vis} />
<span style={{ color: '#9ca3af', fontSize: 'clamp(18px,3vw,28px)' }}>+</span>
<span style={{ color: '#222', fontSize: 'clamp(18px,3vw,28px)' }}>+</span>
</div>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af', letterSpacing: '0.12em', marginTop: 8 }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#333', letterSpacing: '0.12em', marginTop: 8 }}>
{label}
</div>
</div>
Expand Down Expand Up @@ -616,7 +616,7 @@ function FeaturesSection() {
borderTop: '1px solid #111',
maxWidth: 720, margin: '0 auto',
}}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 40 }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#333', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 40 }}>
FEATURES
</div>
{FEATURES.map((f, i) => (
Expand All @@ -642,7 +642,7 @@ function SetupSection() {
transition: 'all 0.7s ease',
}}
>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af', letterSpacing: '0.12em', marginBottom: 24 }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#333', letterSpacing: '0.12em', marginBottom: 24 }}>
SETUP
</div>

Expand All @@ -652,12 +652,12 @@ function SetupSection() {
textAlign: 'left', marginBottom: 32,
fontFamily: MONO, fontSize: 13, lineHeight: 1.8,
}}>
<div style={{ color: '#9ca3af' }}># start tracking in 30 seconds</div>
<div style={{ color: '#333' }}># start tracking in 30 seconds</div>
<div style={{ color: TEXT }}>
<span style={{ color: A }}>→</span> sign in at{' '}
<span style={{ color: A }}>devtrack.vercel.app</span>
</div>
<div style={{ color: '#9ca3af', marginTop: 4 }}># or self-host</div>
<div style={{ color: '#333', marginTop: 4 }}># or self-host</div>
<div style={{ color: TEXT }}>
<span style={{ color: A }}>$</span> git clone github.com/…/devtrack
</div>
Expand All @@ -667,7 +667,7 @@ function SetupSection() {
</div>

<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', justifyContent: 'center' }}>
<a href="/auth/signin" className="lnd-cta-primary">
<a href="/api/auth/signin/github?callbackUrl=/dashboard" className="lnd-cta-primary">
Sign in with GitHub
</a>
<a
Expand All @@ -680,7 +680,7 @@ function SetupSection() {
</a>
</div>

<div style={{ fontFamily: MONO, fontSize: 11, color: '#9ca3af', marginTop: 20, letterSpacing: '0.06em' }}>
<div style={{ fontFamily: MONO, fontSize: 11, color: '#222', marginTop: 20, letterSpacing: '0.06em' }}>
MIT License · Self-hostable · Free forever · Zero vendor lock-in
</div>
</section>
Expand All @@ -694,10 +694,10 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
const [ref, vis] = useScrollReveal(0.08);

const statTiles = [
{ icon: '★', value: stats.stars, suffix: '', label: 'GITHUB STARS' },
{ icon: '⑂', value: stats.forks, suffix: '', label: 'FORKS' },
{ icon: '★', value: stats.stars, suffix: '', label: 'GITHUB STARS' },
{ icon: '⑂', value: stats.forks, suffix: '', label: 'FORKS' },
{ icon: '◎', value: stats.contributorCount, suffix: '+', label: 'CONTRIBUTORS' },
{ icon: '◈', value: stats.goodFirstIssues, suffix: '', label: 'GOOD FIRST ISSUES' },
{ icon: '◈', value: stats.goodFirstIssues, suffix: '', label: 'GOOD FIRST ISSUES' },
];

return (
Expand All @@ -712,7 +712,7 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
}}
>
{/* Label */}
<div style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 40 }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#333', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 40 }}>
OPEN SOURCE
</div>

Expand All @@ -726,7 +726,7 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
borderRadius: 8, padding: '20px 20px 16px',
}}
>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#9ca3af', letterSpacing: '0.1em', marginBottom: 10 }}>
<div style={{ fontFamily: MONO, fontSize: 10, color: '#444', letterSpacing: '0.1em', marginBottom: 10 }}>
{s.icon} {s.label}
</div>
<div style={{
Expand All @@ -735,7 +735,7 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
lineHeight: 1, letterSpacing: '-0.03em',
}}>
<Counter end={s.value} active={vis} />
{s.suffix && <span style={{ color: '#9ca3af', fontSize: '0.55em' }}>{s.suffix}</span>}
{s.suffix && <span style={{ color: '#444', fontSize: '0.55em' }}>{s.suffix}</span>}
</div>
</div>
))}
Expand Down Expand Up @@ -772,10 +772,10 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
href={c.html_url}
target="_blank"
rel="noopener noreferrer"
title={c.isSponsor ? `@${c.login} (Sponsor 💎)` : `@${c.login}`}
title={`@${c.login}`}
style={{
width: 38, height: 38, borderRadius: '50%',
border: `2px solid ${c.isSponsor ? '#ec4899' : BG}`,
border: `2px solid ${BG}`,
marginLeft: i > 0 ? -11 : 0,
overflow: 'hidden', display: 'block',
position: 'relative', zIndex: stats.contributors.length - i,
Expand Down Expand Up @@ -854,14 +854,15 @@ function ContributeSection({ stats }: { stats: RepoStats }) {
═══════════════════════════════════════════ */
function LandingFooter() {
return (
<footer
<footer
data-testid="landing-footer"
style={{
borderTop: `1px solid #111`,
padding: '24px clamp(20px,4vw,48px)',
display: 'flex', flexWrap: 'wrap', gap: '8px 32px',
justifyContent: 'space-between', alignItems: 'center',
}}>
}}
>
<span style={{ fontFamily: MONO, fontSize: 11, color: '#222' }}>
© {new Date().getFullYear()} DEVTRACK
</span>
Expand Down Expand Up @@ -899,7 +900,6 @@ export default function LandingPage({ repoStats }: { repoStats: RepoStats }) {
<ContributeSection stats={repoStats} />
<SetupSection />
<LandingFooter />

</div>
);
}
Loading
Loading