polkadot-js/apps

View on GitHub
packages/page-democracy/src/Overview/Propose.tsx

Summary

Maintainability
D
2 days
Test Coverage
// Copyright 2017-2024 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';

import React, { useCallback, useEffect, useState } from 'react';

import { Input, InputAddress, InputBalance, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCall, usePreimage } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { BN_ZERO, isFunction, isHex } from '@polkadot/util';

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

interface Props {
  className?: string;
  onClose: () => void;
}

interface HashState {
  imageHash?: HexString | null;
  isImageHashValid: boolean;
}

interface ImageState {
  imageLen: BN;
  imageLenDefault?: BN;
  isImageLenValid: boolean;
}

function Propose ({ className = '', onClose }: Props): React.ReactElement<Props> {
  const { t } = useTranslation();
  const { api } = useApi();
  const [accountId, setAccountId] = useState<string | null>(null);
  const [balance, setBalance] = useState<BN | undefined>();
  const [{ imageHash, isImageHashValid }, setImageHash] = useState<HashState>({ imageHash: null, isImageHashValid: false });
  const [{ imageLen, imageLenDefault, isImageLenValid }, setImageLen] = useState<ImageState>({ imageLen: BN_ZERO, isImageLenValid: false });
  const publicProps = useCall<unknown[]>(api.query.democracy.publicProps);
  const preimage = usePreimage(imageHash);

  useEffect((): void => {
    preimage?.proposalLength && setImageLen((prev) => ({
      imageLen: prev.imageLen,
      imageLenDefault: preimage.proposalLength,
      isImageLenValid: prev.isImageLenValid
    }));
  }, [preimage]);

  const _onChangeImageHash = useCallback(
    (h?: string) =>
      setImageHash({
        imageHash: h as HexString,
        isImageHashValid: isHex(h, 256)
      }),
    []
  );

  const _onChangeImageLen = useCallback(
    (value?: BN): void => {
      value && setImageLen((prev) => ({
        imageLen: value,
        imageLenDefault: prev.imageLenDefault,
        isImageLenValid: !value.isZero()
      }));
    },
    []
  );

  const hasMinLocked = balance?.gte(api.consts.democracy.minimumDeposit);

  return (
    <Modal
      className={className}
      header={t('Submit proposal')}
      onClose={onClose}
      size='large'
    >
      <Modal.Content>
        <Modal.Columns hint={t('The proposal will be registered from this account and the balance lock will be applied here.')}>
          <InputAddress
            label={t('send from account')}
            labelExtra={
              <Available
                label={<span className='label'>{t('transferrable')}</span>}
                params={accountId}
              />
            }
            onChange={setAccountId}
            type='account'
          />
        </Modal.Columns>
        <Modal.Columns
          hint={
            <>
              <p>{t('The hash of the preimage for the proposal as previously submitted or intended.')}</p>
              <p>{t('The length value will be auto-populated from the on-chain value if it is found.')}</p>
            </>
          }
        >
          <Input
            autoFocus
            isError={!isImageHashValid}
            label={t('preimage hash')}
            onChange={_onChangeImageHash}
            value={imageHash || ''}
          />
          <InputNumber
            defaultValue={imageLenDefault}
            isDisabled={!!preimage?.proposalLength && !preimage?.proposalLength.isZero() && isImageHashValid && isImageLenValid}
            isError={!isImageLenValid}
            key='inputLength'
            label={t('preimage length')}
            onChange={_onChangeImageLen}
            value={imageLen}
          />
        </Modal.Columns>
        <Modal.Columns hint={t('The associated deposit for this proposal should be more then the minimum on-chain deposit required. It will be locked until the proposal passes.')}>
          <InputBalance
            defaultValue={api.consts.democracy.minimumDeposit}
            isError={!hasMinLocked}
            label={t('locked balance')}
            onChange={setBalance}
          />
          <InputBalance
            defaultValue={api.consts.democracy.minimumDeposit}
            isDisabled
            label={t('minimum deposit')}
          />
        </Modal.Columns>
      </Modal.Content>
      <Modal.Actions>
        <TxButton
          accountId={accountId}
          icon='plus'
          isDisabled={!balance || !hasMinLocked || !isImageHashValid || !accountId || !publicProps || (isFunction(api.tx.preimage?.notePreimage) && !isFunction(api.tx.democracy?.notePreimage) && !preimage)}
          label={t('Submit proposal')}
          onStart={onClose}
          params={
            api.tx.democracy.propose.meta.args.length === 3
              ? [imageHash, balance, publicProps?.length]
              : isFunction(api.tx.preimage?.notePreimage) && !isFunction(api.tx.democracy?.notePreimage)
                ? [preimage && { Lookup: { hash: preimage.proposalHash, len: imageLen } }, balance]
                : [imageHash, balance]
          }
          tx={api.tx.democracy.propose}
        />
      </Modal.Actions>
    </Modal>
  );
}

export default React.memo(Propose);