Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions packages/app-treasury/src/Overview/Proposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@
// of the Apache-2.0 license. See the LICENSE file for details.

import { DerivedTreasuryProposal } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/react-components/types';

import React from 'react';
import { AddressMini, AddressSmall } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';

import translate from '../translate';
import { useTranslation } from '../translate';
import Submission from './Submission';
import Voting from './Voting';

interface Props extends I18nProps {
interface Props {
className?: string;
isMember: boolean;
proposal: DerivedTreasuryProposal;
onRespond: () => void;
}

function ProposalDisplay ({ className, isMember, proposal: { council, id, proposal }, t }: Props): React.ReactElement<Props> | null {
export default function ProposalDisplay ({ className, isMember, proposal: { council, id, proposal } }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();

return (
<tr className={className}>
<td className='number top'>
Expand Down Expand Up @@ -47,13 +50,16 @@ function ProposalDisplay ({ className, isMember, proposal: { council, id, propos
/>
</td>
<td className='top number together'>
<Submission
councilProposals={council}
id={id}
isDisabled={!isMember}
/>
<Voting
councilProposals={council}
isDisabled={!isMember}
proposals={council}
/>
</td>
</tr>
);
}

export default translate(ProposalDisplay);
109 changes: 109 additions & 0 deletions packages/app-treasury/src/Overview/Submission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2017-2019 @polkadot/app-treasury authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { DerivedCollectiveProposal } from '@polkadot/api-derive/types';
import { ProposalIndex } from '@polkadot/types/interfaces';

import React, { useEffect, useState } from 'react';
import { Button, Dropdown, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';

import { useTranslation } from '../translate';

interface Props {
councilProposals: DerivedCollectiveProposal[];
id: ProposalIndex;
isDisabled: boolean;
}

export default function Submission ({ councilProposals, id, isDisabled }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const councilThreshold = useCall<number>(api.query.electionsPhragmen?.members || api.query.elections.members, [], {
transform: (value: any[]): number => (value.length / 2) + 1
});
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [councilType, setCouncilType] = useState('reject');
const [hasProposals, setHasProposals] = useState(true);

useEffect((): void => {
const available = councilProposals
.map(({ votes }): number =>
votes ? votes?.index.toNumber() : -1
)
.filter((index): boolean => index !== -1);

setHasProposals(!!available.length);
}, [councilProposals]);

if (hasProposals) {
return null;
}

return (
<>
{isOpen && (
<Modal
header={t('Submit to council')}
open
size='small'
>
<Modal.Content>
<InputAddress
help={t('Select the council account you wish to use to make the proposal.')}
label={t('submit with council account')}
onChange={setAccountId}
type='account'
withLabel
/>
<Dropdown
help={t('The type of council proposal to submit.')}
label={t('council proposal type')}
onChange={setCouncilType}
options={[
{ value: 'accept', text: t('Acceptance proposal to council') },
{ value: 'reject', text: t('Rejection proposal to council') }
]}
value={councilType}
/>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
icon='cancel'
isNegative
label={t('Cancel')}
onClick={toggleOpen}
/>
<Button.Or />
<TxButton
accountId={accountId}
icon='check'
isDisabled={!accountId || !councilThreshold}
isPrimary
label={t('Send to council')}
onClick={toggleOpen}
params={[
councilThreshold,
councilType === 'reject'
? api.tx.treasury.rejectProposal(id)
: api.tx.treasury.approveProposal(id)
]}
tx='council.propose'
/>
</Button.Group>
</Modal.Actions>
</Modal>
)}
<Button
icon='check'
isDisabled={isDisabled}
isPrimary
label={t('Send to council')}
onClick={toggleOpen}
/>
</>
);
}
29 changes: 13 additions & 16 deletions packages/app-treasury/src/Overview/Voting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@

import { DerivedCollectiveProposal } from '@polkadot/api-derive/types';
import { ProposalIndex, Hash } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';

import React, { useEffect, useState } from 'react';
import { Button, Dropdown, Input, Modal, VoteAccount, VoteActions, VoteToggle } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/react-hooks';
import { useAccounts, useToggle } from '@polkadot/react-hooks';
import { isBoolean } from '@polkadot/util';

import translate from '../translate';
import { useTranslation } from '../translate';

interface Props extends I18nProps {
interface Props {
councilProposals: DerivedCollectiveProposal[];
isDisabled?: boolean;
proposals: DerivedCollectiveProposal[];
}

interface Option {
text: React.ReactNode;
value: number;
}

function Voting ({ isDisabled, proposals, t }: Props): React.ReactElement<Props> | null {
export default function Voting ({ councilProposals, isDisabled }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { hasAccounts } = useAccounts();
const [councilOpts, setCouncilOpts] = useState<Option[]>([]);
const [councilOptId, setCouncilOptId] = useState<number>(0);
const [accountId, setAccountId] = useState<string | null>(null);
const [{ councilId, councilHash }, setCouncilInfo] = useState<{ councilId: ProposalIndex | null; councilHash: Hash | null }>({ councilId: null, councilHash: null });
const [isVotingOpen, setIsVotingOpen] = useState(false);
const [isOpen, toggleOpen] = useToggle();
const [voteValue, setVoteValue] = useState(true);

useEffect((): void => {
const available = proposals
const available = councilProposals
.map(({ proposal: { methodName, sectionName }, votes }): Option => ({
text: `Council #${votes?.index.toNumber()}: ${sectionName}.${methodName} `,
value: votes ? votes?.index.toNumber() : -1
Expand All @@ -42,16 +42,15 @@ function Voting ({ isDisabled, proposals, t }: Props): React.ReactElement<Props>

setCouncilOptId(available.length ? available[0].value : 0);
setCouncilOpts(available);
}, [proposals]);
}, [councilProposals]);

if (!hasAccounts || !councilOpts.length) {
return null;
}

const _toggleVoting = (): void => setIsVotingOpen(!isVotingOpen);
const _onChangeVote = (vote?: boolean): void => setVoteValue(isBoolean(vote) ? vote : true);
const _onChangeProposal = (optionId: number): void => {
const councilProp = proposals.find(({ votes }): boolean => !!(votes?.index.eq(optionId)));
const councilProp = councilProposals.find(({ votes }): boolean => !!(votes?.index.eq(optionId)));

if (councilProp && councilProp.votes) {
setCouncilInfo({ councilId: councilProp.votes.index, councilHash: councilProp.hash });
Expand All @@ -63,7 +62,7 @@ function Voting ({ isDisabled, proposals, t }: Props): React.ReactElement<Props>

return (
<>
{isVotingOpen && (
{isOpen && (
<Modal
header={t('Vote on proposal')}
open
Expand Down Expand Up @@ -92,7 +91,7 @@ function Voting ({ isDisabled, proposals, t }: Props): React.ReactElement<Props>
<VoteActions
accountId={accountId}
isDisabled={!councilHash}
onClick={_toggleVoting}
onClick={toggleOpen}
params={[councilHash, councilId, voteValue]}
tx='council.vote'
/>
Expand All @@ -103,10 +102,8 @@ function Voting ({ isDisabled, proposals, t }: Props): React.ReactElement<Props>
isDisabled={isDisabled}
isPrimary
label={t('Vote')}
onClick={_toggleVoting}
onClick={toggleOpen}
/>
</>
);
}

export default translate(Voting);
6 changes: 5 additions & 1 deletion packages/app-treasury/src/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { withTranslation } from 'react-i18next';
import { useTranslation as useTranslationBase, UseTranslationResponse, withTranslation } from 'react-i18next';

export function useTranslation (): UseTranslationResponse {
return useTranslationBase('app-treasury');
}

export default withTranslation(['app-treasury']);