Skip to content
Merged
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
4 changes: 4 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CompareForm } from "../components/compare-form";
import { ResultDashboard } from "../components/result-dashboard";
import { DashboardSkeleton } from "../components/skeletons";
import { UserResult } from "@/types/user-result";
import { LanguageSwitcher } from "@/components/language-switcher";

type ApiResponse = {
success: boolean;
Expand Down Expand Up @@ -75,6 +76,9 @@ export default function HomePage() {
</span>
</div>

<div className="flex gap-4">
<LanguageSwitcher />
</div>
</div>
</header>
<div className="flex-1 max-w-6xl mx-auto px-4 py-10 space-y-6 w-full">
Expand Down
7 changes: 4 additions & 3 deletions app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";

import { LanguageProvider } from "@/components/language-provider";
import { TooltipProvider } from "@/components/ui/tooltip";

export default function Providers({ children }: { children: React.ReactNode }) {
return (
<TooltipProvider>
{children}
</TooltipProvider>
<LanguageProvider>
<TooltipProvider>{children}</TooltipProvider>{" "}
</LanguageProvider>
);
}
105 changes: 65 additions & 40 deletions components/breakdown-bars.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,78 @@
import { UserResult } from "@/types/user-result";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "./ui/card";
import { Progress } from "./ui/progress";

import { useTranslation } from "./language-provider";

type Props = {
user1: UserResult;
user2: UserResult;
};

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

const getMaxScore = (score1: number, score2: number) =>
Math.max(score1, score2, 1);
const { t,dir } = useTranslation();

return (
<Card>
<CardHeader>
<CardTitle>Detailed Breakdown</CardTitle>
<CardDescription>Progress bars showing relative performance</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{["repoScore", "prScore", "contributionScore"].map((metric) => {
const user1Value = user1[metric as keyof UserResult] as number
const user2Value = user2[metric as keyof UserResult] as number
const maxVal = getMaxScore(user1Value, user2Value)
const metricLabel = metric === "repoScore" ? "Repository Score" : metric === "prScore" ? "Pull Request Score" : "Contribution Score"
return (
<div key={metric} className="space-y-2 pe-2">
<div className="flex justify-between text-sm">
<span>{metricLabel}</span>
<span className="text-muted-foreground">
{user1.username}: {user1Value} | {user2.username}: {user2Value}
</span>
</div>
<div className="space-y-1 ">
<div className="flex items-center gap-2">
<span className="text-xs w-24 truncate">{user1.username}</span>
<Progress value={(user1Value / maxVal) * 100} className="flex-1 h-2" />
<span className="text-xs w-8">{user1Value}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs w-24 truncate">{user2.username}</span>
<Progress value={(user2Value / maxVal) * 100} className="flex-1 h-2" />
<span className="text-xs w-8">{user2Value}</span>
</div>
</div>
</div>
)
})}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>{t('breakdown.title')}</CardTitle>
<CardDescription>
{t('breakdown.description')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{["repoScore", "prScore", "contributionScore"].map((metric) => {
const user1Value = user1[metric as keyof UserResult] as number;
const user2Value = user2[metric as keyof UserResult] as number;
const maxVal = getMaxScore(user1Value, user2Value);
const metricLabel =
metric === "repoScore"
? "breakdown.repo"
: metric === "prScore"
? "breakdown.pr"
: "breakdown.contribution";
return (
<div key={metric} className="space-y-2 pe-2">
<div className="flex justify-between text-sm">
<span>{t(metricLabel)}</span>
<span className="text-muted-foreground">
{user1.username}: {user1Value} | {user2.username}:{" "}
{user2Value}
</span>
</div>
<div className="space-y-1 ">
<div className="flex items-center gap-2">
<span className="text-xs w-24 truncate">
{user1.username}
</span>
<Progress
value={(user1Value / maxVal) * 100}
className="flex-1 h-2"
/>
<span className="text-xs w-8">{user1Value}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs w-24 truncate">
{user2.username}
</span>
<Progress
value={(user2Value / maxVal) * 100}
className="flex-1 h-2"
/>
<span className="text-xs w-8">{user2Value}</span>
</div>
</div>
</div>
);
})}
</CardContent>
</Card>
);
}
18 changes: 9 additions & 9 deletions components/compare-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CardTitle,
} from "./ui/card";
import { Alert, AlertDescription } from "./ui/alert";
import { useTranslation } from "./language-provider";

type CompareFormProps = {
data?: boolean;
Expand All @@ -27,6 +28,7 @@ export function CompareForm({
reset,
error,
}: CompareFormProps) {
const { t } = useTranslation();
const [username1, setUsername1] = useState("pbiggar");
const [username2, setUsername2] = useState("CoralineAda");
const firstInputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -60,23 +62,21 @@ export function CompareForm({
<form onSubmit={submit}>
<Card className="border-0 shadow-lg p-6 backdrop-blur-sm">
<CardHeader>
<CardTitle>Compare GitHub Developers</CardTitle>
<CardDescription>
Enter two GitHub usernames to compare their developer metrics
</CardDescription>
<CardTitle>{t("app.title")}</CardTitle>
<CardDescription>{t("app.subtitle")}</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-3 md:grid-cols-2">
<input
className="h-11 rounded-lg border border-slate-200 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary/60 focus:border-transparent bg-white"
ref={firstInputRef}
placeholder={"Username 1 (e.g., torvalds)"}
placeholder={t("form.username1") }
value={username1}
onChange={(e) => setUsername1(e.target.value)}
/>
<input
className="h-11 rounded-lg border border-slate-200 px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary/60 focus:border-transparent bg-white"
placeholder={"Username 2 (e.g., torvalds)"}
placeholder={t("form.username2") }
value={username2}
onChange={(e) => setUsername2(e.target.value)}
/>
Expand All @@ -88,22 +88,22 @@ export function CompareForm({
disabled={!canSubmit}
className="min-w-[140px] shadow-sm transition-transform hover:-translate-y-0.5"
>
{loading ? "Comparing..." : "Compare"}
{loading ? t("form.compare.ing") : t("form.compare")}
</Button>
{data && (
<>
<Button
onClick={handleSwap}
disabled={loading}
type="button"
title={"Swap users"}
title={t("form.swap")}
>
<ArrowLeftRight className="h-4 w-4" />
</Button>
<Button
onClick={handleReset}
disabled={loading}
title={"Reset"}
title={t("form.reset")}
type="button"
>
<RefreshCw className="h-4 w-4" />
Expand Down
20 changes: 12 additions & 8 deletions components/comparison-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,38 @@ import {
} from "./ui/card";
import { BarChart3 } from "lucide-react";
import { UserResult } from "@/types/user-result";
import { useTranslation } from "./language-provider";

type Props = {
user1: UserResult;
user2: UserResult;
};

const metrics = [
{ key: "repoScore", label: "Repos" },
{ key: "prScore", label: "PRs" },
{ key: "contributionScore", label: "Activity" },
{ key: "repoScore", label: "comparsion.repo.score" },
{ key: "prScore", label: "comparsion.pr.score" },
{ key: "contributionScore", label: "comparsion.activity.score" },
];

export function ComparisonChart({ user1, user2 }: Props) {
const {t} = useTranslation();

const data = metrics.map((m) => ({
name: m.label,
name: t(m.label),
[user1.username]: user1[m.key as keyof UserResult] ?? 0,
[user2.username]: user2[m.key as keyof UserResult] ?? 0,
}));

const renderLegendText = (value: string) => {
return <span className="ms-2">{value}</span>;
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
Score Comparison
{t('barchart.title')}
</CardTitle>
<CardDescription>Visual breakdown of key metrics</CardDescription>
<CardDescription>{t('barchart.desc')}</CardDescription>
</CardHeader>
<CardContent>
<div className="h-80">
Expand All @@ -59,7 +63,7 @@ export function ComparisonChart({ user1, user2 }: Props) {
<Tooltip
contentStyle={{ borderRadius: 12, border: "1px solid #e2e8f0" }}
/>
<Legend />
<Legend formatter={renderLegendText} />
<Bar
dataKey={user1.username}
fill={user1.isWinner ? "#3b82f6" : "#22D3EE"}
Expand Down
11 changes: 6 additions & 5 deletions components/comparison-table.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Image from "next/image";
import { UserResult } from "@/types/user-result";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { useTranslation } from "./language-provider";

type ComparisonTableProps = {
user1: UserResult;
user2: UserResult;
};

export function ComparisonTable({ user1, user2 }: ComparisonTableProps) {

const {t} = useTranslation();
return (
<div className="grid md:grid-cols-2 gap-6">
{[user1, user2].map((user, idx) => (
Expand All @@ -32,23 +33,23 @@ export function ComparisonTable({ user1, user2 }: ComparisonTableProps) {
</CardHeader>
<CardContent className="pt-6 space-y-4">
<div className="flex justify-between items-center border-b pb-2">
<span className="text-muted-foreground">Final Score</span>
<span className="text-muted-foreground">{t("comparsion.final.score")}</span>
<span className="text-2xl font-bold">{user.finalScore}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Repo Score</span>
<span className="text-muted-foreground">{t("comparsion.repo.score")}</span>
<span className={`font-semibold ${user.repoScore > (idx === 0 ? user2.repoScore : user1.repoScore) ? "text-primary" : ""}`}>
{user.repoScore}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">PR Score</span>
<span className="text-muted-foreground">{t("comparsion.pr.score")}</span>
<span className={`font-semibold ${user.prScore > (idx === 0 ? user2.prScore : user1.prScore) ? "text-primary" : ""}`}>
{user.prScore}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Contribution Score</span>
<span className="text-muted-foreground">{t("comparsion.contribution.score")}</span>
<span className={`font-semibold ${user.contributionScore > (idx === 0 ? user2.contributionScore : user1.contributionScore) ? "text-primary" : ""}`}>
{user.contributionScore}
</span>
Expand Down
5 changes: 3 additions & 2 deletions components/insights-list.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { TrendingUp } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { useTranslation } from "./language-provider";

type Props = {
insights: string[];
};

export function InsightsList({ insights }: Props) {

const { t } = useTranslation();
return (
<Card className="bg-gradient-to-r from-primary/5 to-transparent">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Key Insights
{t('insights.title')}
</CardTitle>
</CardHeader>
<CardContent>
Expand Down
26 changes: 26 additions & 0 deletions components/language-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { createContext, useContext } from "react";
import { useI18nProvider, type Locale } from "../lib/i18n";

type I18nContextValue = {
locale: Locale;
setLocale: (l: Locale) => void;
t: (key: string, params?: Record<string, string | number>) => string;
dir: "ltr" | "rtl";
locales: { value: Locale; label: string }[];
ready: boolean;
};

const I18nContext = createContext<I18nContextValue | null>(null);

export function LanguageProvider({ children }: { children: React.ReactNode }) {
const value = useI18nProvider();
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}

export function useTranslation() {
const ctx = useContext(I18nContext);
if (!ctx) throw new Error("useTranslation must be used inside LanguageProvider");
return ctx;
}
23 changes: 23 additions & 0 deletions components/language-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { useTranslation } from "./language-provider";
import { cn } from "../lib/utils";

export function LanguageSwitcher() {
const { locale, setLocale, locales, dir } = useTranslation();
return (
<div className={cn("flex items-center gap-2 text-sm", dir === "rtl" && "flex-row-reverse")}>
<select
className="h-9 rounded-lg border border-slate-200 bg-white px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary/60"
value={locale}
onChange={(e) => setLocale(e.target.value as any)}
>
{locales.map((l) => (
<option key={l.value} value={l.value}>
{l.label}
</option>
))}
</select>
</div>
);
}
Loading
Loading