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 */}
);
}
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')}
>
);
}