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
2 changes: 1 addition & 1 deletion app/bounty/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export default function BountiesPage() {
<Search className="absolute left-3 top-2.5 size-4 group-focus-within:text-primary transition-colors" />
<Input
placeholder="Keywords, tags..."
className="pl-9 bg-background/50 text-white border-gray-700 hover:border-gray-600 focus:border-primary/50 transition-colors h-9 text-sm"
className="pl-9 h-9 text-sm"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
Expand Down
1 change: 1 addition & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
* {
@apply border-border outline-ring/50;
}

body {
@apply bg-background text-foreground;
}
Expand Down
4 changes: 2 additions & 2 deletions app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ export default function LeaderboardPage() {
{/* Hero Header */}
<div className="border-b border-border/40">
<div className="container mx-auto px-4 py-12">
<h1 className="text-3xl md:text-4xl font-bold tracking-tight mb-3">
<h1 className="text-3xl md:text-4xl font-bold text-foreground tracking-tight mb-3">
Leaderboard
</h1>
<p className="text-lg max-w-2xl">
<p className="text-lg text-muted-foreground max-w-2xl">
Recognizing the top contributors in the ecosystem.
</p>
</div>
Expand Down
157 changes: 157 additions & 0 deletions app/profile/[userId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"use client";

import { useContributorReputation } from "@/hooks/use-reputation";
import { ReputationCard } from "@/components/reputation/reputation-card";
import { CompletionHistory } from "@/components/reputation/completion-history";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { AlertCircle, ChevronLeft } from "lucide-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useMemo } from "react";

export default function ProfilePage() {
const params = useParams();
const userId = params.userId as string;
const { data: reputation, isLoading, error } = useContributorReputation(userId);

const MAX_MOCK_HISTORY = 50;

const mockHistory = useMemo(() => {
if (!reputation) return [];
const count = Math.min(reputation.stats.totalCompleted ?? 0, MAX_MOCK_HISTORY);
return Array(count).fill(null).map((_, i) => ({
id: `bounty-${i}`,
bountyId: `b-${i}`,
bountyTitle: `Implemented feature #${100 + i}`,
projectName: "Drips Protocol",
projectLogoUrl: null,
difficulty: ["BEGINNER", "INTERMEDIATE", "ADVANCED"][i % 3] as "BEGINNER" | "INTERMEDIATE" | "ADVANCED",
rewardAmount: 500,
rewardCurrency: "USDC",
claimedAt: "2023-01-01T00:00:00Z",
completedAt: "2024-01-15T12:00:00Z",
completionTimeHours: 48,
maintainerRating: 5,
maintainerFeedback: "Great work!",
pointsEarned: 150
}));
}, [reputation]);

if (isLoading) {
return (
<div className="container mx-auto py-8">
<Skeleton className="h-10 w-32 mb-8" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<Skeleton className="h-[400px] md:col-span-1" />
<Skeleton className="h-[400px] md:col-span-2" />
</div>
</div>
);
}

if (error) {
// Check if it's a 404 (Not Found)
const apiError = error as { status?: number; message?: string };
const isNotFound = apiError?.status === 404 || apiError?.message?.includes("404");

if (isNotFound) {
return (
<div className="container mx-auto py-16 text-center">
<AlertCircle className="w-12 h-12 mx-auto text-muted-foreground mb-4" />
<h1 className="text-2xl font-bold mb-2">Profile Not Found</h1>
<p className="text-muted-foreground mb-6">
We could not find a reputation profile for this user.
</p>
<Button asChild variant="outline">
<Link href="/">Return Home</Link>
</Button>
</div>
);
}

// Generic Error
return (
<div className="container mx-auto py-16 text-center">
<AlertCircle className="w-12 h-12 mx-auto text-destructive mb-4" />
<h1 className="text-2xl font-bold mb-2">Something went wrong</h1>
<p className="text-muted-foreground mb-6">
We encountered an error while loading the profile.
</p>
<Button variant="outline" onClick={() => window.location.reload()}>
Try Again
</Button>
</div>
);
}

if (!reputation) {
return (
<div className="container mx-auto py-16 text-center">
<AlertCircle className="w-12 h-12 mx-auto text-muted-foreground mb-4" />
<h1 className="text-2xl font-bold mb-2">Profile Not Found</h1>
<p className="text-muted-foreground mb-6">
We could not find a reputation profile for this user.
</p>
<Button asChild variant="outline">
<Link href="/">Return Home</Link>
</Button>
</div>
);
}

return (
<div className="container max-w-6xl mx-auto py-8 px-4">
<Button asChild variant="ghost" size="sm" className="mb-6 -ml-2 text-muted-foreground">
<Link href="/">
<ChevronLeft className="w-4 h-4 mr-1" />
Back to Home
</Link>
</Button>

<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Left Sidebar: Reputation Card */}
<div className="lg:col-span-4 space-y-6">
<ReputationCard reputation={reputation} />

{/* Additional Sidebar Info could go here */}
</div>

{/* Main Content: Activity & History */}
<div className="lg:col-span-8">
<Tabs defaultValue="history" className="w-full">
<TabsList className="w-full justify-start border-b rounded-none h-auto p-0 bg-transparent">
<TabsTrigger
value="history"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 py-3"
>
Bounty History
</TabsTrigger>
<TabsTrigger
value="analytics"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 py-3"
>
Analytics
</TabsTrigger>
</TabsList>

<TabsContent value="history" className="mt-6">
<h2 className="text-xl font-bold mb-4">Activity History</h2>
<CompletionHistory
records={mockHistory}
description={`Showing the last ${mockHistory.length} completed bounties.`}
/>
</TabsContent>

<TabsContent value="analytics" className="mt-6">
<div className="p-8 border rounded-lg text-center text-muted-foreground bg-secondary/5">
Detailed analytics coming soon.
</div>
</TabsContent>
</Tabs>
</div>
</div>
</div>
);
}
10 changes: 10 additions & 0 deletions components/bounty/bounty-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { formatDistanceToNow } from "date-fns"
// import { useRouter } from "next/navigation" // If we need refresh
import { ApplicationDialog } from "./application-dialog"
import { toast } from "sonner"
import { ParticipantCard } from "./participant-card"

interface BountySidebarProps {
bounty: Bounty
Expand Down Expand Up @@ -166,6 +167,15 @@ export function BountySidebar({ bounty }: BountySidebarProps) {

<Separator className="bg-gray-800" />

{bounty.claimedBy && (
<>
<div className="space-y-3">
<ParticipantCard userId={bounty.claimedBy} label="Claimed by" />
</div>
<Separator className="bg-gray-800" />
</>
)}

<a
href={`https://github.com/${bounty.githubRepo}`}
target="_blank"
Expand Down
64 changes: 64 additions & 0 deletions components/bounty/participant-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useContributorReputation } from "@/hooks/use-reputation";
import { ReputationTooltip } from "@/components/reputation/reputation-tooltip";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Skeleton } from "@/components/ui/skeleton";
import Link from "next/link";

interface ParticipantCardProps {
userId: string;
label?: string;
}

export function ParticipantCard({ userId, label }: ParticipantCardProps) {
const { data: reputation, isLoading } = useContributorReputation(userId);

if (isLoading) {
return (
<div className="flex items-center gap-3">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="space-y-1">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-3 w-16" />
</div>
</div>
);
}

if (!reputation) {
return (
<div className="flex items-center gap-3 opacity-70">
<Avatar className="h-10 w-10">
<AvatarFallback>?</AvatarFallback>
</Avatar>
<div className="text-sm">
<div className="font-medium">Unknown User</div>
<div className="text-xs text-muted-foreground">ID: {userId}</div>
</div>
</div>
);
}

return (
<div className="space-y-1">
{label && <div className="text-xs font-medium text-muted-foreground">{label}</div>}
<ReputationTooltip reputation={reputation}>
<Link href={`/profile/${userId}`} className="flex items-center gap-3 group">
<Avatar className="h-10 w-10 border border-border group-hover:border-primary transition-colors">
<AvatarImage src={reputation.avatarUrl || undefined} />
<AvatarFallback>{reputation.displayName?.[0] || "?"}</AvatarFallback>
</Avatar>
<div className="text-sm">
<div className="font-bold group-hover:text-primary transition-colors">
{reputation.displayName}
</div>
<div className="text-xs text-muted-foreground flex items-center gap-1">
{reputation.tier}
<span className="w-1 h-1 rounded-full bg-muted-foreground/50" />
{reputation.totalScore} pts
</div>
</div>
</Link>
</ReputationTooltip>
</div>
);
}
2 changes: 1 addition & 1 deletion components/leaderboard/leaderboard-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function LeaderboardFilters({ filters, onFilterChange }: LeaderboardFilte
const hasActiveFilters = filters.timeframe !== "ALL_TIME" || filters.tier || (filters.tags?.length || 0) > 0;

return (
<div className="flex flex-wrap items-center gap-3">
<div className="flex flex-wrap items-center gap-3 text-foreground">
{/* Timeframe Select */}
<Select
value={filters.timeframe}
Expand Down
32 changes: 16 additions & 16 deletions components/leaderboard/leaderboard-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { Skeleton } from "@/components/ui/skeleton";
import { LeaderboardEntry } from "@/types/leaderboard";
import { cn } from "@/lib/utils";
import { RankBadge } from "./rank-badge";
import { TierBadge } from "./tier-badge";
import { StreakIndicator } from "./streak-indicator";
import { TierBadge } from "@/components/reputation/tier-badge";
import { StreakBadge } from "@/components/reputation/streak-badge";

interface LeaderboardTableProps {
entries: LeaderboardEntry[];
Expand Down Expand Up @@ -79,13 +79,13 @@ export function LeaderboardTable({
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent border-b border-border">
<TableHead className="w-20 text-center font-bold ">RANK</TableHead>
<TableHead className="font-bold ">CONTRIBUTOR</TableHead>
<TableHead className="hidden md:table-cell font-bold ">TIER</TableHead>
<TableHead className="text-right font-bold ">SCORE</TableHead>
<TableHead className="text-right hidden sm:table-cell font-bold ">COMPLETED</TableHead>
<TableHead className="text-right hidden lg:table-cell font-bold ">EARNINGS</TableHead>
<TableHead className="text-right font-bold ">STREAK</TableHead>
<TableHead className="w-[80px] text-center font-bold text-foreground">RANK</TableHead>
<TableHead className="font-bold text-foreground">CONTRIBUTOR</TableHead>
<TableHead className="hidden md:table-cell font-bold text-foreground">TIER</TableHead>
<TableHead className="text-right font-bold text-foreground">SCORE</TableHead>
<TableHead className="text-right hidden sm:table-cell font-bold text-foreground">COMPLETED</TableHead>
<TableHead className="text-right hidden lg:table-cell font-bold text-foreground">EARNINGS</TableHead>
<TableHead className="text-right font-bold text-foreground">STREAK</TableHead>
</TableRow>
</TableHeader>
<TableBody>
Expand Down Expand Up @@ -117,7 +117,7 @@ export function LeaderboardTable({
<AvatarFallback className="bg-secondary text-secondary-foreground">{entry.contributor.displayName[0]}</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className={cn("font-semibold ", isCurrentUser && "text-primary")}>
<span className={cn("font-semibold text-foreground", isCurrentUser && "text-primary")}>
{entry.contributor.displayName}
{isCurrentUser && " (You)"}
</span>
Expand All @@ -140,21 +140,21 @@ export function LeaderboardTable({
))}
</div>
</TableCell>
<TableCell className="hidden md:table-cell">
<TableCell className="hidden md:table-cell text-foreground">
<TierBadge tier={entry.contributor.tier} />
</TableCell>
<TableCell className="text-right font-mono font-medium">
<TableCell className="text-right font-mono text-foreground font-medium">
{entry.contributor.totalScore.toLocaleString()}
</TableCell>
<TableCell className="text-right hidden sm:table-cell">
<TableCell className="text-right hidden sm:table-cell text-foreground">
{entry.contributor.stats.totalCompleted}
</TableCell>
<TableCell className="text-right hidden lg:table-cell font-mono">
<TableCell className="text-right hidden lg:table-cell font-mono text-foreground">
${entry.contributor.stats.totalEarnings.toLocaleString()}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end">
<StreakIndicator streak={entry.contributor.stats.currentStreak} />
<StreakBadge streak={entry.contributor.stats.currentStreak} />
</div>
</TableCell>
</TableRow>
Expand All @@ -163,7 +163,7 @@ export function LeaderboardTable({
{isFetchingNextPage && (
<TableRow>
<TableCell colSpan={7} className="text-center py-4">
<div className="flex items-center justify-center text-sm">
<div className="flex items-center justify-center text-muted-foreground text-sm">
Loading more...
</div>
</TableCell>
Expand Down
Loading
Loading