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
5 changes: 3 additions & 2 deletions app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LeaderboardTable } from "@/components/leaderboard/leaderboard-table";
import { LeaderboardFilters } from "@/components/leaderboard/leaderboard-filters";
import { UserRankSidebar } from "@/components/leaderboard/user-rank-sidebar";
import { LeaderboardFilters as FiltersType, ReputationTier } from "@/types/leaderboard";
import { LeaderboardTimeframe } from "@/lib/graphql/generated";
import { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { TIMEFRAMES, TIERS } from "@/components/leaderboard/leaderboard-filters";
Expand All @@ -22,7 +23,7 @@ export default function LeaderboardPage() {

const initialTimeframe = TIMEFRAMES.some(t => t.value === rawTimeframe)
? (rawTimeframe as FiltersType["timeframe"])
: "ALL_TIME";
: LeaderboardTimeframe.AllTime;

const initialTier = TIERS.some(t => t.value === rawTier)
? (rawTier as ReputationTier)
Expand Down Expand Up @@ -65,7 +66,7 @@ export default function LeaderboardPage() {
// Sync debounced filters to URL
useEffect(() => {
const params = new URLSearchParams();
if (debouncedFilters.timeframe !== "ALL_TIME") params.set("timeframe", debouncedFilters.timeframe);
if (debouncedFilters.timeframe !== LeaderboardTimeframe.AllTime) params.set("timeframe", debouncedFilters.timeframe);
if (debouncedFilters.tier) params.set("tier", debouncedFilters.tier);
if (debouncedFilters.tags && debouncedFilters.tags.length > 0) {
params.set("tags", debouncedFilters.tags.join(","));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function BountyDetailSubmissionsCard({
const [prUrl, setPrUrl] = useState("");
const [submitComments, setSubmitComments] = useState("");
const [reviewComments, setReviewComments] = useState("");
const [reviewStatus, setReviewStatus] = useState("APPROVED");
const reviewStatus = "APPROVED";
const [transactionHash, setTransactionHash] = useState("");

const submitToBounty = useSubmitToBounty();
Expand Down
25 changes: 12 additions & 13 deletions components/leaderboard/leaderboard-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
SelectValue,
} from "@/components/ui/select";
import {
LeaderboardFilters as FiltersType,
LeaderboardTimeframe,
ReputationTier
LeaderboardFilters as FiltersType
} from "@/types/leaderboard";
import { LeaderboardTimeframe, ReputationTier } from "@/lib/graphql/generated";
import { FilterX } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Expand All @@ -38,17 +37,17 @@ interface LeaderboardFiltersProps {
}

export const TIMEFRAMES: { value: LeaderboardTimeframe; label: string }[] = [
{ value: "ALL_TIME", label: "All Time" },
{ value: "THIS_MONTH", label: "This Month" },
{ value: "THIS_WEEK", label: "This Week" },
{ value: LeaderboardTimeframe.AllTime, label: "All Time" },
{ value: LeaderboardTimeframe.ThisMonth, label: "This Month" },
{ value: LeaderboardTimeframe.ThisWeek, label: "This Week" },
];

export const TIERS: { value: ReputationTier; label: string }[] = [
{ value: "LEGEND", label: "Legend" },
{ value: "EXPERT", label: "Expert" },
{ value: "ESTABLISHED", label: "Established" },
{ value: "CONTRIBUTOR", label: "Contributor" },
{ value: "NEWCOMER", label: "Newcomer" },
{ value: ReputationTier.Legend, label: "Legend" },
{ value: ReputationTier.Expert, label: "Expert" },
{ value: ReputationTier.Established, label: "Established" },
{ value: ReputationTier.Contributor, label: "Contributor" },
{ value: ReputationTier.Newcomer, label: "Newcomer" },
];

// Mock available tags for filter - in real app could be passed as prop
Expand All @@ -72,13 +71,13 @@ export function LeaderboardFilters({ filters, onFilterChange }: LeaderboardFilte

const clearFilters = () => {
onFilterChange({
timeframe: "ALL_TIME",
timeframe: LeaderboardTimeframe.AllTime,
tier: undefined,
tags: [],
});
};

const hasActiveFilters = filters.timeframe !== "ALL_TIME" || filters.tier || (filters.tags?.length || 0) > 0;
const hasActiveFilters = filters.timeframe !== LeaderboardTimeframe.AllTime || filters.tier || (filters.tags?.length || 0) > 0;

return (
<div className="flex flex-wrap items-center gap-3 text-foreground">
Expand Down
3 changes: 2 additions & 1 deletion components/leaderboard/mini-leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TierBadge } from "@/components/reputation/tier-badge";
import { Trophy, ChevronRight, AlertCircle } from "lucide-react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { LeaderboardContributor } from "@/types/leaderboard";

interface MiniLeaderboardProps {
className?: string;
Expand Down Expand Up @@ -61,7 +62,7 @@ export function MiniLeaderboard({
</div>
) : (
<div className="flex flex-col">
{contributors?.map((contributor, index) => (
{contributors?.map((contributor: LeaderboardContributor, index: number) => (
<Link
key={contributor.id}
href={`/profile/${contributor.userId}`}
Expand Down
60 changes: 46 additions & 14 deletions hooks/use-leaderboard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { leaderboardApi } from '@/lib/api/leaderboard';
import { LeaderboardFilters } from '@/types/leaderboard';
import { fetcher } from '@/lib/graphql/client';
import {
LeaderboardQuery,
LeaderboardQueryVariables,
UserLeaderboardRankQuery,
UserLeaderboardRankQueryVariables,
TopContributorsQuery,
TopContributorsQueryVariables,
LeaderboardDocument,
UserLeaderboardRankDocument,
TopContributorsDocument
} from '@/lib/graphql/generated';
import { LeaderboardFilters, LeaderboardResponse } from '@/types/leaderboard';

export const LEADERBOARD_KEYS = {
all: ['leaderboard'] as const,
Expand All @@ -13,11 +24,16 @@ export const LEADERBOARD_KEYS = {
export const useLeaderboard = (filters: LeaderboardFilters, limit: number = 20) => {
return useInfiniteQuery({
queryKey: LEADERBOARD_KEYS.list(filters),
queryFn: ({ pageParam = 1 }) =>
leaderboardApi.fetchLeaderboard(filters, { page: pageParam, limit }),
getNextPageParam: (lastPage, allPages) => {
// Optimization: Use simple math instead of iterating all entries
if (allPages.length * limit < lastPage.totalCount) {
queryFn: ({ pageParam = 1 }) => {
return fetcher<LeaderboardQuery, LeaderboardQueryVariables>(
LeaderboardDocument,
{ filters, pagination: { page: pageParam as number, limit } }
)().then(data => data.leaderboard);
},
getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
// Use actual loaded entries count instead of optimistic calculation
const loadedCount = allPages.flatMap(p => p.entries).length;
if (loadedCount < lastPage.totalCount) {
return allPages.length + 1;
}
return undefined;
Expand All @@ -30,29 +46,45 @@ export const useLeaderboard = (filters: LeaderboardFilters, limit: number = 20)
export const useUserRank = (userId?: string) => {
return useQuery({
queryKey: LEADERBOARD_KEYS.user(userId || ''),
queryFn: () => leaderboardApi.fetchUserRank(userId),
queryFn: () => {
if (!userId) return null;
return fetcher<UserLeaderboardRankQuery, UserLeaderboardRankQueryVariables>(
UserLeaderboardRankDocument,
{ userId }
)().then(data => data.userLeaderboardRank);
},
enabled: !!userId,
});
};

export const useTopContributors = (count: number = 5) => {
return useQuery({
queryKey: LEADERBOARD_KEYS.top(count),
queryFn: () => leaderboardApi.fetchTopContributors(count),
queryFn: () => {
return fetcher<TopContributorsQuery, TopContributorsQueryVariables>(
TopContributorsDocument,
{ count }
)().then(data => data.topContributors);
},
staleTime: 1000 * 60 * 15, // 15 minutes
});
};

export const usePrefetchLeaderboardPage = () => {
const queryClient = useQueryClient();

return (filters: LeaderboardFilters, page: number, limit: number) => {
queryClient.prefetchInfiniteQuery({
return (filters: LeaderboardFilters, page: number, limit: number): Promise<void> => {
return queryClient.prefetchInfiniteQuery({
queryKey: LEADERBOARD_KEYS.list(filters),
queryFn: ({ pageParam }) => leaderboardApi.fetchLeaderboard(filters, { page: pageParam as number, limit }),
queryFn: ({ pageParam }: { pageParam: number }) => {
return fetcher<LeaderboardQuery, LeaderboardQueryVariables>(
LeaderboardDocument,
{ filters, pagination: { page: pageParam, limit } }
)().then(data => data.leaderboard);
},
initialPageParam: 1,
getNextPageParam: (lastPage, allPages) => {
const loadedCount = allPages.flatMap(p => p.entries).length;
getNextPageParam: (lastPage: LeaderboardResponse, allPages: LeaderboardResponse[]) => {
const loadedCount = allPages.flatMap((p: LeaderboardResponse) => p.entries).length;
if (loadedCount < lastPage.totalCount) {
return allPages.length + 1;
}
Expand Down
35 changes: 0 additions & 35 deletions lib/api/leaderboard.ts

This file was deleted.

Loading