mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 08:58:00 +00:00
123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
// Copyright 2017-2026 @pezkuwi/app-tech-comm authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
|
|
import type { CollectiveType } from '@pezkuwi/react-hooks/types';
|
|
|
|
import React, { useCallback, useState } from 'react';
|
|
|
|
import { Button, InputAddress, InputNumber, Modal, TxButton } from '@pezkuwi/react-components';
|
|
import { useApi, useCollectiveInstance, useModal } from '@pezkuwi/react-hooks';
|
|
import { Extrinsic } from '@pezkuwi/react-params';
|
|
import { BN } from '@pezkuwi/util';
|
|
|
|
import { useTranslation } from '../translate.js';
|
|
|
|
interface Props {
|
|
defaultThreshold?: number;
|
|
defaultValue?: SubmittableExtrinsicFunction<'promise'>;
|
|
filter?: (section: string, method?: string) => boolean;
|
|
isMember: boolean;
|
|
members: string[];
|
|
type: CollectiveType;
|
|
}
|
|
|
|
interface ProposalState {
|
|
proposal?: SubmittableExtrinsic<'promise'> | null;
|
|
proposalLength: number;
|
|
}
|
|
|
|
// TODO We probably want to pull this from config
|
|
const DEFAULT_THRESHOLD = 1 / 2;
|
|
|
|
function Propose ({ defaultThreshold = DEFAULT_THRESHOLD, defaultValue, filter, isMember, members, type }: Props): React.ReactElement<Props> | null {
|
|
const { t } = useTranslation();
|
|
const { api, apiDefaultTxSudo } = useApi();
|
|
const { isOpen, onClose, onOpen } = useModal();
|
|
const [accountId, setAcountId] = useState<string | null>(null);
|
|
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>({ proposalLength: 0 });
|
|
const [[threshold, hasThreshold], setThreshold] = useState<[BN | null, boolean]>([
|
|
new BN(Math.min(members.length, (members.length * defaultThreshold) + 1)),
|
|
true
|
|
]);
|
|
const modLocation = useCollectiveInstance(type);
|
|
|
|
const _hasThreshold = useCallback(
|
|
(threshold?: BN | null): boolean =>
|
|
!!threshold && !threshold.isZero() && threshold.lten(members.length),
|
|
[members]
|
|
);
|
|
|
|
const _onChangeExtrinsic = useCallback(
|
|
(proposal?: SubmittableExtrinsic<'promise'>): void => setProposal({
|
|
proposal,
|
|
proposalLength: proposal?.length || 0
|
|
}),
|
|
[]
|
|
);
|
|
const _onChangeThreshold = useCallback(
|
|
(threshold?: BN): void => setThreshold([threshold || null, _hasThreshold(threshold)]),
|
|
[_hasThreshold]
|
|
);
|
|
|
|
if (!modLocation) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{isOpen && (
|
|
<Modal
|
|
header={t('Propose a committee motion')}
|
|
onClose={onClose}
|
|
>
|
|
<Modal.Content>
|
|
<InputAddress
|
|
filter={members}
|
|
label={t('propose from account')}
|
|
onChange={setAcountId}
|
|
type='account'
|
|
withLabel
|
|
/>
|
|
<InputNumber
|
|
className='medium'
|
|
isError={!hasThreshold}
|
|
label={t('threshold')}
|
|
onChange={_onChangeThreshold}
|
|
placeholder={t('Positive number between 1 and {{count}}', { replace: { count: members.length } })}
|
|
value={threshold || undefined}
|
|
/>
|
|
<Extrinsic
|
|
defaultValue={defaultValue || apiDefaultTxSudo}
|
|
filter={filter}
|
|
label={t('proposal')}
|
|
onChange={_onChangeExtrinsic}
|
|
/>
|
|
</Modal.Content>
|
|
<Modal.Actions>
|
|
<TxButton
|
|
accountId={accountId}
|
|
isDisabled={!hasThreshold || !proposal}
|
|
onStart={onClose}
|
|
params={
|
|
api.tx[modLocation].propose.meta.args.length === 3
|
|
? [threshold, proposal, proposalLength]
|
|
: [threshold, proposal]
|
|
}
|
|
tx={api.tx[modLocation].propose}
|
|
/>
|
|
</Modal.Actions>
|
|
</Modal>
|
|
)}
|
|
<Button
|
|
icon='plus'
|
|
isDisabled={!isMember}
|
|
label={t('Submit proposal')}
|
|
onClick={onOpen}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default React.memo(Propose);
|