@@ -2,15 +2,13 @@ import React, { useState, useEffect, useRef } from "react";
2
2
import styled from "styled-components" ;
3
3
import Skeleton from "react-loading-skeleton" ;
4
4
import { useClickAway } from "react-use" ;
5
- import { Tabs } from "@kleros/ui-components-library" ;
5
+ import { Searchbar } from "@kleros/ui-components-library" ;
6
6
import { Alchemy } from "alchemy-sdk" ;
7
7
import { useAccount , useNetwork } from "wagmi" ;
8
8
import { useNewTransactionContext } from "context/NewTransactionContext" ;
9
9
import { initializeTokens } from "utils/initializeTokens" ;
10
10
import { alchemyConfig } from "utils/alchemyConfig" ;
11
11
import { Overlay } from "components/Overlay" ;
12
- import TokensTab from "./TokensTab" ;
13
- import AddCustomTokenTab from "./AddCustomTokenTab" ;
14
12
import { StyledModal } from "pages/MyTransactions/Modal/StyledModal" ;
15
13
import { useLocalStorage } from "hooks/useLocalStorage" ;
16
14
@@ -48,15 +46,25 @@ const DropdownArrow = styled.span`
48
46
margin-left: 8px;
49
47
` ;
50
48
51
- export const Item = styled . div `
49
+ export const Item = styled . div < { selected : boolean } > `
52
50
display: flex;
53
51
align-items: center;
54
52
gap: 8px;
55
- padding: 8px ;
53
+ padding: 10px 16px ;
56
54
cursor: pointer;
57
55
&:hover {
58
- background: ${ ( { theme } ) => theme . mediumBlue } ;
56
+ background: ${ ( { theme } ) => theme . lightBlue } ;
59
57
}
58
+ ${ ( { selected, theme } ) =>
59
+ selected &&
60
+ `
61
+ background: ${ theme . mediumBlue } ;
62
+ border-left: 3px solid ${ theme . primaryBlue } ;
63
+ padding-left: 13px;
64
+ &:hover {
65
+ background: ${ theme . mediumBlue } ;
66
+ }
67
+ ` }
60
68
` ;
61
69
62
70
export const TokenLogo = styled . img `
@@ -81,13 +89,40 @@ const StyledLogoSkeleton = styled(Skeleton)`
81
89
border-radius: 50%;
82
90
` ;
83
91
84
- const TokenSelector : React . FC = ( ) => {
92
+ const ReStyledModal = styled ( StyledModal ) `
93
+ display: flex;
94
+ width: 500px;
95
+ ` ;
96
+
97
+ const StyledSearchbar = styled ( Searchbar ) `
98
+ width: 100%;
99
+ input {
100
+ font-size: 16px;
101
+ }
102
+ ` ;
103
+
104
+ const ItemsContainer = styled . div `
105
+ display: flex;
106
+ width: 100%;
107
+ flex-direction: column;
108
+ margin-top: 24px;
109
+ ` ;
110
+
111
+ const StyledP = styled . p `
112
+ display: flex;
113
+ align-self: flex-start;
114
+ font-weight: 600;
115
+ margin: 0;
116
+ margin-bottom: 28px;
117
+ ` ;
118
+
119
+ const TokenSelector = ( ) => {
85
120
const { address } = useAccount ( ) ;
86
121
const { chain } = useNetwork ( ) ;
87
122
const { sendingToken, setSendingToken } = useNewTransactionContext ( ) ;
88
- const [ tokens , setTokens ] = useLocalStorage < any [ ] > ( "tokens" , [ ] ) ;
123
+ const [ tokens , setTokens ] = useLocalStorage ( "tokens" , [ ] ) ;
124
+ const [ filteredTokens , setFilteredTokens ] = useState ( [ ] ) ;
89
125
const [ isOpen , setIsOpen ] = useState ( false ) ;
90
- const [ activeTab , setActiveTab ] = useState ( "tokens" ) ;
91
126
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
92
127
const containerRef = useRef ( null ) ;
93
128
const [ loading , setLoading ] = useState ( true ) ;
@@ -101,20 +136,56 @@ const TokenSelector: React.FC = () => {
101
136
} , [ address , chain ] ) ;
102
137
103
138
useEffect ( ( ) => {
104
- if ( tokens . length > 0 ) {
139
+ if ( tokens ? .length > 0 ) {
105
140
const nativeToken = tokens . find ( ( token ) => token . address === "native" ) ;
106
141
setSendingToken ( JSON . parse ( localStorage . getItem ( "selectedToken" ) ) || nativeToken ) ;
107
142
}
108
- } , [ tokens ] ) ;
143
+ } , [ tokens , setSendingToken ] ) ;
109
144
110
145
const handleSelectToken = ( token ) => {
111
146
setSendingToken ( token ) ;
112
147
localStorage . setItem ( "selectedToken" , JSON . stringify ( token ) ) ;
113
148
setIsOpen ( false ) ;
114
149
} ;
115
150
116
- const filteredTokens =
117
- tokens && tokens . filter ( ( token ) => token ?. symbol ?. toLowerCase ( ) . includes ( searchQuery ?. toLowerCase ( ) ) ) ;
151
+ const handleSearch = async ( query ) => {
152
+ setSearchQuery ( query ) ;
153
+
154
+ if ( ! query ) {
155
+ setFilteredTokens ( tokens ) ;
156
+ return ;
157
+ }
158
+
159
+ const isAddress = query . startsWith ( "0x" ) && query . length === 42 ;
160
+ if ( isAddress ) {
161
+ try {
162
+ const metadata = await alchemyInstance . core . getTokenMetadata ( query . toLowerCase ( ) ) ;
163
+ const resultToken = {
164
+ symbol : metadata . symbol ,
165
+ address : query . toLowerCase ( ) ,
166
+ logo : metadata . logo || "https://via.placeholder.com/24" ,
167
+ } ;
168
+
169
+ const updatedTokens = [ ...tokens , resultToken ] ;
170
+ const uniqueTokens = Array . from ( new Set ( updatedTokens . map ( ( a ) => a . address ) ) ) . map ( ( address ) => {
171
+ return updatedTokens . find ( ( a ) => a . address === address ) ;
172
+ } ) ;
173
+
174
+ setFilteredTokens ( [ resultToken ] ) ;
175
+ setTokens ( uniqueTokens ) ;
176
+ localStorage . setItem ( "tokens" , JSON . stringify ( uniqueTokens ) ) ;
177
+ } catch ( error ) {
178
+ console . error ( "Error fetching token info:" , error ) ;
179
+ }
180
+ } else {
181
+ const filteredTokens = tokens . filter ( ( token ) => token . symbol . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ) ;
182
+ setFilteredTokens ( filteredTokens ) ;
183
+ }
184
+ } ;
185
+
186
+ const tokensToDisplay = searchQuery
187
+ ? filteredTokens
188
+ : [ sendingToken , ...tokens . filter ( ( token ) => token . address !== sendingToken ?. address ) ] ;
118
189
119
190
return (
120
191
< TokenSelectorWrapper >
@@ -126,38 +197,33 @@ const TokenSelector: React.FC = () => {
126
197
) : (
127
198
sendingToken && < TokenLogo src = { sendingToken . logo } alt = { `${ sendingToken . symbol } logo` } />
128
199
) }
129
- { loading ? < Skeleton width = { 40 } height = { 16 } /> : sendingToken ? sendingToken . symbol : "Select a token" }
200
+ { loading ? < Skeleton width = { 40 } height = { 16 } /> : sendingToken ? .symbol }
130
201
</ DropdownContent >
131
202
< DropdownArrow />
132
203
</ DropdownButton >
133
204
{ isOpen && (
134
205
< >
135
206
< Overlay />
136
- < StyledModal ref = { containerRef } >
137
- < Tabs
138
- items = { [
139
- { text : "Tokens" , value : "tokens" } ,
140
- { text : "Add Custom Token" , value : "addCustomToken" } ,
141
- ] }
142
- callback = { setActiveTab }
143
- currentValue = { activeTab }
207
+ < ReStyledModal ref = { containerRef } >
208
+ < StyledP > Select a token</ StyledP >
209
+ < StyledSearchbar
210
+ placeholder = "Search by name or paste address"
211
+ value = { searchQuery }
212
+ onChange = { ( e ) => handleSearch ( e . target . value ) }
144
213
/>
145
- { activeTab === "tokens" && (
146
- < TokensTab
147
- searchQuery = { searchQuery }
148
- setSearchQuery = { setSearchQuery }
149
- filteredTokens = { filteredTokens }
150
- handleSelectToken = { handleSelectToken }
151
- />
152
- ) }
153
- { activeTab === "addCustomToken" && (
154
- < AddCustomTokenTab
155
- setTokens = { setTokens }
156
- setActiveTab = { setActiveTab }
157
- alchemyInstance = { alchemyInstance }
158
- />
159
- ) }
160
- </ StyledModal >
214
+ < ItemsContainer >
215
+ { tokensToDisplay ?. map ( ( token ) => (
216
+ < Item
217
+ key = { token . address }
218
+ onClick = { ( ) => handleSelectToken ( token ) }
219
+ selected = { sendingToken ?. address === token . address }
220
+ >
221
+ < TokenLogo src = { token . logo } alt = { `${ token ?. symbol } logo` } />
222
+ < TokenLabel > { token . symbol } </ TokenLabel >
223
+ </ Item >
224
+ ) ) }
225
+ </ ItemsContainer >
226
+ </ ReStyledModal >
161
227
</ >
162
228
) }
163
229
</ Container >
0 commit comments