Skip to content

Commit

Permalink
Treasury tipping (#2551)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacogr committed Apr 11, 2020
1 parent 215e12c commit 936cc70
Show file tree
Hide file tree
Showing 16 changed files with 423 additions and 38 deletions.
2 changes: 1 addition & 1 deletion packages/page-treasury/src/Overview/Approve.tsx
@@ -1,4 +1,4 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// Copyright 2017-2020 @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.

Expand Down
2 changes: 1 addition & 1 deletion packages/page-treasury/src/Overview/Proposal.tsx
@@ -1,4 +1,4 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// Copyright 2017-2020 @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.

Expand Down
Expand Up @@ -17,13 +17,13 @@ function Propose ({ className }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [accountId, setAccountId] = useState<string | null>(null);
const [beneficiary, setBeneficiary] = useState<string | null>(null);
const [isProposeOpen, togglePropose] = useToggle();
const [isOpen, toggleOpen] = useToggle();
const [value, setValue] = useState<BN | undefined>();
const hasValue = value?.gtn(0);

return (
<>
{isProposeOpen && (
{isOpen && (
<Modal
className={className}
header={t('Submit treasury proposal')}
Expand Down Expand Up @@ -52,24 +52,24 @@ function Propose ({ className }: Props): React.ReactElement<Props> | null {
onChange={setValue}
/>
</Modal.Content>
<Modal.Actions onCancel={togglePropose}>
<Modal.Actions onCancel={toggleOpen}>
<TxButton
accountId={accountId}
icon='add'
isDisabled={!accountId || !hasValue}
isPrimary
label={t('Submit proposal')}
onStart={togglePropose}
onStart={toggleOpen}
params={[value, beneficiary]}
tx='treasury.proposeSpend'
/>
</Modal.Actions>
</Modal>
)}
<Button
icon='check'
icon='plus'
label={t('Submit proposal')}
onClick={togglePropose}
onClick={toggleOpen}
/>
</>
);
Expand Down
21 changes: 4 additions & 17 deletions packages/page-treasury/src/Overview/Proposals.tsx
@@ -1,40 +1,27 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// Copyright 2017-2020 @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 { DeriveTreasuryProposal } from '@polkadot/api-derive/types';
import { AccountId, Balance } from '@polkadot/types/interfaces';

import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { Table } from '@polkadot/react-components';
import { useApi, useAccounts, useCall } from '@polkadot/react-hooks';

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

interface Props {
className?: string;
isApprovals?: boolean;
isMember: boolean;
proposals?: DeriveTreasuryProposal[];
}

function ProposalsBase ({ className, isApprovals, proposals }: Props): React.ReactElement<Props> {
function ProposalsBase ({ className, isApprovals, isMember, proposals }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const members = useCall<[AccountId, Balance][]>((api.query.electionsPhragmen || api.query.elections).members, []);
const [isMember, setIsMember] = useState(false);
const history = useHistory();

useEffect((): void => {
allAccounts && members && setIsMember(
members
.map(([accountId]): string => accountId.toString())
.some((accountId): boolean => allAccounts.includes(accountId))
);
}, [allAccounts, members]);

const _onRespond = useCallback(
(): void => {
history.push('/council/motions');
Expand Down
2 changes: 1 addition & 1 deletion packages/page-treasury/src/Overview/Submission.tsx
Expand Up @@ -50,7 +50,7 @@ function Submission ({ councilProposals, id, isDisabled }: Props): React.ReactEl
<>
{isOpen && (
<Modal
header={t('Submit to council')}
header={t('To council')}
size='small'
>
<Modal.Content>
Expand Down
2 changes: 1 addition & 1 deletion packages/page-treasury/src/Overview/Summary.tsx
@@ -1,4 +1,4 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// Copyright 2017-2020 @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.

Expand Down
79 changes: 79 additions & 0 deletions packages/page-treasury/src/Overview/Tip.tsx
@@ -0,0 +1,79 @@
// Copyright 2017-2020 @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 { OpenTip } from '@polkadot/types/interfaces';

import React from 'react';
import { AddressSmall, AddressMini, Expander } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { Option } from '@polkadot/types';

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

interface Props {
className?: string;
hash: string;
isMember: boolean;
members: string[];
}

function Tip ({ className, hash, isMember, members }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const tip = useCall<OpenTip | null>(api.query.treasury.tips, [hash], {
transform: (optTip: Option<OpenTip>) => optTip.unwrapOr(null)
});

if (!tip) {
return null;
}

const { finder, reason, tips, who } = tip;
const finderInfo = finder.unwrapOr(null);

return (
<tr className={className}>
<td className='address'>
<AddressSmall value={who} />
</td>
<td className='address'>
{finderInfo && (
<AddressMini value={finderInfo[0]} />
)}
</td>
<td className='number'>
{finderInfo && (
<FormatBalance value={finderInfo[1]} />
)}
</td>
<TipReason hash={reason} />
<td className='start all'>
{tips.length !== 0 && (
<Expander summary={t('Endorsements ({{count}})', { replace: { count: tips.length } })}>
{tips.map(([tipper, balance]) => (
<AddressMini
balance={balance}
key={tipper.toString()}
value={tipper}
withBalance
/>
))}
</Expander>
)}
</td>
<td className='button'>
<TipEndorse
hash={hash}
isMember={isMember}
members={members}
/>
</td>
</tr>
);
}

export default React.memo(Tip);
108 changes: 108 additions & 0 deletions packages/page-treasury/src/Overview/TipCreate.tsx
@@ -0,0 +1,108 @@
// Copyright 2017-2020 @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 BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import { Button, Input, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';

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

interface Props {
members: string[];
refresh: () => void;
}

const MAX_REASON_LEN = 128;
const MIN_REASON_LEN = 5;

function TipCreate ({ members, refresh }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [beneficiary, setBeneficiary] = useState<string | null>(null);
const [isMember, setIsMember] = useState(false);
const [reason, setReason] = useState('');
const [value, setValue] = useState<BN | undefined>();
const hasValue = value?.gtn(0);
const hasReason = reason?.length >= MIN_REASON_LEN && reason?.length <= MAX_REASON_LEN;

useEffect((): void => {
setIsMember(
accountId
? members.includes(accountId)
: false
);
}, [accountId, members]);

return (
<>
<Button
icon='plus'
label={t('Tip')}
onClick={toggleOpen}
/>
{isOpen && (
<Modal
header={t('Submit tip request')}
size='small'
>
<Modal.Content>
<InputAddress
help={t('Select the account you wish to submit the tip from.')}
label={t('submit with account')}
onChange={setAccountId}
type='account'
withLabel
/>
<InputAddress
help={t('The account to which the tip will be transferred if approved')}
label={t('beneficiary')}
onChange={setBeneficiary}
type='allPlus'
/>
<Input
autoFocus
help={t('The reason why this tip should be paid.')}
isError={!hasReason}
label={t('tip reason')}
onChange={setReason}
/>
{isMember && (
<InputBalance
help={t('The suggested value for this tip')}
isError={!hasValue}
label={t('tip value')}
onChange={setValue}
/>
)}
</Modal.Content>
<Modal.Actions onCancel={toggleOpen}>
<TxButton
accountId={accountId}
icon='add'
isDisabled={!accountId || (isMember ? !hasValue : false) || !hasReason}
isPrimary
label={t('Propose tip')}
onStart={toggleOpen}
onSuccess={refresh}
params={
isMember
? [reason, beneficiary, value]
: [reason, beneficiary]
}
tx={
isMember
? 'treasury.tipNew'
: 'treasury.reportAwesome'
}
/>
</Modal.Actions>
</Modal>
)}
</>
);
}

export default React.memo(TipCreate);
72 changes: 72 additions & 0 deletions packages/page-treasury/src/Overview/TipEndorse.tsx
@@ -0,0 +1,72 @@
// Copyright 2017-2020 @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 BN from 'bn.js';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';

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

interface Props {
hash: string;
isMember: boolean;
members: string[];
}

function TipEndorse ({ hash, isMember, members }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [value, setValue] = useState<BN | undefined>();
const hasValue = value?.gtn(0);

return (
<>
<Button
icon='check'
isDisabled={!isMember}
label={t('Endorse')}
onClick={toggleOpen}
/>
{isOpen && (
<Modal
header={t('Submit tip endorsement')}
size='small'
>
<Modal.Content>
<InputAddress
filter={members}
help={t('Select the account you wish to submit the tip from.')}
label={t('submit with account')}
onChange={setAccountId}
type='account'
withLabel
/>
<InputBalance
help={t('The tip amount that should be allocated')}
isError={!hasValue}
label={t('value')}
onChange={setValue}
/>
</Modal.Content>
<Modal.Actions onCancel={toggleOpen}>
<TxButton
accountId={accountId}
icon='add'
isDisabled={!accountId || !hasValue }
isPrimary
label={t('Submit tip')}
onStart={toggleOpen}
params={[hash, value]}
tx='treasury.tip'
/>
</Modal.Actions>
</Modal>
)}
</>
);
}

export default React.memo(TipEndorse);

0 comments on commit 936cc70

Please sign in to comment.