From e7ad53426e5b31b550bf4ee80d701be286256f68 Mon Sep 17 00:00:00 2001 From: Isaac Onyemaechi Date: Fri, 30 Jan 2026 05:00:24 +0100 Subject: [PATCH 1/4] feat: Implement user profile page and a comprehensive reputation system with dedicated components. --- app/bounty/page.tsx | 4 +- app/globals.css | 3 +- app/leaderboard/page.tsx | 4 +- app/profile/[userId]/page.tsx | 117 ++++++++++++++++++ components/bounty/bounty-sidebar.tsx | 10 ++ components/bounty/participant-card.tsx | 64 ++++++++++ .../leaderboard/leaderboard-filters.tsx | 2 +- components/leaderboard/leaderboard-table.tsx | 32 ++--- components/leaderboard/mini-leaderboard.tsx | 16 +-- components/leaderboard/rank-badge.tsx | 2 +- components/leaderboard/streak-indicator.tsx | 2 +- components/leaderboard/tier-badge.tsx | 2 +- components/leaderboard/user-rank-sidebar.tsx | 24 ++-- components/reputation/completion-history.tsx | 78 ++++++++++++ .../reputation/reputation-breakdown.tsx | 39 ++++++ components/reputation/reputation-card.tsx | 76 ++++++++++++ components/reputation/reputation-score.tsx | 23 ++++ components/reputation/reputation-tooltip.tsx | 60 +++++++++ components/reputation/stats-grid.tsx | 54 ++++++++ components/reputation/streak-badge.tsx | 28 +++++ components/reputation/tier-badge.tsx | 33 +++++ components/reputation/tier-progress.tsx | 22 ++++ 22 files changed, 650 insertions(+), 45 deletions(-) create mode 100644 app/profile/[userId]/page.tsx create mode 100644 components/bounty/participant-card.tsx create mode 100644 components/reputation/completion-history.tsx create mode 100644 components/reputation/reputation-breakdown.tsx create mode 100644 components/reputation/reputation-card.tsx create mode 100644 components/reputation/reputation-score.tsx create mode 100644 components/reputation/reputation-tooltip.tsx create mode 100644 components/reputation/stats-grid.tsx create mode 100644 components/reputation/streak-badge.tsx create mode 100644 components/reputation/tier-badge.tsx create mode 100644 components/reputation/tier-progress.tsx diff --git a/app/bounty/page.tsx b/app/bounty/page.tsx index b51712f..bf339b7 100644 --- a/app/bounty/page.tsx +++ b/app/bounty/page.tsx @@ -180,7 +180,7 @@ export default function BountiesPage() { return (
{/* Background ambient glow */} -
+
@@ -231,7 +231,7 @@ export default function BountiesPage() { setSearchQuery(e.target.value)} /> diff --git a/app/globals.css b/app/globals.css index 370ef43..4c53f48 100644 --- a/app/globals.css +++ b/app/globals.css @@ -17,7 +17,7 @@ --secondary: oklch(0.967 0.003 264.542); --secondary-foreground: oklch(0.21 0.034 264.665); --muted: oklch(0.967 0.003 264.542); - --muted-foreground: oklch(0.551 0.027 264.364); + --muted-foreground: oklch(0.45 0.01 264.364); --accent: oklch(0.967 0.003 264.542); --accent-foreground: oklch(0.21 0.034 264.665); --destructive: oklch(0.577 0.245 27.325); @@ -118,6 +118,7 @@ * { @apply border-border outline-ring/50; } + body { @apply bg-background text-foreground; } diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx index 3a36395..ae98942 100644 --- a/app/leaderboard/page.tsx +++ b/app/leaderboard/page.tsx @@ -82,10 +82,10 @@ export default function LeaderboardPage() { {/* Hero Header */}
-

+

Leaderboard

-

+

Recognizing the top contributors in the ecosystem.

diff --git a/app/profile/[userId]/page.tsx b/app/profile/[userId]/page.tsx new file mode 100644 index 0000000..a9c42ac --- /dev/null +++ b/app/profile/[userId]/page.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { useContributorReputation, useLinkWallet } 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"; + +export default function ProfilePage() { + const params = useParams(); + const userId = params.userId as string; + const { data: reputation, isLoading, error } = useContributorReputation(userId); + + if (isLoading) { + return ( +
+ +
+ + +
+
+ ); + } + + if (error || !reputation) { + return ( +
+ +

Profile Not Found

+

+ We couldn't find a reputation profile for this user. +

+ +
+ ); + } + + // Mock history data using the reputation stats (Since we don't have a real history endpoint yet) + // In a real app, we would fetch this from a separate endpoint or include it in the reputation data + const mockHistory = Array(reputation.stats.totalCompleted).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 any, + rewardAmount: 500, + rewardCurrency: "USDC", + claimedAt: "2023-01-01T00:00:00Z", + completedAt: new Date(Date.now() - i * 86400000 * 5).toISOString(), + completionTimeHours: 48, + maintainerRating: 5, + maintainerFeedback: "Great work!", + pointsEarned: 150 + })); + + return ( +
+ + +
+ {/* Left Sidebar: Reputation Card */} +
+ + + {/* Additional Sidebar Info could go here */} +
+ + {/* Main Content: Activity & History */} +
+ + + + Bounty History + + + Analytics + + + + +

Activity History

+ +
+ + +
+ Detailed analytics coming soon. +
+
+
+
+
+
+ ); +} diff --git a/components/bounty/bounty-sidebar.tsx b/components/bounty/bounty-sidebar.tsx index 0b2943a..5aa9dcf 100644 --- a/components/bounty/bounty-sidebar.tsx +++ b/components/bounty/bounty-sidebar.tsx @@ -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 @@ -166,6 +167,15 @@ export function BountySidebar({ bounty }: BountySidebarProps) { + {bounty.claimedBy && ( + <> +
+ +
+ + + )} + + +
+ + +
+
+ ); + } + + if (!reputation) { + return ( +
+ + ? + +
+
Unknown User
+
ID: {userId}
+
+
+ ); + } + + return ( +
+ {label &&
{label}
} + + + + + {reputation.displayName?.[0] || "?"} + +
+
+ {reputation.displayName} +
+
+ {reputation.tier} + + {reputation.totalScore} pts +
+
+ +
+
+ ); +} diff --git a/components/leaderboard/leaderboard-filters.tsx b/components/leaderboard/leaderboard-filters.tsx index 3483053..a03c5cd 100644 --- a/components/leaderboard/leaderboard-filters.tsx +++ b/components/leaderboard/leaderboard-filters.tsx @@ -81,7 +81,7 @@ export function LeaderboardFilters({ filters, onFilterChange }: LeaderboardFilte const hasActiveFilters = filters.timeframe !== "ALL_TIME" || filters.tier || (filters.tags?.length || 0) > 0; return ( -
+
{/* Timeframe Select */}