kleros/kleros-v2

View on GitHub
web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";

import Modal from "react-modal";
import { toast } from "react-toastify";
import { useWalletClient, usePublicClient, useConfig } from "wagmi";

import { Textarea, Button, FileUploader } from "@kleros/ui-components-library";

import { useAtlasProvider } from "context/AtlasProvider";
import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated";
import { Roles } from "utils/atlas";
import { wrapWithToast, OPTIONS as toastOptions } from "utils/wrapWithToast";

import EnsureAuth from "components/EnsureAuth";
import { EnsureChain } from "components/EnsureChain";
import { isEmpty } from "src/utils";

const StyledModal = styled(Modal)`
  position: absolute;
  top: 50%;
  left: 50%;
  right: auto;
  bottom: auto;
  margin-right: -50%;
  transform: translate(-50%, -50%);
  height: auto;
  width: 80%;
  border: 1px solid ${({ theme }) => theme.stroke};
  border-radius: 3px;
  background-color: ${({ theme }) => theme.whiteBackground};

  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16px;
  gap: 16px;
`;

const StyledTextArea = styled(Textarea)`
  width: 100%;
  height: 200px;
`;

const StyledFileUploader = styled(FileUploader)`
  width: 100%;
`;

const ButtonArea = styled.div`
  width: 100%;
  display: flex;
  justify-content: space-between;
`;

const SubmitEvidenceModal: React.FC<{
  isOpen: boolean;
  evidenceGroup: bigint;
  close: () => void;
}> = ({ isOpen, evidenceGroup, close }) => {
  const { data: walletClient } = useWalletClient();
  const publicClient = usePublicClient();
  const wagmiConfig = useConfig();
  const [isSending, setIsSending] = useState(false);
  const [message, setMessage] = useState("");
  const [file, setFile] = useState<File>();
  const { uploadFile } = useAtlasProvider();

  const isDisabled = useMemo(() => isSending || isEmpty(message), [isSending, message]);

  const submitEvidence = useCallback(async () => {
    try {
      setIsSending(true);
      const evidenceJSON = await constructEvidence(uploadFile, message, file);

      const { request } = await simulateEvidenceModuleSubmitEvidence(wagmiConfig, {
        args: [BigInt(evidenceGroup), JSON.stringify(evidenceJSON)],
      });

      if (!walletClient || !publicClient) return;
      await wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
        .then(() => {
          setMessage("");
          close();
        })
        .finally(() => setIsSending(false));
    } catch (error) {
      setIsSending(false);
      toast.error("Failed to submit evidence.", toastOptions);
      console.error("Error in submitEvidence:", error);
    }
  }, [publicClient, wagmiConfig, walletClient, close, evidenceGroup, file, message, setIsSending, uploadFile]);

  return (
    <StyledModal {...{ isOpen }}>
      <h1>Submit New Evidence</h1>
      <StyledTextArea value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Your Arguments" />
      <StyledFileUploader callback={(file: File) => setFile(file)} />
      <ButtonArea>
        <Button variant="secondary" disabled={isSending} text="Return" onClick={close} />
        <EnsureChain>
          <EnsureAuth>
            <Button text="Submit" isLoading={isSending} disabled={isDisabled} onClick={submitEvidence} />
          </EnsureAuth>
        </EnsureChain>
      </ButtonArea>
    </StyledModal>
  );
};

const constructEvidence = async (
  uploadFile: (file: File, role: Roles) => Promise<string | null>,
  msg: string,
  file?: File
) => {
  let fileURI: string | null = null;
  if (file) {
    toast.info("Uploading to IPFS", toastOptions);
    fileURI = await uploadFile(file, Roles.Evidence);
    if (!fileURI) throw new Error("Error uploading evidence file");
  }
  return { name: "Evidence", description: msg, fileURI };
};

export default SubmitEvidenceModal;