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
48 changes: 40 additions & 8 deletions components/Leaderboard/LeaderboardCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Trophy, GitMerge, GitPullRequest, AlertCircle, Eye, Tag, UserPlus, CheckCircle } from "lucide-react";
import { Trophy, GitMerge, GitPullRequest, AlertCircle, Eye, Tag, UserPlus, CheckCircle, Flame } from "lucide-react";
import { cn } from "@/lib/utils";
import ActivityTrendChart from "./ActivityTrendChart";
import "./LeaderboardCard.css";
Expand Down Expand Up @@ -85,6 +85,11 @@ export type LeaderboardEntry = {
points: number;
count: number;
}>;
badges?: Array<{
slug: string;
name: string;
variant: "bronze" | "silver" | "gold";
}>;
};

interface LeaderboardCardProps {
Expand Down Expand Up @@ -162,6 +167,27 @@ export function LeaderboardCard({
return "";
};

const renderBadges = (badges?: LeaderboardEntry['badges']) => {
if (!badges || badges.length === 0) return null;
return (
<div className="flex gap-1.5 mt-1">
{badges.map((badge) => {
const colors = {
gold: "text-yellow-600 bg-yellow-500/15 border-yellow-500/30 dark:text-yellow-400 dark:bg-yellow-500/20",
silver: "text-slate-600 bg-slate-400/15 border-slate-400/30 dark:text-slate-300 dark:bg-slate-400/20",
bronze: "text-orange-600 bg-orange-600/10 border-orange-600/20 dark:text-orange-400 dark:bg-orange-500/15",
}[badge.variant];
return (
<div key={badge.slug} className={`flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-sm border ${colors}`} title={badge.name}>
<Flame className="w-3 h-3" />
<span className="font-bold">{badge.name}</span>
Comment on lines +182 to +183

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

Hide decorative Flame icon from assistive tech.

This icon is decorative next to the badge label; set aria-hidden="true" (and optionally focusable="false").

</div>
);
})}
</div>
);
};

const styles = getRankStyles(rank);

if (variant === "list") {
Expand Down Expand Up @@ -224,10 +250,11 @@ export function LeaderboardCard({
@{entry.username}
</p>
{entry.role && (
<Badge variant="secondary" className="text-xs mb-2 bg-[#42B883]/10 text-[#42B883]">
<Badge variant="secondary" className="text-xs mb-1 bg-[#42B883]/10 text-[#42B883]">
{entry.role}
</Badge>
)}
{renderBadges(entry.badges)}

{/* Points */}
<div className="mb-2">
Expand Down Expand Up @@ -317,6 +344,7 @@ export function LeaderboardCard({
{entry.role}
</span>
)}
{renderBadges(entry.badges)}
</div>

<span
Expand Down Expand Up @@ -455,11 +483,14 @@ export function LeaderboardCard({

<div className="flex flex-col gap-2 w-[60%]">
<div className="flex items-center justify-between">
{entry.role && (
<Badge variant="secondary" className="text-xs bg-[#42B883]/10 text-[#42B883]">
{entry.role}
</Badge>
)}
<div className="flex flex-col gap-1 items-start">
{entry.role && (
<Badge variant="secondary" className="text-xs bg-[#42B883]/10 text-[#42B883]">
{entry.role}
</Badge>
)}
{renderBadges(entry.badges)}
</div>
<div className="text-right">
<div className="flex items-center justify-center gap-1 text-sm">
<Trophy className="w-3 h-3 text-yellow-500" />
Expand Down Expand Up @@ -548,10 +579,11 @@ export function LeaderboardCard({
@{entry.username}
</p>
{entry.role && (
<Badge variant="secondary" className="text-xs mb-2 bg-[#42B883]/10 text-[#42B883]">
<Badge variant="secondary" className="text-xs mb-1 bg-[#42B883]/10 text-[#42B883]">
{entry.role}
</Badge>
)}
{renderBadges(entry.badges)}

{/* Points */}
<div className="mb-2">
Expand Down
27 changes: 25 additions & 2 deletions components/people/ContributorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { Trophy, GitPullRequest, GitMerge, Calendar, TrendingUp, AlertCircle } from "lucide-react";
import { Trophy, GitPullRequest, GitMerge, Calendar, TrendingUp, AlertCircle, Flame } from "lucide-react";

interface ContributorEntry {
username: string;
Expand All @@ -13,6 +13,11 @@ interface ContributorEntry {
total_points: number;
activity_breakdown: Record<string, { count: number; points: number }>;
daily_activity: Array<{ date: string; count: number; points: number }>;
badges?: Array<{
slug: string;
name: string;
variant: "bronze" | "silver" | "gold";
}>;
}

interface ContributorCardProps {
Expand Down Expand Up @@ -96,7 +101,25 @@ export function ContributorCard({
@{contributor.username}
</p>

<Badge variant="secondary">{contributor.role}</Badge>
<Badge variant="secondary" className="mb-2">{contributor.role}</Badge>

{contributor.badges && contributor.badges.length > 0 && (
<div className="flex justify-center gap-1.5 mb-2">
{contributor.badges.map((badge) => {
const colors = {
gold: "text-yellow-600 bg-yellow-500/15 border-yellow-500/30 dark:text-yellow-400 dark:bg-yellow-500/20",
silver: "text-slate-600 bg-slate-400/15 border-slate-400/30 dark:text-slate-300 dark:bg-slate-400/20",
bronze: "text-orange-600 bg-orange-600/10 border-orange-600/20 dark:text-orange-400 dark:bg-orange-500/15",
}[badge.variant];
return (
<div key={badge.slug} className={`flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-sm border ${colors}`} title={badge.name}>
<Flame className="w-3 h-3" />
<span className="font-bold">{badge.name}</span>
</div>
);
})}
</div>
)}

{showStats && (
<>
Expand Down
14 changes: 14 additions & 0 deletions lib/badges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type BadgeVariant = "bronze" | "silver" | "gold";

export type EarnedBadge = {
slug: string;
name: string;
variant: BadgeVariant;
};
Comment on lines +3 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion | 🟠 Major | ⚑ Quick win

Use interface for EarnedBadge object shape.

This object shape should be declared as an interface to match repo TypeScript conventions.

Proposed fix
-export type EarnedBadge = {
+export interface EarnedBadge {
   slug: string;
   name: string;
   variant: BadgeVariant;
-};
+}

As per coding guidelines, **/*.{ts,tsx}: Use interface for object shapes and type for unions/intersections.


// EOD Streak Thresholds (consecutive days with points > 0)
export const STREAK_THRESHOLDS = {
BRONZE: 5,
SILVER: 10,
GOLD: 15,
} as const;
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
22 changes: 21 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading