import { useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { ErrorState } from '@/components/ErrorState'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Skeleton } from '@/components/ui/skeleton'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useTranslation } from '@/contexts/language'; import { useAuthor } from '@/hooks/useAuthor'; import { useSkateShops } from '@/hooks/useSkateShops'; import { useSkateSpots } from '@/hooks/useSkateSpots'; import { genUserName } from '@/lib/genUserName'; import { nip19 } from 'nostr-tools'; import { Trophy, Medal, Crown, Store, MapPin, Zap, Camera, FileText, Users, Sparkles } from 'lucide-react'; import { cn } from '@/lib/utils'; // Leaderboard entry component function LeaderboardEntry({ rank, pubkey, count, type, isCurrentUser }: { rank: number; pubkey: string; count: number; type: 'shops' | 'spots' | 'total'; isCurrentUser: boolean; }) { const { t } = useTranslation(); const author = useAuthor(pubkey); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(pubkey); const avatar = metadata?.picture; // Rank styling const getRankIcon = () => { if (rank === 1) return ; if (rank === 2) return ; if (rank === 3) return ; return #{rank}; }; const getRankBg = () => { if (rank === 1) return 'bg-gradient-to-r from-yellow-950/50 to-orange-950/30 border-yellow-500/50'; if (rank === 2) return 'bg-gradient-to-r from-gray-900/50 to-gray-800/30 border-gray-500/50'; if (rank === 3) return 'bg-gradient-to-r from-amber-950/50 to-orange-950/30 border-amber-600/50'; return 'bg-black/30 border-gray-800'; }; const getTypeLabel = () => { if (type === 'shops') return t('walletUi.leaderboard.shopsLower'); if (type === 'spots') return t('walletUi.leaderboard.spotsLower'); return t('walletUi.leaderboard.contributions'); }; return (
{/* Rank */}
{getRankIcon()}
{/* Avatar */} {displayName.slice(0, 2).toUpperCase()} {/* Info */}

{displayName}

{isCurrentUser && ( {t('walletUi.leaderboard.you')} )}

{getTypeLabel()}

{/* Count */}

{count}

); } function LeaderboardSkeleton() { return (
{Array.from({ length: 10 }).map((_, i) => (
))}
); } export default function BountyLeaderboardPage() { const { t } = useTranslation(); useSeoMeta({ title: t('seoUi.bountyLeaderboardTitle'), description: t('seoUi.bountyLeaderboardDesc'), }); const { user } = useCurrentUser(); const { data: shops, isLoading: shopsLoading, isError: shopsError, refetch: refetchShops } = useSkateShops(500); const { data: spots, isLoading: spotsLoading, isError: spotsError, refetch: refetchSpots } = useSkateSpots(500); // Calculate shop leaderboard const shopLeaderboard = useMemo(() => { if (!shops) return []; const countByPubkey: Record = {}; shops.forEach(shop => { countByPubkey[shop.pubkey] = (countByPubkey[shop.pubkey] || 0) + 1; }); return Object.entries(countByPubkey) .map(([pubkey, count]) => ({ pubkey, count })) .sort((a, b) => b.count - a.count); }, [shops]); // Calculate spot leaderboard const spotLeaderboard = useMemo(() => { if (!spots) return []; const countByPubkey: Record = {}; spots.forEach(spot => { countByPubkey[spot.pubkey] = (countByPubkey[spot.pubkey] || 0) + 1; }); return Object.entries(countByPubkey) .map(([pubkey, count]) => ({ pubkey, count })) .sort((a, b) => b.count - a.count); }, [spots]); // Combined leaderboard (total contributions) const combinedLeaderboard = useMemo(() => { const combined: Record = {}; shops?.forEach(shop => { if (!combined[shop.pubkey]) combined[shop.pubkey] = { shops: 0, spots: 0, total: 0 }; combined[shop.pubkey].shops++; combined[shop.pubkey].total++; }); spots?.forEach(spot => { if (!combined[spot.pubkey]) combined[spot.pubkey] = { shops: 0, spots: 0, total: 0 }; combined[spot.pubkey].spots++; combined[spot.pubkey].total++; }); return Object.entries(combined) .map(([pubkey, data]) => ({ pubkey, ...data })) .sort((a, b) => b.total - a.total); }, [shops, spots]); // Find user's rank const userCombinedRank = combinedLeaderboard.findIndex(e => e.pubkey === user?.pubkey) + 1; const userContributions = combinedLeaderboard.find(e => e.pubkey === user?.pubkey); // Stats const totalShops = shops?.length || 0; const totalSpots = spots?.length || 0; const totalMappers = combinedLeaderboard.length; if (shopsError || spotsError) { return ( <>
{ refetchShops(); refetchSpots(); }} />
); } return ( <>
{/* Header */}
{t('walletUi.leaderboard.mapEarn')}

{t('walletUi.leaderboard.helpMap')}

{t('walletUi.leaderboard.intro')}

{/* How It Works */}

{t('walletUi.leaderboard.howItWorks')}

{t('walletUi.leaderboard.step1Title')}

{t('walletUi.leaderboard.step1Desc')}

{t('walletUi.leaderboard.step2Title')}

{t('walletUi.leaderboard.step2Desc')}

{t('walletUi.leaderboard.step3Title')}

{t('walletUi.leaderboard.step3Desc')}

{/* Quality Tips */}

{t('walletUi.leaderboard.proTips')}

{t('walletUi.leaderboard.tipPhotos')} {t('walletUi.leaderboard.tipDescriptions')} {t('walletUi.leaderboard.tipLocation')}
{/* Quick Actions */}

{t('walletUi.leaderboard.mapShop')}

{t('walletUi.leaderboard.mapShopDesc')}

{t('walletUi.leaderboard.mapSpot')}

{t('walletUi.leaderboard.mapSpotDesc')}

{/* Stats */}

{totalShops}

{t('walletUi.leaderboard.shops')}

{totalSpots}

{t('walletUi.stats.spots')}

{totalMappers}

{t('walletUi.stats.mappers')}

{/* User's position (if logged in and has contributions) */} {user && userCombinedRank > 0 && userContributions && (
#{userCombinedRank}

{t('walletUi.leaderboard.yourRanking')}

{t('walletUi.leaderboard.spotsShops', { spots: userContributions.spots, shops: userContributions.shops })}

{userContributions.total}

{t('walletUi.leaderboard.totalLower')}

)} {/* Leaderboard */}

{t('walletUi.stats.topMappers')}

{t('walletUi.leaderboard.overall')} {t('walletUi.leaderboard.shops')} {t('walletUi.stats.spots')} {shopsLoading || spotsLoading ? ( ) : combinedLeaderboard.length > 0 ? (
{combinedLeaderboard.slice(0, 20).map((entry, i) => ( ))}
) : (

{t('walletUi.leaderboard.noContributions')}

)}
{shopsLoading ? ( ) : shopLeaderboard.length > 0 ? (
{shopLeaderboard.slice(0, 20).map((entry, i) => ( ))}
) : (

{t('walletUi.leaderboard.noShops')}

)}
{spotsLoading ? ( ) : spotLeaderboard.length > 0 ? (
{spotLeaderboard.slice(0, 20).map((entry, i) => ( ))}
) : (

{t('walletUi.leaderboard.noSpots')}

)}
{/* Community Note */}

{t('walletUi.leaderboard.poweredBy')}

{t('walletUi.leaderboard.poweredByDesc')}

); }