Align slashing layout with the rest (#3614)
* Align slashing layout with the rest * Adjust * Adjust banners * Extra...
This commit is contained in:
parent
2cee403ee4
commit
230938b6b7
@ -13,7 +13,7 @@ interface Props {
|
||||
|
||||
function Banner ({ children, className = '', type }: Props): React.ReactElement<Props> | null {
|
||||
return (
|
||||
<article className={`${className} ${type} nomargin`}>
|
||||
<article className={`${className} ${type} centered`}>
|
||||
<div className='box'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -169,8 +169,6 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<BannerExtension />
|
||||
<BannerClaims />
|
||||
{isCreateOpen && (
|
||||
<CreateModal
|
||||
onClose={toggleCreate}
|
||||
@ -241,6 +239,8 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
onClick={toggleProxy}
|
||||
/>
|
||||
</Button.Group>
|
||||
<BannerExtension />
|
||||
<BannerClaims />
|
||||
<Table
|
||||
empty={(!hasAccounts || (!isLoading && sortedAccountsWithDelegation)) && t<string>("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")}
|
||||
filter={filter}
|
||||
|
||||
@ -233,7 +233,7 @@ function VanityApp ({ className = '', onStatusChange }: Props): React.ReactEleme
|
||||
</Button.Group>
|
||||
{matches.length !== 0 && (
|
||||
<>
|
||||
<article className='warning nomargin'>{t<string>('Ensure that you utilized the "Save" functionality before using a generated address to receive funds. Without saving the address any funds and the associated seed any funds sent to it will be lost.')}</article>
|
||||
<article className='warning centered'>{t<string>('Ensure that you utilized the "Save" functionality before using a generated address to receive funds. Without saving the address any funds and the associated seed any funds sent to it will be lost.')}</article>
|
||||
<Table
|
||||
className='vanity--App-matches'
|
||||
empty={t<string>('No matches found')}
|
||||
|
||||
@ -81,7 +81,6 @@ function Actions ({ className = '', isInElection, ownStashes, targets }: Props):
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Button.Group>
|
||||
<NewNominator
|
||||
isInElection={isInElection}
|
||||
@ -90,6 +89,7 @@ function Actions ({ className = '', isInElection, ownStashes, targets }: Props):
|
||||
<NewValidator isInElection={isInElection} />
|
||||
<NewStash />
|
||||
</Button.Group>
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Table
|
||||
empty={foundStashes && t<string>('No funds staked yet. Bond funds to validate or nominate a validator')}
|
||||
footer={footer}
|
||||
|
||||
@ -18,7 +18,7 @@ function ElectionBanner ({ isInElection }: Props): React.ReactElement<Props> | n
|
||||
}
|
||||
|
||||
return (
|
||||
<article className='warning nomargin'>
|
||||
<article className='warning centered'>
|
||||
{t<string>('There is currently an ongoing election for new validator candidates. As such staking operations are not permitted.')}
|
||||
</article>
|
||||
);
|
||||
|
||||
@ -11,7 +11,7 @@ import BN from 'bn.js';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
import { Button, Table, ToggleGroup } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useOwnEraRewards } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { BN_ZERO, isFunction } from '@polkadot/util';
|
||||
@ -20,7 +20,6 @@ import ElectionBanner from '../ElectionBanner';
|
||||
import { useTranslation } from '../translate';
|
||||
import useStakerPayouts from './useStakerPayouts';
|
||||
import PayButton from './PayButton';
|
||||
import PayToggle from './PayToggle';
|
||||
import Stash from './Stash';
|
||||
import Validator from './Validator';
|
||||
|
||||
@ -207,24 +206,17 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
{api.tx.staking.payoutStakers && !isLoadingRewards && !stashes?.length && (
|
||||
<article className='warning nomargin'>
|
||||
<p>{t('Payouts of rewards for a validator can be initiated by any account. This means that as soon as a validator or nominator requests a payout for an era, all the nominators for that validator will be rewarded. Each user does not need to claim individually and the suggestion is that validators should claim rewards for everybody as soon as an era ends.')}</p>
|
||||
<p>{t('If you have not claimed rewards straight after the end of the era, the validator is in the active set and you are seeing no rewards, this would mean that the reward payout transaction was made by another account on your behalf. Always check your favorite explorer to see any historic payouts made to your accounts.')}</p>
|
||||
</article>
|
||||
)}
|
||||
{api.tx.staking.payoutStakers && (
|
||||
<Button.Group>
|
||||
<PayToggle
|
||||
<ToggleGroup
|
||||
onChange={setMyStashesIndex}
|
||||
options={valOptions}
|
||||
selected={myStashesIndex}
|
||||
value={myStashesIndex}
|
||||
/>
|
||||
<PayToggle
|
||||
<ToggleGroup
|
||||
onChange={setEraSelectionIndex}
|
||||
options={eraSelection}
|
||||
selected={eraSelectionIndex}
|
||||
value={eraSelectionIndex}
|
||||
/>
|
||||
<PayButton
|
||||
isAll
|
||||
@ -233,6 +225,13 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React
|
||||
/>
|
||||
</Button.Group>
|
||||
)}
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
{api.tx.staking.payoutStakers && !isLoadingRewards && !stashes?.length && (
|
||||
<article className='warning centered'>
|
||||
<p>{t('Payouts of rewards for a validator can be initiated by any account. This means that as soon as a validator or nominator requests a payout for an era, all the nominators for that validator will be rewarded. Each user does not need to claim individually and the suggestion is that validators should claim rewards for everybody as soon as an era ends.')}</p>
|
||||
<p>{t('If you have not claimed rewards straight after the end of the era, the validator is in the active set and you are seeing no rewards, this would mean that the reward payout transaction was made by another account on your behalf. Always check your favorite explorer to see any historic payouts made to your accounts.')}</p>
|
||||
</article>
|
||||
)}
|
||||
<Table
|
||||
empty={!isLoadingRewards && stashes && t<string>('No pending payouts for your stashes')}
|
||||
emptySpinner={t<string>('Retrieving info for the selected eras, this will take some time')}
|
||||
|
||||
@ -14,6 +14,7 @@ import Row from './Row';
|
||||
import Summary from './Summary';
|
||||
|
||||
interface Props {
|
||||
buttons: React.ReactNode;
|
||||
councilId: string | null;
|
||||
councilThreshold: number;
|
||||
slash: SlashEra;
|
||||
@ -25,7 +26,7 @@ interface Selected {
|
||||
txSome: SubmittableExtrinsic<'promise'> | null;
|
||||
}
|
||||
|
||||
function Slashes ({ councilId, councilThreshold, slash }: Props): React.ReactElement<Props> | null {
|
||||
function Slashes ({ buttons, councilId, councilThreshold, slash }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [{ selected, txAll, txSome }, setSelected] = useState<Selected>((): Selected => {
|
||||
@ -35,7 +36,7 @@ function Slashes ({ councilId, councilThreshold, slash }: Props): React.ReactEle
|
||||
});
|
||||
|
||||
const headerRef = useRef<[string?, string?, number?][]>([
|
||||
[undefined, 'start', 3],
|
||||
[t('era {{era}}/unapplied', { replace: { era: slash.era.toString() } }), 'start', 3],
|
||||
[t('reporters'), 'address'],
|
||||
[t('own')],
|
||||
[t('other')],
|
||||
@ -62,52 +63,41 @@ function Slashes ({ councilId, councilThreshold, slash }: Props): React.ReactEle
|
||||
);
|
||||
|
||||
return (
|
||||
<Table header={[[t('era {{era}}/unapplied', { replace: { era: slash.era.toString() } }), 'start', 8]]}>
|
||||
<Summary slash={slash}>
|
||||
{councilId && (
|
||||
<Button.Group>
|
||||
<TxButton
|
||||
accountId={councilId}
|
||||
extrinsic={txSome}
|
||||
isDisabled={!txSome}
|
||||
isToplevel
|
||||
label={t('Cancel selected')}
|
||||
/>
|
||||
<TxButton
|
||||
accountId={councilId}
|
||||
extrinsic={txAll}
|
||||
isDisabled={!txAll}
|
||||
isToplevel
|
||||
label={t('Cancel all')}
|
||||
/>
|
||||
</Button.Group>
|
||||
)}
|
||||
</Summary>
|
||||
<tr className='transparent'>
|
||||
{headerRef.current.map(([label, className, colSpan = 1], index): React.ReactNode => (
|
||||
<td
|
||||
className={className}
|
||||
colSpan={colSpan}
|
||||
key={index}
|
||||
>
|
||||
<label>{label}</label>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
{slash.slashes.map((slash, index): React.ReactNode => (
|
||||
<Row
|
||||
index={index}
|
||||
isSelected={selected.includes(index)}
|
||||
key={index}
|
||||
onSelect={
|
||||
councilId
|
||||
? _onSelect
|
||||
: undefined
|
||||
}
|
||||
slash={slash}
|
||||
<>
|
||||
<Summary slash={slash} />
|
||||
<Button.Group>
|
||||
{buttons}
|
||||
<TxButton
|
||||
accountId={councilId}
|
||||
extrinsic={txSome}
|
||||
isDisabled={!txSome}
|
||||
isToplevel
|
||||
label={t('Cancel selected')}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
<TxButton
|
||||
accountId={councilId}
|
||||
extrinsic={txAll}
|
||||
isDisabled={!txAll}
|
||||
isToplevel
|
||||
label={t('Cancel all')}
|
||||
/>
|
||||
</Button.Group>
|
||||
<Table header={headerRef.current}>
|
||||
{slash.slashes.map((slash, index): React.ReactNode => (
|
||||
<Row
|
||||
index={index}
|
||||
isSelected={selected.includes(index)}
|
||||
key={index}
|
||||
onSelect={
|
||||
councilId
|
||||
? _onSelect
|
||||
: undefined
|
||||
}
|
||||
slash={slash}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -15,11 +15,10 @@ import { formatNumber } from '@polkadot/util';
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
slash: SlashEra;
|
||||
}
|
||||
|
||||
function Header ({ children, slash: { era, nominators, reporters, total, validators } }: Props): React.ReactElement<Props> | null {
|
||||
function Header ({ slash: { era, nominators, reporters, total, validators } }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const sessionInfo = useCall<DeriveSessionProgress>(api.derive.session?.progress);
|
||||
@ -35,41 +34,35 @@ function Header ({ children, slash: { era, nominators, reporters, total, validat
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={9}>
|
||||
<SummaryBox isSmall>
|
||||
<section>
|
||||
<CardSummary label={t<string>('validators')}>
|
||||
{formatNumber(validators.length)}
|
||||
</CardSummary>
|
||||
<CardSummary label={t<string>('nominators')}>
|
||||
{formatNumber(nominators.length)}
|
||||
</CardSummary>
|
||||
<CardSummary label={t<string>('reporters')}>
|
||||
{formatNumber(reporters.length)}
|
||||
</CardSummary>
|
||||
</section>
|
||||
{blockProgress.gtn(0) && (
|
||||
<section>
|
||||
<CardSummary
|
||||
label={t<string>('defer')}
|
||||
progress={{
|
||||
total: blockEnd,
|
||||
value: blockProgress,
|
||||
withTime: true
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
<CardSummary label={t<string>('total')}>
|
||||
<FormatBalance value={total} />
|
||||
</CardSummary>
|
||||
</section>
|
||||
</SummaryBox>
|
||||
{children}
|
||||
</td>
|
||||
</tr>
|
||||
<SummaryBox>
|
||||
<section>
|
||||
<CardSummary label={t<string>('era')}>
|
||||
{formatNumber(era)}
|
||||
</CardSummary>
|
||||
<CardSummary label={t<string>('validators')}>
|
||||
{formatNumber(validators.length)}
|
||||
</CardSummary>
|
||||
<CardSummary label={t<string>('nominators')}>
|
||||
{formatNumber(nominators.length)}
|
||||
</CardSummary>
|
||||
<CardSummary label={t<string>('reporters')}>
|
||||
{formatNumber(reporters.length)}
|
||||
</CardSummary>
|
||||
</section>
|
||||
{blockProgress.gtn(0) && (
|
||||
<CardSummary
|
||||
label={t<string>('defer')}
|
||||
progress={{
|
||||
total: blockEnd,
|
||||
value: blockProgress,
|
||||
withTime: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CardSummary label={t<string>('total')}>
|
||||
<FormatBalance value={total} />
|
||||
</CardSummary>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,10 +7,11 @@ import { UnappliedSlash } from '@polkadot/types/interfaces';
|
||||
import { Slash, SlashEra } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Table, ToggleGroup } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useMembers } from '@polkadot/react-hooks';
|
||||
import { getSlashThreshold } from '@polkadot/app-council/thresholds';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import Era from './Era';
|
||||
@ -90,12 +91,21 @@ function Slashes ({ ownStashes = [], slashes }: Props): React.ReactElement<Props
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const { members } = useMembers();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const rows = useMemo(
|
||||
() => calcSlashEras(slashes, ownStashes),
|
||||
[ownStashes, slashes]
|
||||
);
|
||||
|
||||
const eraOpts = useMemo(
|
||||
() => rows.map(({ era }) => ({
|
||||
text: t<string>('era {{era}}', { replace: { era: formatNumber(era) } }),
|
||||
value: era.toString()
|
||||
})),
|
||||
[rows, t]
|
||||
);
|
||||
|
||||
const councilId = useMemo(
|
||||
() => allAccounts.find((accountId) => members.includes(accountId)) || null,
|
||||
[allAccounts, members]
|
||||
@ -113,16 +123,19 @@ function Slashes ({ ownStashes = [], slashes }: Props): React.ReactElement<Props
|
||||
const councilThreshold = Math.ceil((members.length || 0) * getSlashThreshold(api));
|
||||
|
||||
return (
|
||||
<>
|
||||
{rows.map((slash): React.ReactNode => (
|
||||
<Era
|
||||
councilId={councilId}
|
||||
councilThreshold={councilThreshold}
|
||||
key={slash.era.toString()}
|
||||
slash={slash}
|
||||
<Era
|
||||
buttons={
|
||||
<ToggleGroup
|
||||
onChange={setSelectedIndex}
|
||||
options={eraOpts}
|
||||
value={selectedIndex}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
councilId={councilId}
|
||||
councilThreshold={councilThreshold}
|
||||
key={rows[selectedIndex].era.toString()}
|
||||
slash={rows[selectedIndex]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -184,7 +184,6 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak
|
||||
numValidators={validators?.length}
|
||||
totalStaked={totalStaked}
|
||||
/>
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='check'
|
||||
@ -198,6 +197,7 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak
|
||||
targets={selected}
|
||||
/>
|
||||
</Button.Group>
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Table
|
||||
empty={sorted && t<string>('No active validators to check')}
|
||||
filter={filter}
|
||||
|
||||
@ -102,7 +102,7 @@ export default React.memo(styled(CardSummary)`
|
||||
flex: 0 1 auto;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-end;
|
||||
padding: 0rem 1.5rem 0.5rem 1.5rem;
|
||||
padding: 0 1.5rem;
|
||||
|
||||
.ui--FormatBalance .balance-postfix {
|
||||
opacity: 1;
|
||||
|
||||
@ -21,10 +21,14 @@ function SummaryBox ({ children, className = '', isSmall }: Props): React.ReactE
|
||||
|
||||
export default React.memo(styled(SummaryBox)`
|
||||
align-items: stretch;
|
||||
border-radius: 4px;
|
||||
// background: rgba(255, 255, 255, 0.35);
|
||||
border-radius: 0.25rem;
|
||||
// border-bottom: 1px solid #e6e6e6;
|
||||
display: flex;
|
||||
flex-wrap: no-wrap;
|
||||
justify-content: space-between;
|
||||
margin: -1.5rem -2rem 1.5rem;
|
||||
padding: 1.5rem 2rem 0;
|
||||
|
||||
> section {
|
||||
display: flex;
|
||||
@ -47,14 +51,6 @@ export default React.memo(styled(SummaryBox)`
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: 767px) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media(min-width: 768px) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
&.isSmall {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2017-2020 @polkadot/app-staking authors & contributors
|
||||
// Copyright 2017-2020 @polkadot/react-components authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
@ -10,7 +10,7 @@ interface Props {
|
||||
className?: string;
|
||||
onChange: (index: number) => void;
|
||||
options: { isDisabled?: boolean, text: string, value: number }[];
|
||||
selected: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface ToggleProps {
|
||||
@ -44,18 +44,18 @@ function ToggleIndex ({ index, isDisabled, isSelected, onChange, text }: ToggleP
|
||||
|
||||
const ToggleIndexMemo = React.memo(ToggleIndex);
|
||||
|
||||
function PayToggle ({ className = '', onChange, options, selected }: Props): React.ReactElement<Props> | null {
|
||||
function ToggleGroup ({ className = '', onChange, options, value }: Props): React.ReactElement<Props> | null {
|
||||
if (!options.length || !options[0].value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`ui--ToggleButton ${className}`}>
|
||||
<div className={`ui--ToggleGroup ${className}`}>
|
||||
{options.map(({ isDisabled, text }, index): React.ReactNode => (
|
||||
<ToggleIndexMemo
|
||||
index={index}
|
||||
isDisabled={isDisabled}
|
||||
isSelected={selected === index}
|
||||
isSelected={value === index}
|
||||
key={index}
|
||||
onChange={onChange}
|
||||
text={text}
|
||||
@ -65,7 +65,7 @@ function PayToggle ({ className = '', onChange, options, selected }: Props): Rea
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(PayToggle)`
|
||||
export default React.memo(styled(ToggleGroup)`
|
||||
display: inline-block;
|
||||
margin-right: 1.5rem;
|
||||
|
||||
@ -90,6 +90,7 @@ export { default as Tabs } from './Tabs';
|
||||
export { default as Tag } from './Tag';
|
||||
export { default as Tags } from './Tags';
|
||||
export { default as Toggle } from './Toggle';
|
||||
export { default as ToggleGroup } from './ToggleGroup';
|
||||
export { default as Tooltip } from './Tooltip';
|
||||
export { default as TxButton } from './TxButton';
|
||||
export { default as TxComponent } from './TxComponent';
|
||||
|
||||
@ -233,8 +233,8 @@ export default createGlobalStyle<Props>`
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
&.nomargin {
|
||||
margin: 0.5rem auto;
|
||||
&.centered {
|
||||
margin: 1.5rem auto;
|
||||
max-width: 75rem;
|
||||
|
||||
&+.ui--Button-Group {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user