kleros/kleros-v2

View on GitHub
web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React, { useState, useMemo, useEffect } from "react";
import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";

import { useParams } from "react-router-dom";
import { useDebounce } from "react-use";
import { useAccount } from "wagmi";

import { REFETCH_INTERVAL } from "consts/index";

import { useReadSortitionModuleGetJurorBalance, useReadPnkBalanceOf } from "hooks/contracts/generated";
import { useParsedAmount } from "hooks/useParsedAmount";

import { commify, uncommify } from "utils/commify";
import { formatPNK, roundNumberDown } from "utils/format";
import { isUndefined } from "utils/index";

import { NumberInputField } from "components/NumberInputField";
import StakeWithdrawButton, { ActionType } from "./StakeWithdrawButton";

const StyledField = styled(NumberInputField)`
  height: fit-content;
`;

const LabelArea = styled.div`
  display: flex;
  justify-content: space-between;

  ${landscapeStyle(
    () => css`
      width: 92%;
    `
  )}
`;

const StyledLabel = styled.label`
  color: ${({ theme }) => theme.primaryBlue};
  cursor: pointer;
`;

const InputArea = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;

  ${landscapeStyle(
    () => css`
      width: 92%;
    `
  )}
`;

const InputFieldAndButton = styled.div`
  display: flex;
  flex-direction: row;
  width: 100%;
`;

const EnsureChainContainer = styled.div`
  button {
    height: 45px;
    border: 1px solid ${({ theme }) => theme.stroke};
  }
`;

interface IInputDisplay {
  action: ActionType;
  isSending: boolean;
  setIsSending: (arg0: boolean) => void;
  setIsPopupOpen: (arg0: boolean) => void;
  amount: string;
  setAmount: (arg0: string) => void;
}

const InputDisplay: React.FC<IInputDisplay> = ({
  action,
  isSending,
  setIsSending,
  setIsPopupOpen,
  amount,
  setAmount,
}) => {
  const [debouncedAmount, setDebouncedAmount] = useState("");
  const [errorMsg, setErrorMsg] = useState<string | undefined>();
  useDebounce(() => setDebouncedAmount(amount), 500, [amount]);
  const parsedAmount = useParsedAmount(uncommify(debouncedAmount) as `${number}`);

  const { id } = useParams();
  const { address } = useAccount();
  const { data: balance } = useReadPnkBalanceOf({
    query: {
      enabled: !isUndefined(address),
      refetchInterval: REFETCH_INTERVAL,
    },
    args: [address ?? "0x"],
  });
  const parsedBalance = formatPNK(balance ?? 0n, 0, true);
  const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
    query: {
      enabled: !isUndefined(address),
      refetchInterval: REFETCH_INTERVAL,
    },
    args: [address ?? "0x", BigInt(id ?? "0")],
  });
  const parsedStake = formatPNK(jurorBalance?.[2] || 0n, 0, true);
  const isStaking = useMemo(() => action === ActionType.stake, [action]);

  useEffect(() => {
    if (parsedAmount > 0n && balance === 0n && isStaking) {
      setErrorMsg("You need a non-zero PNK balance to stake");
    } else if (isStaking && balance && parsedAmount > balance) {
      setErrorMsg("Insufficient balance to stake this amount");
    } else if (!isStaking && jurorBalance && parsedAmount > jurorBalance[2]) {
      setErrorMsg("Insufficient staked amount to withdraw this amount");
    } else {
      setErrorMsg(undefined);
    }
  }, [parsedAmount, isStaking, balance, jurorBalance]);

  return (
    <>
      <LabelArea>
        <label>{`Available ${isStaking ? parsedBalance : parsedStake} PNK`}</label>
        <StyledLabel
          onClick={() => {
            const amount = isStaking ? parsedBalance : parsedStake;
            setAmount(amount);
          }}
        >
          {isStaking ? "Stake" : "Withdraw"} all
        </StyledLabel>
      </LabelArea>
      <InputArea>
        <InputFieldAndButton>
          <StyledField
            value={uncommify(amount)}
            onChange={(e) => {
              setAmount(e);
            }}
            placeholder={isStaking ? "Amount to stake" : "Amount to withdraw"}
            message={errorMsg ?? undefined}
            variant={!isUndefined(errorMsg) ? "error" : "info"}
            formatter={(number: string) => (number !== "" ? commify(roundNumberDown(Number(number))) : "")}
          />
          <EnsureChainContainer>
            <StakeWithdrawButton
              {...{
                parsedAmount,
                action,
                setAmount,
                isSending,
                setIsSending,
                setIsPopupOpen,
              }}
            />
          </EnsureChainContainer>
        </InputFieldAndButton>
      </InputArea>
    </>
  );
};

export default InputDisplay;