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
6 changes: 4 additions & 2 deletions app/api/compare/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export async function GET(request: Request) {

return {
username,
name: data.name,
avatarUrl: data.avatarUrl,
repoScore: Math.round(score.repoScore),
prScore: Math.round(score.prScore),
contributionScore: Math.round(score.contributionScore),
Expand All @@ -34,10 +36,10 @@ export async function GET(request: Request) {
);

return NextResponse.json({ success: true, users: results });
} catch (error: any) {
} catch (error: unknown) {
console.error("GitHub score error:", error);
const message =
error?.message === "User not found"
error instanceof Error && error.message === "User not found"
? "GitHub user not found"
: "Failed to calculate score";
return NextResponse.json(
Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export default function HomePage() {
} else {
setData({ user1: body.users[0], user2: body.users[1] });
}
} catch (err: any) {
setError(err.message || "Failed to fetch");
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "Failed to fetch");
} finally {
setLoading(false);
}
Expand Down
6 changes: 0 additions & 6 deletions components/breakdown-bars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ type Props = {
user2: UserResult;
};

const items = [
{ key: "repoScore", label: "Repos", color: "bg-blue-500" },
{ key: "prScore", label: "Pull Requests", color: "bg-purple-500" },
{ key: "contributionScore", label: "Activity", color: "bg-emerald-500" },
];

export function BreakdownBars({ user1, user2 }: Props) {
const getMaxScore = (score1: number, score2: number) => Math.max(score1, score2, 1)

Expand Down
2 changes: 1 addition & 1 deletion components/compare-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { Alert, AlertDescription } from "./ui/alert";

type CompareFormProps = {
data?: any;
data?: unknown;
onSubmit: (u1: string, u2: string) => void;
loading?: boolean;
reset?: () => void;
Expand Down
28 changes: 13 additions & 15 deletions components/comparison-table.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import Image from "next/image";
import { UserResult } from "@/types/user-result";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";

type ScoreRow = {
label: string;
key: "finalScore" | "repoScore" | "prScore" | "contributionScore";
};

const rows: ScoreRow[] = [
{ label: "result.table.final", key: "finalScore" },
{ label: "result.table.repo", key: "repoScore" },
{ label: "result.table.pr", key: "prScore" },
{ label: "result.table.contribution", key: "contributionScore" },
];

type ComparisonTableProps = {
user1: UserResult;
user2: UserResult;
Expand All @@ -23,10 +12,19 @@ export function ComparisonTable({ user1, user2 }: ComparisonTableProps) {
return (
<div className="grid md:grid-cols-2 gap-6">
{[user1, user2].map((user, idx) => (
<Card key={user.username} className="overflow-hidden transition-all hover:shadow-lg">
<CardHeader className={user.isWinner ? "border-b-2 border-primary/30" : "border-b-2 border-muted"}>
<Card key={`${user.username}-${idx}`} className="overflow-hidden transition-all hover:shadow-lg">
<CardHeader className={`pb-4 ${user.isWinner ? "border-b-2 border-primary/30" : "border-b-2 border-muted"}`}>
<CardTitle className="flex items-center justify-between">
<span>{user.username}</span>
<div className="flex items-center gap-3">
<Image
src={user.avatarUrl}
alt={`${user.name || user.username}'s avatar`}
width={40}
height={40}
className="rounded-full ring-2 ring-border"
/>
<span>{user.name || user.username}</span>
</div>
{user.isWinner && (
<span className="text-xs bg-primary/20 text-primary px-2 py-1 rounded-full">Winner</span>
)}
Expand Down
2 changes: 1 addition & 1 deletion components/result-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function ResultDashboard({ user1, user2 }: Props) {
Metric
</p>
<h2 className="text-xl font-semibold">
It's a tie — both developers are evenly matched.
It&apos;s a tie — both developers are evenly matched.
</h2>
</>
)}
Expand Down
3 changes: 2 additions & 1 deletion components/top-list.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type ReactNode } from "react";
import {
Eye,
GitFork,
Expand Down Expand Up @@ -29,7 +30,7 @@ export function TopList({ userResults }: Props) {
title: string;
subtitle?: string;
score?: number;
badges: { tooltip?: string; label?: any; icon: any }[];
badges: { tooltip?: string; label?: ReactNode; icon: ReactNode }[];
key: string | number;
}) => (
<div
Expand Down
18 changes: 18 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import nextTypescript from "eslint-config-next/typescript";

const eslintConfig = [
...nextCoreWebVitals,
...nextTypescript,
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
],
},
];

export default eslintConfig;
20 changes: 16 additions & 4 deletions lib/github.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { ContributionTotals, GitHubUserData, PullRequestNode, RepoNode } from "@/types/github";
import { graphql } from "@octokit/graphql";

type GitHubRawUser = {
name: string | null;
avatarUrl: string;
repositories: { nodes: RepoNode[] };
pullRequests: { nodes: PullRequestNode[] };
contributionsCollection: ContributionTotals;
};

if (!process.env.GITHUB_TOKEN) {
throw new Error("Missing GITHUB_TOKEN");
}
Expand All @@ -15,6 +23,8 @@ const client = graphql.defaults({
const QUERY = /* GraphQL */ `
query FetchUserData($login: String!, $repoCount: Int = 100, $prCount: Int = 100) {
user(login: $login) {
name
avatarUrl(size: 80)
repositories(
first: $repoCount
privacy: PUBLIC
Expand Down Expand Up @@ -60,15 +70,17 @@ const QUERY = /* GraphQL */ `
export async function fetchGitHubUserData(
username: string
): Promise<GitHubUserData> {
const { user } = await client<{ user: any }>(QUERY, { login: username });
const { user } = await client<{ user: GitHubRawUser | null }>(QUERY, { login: username });

if (!user) {
throw new Error("User not found");
}

return {
repos: user.repositories.nodes as RepoNode[],
pullRequests: user.pullRequests.nodes as PullRequestNode[],
contributions: user.contributionsCollection as ContributionTotals,
name: user.name,
avatarUrl: user.avatarUrl,
repos: user.repositories.nodes,
pullRequests: user.pullRequests.nodes,
contributions: user.contributionsCollection,
};
}
8 changes: 8 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
const nextConfig = {
reactStrictMode: true,
typedRoutes: true,
images: {
remotePatterns: [
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
},
};

module.exports = nextConfig;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "eslint ."
},
"dependencies": {
"@octokit/graphql": "^9.0.3",
Expand Down
Loading