Align slashing layout with the rest (#3614)

* Align slashing layout with the rest

* Adjust

* Adjust banners

* Extra...
This commit is contained in:
Jaco Greeff 2020-09-05 11:28:56 +02:00 committed by GitHub
parent 2cee403ee4
commit 230938b6b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 125 additions and 133 deletions

View File

@ -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>

View File

@ -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}

View File

@ -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')}

View File

@ -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}

View File

@ -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>
);

View File

@ -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')}

View File

@ -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>
</>
);
}

View File

@ -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>
);
}

View File

@ -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]}
/>
);
}

View File

@ -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}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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';

View File

@ -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 {