Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consensus account details cards #1261

Merged
merged 13 commits into from
Mar 8, 2024
Merged
1 change: 1 addition & 0 deletions .changelog/1261.trivial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add secondary cards to Consensus account details page
2 changes: 1 addition & 1 deletion src/app/components/AccountList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const AccountList: FC<AccountListProps> = ({ isLoading, limit, pagination
{ key: 'address', content: t('common.address') },
...(verbose ? [{ key: 'creationDate', content: t('account.birth') }] : []),
{ align: TableCellAlign.Right, key: 'available', content: t('account.available') },
{ align: TableCellAlign.Right, key: 'staked', content: t('account.staked') },
{ align: TableCellAlign.Right, key: 'staked', content: t('common.staked') },
{ align: TableCellAlign.Right, key: 'debonding', content: t('account.debonding') },
{ align: TableCellAlign.Right, key: 'total', content: <strong>{t('account.totalBalance')}</strong> },
]
Expand Down
56 changes: 56 additions & 0 deletions src/app/components/Delegations/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { Table, TableCellAlign, TableColProps } from '../../components/Table'
import { Delegation } from '../../../oasis-nexus/api'
import { TablePaginationProps } from '../Table/TablePagination'
import { RoundedBalance } from '../RoundedBalance'
import { ValidatorLink } from '../Validators/ValidatorLink'

type DelegationsProps = {
delegations?: Delegation[]
isLoading: boolean
limit: number
pagination: false | TablePaginationProps
}

export const Delegations: FC<DelegationsProps> = ({ delegations, isLoading, limit, pagination }) => {
const { t } = useTranslation()

const tableColumns: TableColProps[] = [
{ key: 'name', content: t('validator.title') },
{ align: TableCellAlign.Right, key: 'shares', content: t('validator.shares') },
{ align: TableCellAlign.Right, key: 'staked', content: t('common.staked') },
]
const tableRows = delegations?.map(delegation => ({
key: delegation.validator,
data: [
{
// TODO: Use trimmed name when API is updated
content: <ValidatorLink address={delegation.validator} alwaysTrim network={delegation.network} />,
key: 'name',
},
{
align: TableCellAlign.Right,
content: <RoundedBalance value={delegation.shares} />,
key: 'shares',
},
{
align: TableCellAlign.Right,

content: <RoundedBalance value={delegation.amount} ticker={delegation.ticker} />,
key: 'staked',
},
],
}))

return (
<Table
columns={tableColumns}
rows={tableRows}
rowsNumber={limit}
name={t('common.staking')}
isLoading={isLoading}
pagination={pagination}
/>
)
}
22 changes: 18 additions & 4 deletions src/app/components/Validators/ValidatorLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ type ValidatorLinkProps = {
address: string
name?: string
network: Network
alwaysTrim?: boolean
}

export const ValidatorLink: FC<ValidatorLinkProps> = ({ address, name, network }) => {
export const ValidatorLink: FC<ValidatorLinkProps> = ({ address, name, network, alwaysTrim }) => {
const { isTablet } = useScreenSize()
const to = RouteUtils.getValidatorRoute(network, address)
return (
<Typography variant="mono" component="span" sx={{ color: COLORS.brandDark, fontWeight: 700 }}>
{isTablet ? (
<TabletValidatorLink address={address} name={name} to={to} />
) : (
<Link component={RouterLink} to={to}>
{name || address}
</Link>
<DesktopValidatorLink address={address} alwaysTrim={alwaysTrim} name={name} to={to} />
)}
</Typography>
)
Expand All @@ -51,3 +50,18 @@ const TabletValidatorLink: FC<TabletValidatorLinkProps> = ({ address, name, to }
}
return <TrimLinkLabel label={address} to={to} />
}

type DesktopValidatorLinkProps = TabletValidatorLinkProps & {
alwaysTrim?: boolean
}

const DesktopValidatorLink: FC<DesktopValidatorLinkProps> = ({ address, name, to, alwaysTrim }) => {
if (alwaysTrim) {
return <TrimLinkLabel label={address} to={to} />
}
return (
<Link component={RouterLink} to={to}>
{name || address}
</Link>
)
}
2 changes: 1 addition & 1 deletion src/app/components/Validators/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Validators: FC<ValidatorsProps> = ({ isLoading, limit, pagination,
{ key: 'name', content: t('validator.title') },
{ align: TableCellAlign.Right, key: 'cumulativeVoting', content: t('validator.cumulativeVoting') },
{ align: TableCellAlign.Right, key: 'voting', content: t('validator.voting') },
{ align: TableCellAlign.Right, key: 'staked', content: t('validator.staked') },
{ align: TableCellAlign.Right, key: 'staked', content: t('common.staked') },
{ align: TableCellAlign.Right, key: 'change', content: t('validator.change') },
{ align: TableCellAlign.Right, key: 'delegators', content: t('validator.delegators') },
{ align: TableCellAlign.Right, key: 'commission', content: t('validator.commission') },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Skeleton from '@mui/material/Skeleton'
import Typography from '@mui/material/Typography'
import { Account } from '../../../oasis-nexus/api'
import { PieChart } from '../../components/charts/PieChart'
import { useScreenSize } from '../../hooks/useScreensize'
import { getPreciseNumberFormat } from 'locales/getPreciseNumberFormat'
import { COLORS } from '../../../styles/theme/colors'
import { ConsensusAccountCardEmptyState } from './ConsensusAccountCardEmptyState'

type BalanceDistributionProps = {
account: Account | undefined
isLoading: boolean
}

export const BalanceDistribution: FC<BalanceDistributionProps> = ({ account, isLoading }) => {
const { t } = useTranslation()

return (
<Card sx={{ height: '100%' }}>
<CardHeader disableTypography component="h3" title={t('account.balanceDistribution')} />
<CardContent>
{isLoading && <Skeleton variant="rectangular" height={300} />}
{account && <BalanceDistributionContent account={account} />}
</CardContent>
</Card>
)
}

type BalanceDistributionContentProps = {
account: Account
}

const BalanceDistributionContent: FC<BalanceDistributionContentProps> = ({ account }) => {
const { t } = useTranslation()
const { isMobile } = useScreenSize()
const totalValue = t('common.valueInToken', {
...getPreciseNumberFormat(account.total),
ticker: account.ticker,
})

if (account.total === '0') {
return <ConsensusAccountCardEmptyState label={t('account.noBalances')} />
}

const data = [
{
label: t('account.available'),
value: Number(account.available),
},
{
label: t('common.staking'),
value: Number(account.delegations_balance),
},
{
label: t('account.debonding'),
value: Number(account.debonding_delegations_balance),
},
]

return (
<>
<Typography
sx={{
fontSize: isMobile ? '14px' : '24px',
fontWeight: isMobile ? 500 : 700,
color: COLORS.brandDark,
mb: 3,
}}
>
{t('account.totalValue', {
value: totalValue,
})}
</Typography>
<Box sx={{ height: '250px' }}>
<PieChart
compact={false}
data={data}
dataKey="value"
formatters={{
data: (value: number) =>
t('common.valueInToken', {
...getPreciseNumberFormat(value.toString()),
ticker: account.ticker,
}),
label: (label: string) => label,
}}
/>
</Box>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { FC, ReactNode } from 'react'
import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
import { styled } from '@mui/material/styles'
import StackedBarChartIcon from '@mui/icons-material/StackedBarChart'
import { COLORS } from '../../../styles/theme/colors'

const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
[theme.breakpoints.down('sm')]: {
minHeight: '150px',
paddingTop: theme.spacing(3),
},
[theme.breakpoints.up('sm')]: {
minHeight: '200px',
paddingTop: theme.spacing(6),
},
}))

type ConsensusAccountCardEmptyStateProps = {
children?: ReactNode
label: string
}

export const ConsensusAccountCardEmptyState: FC<ConsensusAccountCardEmptyStateProps> = ({
children,
label,
}) => {
return (
<StyledBox gap={3}>
<StackedBarChartIcon sx={{ color: COLORS.grayMedium, fontSize: 40, opacity: 0.5 }} />
<Typography
sx={{
color: COLORS.grayMedium,
fontWeight: 700,
maxWidth: '170px',
textAlign: 'center',
opacity: 0.5,
}}
>
{label}
</Typography>
{children}
</StyledBox>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const ConsensusAccountDetails: FC<ConsensusAccountDetailsCardProps> = ({ account
ticker: account.ticker,
})}
</dd>
<StyledListTitle>{t('account.staking')}</StyledListTitle>
<StyledListTitle>{t('common.staking')}</StyledListTitle>
<dd>
{t('common.valueInToken', {
...getPreciseNumberFormat(account.delegations_balance!),
Expand Down
95 changes: 95 additions & 0 deletions src/app/pages/ConsensusAccountDetailsPage/Staking.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { 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 from '@mui/material/Link'
import Skeleton from '@mui/material/Skeleton'
import { COLORS } from '../../../styles/theme/colors'
import { Account, useGetConsensusAccountsAddressDelegations } from '../../../oasis-nexus/api'
import { useRequiredScopeParam } from '../../../app/hooks/useScopeParam'
import { AppErrors } from '../../../types/errors'
import { NUMBER_OF_ITEMS_ON_DASHBOARD as PAGE_SIZE } from '../../config'
import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination'
import { Delegations } from '../..//components/Delegations'
import { docs, wallet } from '../../utils/externalLinks'
import { t } from 'i18next'
import { ConsensusAccountCardEmptyState } from './ConsensusAccountCardEmptyState'

type StakingProps = {
account: Account | undefined
isLoading: boolean
}

export const Staking: FC<StakingProps> = ({ account, isLoading }) => {
const { t } = useTranslation()

return (
<Card sx={{ height: '100%' }}>
<CardHeader
action={
<Link
href={docs.consensusStaking}
rel="noopener noreferrer"
target="_blank"
sx={{ color: COLORS.brandDark }}
>
{t('validator.sharesDocs')}
</Link>
}
disableTypography
component="h3"
title={t('common.staking')}
/>
<CardContent>
{isLoading && <Skeleton variant="rectangular" height={300} />}
{account && <StakingContent address={account?.address} />}
</CardContent>
</Card>
)
}

type StakingContentProps = {
address: string
}

const StakingContent: FC<StakingContentProps> = ({ address }) => {
const pagination = useSearchParamsPagination('page')
const offset = (pagination.selectedPage - 1) * PAGE_SIZE
const scope = useRequiredScopeParam()
const { network } = scope
const delegationsQuery = useGetConsensusAccountsAddressDelegations(network, address)
const { isLoading, isFetched, data } = delegationsQuery
if (isFetched && offset && !delegationsQuery.data?.data?.delegations?.length) {
throw AppErrors.PageDoesNotExist
}

if (isFetched && !delegationsQuery.data?.data.delegations.length) {
return (
<ConsensusAccountCardEmptyState label={t('account.notStaking')}>
<Link href={wallet.homepage} rel="noopener noreferrer" target="_blank">
{t('account.startStaking')}
</Link>
</ConsensusAccountCardEmptyState>
)
}

return (
<>
{isFetched && (
<Delegations
delegations={delegationsQuery.data?.data.delegations}
isLoading={isLoading}
limit={PAGE_SIZE}
pagination={{
selectedPage: pagination.selectedPage,
linkToPage: pagination.linkToPage,
totalCount: data?.data.total_count,
isTotalCountClipped: data?.data.is_total_count_clipped,
rowsPerPage: PAGE_SIZE,
}}
/>
)}
</>
)
}
Loading
Loading