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