import BigNumber from 'bignumber.js';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createSearchParams, useSearchParams } from 'react-router-dom';
import { AvailableAmount, DenomArr, MainContainer, Select, Slider } from 'src/components';
import { OptionSelect } from 'src/components/Select';
import { BASE_DENOM, CHAIN_ID } from 'src/constants/config';
import { PATTERN_CYBER } from 'src/constants/patterns';
import { useIbcDenom } from 'src/contexts/ibcDenom';
import { useQueryClient } from 'src/contexts/queryClient';
import useAdviserTexts from 'src/features/adviser/useAdviserTexts';
import useAccountsPassports from 'src/features/passport/hooks/useAccountsPassports';
import useSetActiveAddress from 'src/hooks/useSetActiveAddress';
import { useAppSelector } from 'src/redux/hooks';
import { RootState } from 'src/redux/store';
import { Option } from 'src/types';
import { ObjKeyValue } from 'src/types/data';
import { getDisplayAmount, getDisplayAmountReverce, reduceBalances } from 'src/utils/utils';
import { Col, GridContainer, TeleportContainer } from '../components/containers/Containers';
import { AccountInput, InputMemo, InputNumberDecimalScale } from '../components/Inputs';
import useGetSendTxsByAddressByType from '../hooks/useGetSendTxsByAddress';
import { useTeleport } from '../Teleport.context';
import ActionBar from './actionBar.send';
import DataSendTxs from './components/dataSendTxs/DataSendTxs';

const tokenDefaultValue = BASE_DENOM;

