-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Tokens page and dashboard component
- Loading branch information
Showing
10 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Add token overview page and dashboard component | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { FC } from 'react' | ||
import { Link as RouterLink } from 'react-router-dom' | ||
import Link from '@mui/material/Link' | ||
|
||
import { RouteUtils } from '../../utils/route-utils' | ||
import { SearchScope } from '../../../types/searchScope' | ||
|
||
export const TokenLink: FC<{ scope: SearchScope; address: string; name: string | undefined }> = ({ | ||
scope, | ||
address, | ||
name, | ||
}) => { | ||
return ( | ||
<Link component={RouterLink} to={RouteUtils.getTokenRoute(scope, address)}> | ||
{name || address} | ||
</Link> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { useTranslation } from 'react-i18next' | ||
import { EvmToken } from '../../../oasis-indexer/api' | ||
import { Table, TableCellAlign, TableColProps } from '../../components/Table' | ||
import { TablePaginationProps } from '../Table/TablePagination' | ||
import { AccountLink } from '../Account/AccountLink' | ||
import { TokenLink } from './TokenLink' | ||
import { CopyToClipboard } from '../CopyToClipboard' | ||
|
||
type TokensProps = { | ||
tokens?: EvmToken[] | ||
isLoading: boolean | ||
limit: number | ||
verbose?: boolean | ||
pagination: false | TablePaginationProps | ||
} | ||
|
||
export const Tokens = (props: TokensProps) => { | ||
const { isLoading, tokens, pagination, limit } = props | ||
const { t } = useTranslation() | ||
const tableColumns: TableColProps[] = [ | ||
{ content: '' }, | ||
{ content: t('common.name') }, | ||
{ content: t('common.smartContract') }, | ||
{ | ||
content: t('tokens.holdersCount'), | ||
align: TableCellAlign.Right, | ||
}, | ||
{ content: t('tokens.supply'), align: TableCellAlign.Right }, | ||
{ content: t('common.ticker'), align: TableCellAlign.Right }, | ||
] | ||
|
||
const tableRows = tokens?.map((token, index) => { | ||
return { | ||
key: token.contract_addr, | ||
data: [ | ||
{ | ||
content: (index + 1).toLocaleString(), | ||
key: 'index', | ||
}, | ||
{ | ||
content: <TokenLink scope={token} address={token.evm_contract_addr} name={token.name} />, | ||
key: 'name', | ||
}, | ||
{ | ||
content: ( | ||
<span> | ||
<AccountLink scope={token} address={token.evm_contract_addr} /> | ||
<CopyToClipboard value={token.evm_contract_addr} /> | ||
</span> | ||
), | ||
key: 'contactAddress', | ||
}, | ||
{ | ||
content: token.num_holders.toLocaleString(), | ||
key: 'holdersCount', | ||
align: TableCellAlign.Right, | ||
}, | ||
{ | ||
content: token.total_supply, | ||
key: 'supply', | ||
align: TableCellAlign.Right, | ||
}, | ||
{ | ||
content: token.symbol, | ||
key: 'ticker', | ||
align: TableCellAlign.Right, | ||
}, | ||
], | ||
} | ||
}) | ||
|
||
return ( | ||
<Table | ||
columns={tableColumns} | ||
rows={tableRows} | ||
rowsNumber={limit} | ||
name={t('tokens.title')} | ||
isLoading={isLoading} | ||
pagination={pagination} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React, { FC } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import Card from '@mui/material/Card' | ||
import CardHeader from '@mui/material/CardHeader' | ||
import CardContent from '@mui/material/CardContent' | ||
import { Link as RouterLink } from 'react-router-dom' | ||
import Link from '@mui/material/Link' | ||
import { Layer, useGetRuntimeEvmTokens } from '../../../oasis-indexer/api' | ||
import { NUMBER_OF_ITEMS_ON_DASHBOARD } from '../../config' | ||
import { COLORS } from '../../../styles/theme/colors' | ||
import { AppErrors } from '../../../types/errors' | ||
import { useRequiredScopeParam } from '../../hooks/useScopeParam' | ||
import { RouteUtils } from '../../utils/route-utils' | ||
import { Tokens } from '../../components/Tokens' | ||
|
||
const limit = NUMBER_OF_ITEMS_ON_DASHBOARD | ||
|
||
export const TopTokens: FC = () => { | ||
const { t } = useTranslation() | ||
const scope = useRequiredScopeParam() | ||
const { network, layer } = scope | ||
if (layer === Layer.consensus) { | ||
throw AppErrors.UnsupportedLayer | ||
// Listing the latest consensus transactions is not yet supported. | ||
// We should use useGetConsensusTransactions() | ||
} | ||
const tokensQuery = useGetRuntimeEvmTokens(network, layer, { limit }) | ||
|
||
return ( | ||
<Card> | ||
<CardHeader | ||
disableTypography | ||
component="h3" | ||
title={t('tokens.title')} | ||
action={ | ||
<Link | ||
component={RouterLink} | ||
to={RouteUtils.getTopTokensRoute(scope)} | ||
sx={{ color: COLORS.brandExtraDark }} | ||
> | ||
{t('common.viewAll')} | ||
</Link> | ||
} | ||
/> | ||
<CardContent> | ||
<Tokens | ||
tokens={tokensQuery.data?.data.evm_tokens} | ||
isLoading={tokensQuery.isLoading} | ||
limit={limit} | ||
pagination={false} | ||
/> | ||
</CardContent> | ||
</Card> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { FC } from 'react' | ||
import { EvmToken } from '../../../oasis-indexer/api' | ||
import { TextSkeleton } from '../../components/Skeleton' | ||
import { StyledDescriptionList } from '../../components/StyledDescriptionList' | ||
import { useScreenSize } from '../../hooks/useScreensize' | ||
import { DashboardLink } from '../DashboardPage/DashboardLink' | ||
import { useTranslation } from 'react-i18next' | ||
import { TokenLink } from '../../components/Tokens/TokenLink' | ||
import { CopyToClipboard } from '../../components/CopyToClipboard' | ||
import { AccountLink } from '../../components/Account/AccountLink' | ||
|
||
export const TokenDetailView: FC<{ | ||
isLoading?: boolean | ||
token: EvmToken | undefined | ||
showLayer?: boolean | ||
standalone?: boolean | ||
}> = ({ isLoading, token, showLayer, standalone = false }) => { | ||
const { t } = useTranslation() | ||
const { isMobile } = useScreenSize() | ||
|
||
if (isLoading) return <TextSkeleton numberOfRows={7} /> | ||
if (!token) return null | ||
|
||
return ( | ||
<StyledDescriptionList titleWidth={isMobile ? '100px' : '200px'} standalone={standalone}> | ||
{showLayer && ( | ||
<> | ||
<dt>{t('common.paratime')}</dt> | ||
<dd> | ||
<DashboardLink scope={token} /> | ||
</dd> | ||
</> | ||
)} | ||
<dt>{t('common.name')}</dt> | ||
<dd> | ||
<TokenLink scope={token} address={token.evm_contract_addr} name={token.name} /> | ||
</dd> | ||
|
||
<dt>{t('common.smartContract')}</dt> | ||
<dd> | ||
<AccountLink scope={token} address={token.evm_contract_addr} /> | ||
<CopyToClipboard value={token.evm_contract_addr} /> | ||
</dd> | ||
|
||
<dt>{t('tokens.holdersCount')}</dt> | ||
<dd>{token.num_holders.toLocaleString()}</dd> | ||
|
||
<dt>{t('tokens.supply')}</dt> | ||
<dd>{token.total_supply}</dd> | ||
|
||
<td>{t('common.ticker')}</td> | ||
<dd>{token.symbol}</dd> | ||
</StyledDescriptionList> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { FC, useEffect, useState } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import Divider from '@mui/material/Divider' | ||
import { useScreenSize } from '../../hooks/useScreensize' | ||
import { styled } from '@mui/material/styles' | ||
import { PageLayout } from '../../components/PageLayout' | ||
import { SubPageCard } from '../../components/SubPageCard' | ||
import { Layer, useGetRuntimeEvmTokens } from '../../../oasis-indexer/api' | ||
import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE, REFETCH_INTERVAL } from '../../config' | ||
import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination' | ||
import Box from '@mui/material/Box' | ||
import { COLORS } from '../../../styles/theme/colors' | ||
import { AppErrors } from '../../../types/errors' | ||
import { TableLayout, TableLayoutButton } from '../../components/TableLayoutButton' | ||
import { LoadMoreButton } from '../../components/LoadMoreButton' | ||
import { useRequiredScopeParam } from '../../hooks/useScopeParam' | ||
import { Tokens } from '../../components/Tokens' | ||
import { TokenDetailView } from '../TokenkDetailPage' | ||
|
||
const PAGE_SIZE = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE | ||
|
||
const TokenDetails = styled(Box)(({ theme }) => ({ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: `0 ${theme.spacing(2)}`, | ||
backgroundColor: COLORS.brandDark, | ||
})) | ||
|
||
export const TokensPage: FC = () => { | ||
const [tableView, setTableView] = useState<TableLayout>(TableLayout.Horizontal) | ||
const { isMobile } = useScreenSize() | ||
const { t } = useTranslation() | ||
const pagination = useSearchParamsPagination('page') | ||
const offset = (pagination.selectedPage - 1) * PAGE_SIZE | ||
const scope = useRequiredScopeParam() | ||
// Consensus is not yet enabled in ENABLED_LAYERS, just some preparation | ||
if (scope.layer === Layer.consensus) { | ||
throw AppErrors.UnsupportedLayer | ||
// Listing the latest consensus blocks is not yet implemented. | ||
// we should call useGetConsensusBlocks() | ||
} | ||
|
||
useEffect(() => { | ||
if (!isMobile) { | ||
setTableView(TableLayout.Horizontal) | ||
} | ||
}, [isMobile, setTableView]) | ||
|
||
const tokensQuery = useGetRuntimeEvmTokens( | ||
scope.network, | ||
scope.layer, // This is OK, since consensus is already handled separately | ||
{ | ||
limit: tableView === TableLayout.Vertical ? offset + PAGE_SIZE : PAGE_SIZE, | ||
offset: tableView === TableLayout.Vertical ? 0 : offset, | ||
}, | ||
{ | ||
query: { | ||
refetchInterval: REFETCH_INTERVAL, | ||
// Keep showing data while loading more | ||
keepPreviousData: tableView === TableLayout.Vertical, | ||
}, | ||
}, | ||
) | ||
|
||
return ( | ||
<PageLayout | ||
mobileFooterAction={ | ||
tableView === TableLayout.Vertical && ( | ||
<LoadMoreButton pagination={pagination} isLoading={tokensQuery.isLoading} /> | ||
) | ||
} | ||
> | ||
{!isMobile && <Divider variant="layout" />} | ||
<SubPageCard | ||
title={t('tokens.title')} | ||
action={isMobile && <TableLayoutButton tableView={tableView} setTableView={setTableView} />} | ||
noPadding={tableView === TableLayout.Vertical} | ||
> | ||
{tableView === TableLayout.Horizontal && ( | ||
<Tokens | ||
isLoading={tokensQuery.isLoading} | ||
tokens={tokensQuery.data?.data.evm_tokens} | ||
limit={PAGE_SIZE} | ||
verbose={true} | ||
pagination={{ | ||
selectedPage: pagination.selectedPage, | ||
linkToPage: pagination.linkToPage, | ||
totalCount: tokensQuery.data?.data.total_count, | ||
isTotalCountClipped: tokensQuery.data?.data.is_total_count_clipped, | ||
rowsPerPage: NUMBER_OF_ITEMS_ON_SEPARATE_PAGE, | ||
}} | ||
/> | ||
)} | ||
{tableView === TableLayout.Vertical && ( | ||
<TokenDetails> | ||
{tokensQuery.isLoading && | ||
[...Array(PAGE_SIZE).keys()].map(key => ( | ||
<TokenDetailView key={key} isLoading={true} token={undefined} standalone /> | ||
))} | ||
|
||
{!tokensQuery.isLoading && | ||
tokensQuery.data?.data.evm_tokens.map(token => ( | ||
<TokenDetailView key={token.contract_addr} token={token} standalone /> | ||
))} | ||
</TokenDetails> | ||
)} | ||
</SubPageCard> | ||
</PageLayout> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters