1- import React , { useCallback , useEffect , useState } from 'react' ;
2- import { RefreshControl , ScrollView } from 'react-native' ;
1+ import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
2+ import { FlatList , RefreshControl , TouchableOpacity } from 'react-native' ;
33import { useNavigation } from '@react-navigation/native' ;
44import { useSelector } from 'react-redux' ;
55import { SafeAreaView } from 'react-native-safe-area-context' ;
@@ -21,15 +21,11 @@ import { useTheme } from '../../../../util/theme';
2121import Logger from '../../../../util/Logger' ;
2222import { strings } from '../../../../../locales/i18n' ;
2323import { TopTradersViewSelectorsIDs } from './TopTradersView.testIds' ;
24- import { TrendingTokenNetworkBottomSheet } from '../../../UI/Trending/components/TrendingTokensBottomSheet' ;
2524import {
2625 TraderRow ,
2726 TraderRowSkeleton ,
28- NetworkFilterButton ,
2927} from '../../Homepage/Sections/TopTraders/components' ;
3028import { useTopTraders } from '../../Homepage/Sections/TopTraders/hooks' ;
31- import type { NetworkFilterSelection } from '../../Homepage/Sections/TopTraders/types' ;
32- import type { CaipChainId } from '@metamask/utils' ;
3329import Routes from '../../../../constants/navigation/Routes' ;
3430import { selectSocialLeaderboardEnabled } from '../../../../selectors/featureFlagController/socialLeaderboard' ;
3531
@@ -39,52 +35,81 @@ const SKELETON_KEYS = Array.from(
3935 ( _ , i ) => `top-trader-skeleton-${ i } ` ,
4036) ;
4137
42- /**
43- * TopTradersView — Social leaderboard detail screen.
44- *
45- * Displays the full ranked list of top-performing traders with
46- * network filtering and Follow / Following actions.
47- */
38+ type ChainFilter = 'all' | 'base' | 'solana' | 'ethereum' ;
39+
40+ const CHAIN_FILTERS : { key : ChainFilter ; label : string } [ ] = [
41+ { key : 'all' , label : 'All' } ,
42+ { key : 'base' , label : 'Base' } ,
43+ { key : 'solana' , label : 'Solana' } ,
44+ { key : 'ethereum' , label : 'Ethereum' } ,
45+ ] ;
46+
47+ interface ChainPillProps {
48+ label : string ;
49+ isSelected : boolean ;
50+ onPress : ( ) => void ;
51+ }
52+
53+ const ChainPill : React . FC < ChainPillProps > = ( {
54+ label,
55+ isSelected,
56+ onPress,
57+ } ) => (
58+ < TouchableOpacity
59+ onPress = { onPress }
60+ testID = { `chain-filter-${ label . toLowerCase ( ) } ` }
61+ >
62+ < Box
63+ twClassName = { `px-4 py-2 rounded-xl border ${
64+ isSelected ? 'bg-default border-white' : 'border-muted'
65+ } `}
66+ >
67+ < Text
68+ variant = { TextVariant . BodySm }
69+ fontWeight = { FontWeight . Medium }
70+ color = { isSelected ? TextColor . TextDefault : TextColor . TextMuted }
71+ >
72+ { label }
73+ </ Text >
74+ </ Box >
75+ </ TouchableOpacity >
76+ ) ;
77+
4878const TopTradersView = ( ) => {
4979 const navigation = useNavigation ( ) ;
5080 const tw = useTailwind ( ) ;
5181 const { colors } = useTheme ( ) ;
5282 const isEnabled = useSelector ( selectSocialLeaderboardEnabled ) ;
5383
84+ const [ selectedChain , setSelectedChain ] = useState < ChainFilter > ( 'all' ) ;
85+ const [ refreshing , setRefreshing ] = useState ( false ) ;
86+
5487 const { traders, isLoading, refresh, toggleFollow } = useTopTraders ( {
88+ limit : 250 ,
5589 enabled : isEnabled ,
5690 } ) ;
5791
58- const [ refreshing , setRefreshing ] = useState ( false ) ;
59-
6092 useEffect ( ( ) => {
6193 if ( ! isEnabled ) {
6294 navigation . goBack ( ) ;
6395 }
6496 } , [ isEnabled , navigation ] ) ;
6597
66- const [ showNetworkBottomSheet , setShowNetworkBottomSheet ] = useState ( false ) ;
67- const [ selectedNetwork , setSelectedNetwork ] =
68- useState < NetworkFilterSelection > ( null ) ;
98+ const filteredTraders = useMemo ( ( ) => {
99+ const filtered =
100+ selectedChain === 'all'
101+ ? traders
102+ : traders . filter ( ( t ) => ( t . pnlPerChain [ selectedChain ] ?? 0 ) !== 0 ) ;
103+
104+ return filtered . slice ( 0 , 50 ) . map ( ( t , i ) => ( { ...t , rank : i + 1 } ) ) ;
105+ } , [ traders , selectedChain ] ) ;
69106
70107 const handleBack = useCallback ( ( ) => {
71108 navigation . goBack ( ) ;
72109 } , [ navigation ] ) ;
73110
74111 const handleSearchPress = useCallback ( ( ) => {
75- // Search UI will be wired when the leaderboard data layer ships.
76- } , [ ] ) ;
77-
78- const handleNetworkPress = useCallback ( ( ) => {
79- setShowNetworkBottomSheet ( true ) ;
80- } , [ ] ) ;
81-
82- const handleNetworkSelect = useCallback ( ( chainIds : CaipChainId [ ] | null ) => {
83- setSelectedNetwork ( chainIds ? chainIds [ 0 ] : null ) ;
84- } , [ ] ) ;
85-
86- const handleNetworkBottomSheetClose = useCallback ( ( ) => {
87- setShowNetworkBottomSheet ( false ) ;
112+ // Search UI will be wired in a future ticket.
88113 } , [ ] ) ;
89114
90115 const handleRefresh = useCallback ( async ( ) => {
@@ -111,16 +136,11 @@ const TopTradersView = () => {
111136 [ navigation ] ,
112137 ) ;
113138
114- const selectedNetworkCaip = selectedNetwork
115- ? ( [ selectedNetwork ] as CaipChainId [ ] )
116- : null ;
117-
118139 return (
119140 < SafeAreaView
120141 style = { tw . style ( 'flex-1 bg-default' ) }
121142 testID = { TopTradersViewSelectorsIDs . CONTAINER }
122143 >
123- { /* Header row */ }
124144 < Box
125145 flexDirection = { BoxFlexDirection . Row }
126146 alignItems = { BoxAlignItems . Center }
@@ -141,7 +161,6 @@ const TopTradersView = () => {
141161 />
142162 </ Box >
143163
144- { /* Title */ }
145164 < Box twClassName = "px-4 pt-2 pb-3" >
146165 < Text
147166 variant = { TextVariant . HeadingLg }
@@ -152,49 +171,48 @@ const TopTradersView = () => {
152171 </ Text >
153172 </ Box >
154173
155- { /* Network filter */ }
156- < Box twClassName = "px-4 pb-3" >
157- < NetworkFilterButton
158- selectedNetwork = { selectedNetwork }
159- onPress = { handleNetworkPress }
160- testID = "top-traders-view-network-filter"
161- />
174+ < Box
175+ flexDirection = { BoxFlexDirection . Row }
176+ twClassName = "px-4 pb-3 justify-between"
177+ >
178+ { CHAIN_FILTERS . map ( ( { key, label } ) => (
179+ < ChainPill
180+ key = { key }
181+ label = { label }
182+ isSelected = { selectedChain === key }
183+ onPress = { ( ) => setSelectedChain ( key ) }
184+ />
185+ ) ) }
162186 </ Box >
163187
164- { /* Trader list */ }
165- < ScrollView
166- showsVerticalScrollIndicator = { false }
167- contentContainerStyle = { tw . style ( 'pb-6' ) }
168- testID = { TopTradersViewSelectorsIDs . TRADER_LIST }
169- refreshControl = {
170- < RefreshControl
171- colors = { [ colors . primary . default ] }
172- tintColor = { colors . icon . default }
173- refreshing = { refreshing }
174- onRefresh = { handleRefresh }
175- />
176- }
177- >
178- { isLoading
179- ? SKELETON_KEYS . map ( ( key ) => < TraderRowSkeleton key = { key } /> )
180- : traders . map ( ( trader ) => (
181- < TraderRow
182- key = { trader . id }
183- trader = { trader }
184- onFollowPress = { toggleFollow }
185- onTraderPress = { handleTraderPress }
186- />
187- ) ) }
188- </ ScrollView >
189-
190- { /* Network filter bottom sheet */ }
191- < TrendingTokenNetworkBottomSheet
192- isVisible = { showNetworkBottomSheet }
193- onClose = { handleNetworkBottomSheetClose }
194- onNetworkSelect = { handleNetworkSelect }
195- selectedNetwork = { selectedNetworkCaip }
196- networks = { [ ] }
197- />
188+ { isLoading ? (
189+ SKELETON_KEYS . map ( ( key ) => < TraderRowSkeleton key = { key } /> )
190+ ) : (
191+ < FlatList
192+ data = { filteredTraders }
193+ keyExtractor = { ( item ) => item . id }
194+ renderItem = { ( { item } ) => (
195+ < TraderRow
196+ trader = { item }
197+ onFollowPress = { toggleFollow }
198+ onTraderPress = { handleTraderPress }
199+ />
200+ ) }
201+ showsVerticalScrollIndicator = { false }
202+ contentContainerStyle = { tw . style ( 'pb-6' ) }
203+ testID = { TopTradersViewSelectorsIDs . TRADER_LIST }
204+ initialNumToRender = { 15 }
205+ windowSize = { 5 }
206+ refreshControl = {
207+ < RefreshControl
208+ colors = { [ colors . primary . default ] }
209+ tintColor = { colors . icon . default }
210+ refreshing = { refreshing }
211+ onRefresh = { handleRefresh }
212+ />
213+ }
214+ />
215+ ) }
198216 </ SafeAreaView >
199217 ) ;
200218} ;
0 commit comments