Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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;
Comment thread
lazzyms marked this conversation as resolved.
Outdated
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>
Comment thread
lazzyms marked this conversation as resolved.
</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