Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
e830a617
Unverified
Commit
e830a617
authored
Aug 19, 2022
by
Igor Stuev
Committed by
GitHub
Aug 19, 2022
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #86 from blockscout/watchlist-backend
watchlist + backend
parents
60a9e905
238330dd
Changes
16
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
319 additions
and
90 deletions
+319
-90
watchlist.tsx
pages/[network_type]/[network_sub_type]/watchlist.tsx
+9
-0
[id].ts
pages/api/account/private-tags/address/[id].ts
+2
-2
[id].ts
pages/api/account/private-tags/transaction/[id].ts
+2
-2
[id].ts
pages/api/account/watchlist/[id].ts
+11
-0
get-with-tokens.ts
pages/api/account/watchlist/get-with-tokens.ts
+30
-0
index.ts
pages/api/account/watchlist/index.ts
+7
-0
account.ts
types/api/account.ts
+15
-9
tokenlist.ts
types/api/tokenlist.ts
+14
-0
account.ts
types/client/account.ts
+5
-3
Watchlist.tsx
ui/pages/Watchlist.tsx
+34
-21
AddressForm.tsx
ui/watchlist/AddressModal/AddressForm.tsx
+101
-19
AddressModal.tsx
ui/watchlist/AddressModal/AddressModal.tsx
+4
-3
DeleteAddressModal.tsx
ui/watchlist/DeleteAddressModal.tsx
+31
-6
WatchListAddressItem.tsx
ui/watchlist/WatchlistTable/WatchListAddressItem.tsx
+26
-17
WatchListTableItem.tsx
ui/watchlist/WatchlistTable/WatchListTableItem.tsx
+26
-6
WatchlistTable.tsx
ui/watchlist/WatchlistTable/WatchlistTable.tsx
+2
-2
No files found.
pages/[network_type]/[network_sub_type]/watchlist.tsx
View file @
e830a617
import
{
useQuery
}
from
'
@tanstack/react-query
'
;
import
type
{
NextPage
}
from
'
next
'
;
import
Head
from
'
next/head
'
;
import
React
from
'
react
'
;
...
...
@@ -5,6 +6,14 @@ import React from 'react';
import
WatchList
from
'
ui/pages/Watchlist
'
;
const
WatchListPage
:
NextPage
=
()
=>
{
useQuery
([
'
watchlist
'
],
async
()
=>
{
const
response
=
await
fetch
(
'
/api/account/watchlist/get-with-tokens
'
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
'
Network response was not ok
'
);
}
return
response
.
json
();
});
return
(
<>
<
Head
><
title
>
Watch list
</
title
></
Head
>
...
...
pages/api/account/private-tags/address/[id].ts
View file @
e830a617
...
...
@@ -6,6 +6,6 @@ const getUrl = (req: NextApiRequest) => {
return
`/account/v1/user/tags/address/
${
req
.
query
.
id
}
`
;
};
const
address
Delete
Handler
=
handler
(
getUrl
,
[
'
DELETE
'
,
'
PUT
'
]);
const
address
Edit
Handler
=
handler
(
getUrl
,
[
'
DELETE
'
,
'
PUT
'
]);
export
default
address
Delete
Handler
;
export
default
address
Edit
Handler
;
pages/api/account/private-tags/transaction/[id].ts
View file @
e830a617
...
...
@@ -6,6 +6,6 @@ const getUrl = (req: NextApiRequest) => {
return
`/account/v1/user/tags/transaction/
${
req
.
query
.
id
}
`
;
};
const
transaction
Delete
Handler
=
handler
(
getUrl
,
[
'
DELETE
'
,
'
PUT
'
]);
const
transaction
Edit
Handler
=
handler
(
getUrl
,
[
'
DELETE
'
,
'
PUT
'
]);
export
default
transaction
Delete
Handler
;
export
default
transaction
Edit
Handler
;
pages/api/account/watchlist/[id].ts
0 → 100644
View file @
e830a617
import
type
{
NextApiRequest
}
from
'
next
'
;
import
handler
from
'
lib/api/handler
'
;
const
getUrl
=
(
req
:
NextApiRequest
)
=>
{
return
`/account/v1/user/watchlist/
${
req
.
query
.
id
}
`
;
};
const
addressEditHandler
=
handler
(
getUrl
,
[
'
DELETE
'
,
'
PUT
'
]);
export
default
addressEditHandler
;
pages/api/account/watchlist/get-with-tokens.ts
0 → 100644
View file @
e830a617
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'
next
'
;
import
type
{
WatchlistAddresses
}
from
'
types/api/account
'
;
import
type
{
Tokenlist
}
from
'
types/api/tokenlist
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
fetch
from
'
lib/api/fetch
'
;
const
watchlistWithTokensHandler
=
async
(
_req
:
NextApiRequest
,
res
:
NextApiResponse
<
Array
<
TWatchlistItem
>>
)
=>
{
const
watchlistResponse
=
await
fetch
(
'
/account/v1/user/watchlist
'
,
{
method
:
'
GET
'
});
if
(
watchlistResponse
.
status
!==
200
)
{
// eslint-disable-next-line no-console
console
.
error
(
watchlistResponse
.
statusText
);
res
.
status
(
500
).
end
(
'
Unknown error
'
);
return
;
}
const
watchlistData
=
await
watchlistResponse
.
json
()
as
WatchlistAddresses
;
const
data
=
await
Promise
.
all
(
watchlistData
.
map
(
async
item
=>
{
const
tokens
=
await
fetch
(
`?module=account&action=tokenlist&address=
${
item
.
address_hash
}
`
);
const
tokensData
=
await
tokens
.
json
()
as
Tokenlist
;
return
({
...
item
,
tokens_count
:
Array
.
isArray
(
tokensData
.
result
)
?
tokensData
.
result
.
length
:
0
});
}));
res
.
status
(
200
).
json
(
data
);
};
export
default
watchlistWithTokensHandler
;
pages/api/account/watchlist/index.ts
0 → 100644
View file @
e830a617
import
type
{
WatchlistAddresses
}
from
'
types/api/account
'
;
import
handler
from
'
lib/api/handler
'
;
const
watchlistHandler
=
handler
<
WatchlistAddresses
>
(()
=>
'
/account/v1/user/watchlist
'
,
[
'
GET
'
,
'
POST
'
]);
export
default
watchlistHandler
;
types/api/account.ts
View file @
e830a617
...
...
@@ -23,9 +23,13 @@ export interface NotificationDirection {
}
export
interface
NotificationSettings
{
_native
?:
NotificationDirection
;
erc20
?:
NotificationDirection
;
erc7211155
?:
NotificationDirection
;
'
native
'
:
NotificationDirection
;
'
ERC-20
'
:
NotificationDirection
;
'
ERC-721
'
:
NotificationDirection
;
}
export
interface
NotificationMethods
{
email
:
boolean
;
}
export
interface
Transaction
{
...
...
@@ -51,12 +55,14 @@ export interface UserInfo {
}
export
interface
WatchlistAddress
{
addressHash
:
string
;
addressName
:
string
;
addressBalance
:
number
;
coinName
:
string
;
exchangeRate
?:
number
;
notificationSettings
:
NotificationSettings
;
address_hash
:
string
;
name
:
string
;
address_balance
:
number
;
coin_name
:
string
;
exchange_rate
:
number
;
notification_settings
:
NotificationSettings
;
notification_methods
:
NotificationMethods
;
id
:
string
;
}
export
interface
WatchlistAddressNew
{
...
...
types/api/tokenlist.ts
0 → 100644
View file @
e830a617
export
type
Tokenlist
=
{
message
:
string
;
result
:
Array
<
TokenlistItem
>
|
string
;
}
export
type
TokenlistItem
=
{
balance
:
number
;
contractAddress
:
string
;
decimals
?:
number
;
id
:
number
;
name
:
string
;
symbol
:
string
;
type
:
string
;
}
types/client/account.ts
View file @
e830a617
// here will be types if some back-end models are needed to be extended
// in order to fit the client's needs
export
{};
import
type
{
WatchlistAddress
}
from
'
../api/account
'
;
export
type
TWatchlistItem
=
WatchlistAddress
&
{
tokens_count
:
number
};
export
type
TWatchlist
=
Array
<
TWatchlistItem
>
;
ui/pages/Watchlist.tsx
View file @
e830a617
import
{
Box
,
Button
,
Text
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Button
,
Text
,
Skeleton
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
import
type
{
TWatchlist
Item
}
from
'
data/watchlis
t
'
;
import
{
watchlist
}
from
'
data/watchlist
'
;
import
type
{
TWatchlist
,
TWatchlistItem
}
from
'
types/client/accoun
t
'
;
import
AccountPageHeader
from
'
ui/shared/AccountPageHeader
'
;
import
Page
from
'
ui/shared/Page/Page
'
;
import
SkeletonTable
from
'
ui/shared/SkeletonTable
'
;
import
AddressModal
from
'
ui/watchlist/AddressModal/AddressModal
'
;
import
DeleteAddressModal
from
'
ui/watchlist/DeleteAddressModal
'
;
import
WatchlistTable
from
'
ui/watchlist/WatchlistTable/WatchlistTable
'
;
const
WatchList
:
React
.
FC
=
()
=>
{
const
queryClient
=
useQueryClient
();
const
watchlistData
=
queryClient
.
getQueryData
([
'
watchlist
'
])
as
TWatchlist
;
const
addressModalProps
=
useDisclosure
();
const
deleteModalProps
=
useDisclosure
();
const
[
addressModalData
,
setAddressModalData
]
=
useState
<
TWatchlistItem
>
();
const
[
deleteModalData
,
setDeleteModalData
]
=
useState
<
string
>
();
const
[
deleteModalData
,
setDeleteModalData
]
=
useState
<
TWatchlistItem
>
();
const
onEditClick
=
useCallback
((
data
:
TWatchlistItem
)
=>
{
setAddressModalData
(
data
);
...
...
@@ -27,7 +32,7 @@ const WatchList: React.FC = () => {
},
[
addressModalProps
]);
const
onDeleteClick
=
useCallback
((
data
:
TWatchlistItem
)
=>
{
setDeleteModalData
(
data
.
address
);
setDeleteModalData
(
data
);
deleteModalProps
.
onOpen
();
},
[
deleteModalProps
]);
...
...
@@ -41,13 +46,19 @@ const WatchList: React.FC = () => {
<
Box
h=
"100%"
>
<
AccountPageHeader
text=
"Watch list"
/>
<
Text
marginBottom=
{
12
}
>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
</
Text
>
{
Boolean
(
watchlist
.
length
)
&&
(
{
!
watchlistData
&&
(
<>
<
SkeletonTable
columns=
{
[
'
70%
'
,
'
30%
'
,
'
160px
'
,
'
108px
'
]
}
/>
<
Skeleton
height=
"44px"
width=
"156px"
marginTop=
{
8
}
/>
</>
)
}
{
Boolean
(
watchlistData
?.
length
)
&&
(
<>
<
WatchlistTable
data=
{
watchlist
}
data=
{
watchlistData
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
/>
)
}
<
Box
marginTop=
{
8
}
>
<
Button
variant=
"primary"
...
...
@@ -57,9 +68,11 @@ const WatchList: React.FC = () => {
Add address
</
Button
>
</
Box
>
</>
)
}
</
Box
>
<
AddressModal
{
...
addressModalProps
}
onClose=
{
onAddressModalClose
}
data=
{
addressModalData
}
/>
<
DeleteAddressModal
{
...
deleteModalProps
}
onClose=
{
onDeleteModalClose
}
address
=
{
deleteModalData
}
/>
<
DeleteAddressModal
{
...
deleteModalProps
}
onClose=
{
onDeleteModalClose
}
data
=
{
deleteModalData
}
/>
</
Page
>
);
};
...
...
ui/watchlist/AddressModal/AddressForm.tsx
View file @
e830a617
...
...
@@ -6,39 +6,104 @@ import {
Grid
,
GridItem
,
}
from
'
@chakra-ui/react
'
;
import
React
,
{
useCallback
,
useEffect
}
from
'
react
'
;
import
{
useMutation
,
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
'
react
'
;
import
type
{
SubmitHandler
,
ControllerRenderProps
}
from
'
react-hook-form
'
;
import
{
useForm
,
Controller
}
from
'
react-hook-form
'
;
import
type
{
TWatchlistItem
}
from
'
data/watchlist
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
AddressInput
from
'
ui/shared/AddressInput
'
;
import
TagInput
from
'
ui/shared/TagInput
'
;
const
NOTIFICATIONS
=
[
'
xDAI
'
,
'
ERC-20
'
,
'
ERC-721, ERC-1155 (NFT)
'
];
const
NOTIFICATIONS
=
[
'
native
'
,
'
ERC-20
'
,
'
ERC-721
'
]
as
const
;
const
NOTIFICATIONS_NAMES
=
[
'
xDAI
'
,
'
ERC-20
'
,
'
ERC-721, ERC-1155 (NFT)
'
];
const
ADDRESS_LENGTH
=
42
;
const
TAG_MAX_LENGTH
=
35
;
type
Props
=
{
data
?:
TWatchlistItem
;
onClose
:
()
=>
void
;
}
type
Inputs
=
{
address
:
string
;
tag
:
string
;
notification
:
boolean
;
notification_settings
:
{
'
native
'
:
{
outcoming
:
boolean
;
incoming
:
boolean
;
};
'
ERC-721
'
:
{
outcoming
:
boolean
;
incoming
:
boolean
;
};
'
ERC-20
'
:
{
outcoming
:
boolean
;
incoming
:
boolean
;
};
};
}
const
AddressForm
:
React
.
FC
<
Props
>
=
({
data
})
=>
{
type
Checkboxes
=
'
notification
'
|
'
notification_settings.native.outcoming
'
|
'
notification_settings.native.incoming
'
|
'
notification_settings.ERC-20.outcoming
'
|
'
notification_settings.ERC-20.incoming
'
|
'
notification_settings.ERC-721.outcoming
'
|
'
notification_settings.ERC-721.incoming
'
;
// TODO: mb we need to create an abstract form here?
const
AddressForm
:
React
.
FC
<
Props
>
=
({
data
,
onClose
})
=>
{
const
[
pending
,
setPending
]
=
useState
(
false
);
const
{
control
,
handleSubmit
,
formState
:
{
errors
},
setValue
}
=
useForm
<
Inputs
>
();
useEffect
(()
=>
{
setValue
(
'
address
'
,
data
?.
address
||
''
);
setValue
(
'
tag
'
,
data
?.
tag
||
''
);
setValue
(
'
notification
'
,
Boolean
(
data
?.
notification
));
},
[
setValue
,
data
]);
const
queryClient
=
useQueryClient
();
const
{
mutate
}
=
useMutation
((
formData
:
Inputs
)
=>
{
const
requestParams
=
{
name
:
formData
?.
tag
,
address_hash
:
formData
?.
address
,
notification_settings
:
formData
.
notification_settings
,
notification_methods
:
{
email
:
formData
.
notification
,
},
};
if
(
data
)
{
// edit address
return
fetch
(
`/api/account/watchlist/
${
data
.
id
}
`
,
{
method
:
'
PUT
'
,
body
:
JSON
.
stringify
(
requestParams
)
});
}
else
{
// add address
return
fetch
(
'
/api/account/watchlist
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
requestParams
)
});
}
},
{
onError
:
()
=>
{
// eslint-disable-next-line no-console
const
onSubmit
:
SubmitHandler
<
Inputs
>
=
data
=>
console
.
log
(
data
);
console
.
log
(
'
error
'
);
},
onSuccess
:
()
=>
{
queryClient
.
refetchQueries
([
'
watchlist
'
]).
then
(()
=>
{
onClose
();
setPending
(
false
);
});
},
});
const
onSubmit
:
SubmitHandler
<
Inputs
>
=
(
formData
)
=>
{
setPending
(
true
);
mutate
(
formData
);
};
useEffect
(()
=>
{
const
notificationsDefault
=
{}
as
Inputs
[
'
notification_settings
'
];
NOTIFICATIONS
.
forEach
(
n
=>
notificationsDefault
[
n
]
=
{
incoming
:
true
,
outcoming
:
true
});
setValue
(
'
address
'
,
data
?.
address_hash
||
''
);
setValue
(
'
tag
'
,
data
?.
name
||
''
);
setValue
(
'
notification
'
,
data
?
data
.
notification_methods
.
email
:
true
);
setValue
(
'
notification_settings
'
,
data
?
data
.
notification_settings
:
notificationsDefault
);
},
[
setValue
,
data
]);
const
renderAddressInput
=
useCallback
(({
field
}:
{
field
:
ControllerRenderProps
<
Inputs
,
'
address
'
>
})
=>
{
return
<
AddressInput
<
Inputs
,
'
address
'
>
field=
{
field
}
isInvalid=
{
Boolean
(
errors
.
address
)
}
/
>
;
...
...
@@ -48,7 +113,8 @@ const AddressForm: React.FC<Props> = ({ data }) => {
return
<
TagInput
field=
{
field
}
isInvalid=
{
Boolean
(
errors
.
tag
)
}
/>;
}
, [ errors ]);
const renderCheckbox = useCallback((
{
field
}
:
{
field
:
ControllerRenderProps
<
Inputs
,
'
notification
'
>
}
) =
>
(
// eslint-disable-next-line react/display-name
const renderCheckbox = useCallback((text: string) =
>
(
{
field
}
:
{
field
:
ControllerRenderProps
<
Inputs
,
Checkboxes
>
}
) =
>
(
<
Checkbox
isChecked=
{
field
.
value
}
onChange=
{
field
.
onChange
}
...
...
@@ -56,7 +122,7 @@ const AddressForm: React.FC<Props> = ({ data }) => {
colorScheme=
"blue"
size=
"lg"
>
Email notifications
{
text
}
</
Checkbox
>
), []);
...
...
@@ -87,14 +153,29 @@ const AddressForm: React.FC<Props> = ({ data }) => {
Please select what types of notifications you will receive
</
Text
>
<
Box
marginBottom=
{
8
}
>
{
/* add them to the form later */
}
<
Grid
templateColumns=
"repeat(3, max-content)"
gap=
"20px 24px"
>
{
NOTIFICATIONS
.
map
((
notification
:
string
)
=>
{
{
NOTIFICATIONS
.
map
((
notification
:
string
,
index
:
number
)
=>
{
const
incomingFieldName
=
`notification_settings.${ notification }.incoming`
as
Checkboxes
;
const
outgoingFieldName
=
`notification_settings.${ notification }.outcoming`
as
Checkboxes
;
return
(
<
React
.
Fragment
key=
{
notification
}
>
<
GridItem
>
{
notification
}
</
GridItem
>
<
GridItem
><
Checkbox
colorScheme=
"blue"
size=
"lg"
>
Incoming
</
Checkbox
></
GridItem
>
<
GridItem
><
Checkbox
colorScheme=
"blue"
size=
"lg"
>
Outgoing
</
Checkbox
></
GridItem
>
<
GridItem
>
{
NOTIFICATIONS_NAMES
[
index
]
}
</
GridItem
>
<
GridItem
>
<
Controller
name=
{
incomingFieldName
}
control=
{
control
}
render=
{
renderCheckbox
(
'
Incoming
'
)
}
/>
</
GridItem
>
<
GridItem
>
<
Controller
name=
{
outgoingFieldName
}
control=
{
control
}
render=
{
renderCheckbox
(
'
Outgoing
'
)
}
/>
</
GridItem
>
</
React
.
Fragment
>
);
})
}
...
...
@@ -102,15 +183,16 @@ const AddressForm: React.FC<Props> = ({ data }) => {
</
Box
>
<
Text
variant=
"secondary"
fontSize=
"sm"
marginBottom=
{
5
}
>
Notification methods
</
Text
>
<
Controller
name=
"notification"
name=
{
'
notification
'
as
Checkboxes
}
control=
{
control
}
render=
{
renderCheckbox
}
render=
{
renderCheckbox
(
'
Email notifications
'
)
}
/>
<
Box
marginTop=
{
8
}
>
<
Button
size=
"lg"
variant=
"primary"
onClick=
{
handleSubmit
(
onSubmit
)
}
isLoading=
{
pending
}
disabled=
{
Object
.
keys
(
errors
).
length
>
0
}
>
{
data
?
'
Save changes
'
:
'
Add address
'
}
...
...
ui/watchlist/AddressModal/AddressModal.tsx
View file @
e830a617
import
React
,
{
useCallback
}
from
'
react
'
;
import
type
{
TWatchlistItem
}
from
'
data/watchlist
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
FormModal
from
'
ui/shared/FormModal
'
;
import
AddressForm
from
'
./AddressForm
'
;
...
...
@@ -16,8 +17,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const
text
=
'
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
'
;
const
renderForm
=
useCallback
(()
=>
{
return
<
AddressForm
data=
{
data
}
/>;
},
[
data
]);
return
<
AddressForm
data=
{
data
}
onClose=
{
onClose
}
/>;
},
[
data
,
onClose
]);
return
(
<
FormModal
<
TWatchlistItem
>
isOpen=
{
isOpen
}
...
...
ui/watchlist/DeleteAddressModal.tsx
View file @
e830a617
import
{
Text
}
from
'
@chakra-ui/react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
{
useMutation
,
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
DeleteModal
from
'
ui/shared/DeleteModal
'
;
type
Props
=
{
isOpen
:
boolean
;
onClose
:
()
=>
void
;
address
?:
string
;
data
?:
TWatchlistItem
;
}
const
DeleteAddressModal
:
React
.
FC
<
Props
>
=
({
isOpen
,
onClose
,
address
})
=>
{
const
onDelete
=
useCallback
(()
=>
{
const
DeleteAddressModal
:
React
.
FC
<
Props
>
=
({
isOpen
,
onClose
,
data
})
=>
{
const
[
pending
,
setPending
]
=
useState
(
false
);
const
queryClient
=
useQueryClient
();
const
{
mutate
}
=
useMutation
(()
=>
{
return
fetch
(
`/api/account/watchlist/
${
data
?.
id
}
`, { method: 'DELETE' });
}, {
onError: () => {
// eslint-disable-next-line no-console
console
.
log
(
'
delete
'
,
address
);
},
[
address
]);
console.log('error');
},
onSuccess: () => {
queryClient.refetchQueries([ 'watchlist' ]).then(() => {
onClose();
setPending(false);
});
},
});
const onDelete = useCallback(() => {
setPending(true);
mutate();
}, [ mutate ]);
const address = data?.address_hash;
const renderText = useCallback(() => {
return (
...
...
@@ -28,6 +52,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, address }) => {
onDelete={ onDelete }
title="Remove address from watch list"
renderContent={ renderText }
pending={ pending }
/>
);
};
...
...
ui/watchlist/WatchlistTable/WatchListAddressItem.tsx
View file @
e830a617
import
{
Link
,
HStack
,
VStack
,
Image
,
Text
,
Icon
,
useColorModeValue
}
from
'
@chakra-ui/react
'
;
import
{
HStack
,
VStack
,
Image
,
Text
,
Icon
,
useColorModeValue
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
TWatchlistItem
}
from
'
data/watchlist
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
TokensIcon
from
'
icons/tokens.svg
'
;
import
WalletIcon
from
'
icons/wallet.svg
'
;
//
import WalletIcon from 'icons/wallet.svg';
import
{
nbsp
}
from
'
lib/html-entities
'
;
import
AddressIcon
from
'
ui/shared/AddressIcon
'
;
import
AddressLinkWithTooltip
from
'
ui/shared/AddressLinkWithTooltip
'
;
// now this component works only for xDAI
// for other networks later we will use config or smth
const
DECIMALS
=
18
;
const
WatchListAddressItem
=
({
item
}:
{
item
:
TWatchlistItem
})
=>
{
const
mainTextColor
=
useColorModeValue
(
'
gray.700
'
,
'
gray.50
'
);
const
nativeBalance
=
((
item
.
address_balance
||
0
)
/
10
**
DECIMALS
).
toFixed
(
1
);
const
nativeBalanceUSD
=
item
.
exchange_rate
?
`$
${
Number
(
nativeBalance
)
*
item
.
exchange_rate
}
USD`
:
'
N/A
'
;
return
(
<
HStack
spacing=
{
3
}
align=
"top"
>
<
AddressIcon
address=
{
item
.
address
}
/>
<
AddressIcon
address=
{
item
.
address
_hash
}
/>
<
VStack
spacing=
{
2
}
align=
"stretch"
overflow=
"hidden"
fontWeight=
{
500
}
color=
"gray.700"
>
<
AddressLinkWithTooltip
address=
{
item
.
address
}
/>
{
item
.
tokenBalance
&&
(
<
AddressLinkWithTooltip
address=
{
item
.
address_hash
}
/>
<
HStack
spacing=
{
0
}
fontSize=
"sm"
h=
{
6
}
>
<
Image
src=
"/xdai.png"
alt=
"chain-logo"
marginRight=
"10px"
w=
"16px"
h=
"16px"
/>
<
Text
color=
{
mainTextColor
}
>
{
`xDAI balance:${ nbsp }`
+
item
.
token
Balance
}
</
Text
>
<
Text
variant=
"secondary"
>
{
`${ nbsp }($${ item.tokenBalanceUSD } USD
)`
}
</
Text
>
<
Text
color=
{
mainTextColor
}
>
{
`xDAI balance:${ nbsp }`
+
native
Balance
}
</
Text
>
<
Text
variant=
"secondary"
>
{
`${ nbsp }(${ nativeBalanceUSD }
)`
}
</
Text
>
</
HStack
>
)
}
{
item
.
tokensAmount
&&
(
{
item
.
tokens_count
&&
(
<
HStack
spacing=
{
0
}
fontSize=
"sm"
h=
{
6
}
>
<
Icon
as=
{
TokensIcon
}
marginRight=
"10px"
w=
"17px"
h=
"16px"
/>
<
Text
color=
{
mainTextColor
}
>
{
`Tokens:${ nbsp }`
+
item
.
tokensAmount
}
</
Text
>
<
Text
variant=
"secondary"
>
{
`${ nbsp }($${ item.tokensUSD } USD)`
}
</
Text
>
<
Text
color=
{
mainTextColor
}
>
{
`Tokens:${ nbsp }`
+
item
.
tokens_count
}
</
Text
>
{
/* api does not provide token prices */
}
{
/* <Text variant="secondary">{ `${ nbsp }($${ item.tokensUSD } USD)` }</Text> */
}
<
Text
variant=
"secondary"
>
{
`${ nbsp }(N/A)`
}
</
Text
>
</
HStack
>
)
}
{
item
.
totalUSD
&&
(
{
/* api does not provide token prices */
}
{
/* { item.address_balance && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<Icon as={ WalletIcon } marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `Net worth:${ nbsp }` }</Text>
<Link href="#">{ `$${ item.totalUSD } USD` }</Link>
</HStack>
)
}
) }
*/
}
</
VStack
>
</
HStack
>
);
...
...
ui/watchlist/WatchlistTable/WatchListTableItem.tsx
View file @
e830a617
...
...
@@ -5,9 +5,11 @@ import {
Switch
,
HStack
,
}
from
'
@chakra-ui/react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
React
,
{
useCallback
,
useState
}
from
'
react
'
;
import
type
{
TWatchlistItem
}
from
'
types/client/account
'
;
import
type
{
TWatchlistItem
}
from
'
data/watchlist
'
;
import
DeleteButton
from
'
ui/shared/DeleteButton
'
;
import
EditButton
from
'
ui/shared/EditButton
'
;
import
TruncatedTextTooltip
from
'
ui/shared/TruncatedTextTooltip
'
;
...
...
@@ -21,6 +23,7 @@ interface Props {
}
const
WatchlistTableItem
=
({
item
,
onEditClick
,
onDeleteClick
}:
Props
)
=>
{
const
[
notificationEnabled
,
setNotificationEnabled
]
=
useState
(
item
.
notification_methods
.
email
);
const
onItemEditClick
=
useCallback
(()
=>
{
return
onEditClick
(
item
);
},
[
item
,
onEditClick
]);
...
...
@@ -29,17 +32,34 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return
onDeleteClick
(
item
);
},
[
item
,
onDeleteClick
]);
const
{
mutate
}
=
useMutation
(()
=>
{
const
data
=
{
...
item
,
notification_methods
:
{
email
:
!
notificationEnabled
}
};
return
fetch
(
`/api/account/watchlist/
${
item
.
id
}
`
,
{
method
:
'
PUT
'
,
body
:
JSON
.
stringify
(
data
)
});
},
{
onError
:
()
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'
error
'
);
},
onSuccess
:
()
=>
{
setNotificationEnabled
(
prevState
=>
!
prevState
);
},
});
const
onSwitch
=
useCallback
(()
=>
{
return
mutate
();
},
[
mutate
]);
return
(
<
Tr
alignItems=
"top"
key=
{
item
.
address
}
>
<
Tr
alignItems=
"top"
key=
{
item
.
address
_hash
}
>
<
Td
><
WatchListAddressItem
item=
{
item
}
/></
Td
>
<
Td
>
<
TruncatedTextTooltip
label=
{
item
.
tag
}
>
<
TruncatedTextTooltip
label=
{
item
.
name
}
>
<
Tag
variant=
"gray"
lineHeight=
"24px"
>
{
item
.
tag
}
{
item
.
name
}
</
Tag
>
</
TruncatedTextTooltip
>
</
Td
>
<
Td
><
Switch
colorScheme=
"blue"
size=
"md"
isChecked=
{
item
.
notification
}
/></
Td
>
<
Td
><
Switch
colorScheme=
"blue"
size=
"md"
isChecked=
{
notificationEnabled
}
onChange=
{
onSwitch
}
/></
Td
>
<
Td
>
<
HStack
spacing=
{
6
}
>
<
EditButton
onClick=
{
onItemEditClick
}
/>
...
...
ui/watchlist/WatchlistTable/WatchlistTable.tsx
View file @
e830a617
...
...
@@ -8,7 +8,7 @@ import {
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
TWatchlist
,
TWatchlistItem
}
from
'
data/watchlis
t
'
;
import
type
{
TWatchlist
,
TWatchlistItem
}
from
'
types/client/accoun
t
'
;
import
WatchlistTableItem
from
'
./WatchListTableItem
'
;
...
...
@@ -34,7 +34,7 @@ const WatchlistTable = ({ data, onDeleteClick, onEditClick }: Props) => {
{
data
.
map
((
item
)
=>
(
<
WatchlistTableItem
item=
{
item
}
key=
{
item
.
address
}
key=
{
item
.
address
_hash
}
onDeleteClick=
{
onDeleteClick
}
onEditClick=
{
onEditClick
}
/>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment