import React, { useEffect, useRef } from 'react';
import {
View,
Text,
TouchableOpacity,
Modal,
Animated,
StyleSheet,
KeyboardAvoidingView,
Platform,
Image,
FlatList,
} from 'react-native';
import { useDubsTheme } from '../theme';
import { useArcadePool } from '../../hooks/useArcadePool';
import { ensurePngAvatar } from '../../utils/avatarUrl';
import type { ArcadeLeaderboardEntry } from '../../types';
export interface ArcadeLeaderboardSheetProps {
visible: boolean;
onDismiss: () => void;
poolId: number;
/** Highlighted wallet address (e.g. current user) */
highlightWallet?: string;
}
function RankLabel({ index }: { index: number }) {
if (index === 0) return {'\uD83E\uDD47'};
if (index === 1) return {'\uD83E\uDD48'};
if (index === 2) return {'\uD83E\uDD49'};
return {index + 1};
}
export function ArcadeLeaderboardSheet({
visible,
onDismiss,
poolId,
highlightWallet,
}: ArcadeLeaderboardSheetProps) {
const t = useDubsTheme();
const { pool, leaderboard, stats, loading, refetch } = useArcadePool(poolId);
const overlayOpacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(overlayOpacity, {
toValue: visible ? 1 : 0,
duration: 250,
useNativeDriver: true,
}).start();
}, [visible, overlayOpacity]);
// Refresh on open
useEffect(() => {
if (visible) refetch();
}, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
const renderItem = ({ item, index }: { item: ArcadeLeaderboardEntry; index: number }) => {
const isMe = highlightWallet && item.wallet_address === highlightWallet;
return (
{item.avatar ? (
) : (
)}
{item.username || `${item.wallet_address.slice(0, 4)}...${item.wallet_address.slice(-4)}`}
{item.lives_used} {item.lives_used === 1 ? 'life' : 'lives'} used
{item.best_score}
);
};
return (
{/* Drag handle */}
{/* Header */}
Leaderboard
{pool && (
{pool.name}
)}
{'\u2715'}
{/* Stats bar */}
{stats && (
{stats.total_entries}
Players
{stats.top_score}
Top Score
{Math.round(stats.avg_score)}
Avg Score
)}
{/* Leaderboard list */}
String(item.id)}
style={styles.list}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
{loading ? 'Loading...' : 'No scores yet'}
}
/>
);
}
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)',
},
overlayTap: { flex: 1 },
keyboardView: { flex: 1, justifyContent: 'flex-end' },
sheetPositioner: { flex: 1, justifyContent: 'flex-end' },
sheet: {
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingHorizontal: 20,
paddingBottom: 40,
maxHeight: '80%',
},
handleRow: { alignItems: 'center', paddingTop: 10, paddingBottom: 8 },
handle: { width: 36, height: 4, borderRadius: 2, opacity: 0.4 },
header: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
paddingVertical: 12,
},
headerTitle: { fontSize: 20, fontWeight: '700' },
poolName: { fontSize: 13, fontWeight: '600', marginTop: 2 },
closeButton: { fontSize: 20, padding: 4 },
statsBar: {
flexDirection: 'row',
borderRadius: 12,
borderWidth: 1,
marginBottom: 12,
overflow: 'hidden',
},
statItem: { flex: 1, alignItems: 'center', paddingVertical: 10 },
statValue: { fontSize: 18, fontWeight: '800' },
statLabel: { fontSize: 11, fontWeight: '600', marginTop: 2 },
statDivider: { width: 1, marginVertical: 8 },
list: { flexGrow: 0 },
listContent: { paddingBottom: 8 },
row: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 10,
marginBottom: 2,
},
rankCol: { width: 32, alignItems: 'center' },
rankEmoji: { fontSize: 18 },
rankNum: { fontSize: 14, fontWeight: '700', color: '#888' },
avatar: { width: 32, height: 32, borderRadius: 16, marginRight: 10 },
nameCol: { flex: 1 },
name: { fontSize: 14, fontWeight: '600' },
lives: { fontSize: 11, marginTop: 1 },
score: { fontSize: 18, fontWeight: '800' },
emptyState: { paddingVertical: 40, alignItems: 'center' },
emptyText: { fontSize: 14, fontWeight: '600' },
});