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
d402eecf
Unverified
Commit
d402eecf
authored
Dec 16, 2022
by
Igor Stuev
Committed by
GitHub
Dec 16, 2022
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #435 from blockscout/address-tt
address token transfers
parents
88db5a36
36bc4ea3
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
152 additions
and
33 deletions
+152
-33
getFilterValueFromQuery.ts
lib/getFilterValueFromQuery.ts
+5
-0
getFilterValuesFromQuery.ts
lib/getFilterValuesFromQuery.ts
+11
-0
useQueryWithPages.ts
lib/hooks/useQueryWithPages.ts
+1
-1
address.ts
types/api/address.ts
+4
-2
AddressTokenTransfers.tsx
ui/address/AddressTokenTransfers.tsx
+21
-0
AddressTxs.tsx
ui/address/AddressTxs.tsx
+5
-10
AddressTxsFilter.tsx
ui/address/AddressTxsFilter.tsx
+3
-1
Address.tsx
ui/pages/Address.tsx
+2
-1
TokenTransfer.tsx
ui/shared/TokenTransfer/TokenTransfer.tsx
+49
-12
TokenTransferFilter.tsx
ui/shared/TokenTransfer/TokenTransferFilter.tsx
+51
-6
No files found.
lib/getFilterValueFromQuery.ts
0 → 100644
View file @
d402eecf
export
default
function
getFilterValue
<
FilterType
>
(
filterValues
:
ReadonlyArray
<
FilterType
>
,
val
:
string
|
Array
<
string
>
|
undefined
)
{
if
(
typeof
val
===
'
string
'
&&
filterValues
.
includes
(
val
as
unknown
as
FilterType
))
{
return
val
as
unknown
as
FilterType
;
}
}
lib/getFilterValuesFromQuery.ts
0 → 100644
View file @
d402eecf
export
default
function
getFilterValue
<
FilterType
>
(
filterValues
:
ReadonlyArray
<
FilterType
>
,
val
:
string
|
Array
<
string
>
|
undefined
)
{
const
valArray
=
[];
if
(
typeof
val
===
'
string
'
)
{
valArray
.
push
(...
val
.
split
(
'
,
'
));
}
if
(
Array
.
isArray
(
val
))
{
val
.
forEach
(
el
=>
valArray
.
push
(...
el
.
split
(
'
,
'
)));
}
return
valArray
.
filter
(
el
=>
filterValues
.
includes
(
el
as
unknown
as
FilterType
))
as
unknown
as
Array
<
FilterType
>
;
}
lib/hooks/useQueryWithPages.ts
View file @
d402eecf
...
...
@@ -124,7 +124,7 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
const
newQuery
=
omit
(
router
.
query
,
PAGINATION_FIELDS
[
queryName
],
'
page
'
,
PAGINATION_FILTERS_FIELDS
[
queryName
]);
if
(
newFilters
)
{
Object
.
entries
(
newFilters
).
forEach
(([
key
,
value
])
=>
{
if
(
value
)
{
if
(
value
&&
value
.
length
)
{
newQuery
[
key
]
=
Array
.
isArray
(
value
)
?
value
.
join
(
'
,
'
)
:
(
value
||
''
);
}
});
...
...
types/api/address.ts
View file @
d402eecf
...
...
@@ -45,7 +45,9 @@ export interface AddressTransactionsResponse {
}
|
null
;
}
type
AddressFromToFilter
=
'
from
'
|
'
to
'
|
undefined
;
export
const
AddressFromToFilterValues
=
[
'
from
'
,
'
to
'
]
as
const
;
export
type
AddressFromToFilter
=
typeof
AddressFromToFilterValues
[
number
]
|
undefined
;
export
type
AddressTxsFilters
=
{
filter
:
AddressFromToFilter
;
...
...
@@ -58,7 +60,7 @@ export interface AddressTokenTransferResponse {
export
type
AddressTokenTransferFilters
=
{
filter
:
AddressFromToFilter
;
type
:
TokenType
;
type
:
Array
<
TokenType
>
;
}
export
interface
AddressCoinBalanceHistoryItem
{
...
...
ui/address/AddressTokenTransfers.tsx
0 → 100644
View file @
d402eecf
import
{
useRouter
}
from
'
next/router
'
;
import
React
from
'
react
'
;
import
{
QueryKeys
}
from
'
types/client/queries
'
;
import
TokenTransfer
from
'
ui/shared/TokenTransfer/TokenTransfer
'
;
const
AddressTokenTransfers
=
()
=>
{
const
router
=
useRouter
();
const
hash
=
router
.
query
.
id
;
return
(
<
TokenTransfer
path=
{
`/node-api/addresses/${ hash }/token-transfers`
}
queryName=
{
QueryKeys
.
addressTokenTransfers
}
baseAddress=
{
typeof
hash
===
'
string
'
?
hash
:
undefined
}
/>
);
};
export
default
AddressTokenTransfers
;
ui/address/AddressTxs.tsx
View file @
d402eecf
...
...
@@ -2,8 +2,11 @@ import { useRouter } from 'next/router';
import
React
from
'
react
'
;
import
{
Element
}
from
'
react-scroll
'
;
import
type
{
AddressFromToFilter
}
from
'
types/api/address
'
;
import
{
AddressFromToFilterValues
}
from
'
types/api/address
'
;
import
{
QueryKeys
}
from
'
types/client/queries
'
;
import
getFilterValueFromQuery
from
'
lib/getFilterValueFromQuery
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
useQueryWithPages
from
'
lib/hooks/useQueryWithPages
'
;
import
ActionBar
from
'
ui/shared/ActionBar
'
;
...
...
@@ -12,15 +15,7 @@ import TxsContent from 'ui/txs/TxsContent';
import
AddressTxsFilter
from
'
./AddressTxsFilter
'
;
const
FILTER_VALUES
=
[
'
from
'
,
'
to
'
]
as
const
;
type
FilterType
=
typeof
FILTER_VALUES
[
number
];
const
getFilterValue
=
(
val
:
string
|
Array
<
string
>
|
undefined
):
FilterType
|
undefined
=>
{
if
(
typeof
val
===
'
string
'
&&
FILTER_VALUES
.
includes
(
val
as
FilterType
))
{
return
val
as
FilterType
;
}
};
const
getFilterValue
=
(
getFilterValueFromQuery
<
AddressFromToFilter
>
).
bind
(
null
,
AddressFromToFilterValues
);
const
SCROLL_ELEM
=
'
address-txs
'
;
const
SCROLL_OFFSET
=
-
100
;
...
...
@@ -30,7 +25,7 @@ const AddressTxs = () => {
const
isMobile
=
useIsMobile
();
const
[
filterValue
,
setFilterValue
]
=
React
.
useState
<
'
from
'
|
'
to
'
|
undefined
>
(
getFilterValue
(
router
.
query
.
filter
));
const
[
filterValue
,
setFilterValue
]
=
React
.
useState
<
AddressFromToFilter
>
(
getFilterValue
(
router
.
query
.
filter
));
const
addressTxsQuery
=
useQueryWithPages
({
apiPath
:
`/node-api/addresses/
${
router
.
query
.
id
}
/transactions`
,
...
...
ui/address/AddressTxsFilter.tsx
View file @
d402eecf
...
...
@@ -8,11 +8,13 @@ import {
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
AddressFromToFilter
}
from
'
types/api/address
'
;
import
FilterButton
from
'
ui/shared/FilterButton
'
;
interface
Props
{
isActive
:
boolean
;
defaultFilter
:
'
from
'
|
'
to
'
|
undefined
;
defaultFilter
:
AddressFromToFilter
;
onFilterChange
:
(
nextValue
:
string
|
Array
<
string
>
)
=>
void
;
}
...
...
ui/pages/Address.tsx
View file @
d402eecf
...
...
@@ -11,6 +11,7 @@ import useFetch from 'lib/hooks/useFetch';
import
AddressBlocksValidated
from
'
ui/address/AddressBlocksValidated
'
;
import
AddressCoinBalance
from
'
ui/address/AddressCoinBalance
'
;
import
AddressDetails
from
'
ui/address/AddressDetails
'
;
import
AddressTokenTransfers
from
'
ui/address/AddressTokenTransfers
'
;
import
AddressTxs
from
'
ui/address/AddressTxs
'
;
import
Page
from
'
ui/shared/Page/Page
'
;
import
PageTitle
from
'
ui/shared/Page/PageTitle
'
;
...
...
@@ -36,7 +37,7 @@ const AddressPageContent = () => {
const
tabs
:
Array
<
RoutedTab
>
=
[
{
id
:
'
txs
'
,
title
:
'
Transactions
'
,
component
:
<
AddressTxs
/>
},
{
id
:
'
token_transfers
'
,
title
:
'
Token transfers
'
,
component
:
null
},
{
id
:
'
token_transfers
'
,
title
:
'
Token transfers
'
,
component
:
<
AddressTokenTransfers
/>
},
{
id
:
'
tokens
'
,
title
:
'
Tokens
'
,
component
:
null
},
{
id
:
'
internal_txn
'
,
title
:
'
Internal txn
'
,
component
:
null
},
{
id
:
'
coin_balance_history
'
,
title
:
'
Coin balance history
'
,
component
:
<
AddressCoinBalance
addressQuery=
{
addressQuery
}
/>
},
...
...
ui/shared/TokenTransfer/TokenTransfer.tsx
View file @
d402eecf
import
{
Hide
,
Show
,
Text
}
from
'
@chakra-ui/react
'
;
import
{
useRouter
}
from
'
next/router
'
;
import
React
from
'
react
'
;
import
{
Element
}
from
'
react-scroll
'
;
import
type
{
AddressTokenTransferFilters
,
AddressFromToFilter
}
from
'
types/api/address
'
;
import
{
AddressFromToFilterValues
}
from
'
types/api/address
'
;
import
type
{
TokenType
}
from
'
types/api/tokenInfo
'
;
import
type
{
TokenTransferFilters
}
from
'
types/api/tokenTransfer
'
;
import
type
{
QueryKeys
}
from
'
types/client/queries
'
;
import
getFilterValueFromQuery
from
'
lib/getFilterValueFromQuery
'
;
import
getFilterValuesFromQuery
from
'
lib/getFilterValuesFromQuery
'
;
import
useQueryWithPages
from
'
lib/hooks/useQueryWithPages
'
;
import
{
apos
}
from
'
lib/html-entities
'
;
import
EmptySearchResult
from
'
ui/apps/EmptySearchResult
'
;
...
...
@@ -17,11 +24,21 @@ import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
import
TokenTransferSkeletonMobile
from
'
ui/shared/TokenTransfer/TokenTransferSkeletonMobile
'
;
import
TokenTransferTable
from
'
ui/shared/TokenTransfer/TokenTransferTable
'
;
import
{
TOKEN_TYPE
}
from
'
./helpers
'
;
const
TOKEN_TYPES
=
TOKEN_TYPE
.
map
(
i
=>
i
.
id
);
const
SCROLL_ELEM
=
'
token-transfers
'
;
const
SCROLL_OFFSET
=
-
100
;
const
getTokenFilterValue
=
(
getFilterValuesFromQuery
<
TokenType
>
).
bind
(
null
,
TOKEN_TYPES
);
const
getAddressFilterValue
=
(
getFilterValueFromQuery
<
AddressFromToFilter
>
).
bind
(
null
,
AddressFromToFilterValues
);
interface
Props
{
isLoading
?:
boolean
;
isDisabled
?:
boolean
;
path
:
string
;
queryName
:
QueryKeys
.
txTokenTransfers
;
queryName
:
QueryKeys
.
txTokenTransfers
|
QueryKeys
.
addressTokenTransfers
;
queryIds
?:
Array
<
string
>
;
baseAddress
?:
string
;
showTxInfo
?:
boolean
;
...
...
@@ -29,20 +46,33 @@ interface Props {
}
const
TokenTransfer
=
({
isLoading
:
isLoadingProp
,
isDisabled
,
queryName
,
queryIds
,
path
,
baseAddress
,
showTxInfo
=
true
}:
Props
)
=>
{
const
[
filters
,
setFilters
]
=
React
.
useState
<
Array
<
TokenType
>>
([]);
const
{
isError
,
isLoading
,
data
,
pagination
}
=
useQueryWithPages
({
const
router
=
useRouter
();
const
[
filters
,
setFilters
]
=
React
.
useState
<
AddressTokenTransferFilters
&
TokenTransferFilters
>
(
{
type
:
getTokenFilterValue
(
router
.
query
.
type
),
filter
:
getAddressFilterValue
(
router
.
query
.
filter
)
},
);
const
{
isError
,
isLoading
,
data
,
pagination
,
onFilterChange
}
=
useQueryWithPages
({
apiPath
:
path
,
queryName
,
queryIds
,
options
:
{
enabled
:
!
isDisabled
},
filters
:
filters
.
length
?
{
type
:
filters
}
:
undefined
,
filters
:
filters
,
scroll
:
{
elem
:
SCROLL_ELEM
,
offset
:
SCROLL_OFFSET
},
});
const
handleFilterChange
=
React
.
useCallback
((
nextValue
:
Array
<
TokenType
>
)
=>
{
setFilters
(
nextValue
);
},
[]);
const
handleTypeFilterChange
=
React
.
useCallback
((
nextValue
:
Array
<
TokenType
>
)
=>
{
onFilterChange
({
...
filters
,
type
:
nextValue
});
setFilters
((
prevState
)
=>
({
...
prevState
,
type
:
nextValue
}));
},
[
filters
,
onFilterChange
]);
const
handleAddressFilterChange
=
React
.
useCallback
((
nextValue
:
string
)
=>
{
const
filterVal
=
getAddressFilterValue
(
nextValue
);
onFilterChange
({
...
filters
,
filter
:
filterVal
});
setFilters
((
prevState
)
=>
({
...
prevState
,
filter
:
filterVal
}));
},
[
filters
,
onFilterChange
]);
const
isActionBarHidden
=
filters
.
length
===
0
&&
!
data
?.
items
.
length
;
const
numActiveFilters
=
filters
.
type
.
length
+
(
filters
.
filter
?
1
:
0
);
const
isActionBarHidden
=
!
numActiveFilters
&&
!
data
?.
items
.
length
;
const
content
=
(()
=>
{
if
(
isLoading
||
isLoadingProp
)
{
...
...
@@ -65,7 +95,7 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI
return
<
DataFetchAlert
/>;
}
if
(
!
data
.
items
?.
length
&&
filters
.
length
===
0
)
{
if
(
!
data
.
items
?.
length
&&
!
numActiveFilters
)
{
return
<
Text
as=
"span"
>
There are no token transfers
</
Text
>;
}
...
...
@@ -87,15 +117,22 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI
})();
return
(
<>
<
Element
name=
{
SCROLL_ELEM
}
>
{
!
isActionBarHidden
&&
(
<
ActionBar
mt=
{
-
6
}
>
<
TokenTransferFilter
defaultFilters=
{
filters
}
onFilterChange=
{
handleFilterChange
}
appliedFiltersNum=
{
filters
.
length
}
/>
<
TokenTransferFilter
defaultTypeFilters=
{
filters
.
type
}
onTypeFilterChange=
{
handleTypeFilterChange
}
appliedFiltersNum=
{
numActiveFilters
}
withAddressFilter=
{
Boolean
(
baseAddress
)
}
onAddressFilterChange=
{
handleAddressFilterChange
}
defaultAddressFilter=
{
filters
.
filter
}
/>
<
Pagination
ml=
"auto"
{
...
pagination
}
/>
</
ActionBar
>
)
}
{
content
}
</>
</
Element
>
);
};
...
...
ui/shared/TokenTransfer/TokenTransferFilter.tsx
View file @
d402eecf
import
{
Popover
,
PopoverTrigger
,
PopoverContent
,
PopoverBody
,
CheckboxGroup
,
Checkbox
,
Text
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
Popover
,
PopoverTrigger
,
PopoverContent
,
PopoverBody
,
CheckboxGroup
,
Checkbox
,
Text
,
useDisclosure
,
Radio
,
RadioGroup
,
Stack
,
useColorModeValue
,
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
AddressFromToFilter
}
from
'
types/api/address
'
;
import
type
{
TokenType
}
from
'
types/api/tokenInfo
'
;
import
FilterButton
from
'
ui/shared/FilterButton
'
;
...
...
@@ -9,13 +23,25 @@ import { TOKEN_TYPE } from './helpers';
interface
Props
{
appliedFiltersNum
?:
number
;
defaultFilters
:
Array
<
TokenType
>
;
onFilterChange
:
(
nextValue
:
Array
<
TokenType
>
)
=>
void
;
defaultTypeFilters
:
Array
<
TokenType
>
;
onTypeFilterChange
:
(
nextValue
:
Array
<
TokenType
>
)
=>
void
;
withAddressFilter
?:
boolean
;
onAddressFilterChange
?:
(
nextValue
:
string
)
=>
void
;
defaultAddressFilter
?:
AddressFromToFilter
;
}
const
TokenTransfer
=
({
onFilterChange
,
defaultFilters
,
appliedFiltersNum
}:
Props
)
=>
{
const
TokenTransferFilter
=
({
onTypeFilterChange
,
defaultTypeFilters
,
appliedFiltersNum
,
withAddressFilter
,
onAddressFilterChange
,
defaultAddressFilter
,
}:
Props
)
=>
{
const
{
isOpen
,
onToggle
,
onClose
}
=
useDisclosure
();
const
borderColor
=
useColorModeValue
(
'
blackAlpha.200
'
,
'
whiteAlpha.200
'
);
return
(
<
Popover
isOpen=
{
isOpen
}
onClose=
{
onClose
}
placement=
"bottom-start"
isLazy
>
<
PopoverTrigger
>
...
...
@@ -27,8 +53,27 @@ const TokenTransfer = ({ onFilterChange, defaultFilters, appliedFiltersNum }: Pr
</
PopoverTrigger
>
<
PopoverContent
w=
"200px"
>
<
PopoverBody
px=
{
4
}
py=
{
6
}
display=
"flex"
flexDir=
"column"
rowGap=
{
5
}
>
{
withAddressFilter
&&
(
<>
<
Text
variant=
"secondary"
fontWeight=
{
600
}
>
Address
</
Text
>
<
RadioGroup
size=
"lg"
onChange=
{
onAddressFilterChange
}
defaultValue=
{
defaultAddressFilter
||
'
all
'
}
paddingBottom=
{
4
}
borderBottom=
"1px solid"
borderColor=
{
borderColor
}
>
<
Stack
spacing=
{
4
}
>
<
Radio
value=
"all"
><
Text
fontSize=
"md"
>
All
</
Text
></
Radio
>
<
Radio
value=
"from"
><
Text
fontSize=
"md"
>
From
</
Text
></
Radio
>
<
Radio
value=
"to"
><
Text
fontSize=
"md"
>
To
</
Text
></
Radio
>
</
Stack
>
</
RadioGroup
>
</>
)
}
<
Text
variant=
"secondary"
fontWeight=
{
600
}
>
Type
</
Text
>
<
CheckboxGroup
size=
"lg"
onChange=
{
on
FilterChange
}
defaultValue=
{
default
Filters
}
>
<
CheckboxGroup
size=
"lg"
onChange=
{
on
TypeFilterChange
}
defaultValue=
{
defaultType
Filters
}
>
{
TOKEN_TYPE
.
map
(({
title
,
id
})
=>
<
Checkbox
key=
{
id
}
value=
{
id
}
><
Text
fontSize=
"md"
>
{
title
}
</
Text
></
Checkbox
>)
}
</
CheckboxGroup
>
</
PopoverBody
>
...
...
@@ -37,4 +82,4 @@ const TokenTransfer = ({ onFilterChange, defaultFilters, appliedFiltersNum }: Pr
);
};
export
default
React
.
memo
(
TokenTransfer
);
export
default
React
.
memo
(
TokenTransfer
Filter
);
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