Propose tech committee motions (#1996)
* Propose tech committe motions * bug, ignore .vscode * linting * Some cleanups * Move Propose back
This commit is contained in:
parent
f4e2c78cf9
commit
a69bdfeef1
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { AccountId, Hash } from '@polkadot/types/interfaces';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { SummaryBox, CardSummary } from '@polkadot/react-components';
|
||||
@ -14,10 +14,7 @@ import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
members?: AccountId[];
|
||||
proposals?: Hash[];
|
||||
}
|
||||
interface Props extends ComponentProps, I18nProps {}
|
||||
|
||||
function Summary ({ className, members, proposals, t }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
|
||||
@ -2,32 +2,27 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, Hash } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import translate from '../translate';
|
||||
import Members from './Members';
|
||||
import Summary from './Summary';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
members?: AccountId[];
|
||||
proposals?: Hash[];
|
||||
}
|
||||
interface Props extends I18nProps, ComponentProps {}
|
||||
|
||||
export default function Overview ({ className, members, proposals }: Props): React.ReactElement<Props> {
|
||||
function Overview ({ className, members, proposals }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Summary
|
||||
members={members}
|
||||
proposals={proposals}
|
||||
/>
|
||||
{/* <Button.Group>
|
||||
<SubmitCandidacy electionsInfo={electionsInfo} />
|
||||
<Button.Or />
|
||||
<Vote electionsInfo={electionsInfo} />
|
||||
</Button.Group> */}
|
||||
<Members members={members} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(Overview);
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
// Copyright 2017-2019 @polkadot/app-democracy 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 { SetIndex } from '@polkadot/types/interfaces';
|
||||
import { DerivedElectionsInfo } from '@polkadot/api-derive/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
|
||||
export interface ComponentProps {
|
||||
electionsInfo: DerivedElectionsInfo;
|
||||
}
|
||||
|
||||
export interface VoterPosition {
|
||||
setIndex: SetIndex;
|
||||
globalIndex: BN;
|
||||
}
|
||||
@ -21,14 +21,14 @@ interface Props extends I18nProps {
|
||||
|
||||
function Proposal ({ className, hash, t }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const _proposal = trackStream<Option<ProposalType>>(api.query.technicalCommittee.proposalOf, [hash]);
|
||||
const optProposal = trackStream<Option<ProposalType>>(api.query.technicalCommittee.proposalOf, [hash]);
|
||||
const votes = trackStream<Option<Votes>>(api.query.technicalCommittee.voting, [hash]);
|
||||
|
||||
if (!votes?.isSome) {
|
||||
if (!optProposal?.isSome || !votes?.isSome) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proposal = _proposal?.unwrapOr(null);
|
||||
const proposal = optProposal.unwrap();
|
||||
const { ayes, index, nays, threshold } = votes.unwrap();
|
||||
|
||||
return (
|
||||
|
||||
83
packages/app-tech-comm/src/Proposals/Propose.tsx
Normal file
83
packages/app-tech-comm/src/Proposals/Propose.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-staking 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 { I18nProps } from '@polkadot/react-components/types';
|
||||
import { TxSource, TxDef } from '@polkadot/react-hooks/types';
|
||||
import { Call, Proposal } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { registry } from '@polkadot/react-api';
|
||||
import { Extrinsic, InputNumber, TxModalNew as TxModal } from '@polkadot/react-components';
|
||||
import { useApi, useTx } from '@polkadot/react-hooks';
|
||||
import { createType } from '@polkadot/types';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
memberCount?: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Propose ({ t, onClose, memberCount = 0 }: Props): React.ReactElement<Props> {
|
||||
const _hasThreshold = (threshold?: BN | null): boolean =>
|
||||
!!threshold && !threshold.isZero() && threshold.lten(memberCount);
|
||||
|
||||
const { apiDefaultTxSudo } = useApi();
|
||||
const [proposal, setProposal] = useState<Proposal | null>(null);
|
||||
const [[threshold, hasThreshold], setThreshold] = useState<[BN | null, boolean]>([
|
||||
new BN(memberCount / 2 + 1),
|
||||
true
|
||||
]);
|
||||
|
||||
// FIXME Rework this, unless you know, you can never figure out what all these options mean here
|
||||
const txState = useTx(
|
||||
(): TxSource<TxDef> => [
|
||||
[
|
||||
'technicalCommittee.propose',
|
||||
[threshold, proposal]
|
||||
],
|
||||
!!proposal && hasThreshold
|
||||
],
|
||||
[memberCount, proposal, threshold, hasThreshold],
|
||||
{}
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
setThreshold([threshold, _hasThreshold(threshold)]);
|
||||
}, [memberCount]);
|
||||
|
||||
const _onChangeExtrinsic = (method?: Call): void =>
|
||||
setProposal(method ? createType(registry, 'Proposal', method) : null);
|
||||
const _onChangeThreshold = (threshold?: BN): void =>
|
||||
setThreshold([threshold || null, _hasThreshold(threshold)]);
|
||||
|
||||
return (
|
||||
<TxModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
{...txState}
|
||||
header={t('Propose a committee motion')}
|
||||
>
|
||||
<InputNumber
|
||||
className='medium'
|
||||
label={t('threshold')}
|
||||
help={t('The minimum number of committee votes required to approve this motion')}
|
||||
isError={!hasThreshold}
|
||||
onChange={_onChangeThreshold}
|
||||
onEnter={txState.sendTx}
|
||||
placeholder={t('Positive number between 1 and {{memberCount}}', { replace: { memberCount } })}
|
||||
value={threshold || undefined}
|
||||
/>
|
||||
<Extrinsic
|
||||
defaultValue={apiDefaultTxSudo}
|
||||
label={t('proposal')}
|
||||
onChange={_onChangeExtrinsic}
|
||||
onEnter={txState.sendTx}
|
||||
/>
|
||||
</TxModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(Propose);
|
||||
@ -2,23 +2,39 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Hash } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { Hash } from '@polkadot/types/interfaces';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
|
||||
import Proposal from './Proposal';
|
||||
import translate from '../translate';
|
||||
import Proposal from './Proposal';
|
||||
import Propose from './Propose';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
proposals?: Hash[];
|
||||
}
|
||||
interface Props extends ComponentProps, I18nProps {}
|
||||
|
||||
function Proposals ({ className, members, proposals, t }: Props): React.ReactElement<Props> {
|
||||
const [isProposeOpen, setIsProposeOpen] = useState(false);
|
||||
const _toggleProposal = (): void => setIsProposeOpen(!isProposeOpen);
|
||||
|
||||
function Proposals ({ className, proposals, t }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* <Propose /> */}
|
||||
{isProposeOpen && (
|
||||
<Propose
|
||||
memberCount={members?.length}
|
||||
onClose={_toggleProposal}
|
||||
/>
|
||||
)}
|
||||
<Button.Group>
|
||||
<Button
|
||||
isPrimary
|
||||
label={t('Submit proposal')}
|
||||
icon='add'
|
||||
onClick={_toggleProposal}
|
||||
/>
|
||||
</Button.Group>
|
||||
{proposals?.length
|
||||
? (
|
||||
<Table>
|
||||
|
||||
@ -43,7 +43,10 @@ function App ({ basePath, className, t }: Props): React.ReactElement<Props> {
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/proposals`}>
|
||||
<Proposals proposals={proposals} />
|
||||
<Proposals
|
||||
members={members}
|
||||
proposals={proposals}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={basePath}>
|
||||
<Overview
|
||||
|
||||
11
packages/app-tech-comm/src/types.ts
Normal file
11
packages/app-tech-comm/src/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2019 @polkadot/app-democracy 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 { AccountId, Hash } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface ComponentProps {
|
||||
className?: string;
|
||||
proposals?: Hash[];
|
||||
members?: AccountId[];
|
||||
}
|
||||
@ -213,8 +213,8 @@ function InputNumber (props: Props): React.ReactElement<Props> {
|
||||
}, [value, si, bitLength, maxValue]);
|
||||
|
||||
useEffect((): void => {
|
||||
onChange && isValid && onChange(valueBn);
|
||||
}, [isValid, valueBn]);
|
||||
onChange && onChange(valueBn);
|
||||
}, [valueBn]);
|
||||
|
||||
const _onChange = (input: string): void => {
|
||||
setValues(getValuesFromString(input, si, props));
|
||||
|
||||
@ -19,7 +19,7 @@ function TxModal<P extends Props> ({
|
||||
isSending,
|
||||
trigger: Trigger,
|
||||
header = t('Submit signed extrinsic'),
|
||||
content,
|
||||
children,
|
||||
preContent,
|
||||
isDisabled = false,
|
||||
isSubmittable = true,
|
||||
@ -49,6 +49,11 @@ function TxModal<P extends Props> ({
|
||||
props.onClose && props.onClose();
|
||||
};
|
||||
|
||||
const onSend = (): void => {
|
||||
sendTx();
|
||||
onClose();
|
||||
};
|
||||
|
||||
// const onStart = (): void => {
|
||||
// props.onSubmit && props.onSubmit();
|
||||
// };
|
||||
@ -108,7 +113,7 @@ function TxModal<P extends Props> ({
|
||||
type='account'
|
||||
{...inputAddressProps}
|
||||
/>
|
||||
{content}
|
||||
{children}
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
@ -120,12 +125,11 @@ function TxModal<P extends Props> ({
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isLoading={isSending}
|
||||
isDisabled={isDisabled || isSending || !accountId || !isSubmittable}
|
||||
isPrimary
|
||||
label={submitButtonLabel}
|
||||
icon={submitButtonIcon}
|
||||
onClick={sendTx}
|
||||
onClick={onSend}
|
||||
{...submitButtonProps}
|
||||
/>
|
||||
</Button.Group>
|
||||
|
||||
@ -113,8 +113,7 @@ export interface TxModalProps extends I18nProps, TxState {
|
||||
isDisabled?: boolean;
|
||||
isOpen?: boolean;
|
||||
isUnsigned?: boolean;
|
||||
isSubmittable?: boolean;
|
||||
content: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
preContent?: React.ReactNode;
|
||||
trigger?: TxTrigger;
|
||||
onSubmit?: () => void;
|
||||
|
||||
@ -7,7 +7,11 @@ import { AccountId, Balance, BlockNumber, Call, Hash, SessionIndex } from '@polk
|
||||
import { IExtrinsic } from '@polkadot/types/types';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
|
||||
export type TxSources = SubmittableExtrinsic | IExtrinsic | Call | [string, any[] | ConstructTxFn] | null;
|
||||
export type TxDef = [string, any[] | ConstructTxFn];
|
||||
|
||||
export type TxDefs = SubmittableExtrinsic | IExtrinsic | Call | TxDef | null;
|
||||
|
||||
export type TxSource<T extends TxDefs> = [T, boolean];
|
||||
|
||||
export interface Slash {
|
||||
accountId: AccountId;
|
||||
@ -25,6 +29,7 @@ export interface SessionRewards {
|
||||
|
||||
export interface ExtrinsicAndSenders {
|
||||
extrinsic: SubmittableExtrinsic | null;
|
||||
isSubmittable: boolean;
|
||||
sendTx: () => void;
|
||||
sendUnsigned: () => void;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { StringOrNull } from '@polkadot/react-components/types';
|
||||
import { Call } from '@polkadot/types/interfaces';
|
||||
import { ExtrinsicAndSenders, TxSources, TxProps, TxState } from './types';
|
||||
import { ExtrinsicAndSenders, TxDef, TxDefs, TxSource, TxProps, TxState } from './types';
|
||||
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
@ -13,82 +13,73 @@ import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
import { assert, isFunction } from '@polkadot/util';
|
||||
import useApi from './useApi';
|
||||
|
||||
function getExtrinsic<T extends TxSources> (api: ApiPromise, tx: T): SubmittableExtrinsic | null {
|
||||
if (!tx) {
|
||||
function getExtrinsic<T extends TxDefs> (api: ApiPromise, txDef: T): SubmittableExtrinsic | null {
|
||||
if (!txDef) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(tx)) {
|
||||
const [section, method] = tx[0].split('.');
|
||||
|
||||
if (Array.isArray(txDef)) {
|
||||
const [section, method] = txDef[0].split('.');
|
||||
|
||||
assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`);
|
||||
try {
|
||||
return api.tx[section][method](...(
|
||||
isFunction(tx[1])
|
||||
? tx[1]()
|
||||
: tx[1]
|
||||
isFunction(txDef[1])
|
||||
? txDef[1]()
|
||||
: txDef[1]
|
||||
)) as any as SubmittableExtrinsic;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if ((tx as Call).callIndex) {
|
||||
const fn = api.findCall(tx.callIndex);
|
||||
if ((txDef as Call).callIndex) {
|
||||
const fn = api.findCall(txDef.callIndex);
|
||||
|
||||
return api.tx[fn.section][fn.method](...tx.args);
|
||||
return api.tx[fn.section][fn.method](...txDef.args);
|
||||
}
|
||||
|
||||
return tx as any as SubmittableExtrinsic;
|
||||
return txDef as any as SubmittableExtrinsic;
|
||||
}
|
||||
}
|
||||
|
||||
export default function useTx<T extends TxSources> (source: T, { accountId: anAccountId, onChangeAccountId, onStart, onSuccess, onFailed, onUpdate }: TxProps = {}): TxState {
|
||||
export default function useTx<T extends TxDef> (memoFn: (...args: any[]) => TxSource<T>, memoArr: any[], { accountId: anAccountId, onChangeAccountId, onStart, onSuccess, onFailed, onUpdate }: TxProps = {}): TxState {
|
||||
const { api } = useApi();
|
||||
const { queueExtrinsic } = useContext(StatusContext);
|
||||
|
||||
const txSource = useMemo(memoFn, memoArr);
|
||||
|
||||
const [accountId, setAccountId] = useState<StringOrNull>(anAccountId || null);
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
||||
const _onStart = useMemo(
|
||||
(): () => void => (): void => {
|
||||
setIsSending(true);
|
||||
const _onStart = (): void => {
|
||||
setIsSending(true);
|
||||
|
||||
onStart && onStart();
|
||||
},
|
||||
[onStart]
|
||||
);
|
||||
onStart && onStart();
|
||||
};
|
||||
|
||||
const _onSuccess = useMemo(
|
||||
(): () => void => (): void => {
|
||||
setIsSending(false);
|
||||
const _onSuccess = (): void => {
|
||||
setIsSending(false);
|
||||
|
||||
onSuccess && onSuccess();
|
||||
},
|
||||
[onSuccess]
|
||||
);
|
||||
onSuccess && onSuccess();
|
||||
};
|
||||
|
||||
const _onFailed = useMemo(
|
||||
(): () => void => (): void => {
|
||||
setIsSending(false);
|
||||
const _onFailed = (): void => {
|
||||
setIsSending(false);
|
||||
|
||||
onFailed && onFailed();
|
||||
},
|
||||
[onFailed]
|
||||
);
|
||||
onFailed && onFailed();
|
||||
};
|
||||
|
||||
const _onUpdate = useMemo(
|
||||
(): () => void => (): void => {
|
||||
setIsSending(false);
|
||||
const _onUpdate = (): void => {
|
||||
setIsSending(false);
|
||||
|
||||
onUpdate && onUpdate();
|
||||
},
|
||||
[onUpdate]
|
||||
);
|
||||
onUpdate && onUpdate();
|
||||
};
|
||||
|
||||
function getExtrinsicAndSenders (api: ApiPromise, accountId: StringOrNull, tx: T): ExtrinsicAndSenders {
|
||||
const extrinsic = getExtrinsic<T>(api, tx);
|
||||
function getExtrinsicAndSenders (api: ApiPromise, accountId: StringOrNull, [txDef, isSubmittable]: TxSource<T>): ExtrinsicAndSenders {
|
||||
const extrinsic = getExtrinsic<T>(api, txDef);
|
||||
|
||||
function _sendTx (isUnsigned?: boolean): void {
|
||||
!!extrinsic && queueExtrinsic({
|
||||
!!extrinsic && isSubmittable && queueExtrinsic({
|
||||
accountId,
|
||||
extrinsic,
|
||||
isUnsigned,
|
||||
@ -101,14 +92,15 @@ export default function useTx<T extends TxSources> (source: T, { accountId: anAc
|
||||
|
||||
return {
|
||||
extrinsic,
|
||||
isSubmittable: !!accountId && !!extrinsic && isSubmittable,
|
||||
sendTx: (): void => _sendTx(),
|
||||
sendUnsigned: (): void => _sendTx(true)
|
||||
};
|
||||
}
|
||||
|
||||
const txAndSenders = useMemo<ExtrinsicAndSenders>(
|
||||
(): ExtrinsicAndSenders => getExtrinsicAndSenders(api, accountId, source),
|
||||
[accountId, source]
|
||||
(): ExtrinsicAndSenders => getExtrinsicAndSenders(api, accountId, txSource),
|
||||
[accountId, txSource]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user