function Send() {
  const queryClient = useQueryClient();
  const { tracesDenom } = useIbcDenom();
  const { defaultAccount } = useAppSelector((state: RootState) => state.pocket);
  useAccountsPassports();
  const { addressActive } = useSetActiveAddress(defaultAccount);
  const { totalSupplyProofList, accountBalances, refreshBalances } = useTeleport();
  const [_update, setUpdate] = useState(0);
  const [recipient, setRecipient] = useState<string | undefined>(undefined);
  const [searchParams, setSearchParams] = useSearchParams();
  const dataSendTxs = useGetSendTxsByAddressByType(
    addressActive,
    'cosmos.bank.v1beta1.MsgSend'
  );
  const [tokenSelect, setTokenSelect] = useState<string>(tokenDefaultValue);
  const [tokenAmount, setTokenAmount] = useState<string>('');

  const [recipientBalances, setRecipientBalances] = useState<Option<ObjKeyValue>>(undefined);

  const [recipientTokenABalances, setRecipientTokenABalances] = useState(0);
  const [memoValue, setMemoValue] = useState<string>('');
  const [isExceeded, setIsExceeded] = useState<boolean>(false);
  const firstEffectOccured = useRef(false);

  const [tokenACoinDecimals, setTokenACoinDecimals] = useState<number>(0);
  const [tokenABalance, setTokenABalance] = useState<number>(0);

  useEffect(() => {
    if (firstEffectOccured.current) {
      // eslint-disable-next-line react-hooks/exhaustive-deps
      const query = {
        token: tokenSelect,
        recipient: '',
        amount: '',
      };

      if (recipient) {
        query.recipient = recipient;
      }

      if (Number(tokenAmount) > 0) {
        query.amount = tokenAmount;
      }

      setSearchParams(createSearchParams(query), { replace: true });
    } else {
      firstEffectOccured.current = true;
      const param = Object.fromEntries(searchParams.entries());
      if (Object.keys(param).length > 0) {
        const { token, recipient, amount } = param;
        setTokenSelect(token);
        if (recipient) {
          setRecipient(recipient);
        }

        if (amount && Number(amount) > 0) {
          setTokenAmount(amount);
        }
      }
    }
  }, [tokenSelect, recipient, setSearchParams, searchParams, tokenAmount]);

  useEffect(() => {
    const [{ coinDecimals }] = tracesDenom(tokenSelect);
    setTokenACoinDecimals(coinDecimals);
  }, [tokenSelect, tracesDenom]);

  // setTokenABalance
  useEffect(() => {
    if (accountBalances) {
      setTokenABalance(accountBalances[tokenSelect] || 0);
    }
  }, [tokenSelect, accountBalances]);

  const validInputAmountToken = useMemo(() => {
    if (Number(tokenAmount) > 0) {
      const amountToken = parseFloat(getDisplayAmountReverce(tokenAmount, tokenACoinDecimals));

      return amountToken > tokenABalance;
    }

    return false;
  }, [tokenAmount, tokenACoinDecimals, tokenABalance]);

  useEffect(() => {
    const validTokenAmount = !validInputAmountToken && Number(tokenAmount) > 0;
    const validRecipient = recipient?.match(PATTERN_CYBER);

    setIsExceeded(!(validRecipient && validTokenAmount));
  }, [recipient, validInputAmountToken, tokenAmount]);

  useEffect(() => {
    (async () => {
      setRecipientBalances(undefined);

      const isInit = queryClient && recipient && recipient.match(PATTERN_CYBER);

      if (!isInit) {
        return;
      }

      const getAllBalancesPromise = await queryClient.getAllBalances(recipient);
      const dataReduceBalances = reduceBalances(getAllBalancesPromise);

      setRecipientBalances(dataReduceBalances);
    })();
  }, [queryClient, recipient]);

  useEffect(() => {
    setRecipientTokenABalances(recipientBalances ? recipientBalances[tokenSelect] || 0 : 0);
  }, [recipientBalances, tokenSelect]);

  const reduceOptions = useMemo(
    () => {
      if (!totalSupplyProofList) return [];
      return Object.keys(totalSupplyProofList)
        .filter((key) => accountBalances && accountBalances[key] > 0)
        .sort((a, b) => ((accountBalances?.[b] || 0) - (accountBalances?.[a] || 0)))
        .map((key) => ({
          value: key,
          text: <DenomArr denomValue={key} onlyText tooltipStatusText={false} />,
          img: <DenomArr denomValue={key} onlyImg tooltipStatusImg={false} />,
        }));
    },
    [totalSupplyProofList, accountBalances]
  );

  const setPercentageBalanceHook = useCallback(
    (value: number) => {
      if (tokenABalance) {
        const amount = new BigNumber(tokenABalance)
          .multipliedBy(value)
          .dividedBy(100)
          .dp(tokenACoinDecimals, BigNumber.ROUND_FLOOR)
          .toNumber();

        setTokenAmount(getDisplayAmount(amount, tokenACoinDecimals));
      }
    },
    [tokenABalance, tokenACoinDecimals]
  );

  const getPercentsOfToken = useCallback(() => {
    const amountTokenA = getDisplayAmountReverce(tokenAmount, tokenACoinDecimals);

    return tokenABalance > 0
      ? new BigNumber(amountTokenA).dividedBy(tokenABalance).multipliedBy(100).toNumber()
      : 0;
  }, [tokenAmount, tokenABalance, tokenACoinDecimals]);

  const updateFunc = useCallback(() => {
    setUpdate((item) => item + 1);
    dataSendTxs.refetch();
    refreshBalances();
    setMemoValue('');
  }, [dataSendTxs, refreshBalances]);

  const amountTokenChange = useCallback(
    (tokenBalance: number, type: 'sender' | 'recipient') => {
      let amount = new BigNumber(getDisplayAmount(tokenBalance, tokenACoinDecimals));

      let changeAmount = new BigNumber(tokenAmount);

      if (type === 'sender') {
        changeAmount = changeAmount.multipliedBy(-1);
      }

      if (changeAmount.comparedTo(0)) {
        amount = new BigNumber(amount).plus(changeAmount);
      }

      return amount.comparedTo(0) >= 0 ? amount.toNumber() : 0;
    },
    [tokenAmount, tokenACoinDecimals]
  );

  useAdviserTexts({
    defaultText: 'send tokens',
  });

  const stateActionBar = {
    tokenAmount,
    tokenSelect,
    recipient,
    updateFunc,
    isExceeded,
    memoValue,
  };

  return (
    <>
      <MainContainer width="62%">
        <TeleportContainer>
          <Select
            valueSelect={CHAIN_ID}
            currentValue={CHAIN_ID}
            disabled
            options={[
              {
                value: CHAIN_ID,
                text: CHAIN_ID,
                img: (
                  <DenomArr denomValue={CHAIN_ID} onlyImg type="network" tooltipStatusImg={false} />
                ),
              },
            ]}
            width="100%"
            // disabled
            title="choose network"
          />
          <AccountInput recipient={recipient} setRecipient={setRecipient} />
          <GridContainer>
            <Col>
              <InputNumberDecimalScale
                value={tokenAmount}
                availableAmount={tokenABalance}
                onValueChange={(value) => setTokenAmount(value)}
                title="choose amount to send"
                validAmount={validInputAmountToken}
                tokenSelect={tokenSelect}
              />
              <AvailableAmount
                amountToken={amountTokenChange(tokenABalance, 'sender')}
                title={tokenAmount.length === 0 ? 'you have' : 'you will have'}
              />
            </Col>
            <Col>
              <Select
                valueSelect={tokenSelect}
                currentValue={
                  <OptionSelect
                    text="choose"
                    img={<DenomArr denomValue="choose" onlyImg />}
                    value=""
                    bgrImg
                  />
                }
                onChangeSelect={(item: string) => setTokenSelect(item)}
                width="100%"
                options={reduceOptions}
                title="choose token to send"
              />
              <AvailableAmount
                title={tokenAmount.length === 0 ? 'recipient have' : 'recipient will have'}
                amountToken={amountTokenChange(recipientTokenABalances, 'recipient')}
              />
            </Col>
          </GridContainer>

          <Slider valuePercents={getPercentsOfToken()} onChange={setPercentageBalanceHook} />
          <InputMemo value={memoValue} onChangeValue={setMemoValue} />
        </TeleportContainer>
        <TeleportContainer>
          <DataSendTxs dataSendTxs={dataSendTxs} accountUser={addressActive} />
        </TeleportContainer>
      </MainContainer>
      <ActionBar stateActionBar={stateActionBar} />
    </>
  );
}

export default Send;

Synonyms

pussy-ts/src/pages/teleport/send/send.tsx

Neighbours