kleros/kleros-v2

View on GitHub
web/src/components/DisputeView/CardLabels/index.tsx

Summary

Maintainability
C
7 hrs
Test Coverage
import React, { useMemo } from "react";
import styled, { css } from "styled-components";

import Skeleton from "react-loading-skeleton";
import { formatEther, formatUnits } from "viem";
import { useAccount } from "wagmi";

import AppealIcon from "svgs/label-icons/appeal.svg";
import EvidenceIcon from "svgs/label-icons/evidence.svg";
import ForgotToVoteIcon from "svgs/label-icons/forgot-vote.svg";
import FundedIcon from "svgs/label-icons/funded.svg";
import NotDrawnIcon from "svgs/label-icons/minus-circle.svg";
import CanVoteIcon from "svgs/label-icons/vote.svg";
import VotedIcon from "svgs/label-icons/voted.svg";

import { useLabelInfoQuery } from "hooks/queries/useLabelInfoQuery";
import { getLocalRounds } from "utils/getLocalRounds";
import { isUndefined } from "utils/index";

import { ClassicContribution } from "src/graphql/graphql";

import Label, { IColors } from "./Label";
import RewardsAndFundLabel from "./RewardsAndFundLabel";

const Container = styled.div<{ isList: boolean }>`
  display: flex;
  gap: 8px;
  flex-direction: column;
  align-items: end;
  ${({ isList }) =>
    !isList &&
    css`
      margin-top: 16px;
      width: 100%;
      flex-wrap: wrap;
      flex-direction: row;
      align-items: center;
    `}
`;
const RewardsContainer = styled.div`
  display: flex;
  gap: 4px 8px;
  flex-wrap: wrap;
  justify-content: end;
`;
interface ICardLabels {
  disputeId: string;
  round: number;
  isList: boolean;
}

const LabelArgs: Record<string, { text: string; icon: React.FC<React.SVGAttributes<SVGElement>>; color: IColors }> = {
  EvidenceTime: { text: "Evidence Time", icon: EvidenceIcon, color: "blue" },
  NotDrawn: { text: "Not Drawn", icon: NotDrawnIcon, color: "grey" },
  CanVote: { text: "Time to vote", icon: CanVoteIcon, color: "blue" },
  Voted: { text: "I voted", icon: VotedIcon, color: "purple" },
  DidNotVote: { text: "Didn't cast a vote", icon: ForgotToVoteIcon, color: "purple" },
  CanFund: { text: "Appeal possible", icon: AppealIcon, color: "lightPurple" },
  Funded: { text: "I funded", icon: FundedIcon, color: "lightPurple" },
};

const getFundingRewards = (contributions: ClassicContribution[], closed: boolean) => {
  if (isUndefined(contributions) || contributions.length === 0) return 0;
  const contribution = contributions.reduce((acc, val) => {
    if (isUndefined(val?.rewardAmount) && isUndefined(val?.amount)) return acc;
    if (closed) {
      acc += val.rewardAmount === null ? -1 * Number(val.amount) : Number(val.rewardAmount) - Number(val.amount);
    } else {
      acc += Number(val.amount);
    }
    return acc;
  }, 0);
  return Number(formatUnits(BigInt(contribution), 18));
};

const CardLabel: React.FC<ICardLabels> = ({ disputeId, round, isList }) => {
  const { address } = useAccount();
  const { data: labelInfo, isLoading } = useLabelInfoQuery(address?.toLowerCase(), disputeId);
  const localRounds = getLocalRounds(labelInfo?.dispute?.disputeKitDispute);
  const rounds = labelInfo?.dispute?.rounds;
  const currentRound = rounds?.[round];

  const period = labelInfo?.dispute?.period;
  const hasVotedCurrentRound = !isUndefined(currentRound?.drawnJurors?.[0]?.vote?.choice);
  const isDrawnCurrentRound = currentRound?.drawnJurors.length !== 0;
  const hasVotedInDispute = rounds?.some((item) => !isUndefined(item.drawnJurors?.[0]?.vote?.choice));
  const isDrawnInDispute = rounds?.some((item) => item?.drawnJurors.length);
  const hasFundedCurrentRound = localRounds?.[round]?.contributions.length !== 0;
  const currentRoundFund = getFundingRewards(localRounds?.[round]?.contributions, period === "execution");
  const shifts = labelInfo?.dispute?.shifts;

  const contributions = useMemo(
    () =>
      localRounds?.reduce((acc, val) => {
        acc.push(...val.contributions);
        return acc;
      }, []),
    [localRounds]
  );

  const contributionRewards = useMemo(() => getFundingRewards(contributions, true), [contributions]);
  const hasFundedDispute = contributions?.length !== 0; // if ever funded the dispute in any round

  const labelData = useMemo(() => {
    if (period === "evidence") return LabelArgs.EvidenceTime;
    if (!isDrawnCurrentRound && period === "appeal")
      return hasFundedCurrentRound ? LabelArgs.Funded : LabelArgs.CanFund;

    if (!isDrawnCurrentRound && period === "execution" && hasFundedDispute) return LabelArgs.Funded;
    if (period === "execution" && hasVotedInDispute) return LabelArgs.Voted;
    if (period === "execution" && isDrawnInDispute && !hasVotedInDispute) return LabelArgs.DidNotVote;
    if (!isDrawnCurrentRound) return LabelArgs.NotDrawn;

    if (["commit", "vote"].includes(period ?? "") && !hasVotedCurrentRound) return LabelArgs.CanVote;
    if (hasVotedCurrentRound) return LabelArgs.Voted; // plus rewards if execution
    return LabelArgs.DidNotVote; // plus rewards if execution
  }, [
    hasFundedCurrentRound,
    hasVotedCurrentRound,
    hasFundedDispute,
    hasVotedInDispute,
    isDrawnCurrentRound,
    isDrawnInDispute,
    period,
  ]);

  const rewardsData = useMemo(() => {
    const shift = shifts?.reduce(
      (acc, val) => {
        acc.ethShift += Number(formatEther(val.ethAmount));
        acc.pnkShift += Number(formatUnits(val.pnkAmount, 18));
        return acc;
      },
      { ethShift: 0, pnkShift: 0 }
    );
    if (isUndefined(shift)) return undefined;
    shift.ethShift += contributionRewards;
    return shift;
  }, [contributionRewards, shifts]);

  return (
    <Container {...{ isList }}>
      {isLoading ? (
        <Skeleton width={130} height={14} />
      ) : (
        <>
          <Label {...labelData} />
          <RewardsContainer>
            {" "}
            {!isUndefined(rewardsData) && period === "execution" ? (
              <>
                <RewardsAndFundLabel value={rewardsData.ethShift.toString()} unit="ETH" />
                <RewardsAndFundLabel value={rewardsData.pnkShift.toString()} unit="PNK" />
              </>
            ) : null}
            {!isUndefined(currentRoundFund) && period === "appeal" ? (
              <RewardsAndFundLabel value={currentRoundFund.toString()} unit="ETH" isFund />
            ) : null}
          </RewardsContainer>
        </>
      )}
    </Container>
  );
};

export default CardLabel;