@@ -24,7 +24,6 @@ import {
2424 IconNames ,
2525 Paragraph ,
2626 ParagraphSize ,
27- Select ,
2827 SelectOption ,
2928 SimpleTable ,
3029 SimpleTableRow ,
@@ -51,12 +50,18 @@ import {
5150} from '../../../utils/asset' ;
5251import { removeTrailingZerosFromString } from '../../../utils/helpers' ;
5352import { decimalic , fromWei } from '../../../utils/math' ;
54- import { FIXED_MYNT_RATE , FIXED_RATE_ROUTES } from './ConvertPage.constants' ;
53+ import {
54+ CATEGORY_TOKENS ,
55+ FIXED_MYNT_RATE ,
56+ FIXED_RATE_ROUTES ,
57+ } from './ConvertPage.constants' ;
5558import {
5659 DEFAULT_SWAP_ENTRIES ,
5760 SMART_ROUTER_STABLECOINS ,
5861 SWAP_ROUTES ,
5962} from './ConvertPage.constants' ;
63+ import { CategoryType } from './ConvertPage.types' ;
64+ import { AssetDropdownWithFilters } from './components/AssetDropdownWithFilters/AssetDropdownWithFilters' ;
6065import { useConversionMaintenance } from './hooks/useConversionMaintenance' ;
6166import { useGetMaximumAvailableAmount } from './hooks/useGetMaximumAvailableAmount' ;
6267import { useHandleConversion } from './hooks/useHandleConversion' ;
@@ -76,23 +81,56 @@ const ConvertPage: FC = () => {
7681 [ currentChainId ] ,
7782 ) ;
7883
84+ const [ sourceCategories , setSourceCategories ] = useState < CategoryType [ ] > ( [
85+ CategoryType . All ,
86+ ] ) ;
87+
88+ const [ destinationCategories , setDestinationCategories ] = useState <
89+ CategoryType [ ]
90+ > ( [ CategoryType . All ] ) ;
91+
7992 const tokensToOptions = useCallback (
8093 (
8194 addresses : string [ ] ,
8295 chain : ChainId ,
8396 callback : ( options : SelectOption < string > [ ] ) => void ,
84- ) =>
97+ categories : CategoryType [ ] ,
98+ ) => {
8599 Promise . all (
86100 addresses
87101 . filter (
88- // filter out WBTC token on rsk chain
102+ // filter out WBTC token on RSK chain
89103 item =>
90- findAsset ( 'WBTC' , RSK_CHAIN_ID ) . address !== item . toLowerCase ( ) ,
104+ findAsset ( 'WBTC' , RSK_CHAIN_ID ) . address . toLowerCase ( ) !==
105+ item . toLowerCase ( ) ,
91106 )
92107 . map ( address => smartRouter . getTokenDetails ( address , chain ) ) ,
93- ) . then ( tokens =>
108+ ) . then ( tokens => {
109+ const tokensWithCategories = tokens . map ( token => {
110+ const category = Object . keys ( CATEGORY_TOKENS ) . find ( type =>
111+ CATEGORY_TOKENS [ type ] . includes ( token . symbol ) ,
112+ ) as CategoryType ;
113+ return { ...token , category } ;
114+ } ) ;
115+
116+ const filteredTokens = tokensWithCategories . filter ( token => {
117+ if ( categories . includes ( CategoryType . BTC ) ) {
118+ if (
119+ token . symbol . toUpperCase ( ) === CategoryType . BTC ||
120+ token . symbol . includes ( CategoryType . BTC )
121+ ) {
122+ return true ;
123+ }
124+ }
125+
126+ return (
127+ categories . includes ( token . category ) ||
128+ categories . includes ( CategoryType . All )
129+ ) ;
130+ } ) ;
131+
94132 callback (
95- tokens . map ( token => ( {
133+ filteredTokens . map ( token => ( {
96134 value : token . symbol ,
97135 label : (
98136 < AssetRenderer
@@ -103,17 +141,31 @@ const ConvertPage: FC = () => {
103141 />
104142 ) ,
105143 } ) ) ,
106- ) ,
107- ) ,
144+ ) ;
145+ } ) ;
146+ } ,
108147 [ smartRouter ] ,
109148 ) ;
110149
111150 const { account } = useAccount ( ) ;
112151 const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
113152 const fromToken = searchParams . get ( 'from' ) || '' ;
114153 const toToken = searchParams . get ( 'to' ) || '' ;
154+ const categoryFromToken = searchParams . get ( 'categoryFrom' ) || '' ;
155+ const categoryToToken = searchParams . get ( 'categoryTo' ) || '' ;
115156 const { balance : myntBalance } = useAssetBalance ( MYNT_TOKEN ) ;
116157
158+ const parsedSourceCategories = useMemo (
159+ ( ) =>
160+ categoryFromToken ? categoryFromToken . split ( ',' ) : [ CategoryType . All ] ,
161+ [ categoryFromToken ] ,
162+ ) ;
163+
164+ const parsedDestinationCategories = useMemo (
165+ ( ) => ( categoryToToken ? categoryToToken . split ( ',' ) : [ CategoryType . All ] ) ,
166+ [ categoryToToken ] ,
167+ ) ;
168+
117169 const [ slippageTolerance , setSlippageTolerance ] = useState ( '0.5' ) ;
118170
119171 const [ priceInQuote , setPriceQuote ] = useState ( false ) ;
@@ -138,6 +190,28 @@ const ConvertPage: FC = () => {
138190
139191 const [ sourceToken , setSourceToken ] = useState < string > ( defaultSourceToken ) ;
140192
193+ const handleCategorySelect = useCallback (
194+ (
195+ category : CategoryType ,
196+ setCategories : React . Dispatch < React . SetStateAction < CategoryType [ ] > > ,
197+ ) =>
198+ setCategories ( prevCategories =>
199+ category === CategoryType . All
200+ ? [ CategoryType . All ]
201+ : prevCategories . includes ( category )
202+ ? prevCategories . length === 1
203+ ? [ CategoryType . All ]
204+ : prevCategories . filter ( prevCategory => prevCategory !== category )
205+ : [
206+ ...prevCategories . filter (
207+ prevCategory => prevCategory !== CategoryType . All ,
208+ ) ,
209+ category ,
210+ ] ,
211+ ) ,
212+ [ ] ,
213+ ) ;
214+
141215 const [ showAdvancedSettings , setShowAdvancedSettings ] = useState ( true ) ;
142216
143217 const [ tokenOptions , setTokenOptions ] = useState < SelectOption < string > [ ] > ( [ ] ) ;
@@ -157,8 +231,15 @@ const ConvertPage: FC = () => {
157231 useEffect ( ( ) => {
158232 smartRouter
159233 . getEntries ( currentChainId )
160- . then ( tokens => tokensToOptions ( tokens , currentChainId , setTokenOptions ) ) ;
161- } , [ currentChainId , smartRouter , tokensToOptions ] ) ;
234+ . then ( tokens =>
235+ tokensToOptions (
236+ tokens ,
237+ currentChainId ,
238+ setTokenOptions ,
239+ sourceCategories ,
240+ ) ,
241+ ) ;
242+ } , [ currentChainId , smartRouter , tokensToOptions , sourceCategories ] ) ;
162243
163244 useEffect ( ( ) => {
164245 ( async ( ) => {
@@ -168,15 +249,26 @@ const ConvertPage: FC = () => {
168249 ) ;
169250 smartRouter
170251 . getDestination ( currentChainId , sourceTokenDetails . address )
171- . then ( tokens => {
172- tokensToOptions ( tokens , currentChainId , setDestinationTokenOptions ) ;
173- } ) ;
252+ . then ( tokens =>
253+ tokensToOptions (
254+ tokens ,
255+ currentChainId ,
256+ setDestinationTokenOptions ,
257+ destinationCategories ,
258+ ) ,
259+ ) ;
174260
175261 if ( sourceToken === MYNT_TOKEN ) {
176262 setDestinationToken ( COMMON_SYMBOLS . SOV ) ;
177263 }
178264 } ) ( ) ;
179- } , [ currentChainId , smartRouter , sourceToken , tokensToOptions ] ) ;
265+ } , [
266+ currentChainId ,
267+ smartRouter ,
268+ sourceToken ,
269+ tokensToOptions ,
270+ destinationCategories ,
271+ ] ) ;
180272
181273 const sourceTokenOptions = useMemo (
182274 ( ) =>
@@ -331,18 +423,6 @@ const ConvertPage: FC = () => {
331423 setHasQuoteError ( false ) ;
332424 } , [ ] ) ;
333425
334- const getAssetRenderer = useCallback (
335- ( token : string ) => (
336- < AssetRenderer
337- showAssetLogo
338- asset = { token }
339- chainId = { currentChainId }
340- assetClassName = "font-medium"
341- />
342- ) ,
343- [ currentChainId ] ,
344- ) ;
345-
346426 const { handleSubmit } = useHandleConversion (
347427 sourceToken ,
348428 destinationToken ,
@@ -410,7 +490,26 @@ const ConvertPage: FC = () => {
410490 if ( toToken ) {
411491 setDestinationToken ( toToken ) ;
412492 }
413- } , [ fromToken , toToken ] ) ;
493+ if ( categoryFromToken ) {
494+ const sourceCategories = parsedSourceCategories . map (
495+ category => CategoryType [ category ] || CategoryType . All ,
496+ ) ;
497+ setSourceCategories ( sourceCategories ) ;
498+ }
499+ if ( categoryToToken ) {
500+ const destinationCategories = parsedDestinationCategories . map (
501+ category => CategoryType [ category ] || CategoryType . All ,
502+ ) ;
503+ setDestinationCategories ( destinationCategories ) ;
504+ }
505+ } , [
506+ fromToken ,
507+ toToken ,
508+ categoryFromToken ,
509+ categoryToToken ,
510+ parsedSourceCategories ,
511+ parsedDestinationCategories ,
512+ ] ) ;
414513
415514 useEffect ( ( ) => {
416515 if ( hasMyntBalance && fromToken === MYNT_TOKEN ) {
@@ -435,10 +534,51 @@ const ConvertPage: FC = () => {
435534 urlParams . delete ( 'to' ) ;
436535 }
437536
438- if ( toToken !== destinationToken || fromToken !== sourceToken ) {
537+ if ( sourceToken === destinationToken ) {
538+ urlParams . delete ( 'to' ) ;
539+ }
540+
541+ if (
542+ sourceCategories . length > 0 &&
543+ ! sourceCategories . includes ( CategoryType . All )
544+ ) {
545+ urlParams . set ( 'categoryFrom' , sourceCategories . join ( ',' ) ) ;
546+ } else {
547+ urlParams . delete ( 'categoryFrom' ) ;
548+ }
549+
550+ if (
551+ destinationCategories . length > 0 &&
552+ ! destinationCategories . includes ( CategoryType . All )
553+ ) {
554+ urlParams . set ( 'categoryTo' , destinationCategories . join ( ',' ) ) ;
555+ } else {
556+ urlParams . delete ( 'categoryTo' ) ;
557+ }
558+
559+ if ( sourceToken === destinationToken ) {
560+ setDestinationToken ( '' ) ;
561+ }
562+
563+ if (
564+ toToken !== destinationToken ||
565+ fromToken !== sourceToken ||
566+ categoryToToken !== destinationCategories . join ( ',' ) ||
567+ categoryFromToken !== sourceCategories . join ( ',' )
568+ ) {
439569 setSearchParams ( new URLSearchParams ( urlParams ) ) ;
440570 }
441- } , [ sourceToken , destinationToken , setSearchParams , toToken , fromToken ] ) ;
571+ } , [
572+ sourceToken ,
573+ destinationToken ,
574+ setSearchParams ,
575+ toToken ,
576+ fromToken ,
577+ sourceCategories ,
578+ destinationCategories ,
579+ categoryToToken ,
580+ categoryFromToken ,
581+ ] ) ;
442582
443583 useEffect ( ( ) => {
444584 if ( ! account ) {
@@ -491,14 +631,14 @@ const ConvertPage: FC = () => {
491631 dataAttribute = "convert-from-amount"
492632 placeholder = "0"
493633 />
494-
495- < Select
496- value = { sourceToken }
497- onChange = { onSourceTokenChange }
498- options = { sourceTokenOptions }
499- labelRenderer = { ( ) => getAssetRenderer ( sourceToken ) }
500- className = "min-w-[6.7rem]"
501- menuClassName = "max-h-[10rem] sm:max-h-[12rem]"
634+ < AssetDropdownWithFilters
635+ token = { sourceToken }
636+ selectedCategories = { sourceCategories }
637+ tokenOptions = { sourceTokenOptions }
638+ onCategorySelect = { category =>
639+ handleCategorySelect ( category , setSourceCategories )
640+ }
641+ onTokenChange = { onSourceTokenChange }
502642 dataAttribute = "convert-from-asset"
503643 />
504644 </ div >
@@ -540,12 +680,14 @@ const ConvertPage: FC = () => {
540680 className = "w-full flex-grow-0 flex-shrink"
541681 dataAttribute = "convert-to-amount"
542682 />
543- < Select
544- value = { destinationToken }
545- onChange = { onDestinationTokenChange }
546- options = { destinationTokenOptions }
547- className = "min-w-[6.7rem]"
548- menuClassName = "max-h-[10rem] sm:max-h-[12rem]"
683+ < AssetDropdownWithFilters
684+ token = { destinationToken }
685+ selectedCategories = { destinationCategories }
686+ tokenOptions = { destinationTokenOptions }
687+ onCategorySelect = { category =>
688+ handleCategorySelect ( category , setDestinationCategories )
689+ }
690+ onTokenChange = { onDestinationTokenChange }
549691 dataAttribute = "convert-to-asset"
550692 />
551693 </ div >
0 commit comments