Rework voting modal with all type values (#8519)

This commit is contained in:
Jaco 2022-12-12 08:53:17 +02:00 committed by GitHub
parent 50222841dc
commit 17196c4558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 300 additions and 66 deletions

View File

@ -1,18 +1,26 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TFunction } from 'react-i18next';
import type { ApiPromise } from '@polkadot/api';
import type { Preimage } from '@polkadot/app-preimages/types';
import type { BN } from '@polkadot/util';
import type { PalletVote, TrackInfo } from '../types';
import React, { useMemo, useState } from 'react';
import styled from 'styled-components';
import { Button, ConvictionDropdown, Modal, ProposedAction, TxButton, VoteAccount, VoteValue } from '@polkadot/react-components';
import { Button, Modal, ProposedAction, ToggleGroup, TxButton, VoteAccount } from '@polkadot/react-components';
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import VoteAbstain from './VoteAbstain';
import VoteBasic from './VoteBasic';
import VoteSplit from './VoteSplit';
import VoteStandard from './VoteStandard';
interface Props {
className?: string;
id: BN;
isConvictionVote: boolean;
isMember: boolean;
@ -39,14 +47,51 @@ function filterMembers (allAccounts: string[], members?: string[], ranks?: BN[],
return members;
}
function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage, ranks, trackInfo }: Props): React.ReactElement<Props> | null {
function createVoteOpts (api: ApiPromise, t: TFunction, isConvictionVote: boolean): { text: string, value: string }[] {
let hasAbstain = false;
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error This issue will go way with the correct types
hasAbstain = !!api.createType('PalletConvictionVotingVoteAccountVote', { SplitAbstain: { abstain: 1 } }).isSplitAbstain;
} catch {
hasAbstain = false;
}
return isConvictionVote
? hasAbstain
? [
{ text: t<string>('Aye'), value: 'aye' },
{ text: t<string>('Nay'), value: 'nay' },
{ text: t<string>('Split'), value: 'split' },
{ text: t<string>('Abstain'), value: 'abstain' }
]
: [
{ text: t<string>('Aye'), value: 'aye' },
{ text: t<string>('Nay'), value: 'nay' },
{ text: t<string>('Split'), value: 'split' }
]
: [
{ text: t<string>('Aye'), value: 'aye' },
{ text: t<string>('Nay'), value: 'nay' }
];
}
function Voting ({ className, id, isConvictionVote, isMember, members, palletVote, preimage, ranks, trackInfo }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts, hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const [balance, setBalance] = useState<BN | undefined>();
const [conviction, setConviction] = useState(0);
const [isVotingOpen, toggleVoting] = useToggle();
const [voteTypeIndex, setVoteTypeIndex] = useState(0);
const [params, setParams] = useState<unknown[] | undefined>();
// Create the options for the vote toggle using the type of vote and also inspecting
// the actual support of the on-chain runtime (e.g. Abstentions)
const voteTypeOpts = useMemo(
() => createVoteOpts(api, t, isConvictionVote),
[api, isConvictionVote, t]
);
const filteredMembers = useMemo(
() => filterMembers(allAccounts, members, ranks, trackInfo),
@ -62,23 +107,6 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
[filteredMembers, isMember, members]
);
const [paramsAbstain, paramsAye, paramsNay] = useMemo(
() => [
// only available on conviction votes
// FIXME: We want to do vote splits as well
isConvictionVote
? [id, { SplitAbstain: { abstain: balance } }]
: null,
isConvictionVote
? [id, { Standard: { balance, vote: { aye: true, conviction } } }]
: [id, true],
isConvictionVote
? [id, { Standard: { balance, vote: { aye: false, conviction } } }]
: [id, false]
],
[balance, conviction, id, isConvictionVote]
);
if (!hasAccounts) {
return null;
}
@ -87,6 +115,7 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
<>
{isVotingOpen && (
<Modal
className={className}
header={t<string>('Vote on proposal')}
onClose={toggleVoting}
size='large'
@ -106,54 +135,65 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
onChange={setAccountId}
/>
</Modal.Columns>
{isConvictionVote && (
<Modal.Columns
hint={
<>
<p>{t<string>('The balance associated with the vote will be locked as per the conviction specified and will not be available for transfer during this period.')}</p>
<p>{t<string>('Conviction locks do overlap and are not additive, meaning that funds locked during a previous vote can be locked again.')}</p>
</>
}
>
<VoteValue
<Modal.Columns
className='centerVoteType'
hint={t<string>('The type of vote that you wish to cast on the referendum.')}
>
<ToggleGroup
onChange={setVoteTypeIndex}
options={voteTypeOpts}
value={voteTypeIndex}
/>
</Modal.Columns>
{isConvictionVote
? voteTypeIndex === 0
? (
<VoteStandard
accountId={accountId}
id={id}
isAye
onChange={setParams}
/>
)
: voteTypeIndex === 1
? (
<VoteStandard
accountId={accountId}
id={id}
onChange={setParams}
/>
)
: voteTypeIndex === 2
? (
<VoteSplit
accountId={accountId}
id={id}
onChange={setParams}
/>
)
: (
<VoteAbstain
accountId={accountId}
id={id}
onChange={setParams}
/>
)
: (
<VoteBasic
accountId={accountId}
autoFocus
onChange={setBalance}
id={id}
onChange={setParams}
/>
<ConvictionDropdown
help={t<string>('The conviction to use for this vote, with an appropriate lock period.')}
label={t<string>('conviction')}
onChange={setConviction}
value={conviction}
/>
</Modal.Columns>
)}
)
}
</Modal.Content>
<Modal.Actions>
{isConvictionVote && paramsAbstain && (
<TxButton
accountId={accountId}
icon='pause'
label={t<string>('Abstain')}
onStart={toggleVoting}
params={paramsAbstain}
tx={api.tx[palletVote].vote}
/>
)}
<TxButton
accountId={accountId}
icon='ban'
label={t<string>('Vote Nay')}
icon='check-to-slot'
label={t<string>('Vote')}
onStart={toggleVoting}
params={paramsNay}
tx={api.tx[palletVote].vote}
/>
<TxButton
accountId={accountId}
icon='check'
label={t<string>('Vote Aye')}
onStart={toggleVoting}
params={paramsAye}
params={params}
tx={api.tx[palletVote].vote}
/>
</Modal.Actions>
@ -169,4 +209,8 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
);
}
export default React.memo(Voting);
export default React.memo(styled(Voting)`
.ui--Modal-Columns.centerVoteType > div:first-child {
text-align: center;
}
`);

View File

@ -0,0 +1,54 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { VoteTypeProps as Props } from './types';
import React, { useEffect, useState } from 'react';
import { Modal, VoteValue } from '@polkadot/react-components';
import { useTranslation } from '../translate';
function VoteAbstain ({ accountId, id, onChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [balanceAbstain, setBalanceAbstain] = useState<BN | undefined>();
const [balanceAye, setBalanceAye] = useState<BN | undefined>();
const [balanceNay, setBalanceNay] = useState<BN | undefined>();
useEffect((): void => {
onChange([id, {
SplitAbstain: {
abstain: balanceAbstain,
aye: balanceAye,
nay: balanceNay
}
}]);
}, [balanceAbstain, balanceAye, balanceNay, id, onChange]);
return (
<Modal.Columns hint={t<string>('The value of the balance that is to be split to the abstain, aye and nay parts of the vote')}>
<VoteValue
accountId={accountId}
autoFocus
label={t<string>('abstain vote value')}
onChange={setBalanceAbstain}
/>
<VoteValue
accountId={accountId}
autoFocus
label={t<string>('aye vote value')}
noDefault
onChange={setBalanceAye}
/>
<VoteValue
accountId={accountId}
label={t<string>('nay vote value')}
noDefault
onChange={setBalanceNay}
/>
</Modal.Columns>
);
}
export default React.memo(VoteAbstain);

View File

@ -0,0 +1,19 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { VoteTypeProps as Props } from './types';
import React, { useEffect } from 'react';
function VoteBasic ({ id, isAye, onChange }: Props): React.ReactElement<Props> | null {
useEffect((): void => {
onChange([
id,
isAye
]);
}, [id, isAye, onChange]);
return null;
}
export default React.memo(VoteBasic);

View File

@ -0,0 +1,45 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { VoteTypeProps as Props } from './types';
import React, { useEffect, useState } from 'react';
import { Modal, VoteValue } from '@polkadot/react-components';
import { useTranslation } from '../translate';
function VoteSplit ({ accountId, id, onChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [balanceAye, setBalanceAye] = useState<BN | undefined>();
const [balanceNay, setBalanceNay] = useState<BN | undefined>();
useEffect((): void => {
onChange([id, {
Split: {
aye: balanceAye,
nay: balanceNay
}
}]);
}, [balanceAye, balanceNay, id, onChange]);
return (
<Modal.Columns hint={t<string>('The value of the balance that is to be split to the aye and nay parts of the vote')}>
<VoteValue
accountId={accountId}
autoFocus
label={t<string>('aye vote value')}
onChange={setBalanceAye}
/>
<VoteValue
accountId={accountId}
label={t<string>('nay vote value')}
noDefault
onChange={setBalanceNay}
/>
</Modal.Columns>
);
}
export default React.memo(VoteSplit);

View File

@ -0,0 +1,59 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { VoteTypeProps as Props } from './types';
import React, { useEffect, useState } from 'react';
import { ConvictionDropdown, Modal, VoteValue } from '@polkadot/react-components';
import { useTranslation } from '../translate';
function VoteStandard ({ accountId, id, isAye, onChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [balance, setBalance] = useState<BN | undefined>();
const [conviction, setConviction] = useState(0);
useEffect((): void => {
onChange([id, {
Standard: {
balance,
vote: {
aye: isAye,
conviction
}
}
}]);
}, [balance, conviction, id, isAye, onChange]);
return (
<Modal.Columns
hint={
<>
<p>{t<string>('The balance associated with the vote will be locked as per the conviction specified and will not be available for transfer during this period.')}</p>
<p>{t<string>('Conviction locks do overlap and are not additive, meaning that funds locked during a previous vote can be locked again.')}</p>
</>
}
>
<VoteValue
accountId={accountId}
autoFocus
label={
isAye
? t<string>('aye vote value')
: t<string>('nay vote value')
}
onChange={setBalance}
/>
<ConvictionDropdown
help={t<string>('The conviction to use for this vote, with an appropriate lock period.')}
label={t<string>('conviction')}
onChange={setConviction}
value={conviction}
/>
</Modal.Columns>
);
}
export default React.memo(VoteStandard);

View File

@ -0,0 +1,11 @@
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
export interface VoteTypeProps {
accountId: string | null;
id: BN | number;
isAye?: boolean;
onChange: (params: unknown[]) => void;
}

View File

@ -17,6 +17,8 @@ interface Props {
accountId?: string | null;
autoFocus?: boolean;
isCouncil?: boolean;
label?: string;
noDefault?: boolean;
onChange: (value: BN) => void;
}
@ -44,7 +46,7 @@ function getValues (selectedId: string | null | undefined, isCouncil: boolean |
};
}
function VoteValue ({ accountId, autoFocus, isCouncil, onChange }: Props): React.ReactElement<Props> | null {
function VoteValue ({ accountId, autoFocus, isCouncil, label, noDefault, onChange }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const allBalances = useCall<DeriveBalancesAll>(api.derive.balances?.all, [accountId]);
@ -79,14 +81,14 @@ function VoteValue ({ accountId, autoFocus, isCouncil, onChange }: Props): React
<InputBalance
autoFocus={autoFocus}
defaultValue={
isDisabled
isDisabled || noDefault
? undefined
: defaultValue
}
help={t<string>('The amount that is associated with this vote. This value is locked for the duration of the vote.')}
isDisabled={isDisabled}
isZeroable
label={t<string>('vote value')}
label={label || t<string>('vote value')}
labelExtra={
<BalanceVoting
isCouncil={isCouncil}