utils#formatError JavaScript Examples

The following examples show how to use utils#formatError. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From Artion-Client with GNU General Public License v3.0 4 votes vote down vote up
PaintBoard = () => {
  const dispatch = useDispatch();
  const history = useHistory();

  const {
    explorerUrl,
    apiUrl,
    fetchMintableCollections,
    getNonce,
    addUnlockableContent,
    checkBan,
  } = useApi();

  const { registerRoyalty } = useSalesContract();
  const { loadContract } = useContract();

  const { account, chainId } = useWeb3React();

  const imageRef = useRef();

  const [selected, setSelected] = useState([]);
  const [collections, setCollections] = useState([]);
  const [nft, setNft] = useState();
  const [type, setType] = useState();
  const [image, setImage] = useState(null);
  const [fee, setFee] = useState(null);

  const [name, setName] = useState('');
  const [symbol, setSymbol] = useState('');
  const [description, setDescription] = useState('');
  const [royalty, setRoyalty] = useState('');
  const [xtra, setXtra] = useState('');
  const [supply, setSupply] = useState(0);
  const [hasUnlockableContent, setHasUnlockableContent] = useState(false);
  const [unlockableContent, setUnlockableContent] = useState('');

  const [currentMintingStep, setCurrentMintingStep] = useState(0);
  const [isMinting, setIsMinting] = useState(false);

  const [lastMintedTnxId, setLastMintedTnxId] = useState('');

  const authToken = useSelector(state => state.ConnectWallet.authToken);

  const getFee = async () => {
    setFee(null);

    try {
      const contract = await loadContract(nft, FEE_ABI);
      const _fee = await contract.platformFee();
      setFee(parseFloat(_fee.toString()) / 10 ** 18);
    } catch {
      setFee(0);
    }
  };

  const getCollections = async () => {
    try {
      const { data } = await fetchMintableCollections(authToken);
      setCollections(data);
      if (data.length) {
        setSelected([data[0]]);
      }
    } catch (err) {
      console.log(err);
    }
  };

  useEffect(() => {
    if (authToken) {
      getCollections();
    }
  }, [authToken]);

  useEffect(() => {
    if (!nft) return;

    getFee();
  }, [nft]);

  useEffect(() => {
    dispatch(HeaderActions.toggleSearchbar(true));
  }, []);

  const onDrop = useCallback(acceptedFiles => {
    setImage(acceptedFiles[0]);
  }, []);

  const { getRootProps, getInputProps } = useDropzone({
    accept: accept.join(', '),
    multiple: false,
    onDrop,
    maxSize: 15728640,
  });

  const removeImage = () => {
    setImage(null);
    if (imageRef.current) {
      imageRef.current.value = '';
    }
  };

  const imageToBase64 = () => {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.readAsDataURL(image);
      reader.onload = () => {
        resolve(reader.result);
      };
      reader.onerror = err => {
        reject(err);
      };
    });
  };

  const validateMetadata = () => {
    return name !== '' && account !== '' && image;
  };

  const resetMintingStatus = () => {
    setTimeout(() => {
      setIsMinting(false);
      setCurrentMintingStep(0);
    }, 1000);
  };

  const mintNFT = async () => {
    if (!account) {
      showToast('info', 'Connect your wallet first');
      return;
    }
    if (chainId !== ChainId.FANTOM && chainId !== ChainId.FANTOM_TESTNET) {
      showToast('info', 'You are not connected to Fantom Opera Network');
      return;
    }
    const balance = await WalletUtils.checkBalance(account);

    if (balance < fee) {
      showToast(
        'custom',
        `Your balance should be at least ${fee} FTM to mint an NFT`
      );
      return;
    }

    let isBanned = await checkBan(account, authToken);

    if (isBanned) {
      showToast('error', 'You are banned from minting');
      return;
    }

    setLastMintedTnxId('');
    // show stepper
    setIsMinting(true);
    console.log('created from ', account);
    if (!validateMetadata()) {
      resetMintingStatus();
      return;
    }

    let signature;
    let addr;

    if (hasUnlockableContent && unlockableContent.length > 0) {
      const { data: nonce } = await getNonce(account, authToken);
      try {
        const signer = await getSigner();
        const msg = `Approve Signature on Artion.io with nonce ${nonce}`;
        signature = await signer.signMessage(msg);
        addr = ethers.utils.verifyMessage(msg, signature);
      } catch (err) {
        showToast(
          'error',
          'You need to sign the message to be able to update account settings.'
        );
        resetMintingStatus();
        return;
      }
    }

    let formData = new FormData();
    const base64 = await imageToBase64();
    formData.append('image', base64);
    formData.append('name', name);
    formData.append('account', account);
    formData.append('description', description);
    formData.append('symbol', symbol);
    formData.append('xtra', xtra);
    const _royalty = parseInt(royalty) * 100;
    formData.append('royalty', isNaN(_royalty) ? 0 : _royalty);
    try {
      let result = await axios({
        method: 'post',
        url: `${apiUrl}/ipfs/uploadImage2Server`,
        data: formData,
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: 'Bearer ' + authToken,
        },
      });

      console.log('upload image result is ');

      const jsonHash = result.data.jsonHash;

      const contract = await loadContract(
        nft,
        type === 721 ? SINGLE_NFT_ABI : MULTI_NFT_ABI
      );
      try {
        const args =
          type === 721 ? [account, jsonHash] : [account, supply, jsonHash];

        let tx;

        if (!fee) {
          tx = await contract.mint(...args);
        } else {
          const options = {
            value: ethers.utils.parseEther(fee.toString()),
            gasPrice: getHigherGWEI(),
          };
          const gasEstimate = await contract.estimateGas.mint(...args, options);
          options.gasLimit = calculateGasMargin(gasEstimate);
          tx = await contract.mint(...args, options);
        }
        setCurrentMintingStep(1);
        setLastMintedTnxId(tx.hash);

        setCurrentMintingStep(2);
        const confirmedTnx = await tx.wait();
        setCurrentMintingStep(3);
        let mintedTkId;
        if (type === 721) {
          const evtCaught = confirmedTnx.logs[0].topics;
          mintedTkId = BigNumber.from(evtCaught[3]);
        } else {
          mintedTkId = BigNumber.from(
            ethers.utils.hexDataSlice(confirmedTnx.logs[1].data, 0, 32)
          );
        }

        const royaltyTx = await registerRoyalty(
          nft,
          mintedTkId.toNumber(),
          isNaN(_royalty) ? 0 : _royalty
        );
        await royaltyTx.wait();

        // save unlockable content
        if (hasUnlockableContent && unlockableContent.length > 0) {
          await addUnlockableContent(
            nft,
            mintedTkId.toNumber(),
            unlockableContent,
            signature,
            addr,
            authToken
          );
        }

        showToast('success', 'New NFT item minted!');
        removeImage();
        setName('');
        setSymbol('');
        setDescription('');

        setTimeout(() => {
          history.push(`/explore/${nft}/${mintedTkId.toNumber()}`);
        }, 1000 + Math.random() * 2000);
      } catch (error) {
        showToast('error', formatError(error));
      }
    } catch (error) {
      showToast('error', error.message);
    }
    resetMintingStatus();
  };

  return (
    <div className={styles.container}>
      <Header border />
      <div className={styles.body}>
        <div className={styles.board}>
          <div {...getRootProps({ className: styles.uploadCont })}>
            <input {...getInputProps()} ref={imageRef} />
            {image ? (
              <>
                <img
                  className={styles.image}
                  src={URL.createObjectURL(image)}
                />
                <div className={styles.overlay}>
                  <CloseIcon className={styles.remove} onClick={removeImage} />
                </div>
              </>
            ) : (
              <>
                <div className={styles.uploadtitle}>
                  Drop files here or&nbsp;
                  <span
                    className={styles.browse}
                    onClick={() => imageRef.current?.click()}
                  >
                    browse
                  </span>
                </div>
                <div className={styles.uploadsubtitle}>
                  JPG, PNG, BMP, GIF Max 15mb.
                </div>
              </>
            )}
          </div>
        </div>
        <div className={styles.panel}>
          <div className={styles.panelInputs}>
            <div className={styles.panelLeft}>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Collection</p>
                <Select
                  options={collections}
                  disabled={isMinting}
                  values={selected}
                  onChange={([col]) => {
                    setSelected([col]);
                    setNft(col.erc721Address);
                    setType(col.type);
                  }}
                  className={styles.select}
                  placeholder="Choose Collection"
                  itemRenderer={({ item, methods }) => (
                    <div
                      key={item.erc721Address}
                      className={styles.collection}
                      onClick={() => {
                        methods.clearAll();
                        methods.addItem(item);
                      }}
                    >
                      <img
                        src={`https://cloudflare-ipfs.com/ipfs/${item.logoImageHash}`}
                        className={styles.collectionLogo}
                      />
                      <div className={styles.collectionName}>
                        {item.collectionName}
                      </div>
                    </div>
                  )}
                  contentRenderer={({ props: { values } }) =>
                    values.length > 0 ? (
                      <div className={styles.collection}>
                        <img
                          src={`https://cloudflare-ipfs.com/ipfs/${values[0].logoImageHash}`}
                          className={styles.collectionLogo}
                        />
                        <div className={styles.collectionName}>
                          {values[0].collectionName}
                        </div>
                      </div>
                    ) : (
                      <div className={styles.collection} />
                    )
                  }
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Name</p>
                <input
                  className={styles.formInput}
                  maxLength={40}
                  placeholder="Name"
                  value={name}
                  onChange={e => setName(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>{name.length}/40</div>
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Symbol</p>
                <input
                  className={styles.formInput}
                  maxLength={20}
                  placeholder="Symbol"
                  value={symbol}
                  onChange={e => setSymbol(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>{symbol.length}/20</div>
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Description</p>
                <textarea
                  className={cx(styles.formInput, styles.longInput)}
                  maxLength={120}
                  placeholder="Description"
                  value={description}
                  onChange={e => setDescription(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>
                  {description.length}/120
                </div>
              </div>
            </div>
            <div className={styles.panelRight}>
              {type === 1155 && (
                <div className={styles.formGroup}>
                  <p className={styles.formLabel}>Supply</p>
                  <PriceInput
                    className={styles.formInput}
                    placeholder="Supply"
                    decimals={0}
                    value={'' + supply}
                    onChange={setSupply}
                    disabled={isMinting}
                  />
                </div>
              )}
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Royalty (%)&nbsp;
                  <BootstrapTooltip
                    title="If you set a royalty here, you will get X percent of sales price each time an NFT is sold on our platform."
                    placement="top"
                  >
                    <HelpOutlineIcon />
                  </BootstrapTooltip>
                </p>
                <PriceInput
                  className={styles.formInput}
                  placeholder="Royalty"
                  decimals={2}
                  value={'' + royalty}
                  onChange={val =>
                    val[val.length - 1] === '.'
                      ? setRoyalty(val)
                      : setRoyalty(Math.min(100, +val))
                  }
                  disabled={isMinting}
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Link to IP Rights Document (Optional)&nbsp;
                  <BootstrapTooltip
                    title="Link to the document which proves your ownership of this image."
                    placement="top"
                  >
                    <HelpOutlineIcon />
                  </BootstrapTooltip>
                </p>
                <input
                  className={styles.formInput}
                  placeholder="Enter Link"
                  value={xtra}
                  onChange={e => setXtra(e.target.value)}
                  disabled={isMinting}
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Unlockable Content&nbsp;
                  <PurpleSwitch
                    checked={hasUnlockableContent}
                    onChange={e => {
                      setHasUnlockableContent(e.target.checked);
                      setUnlockableContent('');
                    }}
                    name="unlockableContent"
                  />
                </p>
                {hasUnlockableContent && (
                  <textarea
                    className={cx(styles.formInput, styles.longInput)}
                    maxLength={500}
                    placeholder="Unlockable Content"
                    value={unlockableContent}
                    onChange={e => setUnlockableContent(e.target.value)}
                    disabled={isMinting}
                  />
                )}
              </div>
            </div>
          </div>

          {isMinting && (
            <div>
              <Stepper activeStep={currentMintingStep} alternativeLabel>
                {mintSteps.map(label => (
                  <Step key={label}>
                    <StepLabel>{label}</StepLabel>
                  </Step>
                ))}
              </Stepper>
            </div>
          )}
          <div
            className={cx(
              styles.button,
              (isMinting || !account || !validateMetadata()) && styles.disabled
            )}
            onClick={
              isMinting || !account || !validateMetadata() ? null : mintNFT
            }
          >
            {isMinting ? (
              <ClipLoader size="16" color="white"></ClipLoader>
            ) : (
              'Mint'
            )}
          </div>
          <div className={styles.fee}>
            {fee !== null ? (
              <>
                <InfoIcon />
                &nbsp;{fee} FTM are charged to create a new NFT.
              </>
            ) : (
              <Skeleton width={330} height={22} />
            )}
          </div>
          <div className={styles.mintStatusContainer}>
            {lastMintedTnxId !== '' && (
              <a
                className={styles.tnxAnchor}
                target="_blank"
                rel="noopener noreferrer"
                href={`${explorerUrl}/tx/${lastMintedTnxId}`}
              >
                You can track the last transaction here ...
              </a>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
Example #2
Source File: index.jsx    From Artion-Client with GNU General Public License v3.0 4 votes vote down vote up
WFTMModal = ({ visible, onClose }) => {
  const { account, chainId } = useWeb3React();
  const { explorerUrl } = useApi();
  const { getWFTMBalance, wrapFTM, unwrapFTM } = useWFTMContract();

  const [loading, setLoading] = useState(false);
  const [balance, setBalance] = useState(0);
  const [wrappedBalance, setWrappedBalance] = useState(0);
  const [confirming, setConfirming] = useState(false);
  const [wrap, setWrap] = useState(true);
  const [amount, setAmount] = useState('');
  const [inputError, setInputError] = useState(null);

  const { price } = useSelector(state => state.Price);

  const getBalances = async (overrideLoading = false) => {
    if (!overrideLoading) {
      setLoading(true);
    }

    await window.ethereum.enable();
    const provider = new ethers.providers.Web3Provider(window.ethereum);

    let [ftmBal, wftmBal] = await Promise.all([
      await provider.getBalance(account),
      await getWFTMBalance(account),
    ]);

    setBalance(parseFloat(ftmBal.toString()) / 10 ** 18);
    setWrappedBalance(parseFloat(wftmBal.toString()) / 10 ** 18);

    if (!overrideLoading) {
      setLoading(false);
    }

    return [
      parseFloat(ftmBal.toString()) / 10 ** 18,
      parseFloat(wftmBal.toString()) / 10 ** 18,
    ];
  };

  const pollBalanceChange = async (initialFtmBal, initialWftmBal) => {
    setLoading(true);
    let timeout;
    let updated = false;

    await new Promise(
      resolve =>
        (timeout = setTimeout(
          () =>
            getBalances(true).then(([ftmBal, wftmBal]) => {
              if (ftmBal !== initialFtmBal || wftmBal !== initialWftmBal) {
                updated = true;
              }
              resolve();
            }),
          200
        ))
    );

    if (!updated) {
      await pollBalanceChange(initialFtmBal, initialWftmBal);
    }

    clearTimeout(timeout);
    return setLoading(false);
  };

  useEffect(() => {
    if (visible) {
      setLoading(false);
      setConfirming(false);
      setWrap(true);
      setAmount('');
      getBalances();
    }
  }, [visible, chainId]);

  const parseBalance = bal => {
    return bal.toFixed(4);
  };

  const isMax = () => {
    if (wrap) {
      return amount === (balance - 0.01).toString();
    }
    return amount === wrappedBalance.toString();
  };

  const onMax = () => {
    if (wrap) {
      setAmount((balance - 0.01).toString());
    } else {
      setAmount(wrappedBalance.toString());
    }
  };

  const handleWrapFTM = async () => {
    if (confirming || loading) return;

    setConfirming(true);
    try {
      const price = ethers.utils.parseEther(amount);
      if (wrap) {
        const tx = await wrapFTM(price, account);
        await tx.wait();
        await pollBalanceChange(balance, wrappedBalance);
        const toastId = showToast(
          'success',
          'Wrapped FTM successfully!',
          '',
          () => {
            toast.dismiss(toastId);
            window.open(`${explorerUrl}/tx/${tx.hash}`, '_blank');
          }
        );
      } else {
        const tx = await unwrapFTM(price);
        await tx.wait();
        await pollBalanceChange(balance, wrappedBalance);
        const toastId = showToast(
          'success',
          'Unwrap W-FTM successfully!',
          '',
          () => {
            toast.dismiss(toastId);
            window.open(`${explorerUrl}/tx/${tx.hash}`, '_blank');
          }
        );
      }
      setAmount('');
    } catch (err) {
      showToast('error', formatError(err));
      console.log(err);
    } finally {
      setConfirming(false);
    }
    getBalances();
  };

  return (
    <Modal
      visible={visible}
      title="FTM / WFTM Station"
      onClose={onClose}
      submitDisabled={
        confirming ||
        loading ||
        inputError ||
        amount.length === 0 ||
        parseFloat(amount) === 0 ||
        parseFloat(amount) > (wrap ? balance - 0.01 : wrappedBalance)
      }
      submitLabel={
        confirming || loading ? (
          <ClipLoader color="#FFF" size={16} />
        ) : wrap ? (
          'Wrap'
        ) : (
          'Unwrap'
        )
      }
      onSubmit={() =>
        amount.length &&
        parseFloat(amount) > 0 &&
        parseFloat(amount) <= (wrap ? balance - 0.01 : wrappedBalance) &&
        handleWrapFTM()
      }
    >
      <div className={cx(styles.swapContainer, !wrap && styles.reverse)}>
        <div className={styles.swapBox}>
          <div className={styles.symbol}>FTM</div>
          <div className={styles.swapBoxInner}>
            <div className={styles.balance}>
              Balance:{' '}
              {loading ? (
                <Skeleton width={60} height={20} />
              ) : (
                parseBalance(balance)
              )}
              {wrap && !isMax() && !loading && balance > 0 && (
                <div className={styles.max} onClick={onMax}>
                  (Max)
                </div>
              )}
            </div>
            <div className={styles.rightBox}>
              <PriceInput
                className={styles.input}
                placeholder="0.0"
                decimals={18}
                value={'' + amount}
                onChange={setAmount}
                onInputError={setInputError}
              />
              <div className={styles.usdVal}>
                ${formatNumber(((parseFloat(amount) || 0) * price).toFixed(2))}
              </div>
            </div>
          </div>
        </div>
        <div className={styles.swapbtn} onClick={() => setWrap(!wrap)}>
          <SwapVertIcon className={styles.icon} />
        </div>
        <div className={styles.swapBox}>
          <div className={styles.symbol}>WFTM</div>
          <div className={styles.swapBoxInner}>
            <div className={styles.balance}>
              Balance:{' '}
              {loading ? (
                <Skeleton width={60} height={20} />
              ) : (
                parseBalance(wrappedBalance)
              )}
              {!wrap && !isMax() && !loading && balance > 0 && (
                <div className={styles.max} onClick={onMax}>
                  (Max)
                </div>
              )}
            </div>
            <div className={styles.rightBox}>
              <PriceInput
                className={styles.input}
                placeholder="0.0"
                decimals={18}
                value={'' + amount}
                onChange={setAmount}
                onInputError={setInputError}
              />
              <div className={styles.usdVal}>
                ${formatNumber(((parseFloat(amount) || 0) * price).toFixed(2))}
              </div>
            </div>
          </div>
        </div>
      </div>
      <InputError text={inputError} />
    </Modal>
  );
}
Example #3
Source File: index.js    From Artion-Client with GNU General Public License v3.0 4 votes vote down vote up
CollectionCreate = ({ isRegister }) => {
  const dispatch = useDispatch();
  const history = useHistory();

  const { account } = useWeb3React();
  const { apiUrl, getNonce } = useApi();
  const {
    getFactoryContract,
    getPrivateFactoryContract,
    getArtFactoryContract,
    getPrivateArtFactoryContract,
    createNFTContract,
  } = useFactoryContract();

  const inputRef = useRef(null);

  const { authToken } = useSelector(state => state.ConnectWallet);

  const [deploying, setDeploying] = useState(false);
  const [creating, setCreating] = useState(false);
  const [logo, setLogo] = useState(null);
  const [anchorEl, setAnchorEl] = useState(null);
  const [selected, setSelected] = useState([]);
  const [name, setName] = useState('');
  const [nameError, setNameError] = useState(null);
  const [symbol, setSymbol] = useState('');
  const [symbolError, setSymbolError] = useState(null);
  const [description, setDescription] = useState('');
  const [descriptionError, setDescriptionError] = useState(null);
  const [royalty, setRoyalty] = useState('');
  const [feeRecipient, setFeeRecipient] = useState('');
  const [recipientError, setRecipientError] = useState(null);
  const [email, setEmail] = useState('');
  const [emailError, setEmailError] = useState(null);
  const [address, setAddress] = useState('');
  const [addressError, setAddressError] = useState('');
  const [siteUrl, setSiteUrl] = useState('');
  const [discord, setDiscord] = useState('');
  const [twitterHandle, setTwitterHandle] = useState('');
  const [instagramHandle, setInstagramHandle] = useState('');
  const [mediumHandle, setMediumHandle] = useState('');
  const [telegram, setTelegram] = useState('');
  const [isPrivate, setIsPrivate] = useState(false);
  const [isSingle, setIsSingle] = useState(true);

  const isMenuOpen = Boolean(anchorEl);

  useEffect(() => {
    dispatch(HeaderActions.toggleSearchbar(true));
  }, []);

  useEffect(() => {
    setLogo(null);
    setAnchorEl(null);
    setSelected([]);
    setName('');
    setNameError(null);
    setSymbol('');
    setSymbolError(null);
    setDescription('');
    setDescriptionError(null);
    setEmail('');
    setEmailError(null);
    setAddress('');
    setAddressError(null);
    setSiteUrl('');
    setDiscord('');
    setTwitterHandle('');
    setInstagramHandle('');
    setMediumHandle('');
    setTelegram('');
  }, [isRegister]);

  const options = Categories.filter(cat => selected.indexOf(cat.id) === -1);
  const selectedCategories = Categories.filter(
    cat => selected.indexOf(cat.id) > -1
  );

  const removeImage = () => {
    setLogo(null);
  };

  const handleFileSelect = e => {
    if (e.target.files.length > 0) {
      const file = e.target.files[0];

      const reader = new FileReader();

      reader.onload = function(e) {
        setLogo(e.target.result);
      };

      reader.readAsDataURL(file);
    }
  };

  const validateName = () => {
    if (name.length === 0) {
      setNameError("This field can't be blank");
    } else {
      setNameError(null);
    }
  };

  const validateSymbol = () => {
    if (symbol.length === 0) {
      setSymbolError("This field can't be blank");
    } else if (symbol.includes(' ')) {
      setSymbolError("Symbol can't include spaces");
    } else {
      setSymbolError(null);
    }
  };

  const validateDescription = () => {
    if (description.length === 0) {
      setDescriptionError("This field can't be blank");
    } else {
      setDescriptionError(null);
    }
  };

  const validateFeeRecipient = () => {
    if (feeRecipient.length === 0) {
      setRecipientError("This field can't be blank");
    } else if (!isAddress(feeRecipient)) {
      setRecipientError('Invalid address');
    } else {
      setRecipientError(null);
    }
  };

  const validEmail = email => /(.+)@(.+){2,}\.(.+){2,}/.test(email);

  const validateEmail = () => {
    if (email.length === 0) {
      setEmailError("This field can't be blank");
    } else if (validEmail(email)) {
      setEmailError(null);
    } else {
      setEmailError('Invalid email address.');
    }
  };

  const validateAddress = () => {
    if (address.length === 0) {
      setAddressError("This field can't be blank");
    } else {
      setAddressError(null);
    }
  };

  const handleMenuOpen = e => {
    if (selected.length < 3) {
      setAnchorEl(e.currentTarget);
    }
  };

  const handleMenuClose = () => {
    setAnchorEl(null);
  };

  const selectCategory = catId => {
    setSelected([...selected, catId]);
    if (selected.length === 2) {
      setAnchorEl(null);
    }
  };

  const deselectCategory = catId => {
    setSelected(selected.filter(id => id !== catId));
  };

  const isValid = (() => {
    if (!logo) return false;
    if (nameError) return false;
    if (descriptionError) return false;
    if (addressError) return false;
    if (!isRegister && (symbol.length === 0 || symbol.includes(' ')))
      return false;
    if (email.length === 0) return false;
    if (!validEmail(email)) return false;
    if (isRegister && !isAddress(feeRecipient)) return false;
    return true;
  })();

  const clipImage = (image, clipX, clipY, clipWidth, clipHeight, cb) => {
    const CANVAS_SIZE = 128;
    const canvas = document.createElement('canvas');
    canvas.width = CANVAS_SIZE;
    canvas.height = CANVAS_SIZE;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(
      image,
      clipX,
      clipY,
      clipWidth,
      clipHeight,
      0,
      0,
      CANVAS_SIZE,
      CANVAS_SIZE
    );
    cb(canvas.toDataURL());
  };

  const handleRegister = async () => {
    if (creating) return;

    setCreating(true);

    const img = new Image();
    img.onload = function() {
      const w = this.width;
      const h = this.height;
      const size = Math.min(w, h);
      const x = (w - size) / 2;
      const y = (h - size) / 2;
      clipImage(img, x, y, size, size, async logodata => {
        try {
          const { data: nonce } = await getNonce(account, authToken);

          let signature;
          let signatureAddress;

          try {
            const signer = await getSigner();
            const msg = `Approve Signature on Artion.io with nonce ${nonce}`;

            signature = await signer.signMessage(msg);
            signatureAddress = ethers.utils.verifyMessage(msg, signature);
          } catch (err) {
            toast(
              'error',
              'You need to sign the message to be able to register a collection.'
            );
            setCreating(false);
            return;
          }

          const formData = new FormData();
          formData.append('collectionName', name);
          formData.append('erc721Address', address);
          formData.append('imgData', logodata);
          const result = await axios({
            method: 'post',
            url: `${apiUrl}/ipfs/uploadCollectionImage2Server`,
            data: formData,
            headers: {
              'Content-Type': 'multipart/form-data',
              Authorization: `Bearer ${authToken}`,
            },
          });

          const logoImageHash = result.data.data;
          const data = {
            email,
            erc721Address: address,
            collectionName: name,
            description,
            categories: selected.join(','),
            logoImageHash,
            siteUrl,
            discord,
            twitterHandle,
            instagramHandle,
            mediumHandle,
            telegram,
            signature,
            signatureAddress,
            royalty,
            feeRecipient,
          };

          await axios({
            method: 'post',
            url: `${apiUrl}/collection/collectiondetails`,
            data: JSON.stringify(data),
            headers: {
              'Content-Type': 'application/json',
              Authorization: `Bearer ${authToken}`,
            },
          });

          toast(
            'success',
            'Application submitted!',
            'Your collection registration application is successfully submitted for review.\nOnce approved, you will get an email notification.'
          );

          setCreating(false);

          history.push('/explore');
        } catch (e) {
          console.log('Error: ', e);
          setCreating(false);
        }
      });
    };
    img.src = logo;
  };

  const handleCreate = async () => {
    setDeploying(true);
    try {
      const tx = await createNFTContract(
        isSingle
          ? isPrivate
            ? await getPrivateFactoryContract()
            : await getFactoryContract()
          : isPrivate
          ? await getPrivateArtFactoryContract()
          : await getArtFactoryContract(),
        name,
        symbol,
        ethers.utils.parseEther('100'),
        account
      );
      const res = await tx.wait();
      res.events.map(evt => {
        if (
          evt.topics[0] ===
          '0x2d49c67975aadd2d389580b368cfff5b49965b0bd5da33c144922ce01e7a4d7b'
        ) {
          setDeploying(false);
          setCreating(true);

          const address = ethers.utils.hexDataSlice(evt.data, 44);

          const img = new Image();
          img.onload = function() {
            const w = this.width;
            const h = this.height;
            const size = Math.min(w, h);
            const x = (w - size) / 2;
            const y = (h - size) / 2;
            clipImage(img, x, y, size, size, async logodata => {
              try {
                const { data: nonce } = await getNonce(account, authToken);

                let signature;
                try {
                  const signer = await getSigner();
                  signature = await signer.signMessage(
                    `Approve Signature on Artion.io with nonce ${nonce}`
                  );
                } catch (err) {
                  toast(
                    'error',
                    'You need to sign the message to be able to create a collection.'
                  );
                  setCreating(false);
                  return;
                }

                const formData = new FormData();
                formData.append('collectionName', name);
                formData.append('erc721Address', address);
                formData.append('imgData', logodata);
                const result = await axios({
                  method: 'post',
                  url: `${apiUrl}/ipfs/uploadCollectionImage2Server`,
                  data: formData,
                  headers: {
                    'Content-Type': 'multipart/form-data',
                    Authorization: `Bearer ${authToken}`,
                  },
                });
                const logoImageHash = result.data.data;
                const data = {
                  email,
                  erc721Address: address,
                  collectionName: name,
                  description,
                  categories: selected.join(','),
                  logoImageHash,
                  siteUrl,
                  discord,
                  twitterHandle,
                  instagramHandle,
                  mediumHandle,
                  telegram,
                  signature,
                };
                await axios({
                  method: 'post',
                  url: `${apiUrl}/collection/collectiondetails`,
                  data: JSON.stringify(data),
                  headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${authToken}`,
                  },
                });

                toast('success', 'Collection created successfully!');

                setCreating(false);

                history.push('/explore');
              } catch (e) {
                setCreating(false);
              }
            });
          };
          img.src = logo;
        }
      });
    } catch (err) {
      showToast('error', formatError(err));
      console.log(err);
      setDeploying(false);
    }
  };

  const menuId = 'select-category-menu';
  const renderMenu = (
    <Menu
      anchorEl={anchorEl}
      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
      id={menuId}
      keepMounted
      transformOrigin={{ vertical: 'top', horizontal: 'right' }}
      open={isMenuOpen}
      onClose={handleMenuClose}
      classes={{
        paper: styles.menu,
      }}
    >
      {options.map((cat, idx) => (
        <MenuItem
          key={idx}
          className={styles.category}
          onClick={() => selectCategory(cat.id)}
        >
          <img src={cat.icon} />
          <span className={styles.categoryLabel}>{cat.label}</span>
        </MenuItem>
      ))}
    </Menu>
  );

  return (
    <div className={styles.container}>
      <Header border />
      <div className={styles.inner}>
        <div className={styles.title}>
          {isRegister ? 'Register' : 'Create New'} Collection
        </div>
        <br />
        <div style={{ fontSize: '13px' }}>
          Please submit using the owner address of the collection. If you cannot
          use the owner address, please email us on [email protected]
          with the information below (and proof of collection ownership, such as
          from the collection's official email address).
        </div>

        {!isRegister && (
          <div className={styles.inputGroup}>
            <RadioGroup
              className={styles.inputWrapper}
              value={JSON.stringify(isPrivate)}
              onChange={e => setIsPrivate(e.currentTarget.value === 'true')}
            >
              <FormControlLabel
                classes={{
                  root: cx(styles.option, !isPrivate && styles.active),
                  label: styles.optionLabel,
                }}
                value="false"
                control={<CustomRadio color="primary" />}
                label="Allow others mint NFTs under my collection"
              />
              <FormControlLabel
                classes={{
                  root: cx(styles.option, isPrivate && styles.active),
                  label: styles.optionLabel,
                }}
                value="true"
                control={<CustomRadio color="primary" />}
                label="Only I can mint NFTs under my collection"
              />
            </RadioGroup>
          </div>
        )}

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>Logo Image *</div>
          <div className={styles.inputSubTitle}>
            This image will also be used for navigation. 300x300 recommended.
          </div>
          <div className={styles.inputWrapper}>
            <div className={styles.logoUploadBox}>
              {logo ? (
                <>
                  <img src={logo} />
                  <div className={styles.removeOverlay}>
                    <div className={styles.removeIcon} onClick={removeImage}>
                      <img src={closeIcon} />
                    </div>
                  </div>
                </>
              ) : (
                <div
                  className={styles.uploadOverlay}
                  onClick={() => inputRef.current?.click()}
                >
                  <input
                    ref={inputRef}
                    type="file"
                    accept="image/*"
                    hidden
                    onChange={handleFileSelect}
                  />
                  <div className={styles.upload}>
                    <div className={styles.uploadInner}>
                      <img src={uploadIcon} />
                    </div>
                    <div className={styles.plusIcon}>
                      <img src={plusIcon} />
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>Name *</div>
          <div className={styles.inputWrapper}>
            <input
              className={cx(styles.input, nameError && styles.hasError)}
              maxLength={20}
              placeholder="Collection Name"
              value={name}
              onChange={e => setName(e.target.value)}
              onBlur={validateName}
            />
            <div className={styles.lengthIndicator}>{name.length}/20</div>
            {nameError && <div className={styles.error}>{nameError}</div>}
          </div>
        </div>

        {!isRegister && (
          <div className={styles.inputGroup}>
            <div className={styles.inputTitle}>
              Symbol *&nbsp;
              <BootstrapTooltip
                title="A symbol is used when we deploy your NFT contract. If you are not sure about symbol, be aware that name and symbol share the same value."
                placement="top"
              >
                <HelpOutlineIcon />
              </BootstrapTooltip>
            </div>
            <div className={styles.inputWrapper}>
              <input
                className={cx(styles.input, symbolError && styles.hasError)}
                maxLength={20}
                placeholder="Collection Symbol"
                value={symbol}
                onChange={e => setSymbol(e.target.value)}
                onBlur={validateSymbol}
              />
              <div className={styles.lengthIndicator}>{symbol.length}/20</div>
              {symbolError && <div className={styles.error}>{symbolError}</div>}
            </div>
          </div>
        )}

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>Description *</div>
          <div className={styles.inputWrapper}>
            <textarea
              className={cx(
                styles.input,
                styles.longInput,
                descriptionError && styles.hasError
              )}
              maxLength={200}
              placeholder="Provide your description for your collection"
              value={description}
              onChange={e => setDescription(e.target.value)}
              onBlur={validateDescription}
            />
            <div className={styles.lengthIndicator}>
              {description.length}/200
            </div>
            {descriptionError && (
              <div className={styles.error}>{descriptionError}</div>
            )}
          </div>
        </div>

        {isRegister && (
          <div className={styles.inputGroup}>
            <div className={styles.inputTitle}>
              Royalty *&nbsp;
              <BootstrapTooltip
                title="Each NFT under this collection exchanged through Artion will have a percentage of sale given to nominated wallet address."
                placement="top"
              >
                <HelpOutlineIcon />
              </BootstrapTooltip>
            </div>
            <div className={styles.inputWrapper}>
              <PriceInput
                className={styles.input}
                placeholder="Collection Royalty"
                decimals={2}
                value={'' + royalty}
                onChange={val =>
                  val[val.length - 1] === '.'
                    ? setRoyalty(val)
                    : setRoyalty(Math.min(100, +val))
                }
              />
            </div>
          </div>
        )}

        {isRegister && (
          <div className={styles.inputGroup}>
            <div className={styles.inputTitle}>
              Fee Recipient *&nbsp;
              <BootstrapTooltip
                title="The nominated Fantom Opera Network wallet address to receive royalties from each sale in this collection."
                placement="top"
              >
                <HelpOutlineIcon />
              </BootstrapTooltip>
            </div>
            <div className={styles.inputWrapper}>
              <input
                className={cx(styles.input, recipientError && styles.hasError)}
                placeholder="Fee Recipient"
                value={feeRecipient}
                onChange={e => setFeeRecipient(e.target.value)}
                onBlur={validateFeeRecipient}
              />
              {recipientError && (
                <div className={styles.error}>{recipientError}</div>
              )}
            </div>
          </div>
        )}

        {!isRegister && (
          <div className={styles.inputGroup}>
            <RadioGroup
              className={styles.inputWrapper}
              value={JSON.stringify(isSingle)}
              onChange={e => setIsSingle(e.currentTarget.value === 'true')}
            >
              <FormControlLabel
                classes={{
                  root: cx(styles.option, isSingle && styles.active),
                  label: styles.optionLabel,
                }}
                value="true"
                control={<CustomRadio color="primary" />}
                label="Single Token Standard"
              />
              <FormControlLabel
                classes={{
                  root: cx(styles.option, !isSingle && styles.active),
                  label: styles.optionLabel,
                }}
                value="false"
                control={<CustomRadio color="primary" />}
                label="Multi Token Standard"
              />
            </RadioGroup>
          </div>
        )}

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>Category</div>
          <div className={styles.inputSubTitle}>
            Adding a category will help make your item discoverable on Fantom.
          </div>
          <div className={styles.inputSubTitle}>
            For more information, read{' '}
            <a
              href="https://docs.fantom.foundation/tutorials/collection-and-bundle-guide-on-artion"
              target="_blank"
              rel="noopener noreferrer"
            >
              this
            </a>
          </div>
          <div className={cx(styles.inputWrapper, styles.categoryList)}>
            <div
              className={cx(
                styles.categoryButton,
                selected.length === 3 && styles.disabled
              )}
              onClick={handleMenuOpen}
            >
              Add Category
            </div>
            {selectedCategories.map((cat, idx) => (
              <div
                className={styles.selectedCategory}
                key={idx}
                onClick={() => deselectCategory(cat.id)}
              >
                <img src={cat.icon} className={styles.categoryIcon} />
                <span className={styles.categoryLabel}>{cat.label}</span>
                <CloseIcon className={styles.closeIcon} />
              </div>
            ))}
          </div>
        </div>

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>Links *</div>
          <div className={styles.inputWrapper}>
            <div className={styles.linksWrapper}>
              {isRegister && (
                <>
                  <div
                    className={cx(
                      styles.linkItem,
                      addressError && styles.hasError
                    )}
                  >
                    <div className={styles.linkIconWrapper}>
                      <img src={nftIcon} className={styles.linkIcon} />
                    </div>
                    <input
                      className={styles.linkInput}
                      placeholder="Enter your collection's address"
                      value={address}
                      onChange={e => setAddress(e.target.value)}
                      onBlur={validateAddress}
                    />
                  </div>
                  {addressError && (
                    <div className={styles.error}>{addressError}</div>
                  )}
                </>
              )}
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={webIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your website url"
                  value={siteUrl}
                  onChange={e => setSiteUrl(e.target.value)}
                />
              </div>
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={discordIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your Discord url"
                  value={discord}
                  onChange={e => setDiscord(e.target.value)}
                />
              </div>
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={twitterIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your Twitter profile link"
                  value={twitterHandle}
                  onChange={e => setTwitterHandle(e.target.value)}
                />
              </div>
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={instagramIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your Instagram profile link"
                  value={instagramHandle}
                  onChange={e => setInstagramHandle(e.target.value)}
                />
              </div>
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={mediumIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your Medium profile link"
                  value={mediumHandle}
                  onChange={e => setMediumHandle(e.target.value)}
                />
              </div>
              <div className={styles.linkItem}>
                <div className={styles.linkIconWrapper}>
                  <img src={telegramIcon} className={styles.linkIcon} />
                </div>
                <input
                  className={styles.linkInput}
                  placeholder="Enter your Telegram profile link"
                  value={telegram}
                  onChange={e => setTelegram(e.target.value)}
                />
              </div>
            </div>
          </div>
        </div>

        <div className={styles.inputGroup}>
          <div className={styles.inputTitle}>
            Contact Email *&nbsp;
            <BootstrapTooltip
              title="We will use this email to notify you about your collection application. This will not be shared with others."
              placement="top"
            >
              <HelpOutlineIcon />
            </BootstrapTooltip>
          </div>
          <div className={styles.inputWrapper}>
            <input
              className={cx(styles.input, emailError && styles.hasError)}
              placeholder="Email Address"
              value={email}
              onChange={e => setEmail(e.target.value)}
              onBlur={validateEmail}
            />
            {emailError && <div className={styles.error}>{emailError}</div>}
          </div>
        </div>

        <div className={styles.buttonsWrapper}>
          {isRegister ? (
            <div
              className={cx(
                styles.createButton,
                (creating || !isValid) && styles.disabled
              )}
              onClick={isValid ? handleRegister : null}
            >
              {creating ? <ClipLoader color="#FFF" size={16} /> : 'Submit'}
            </div>
          ) : (
            <div
              className={cx(
                styles.createButton,
                (creating || deploying || !isValid) && styles.disabled
              )}
              onClick={isValid && !creating && !deploying ? handleCreate : null}
            >
              {creating ? (
                <ClipLoader color="#FFF" size={16} />
              ) : deploying ? (
                'Deploying'
              ) : (
                'Create'
              )}
            </div>
          )}
        </div>
        {!isRegister && (
          <div className={styles.fee}>
            <InfoIcon />
            &nbsp;100 FTMs are charged to create a new collection.
          </div>
        )}
      </div>
      {renderMenu}
    </div>
  );
}