hooks#useAlert TypeScript Examples

The following examples show how to use hooks#useAlert. 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: useProgram.ts    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
useProgram = (id?: string): [ProgramModel?, Metadata?] => {
  const alert = useAlert();

  const [program, setProgram] = useState<ProgramModel>();

  const metadata = useMemo(() => {
    const meta = program?.meta?.meta;

    if (meta) {
      return JSON.parse(meta) as Metadata;
    }
  }, [program]);

  useEffect(() => {
    if (id) {
      getProgram(id).then(({ result }) => setProgram(result)).catch((err: RPCResponseError) => alert.error(err.message));
    } 
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id]);

  return [program, metadata];
}
Example #2
Source File: Hint.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
Hint: FC<Params> = ({ children }) => {
  const alert = useAlert();

  const handleClick = () => {
    alert.error(`${children}`);
  };

  return (
    <div className={styles.hint}>
      <HelpCircle className={styles.icon} size="16" onClick={handleClick} />
    </div>
  );
}
Example #3
Source File: Message.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
Message = ({ message }: Props) => {
  const { id } = message;

  const { api } = useApi();
  const { account } = useAccount();
  const alert = useAlert();

  const showErrorAlert = (error: string) => {
    alert.error(error);
  };

  const showSuccessAlert = (data: ISubmittableResult) => {
    if (!data.status.isBroadcast) {
      alert.success(`Status: ${data.status}`);
    }
  };

  const handleClaimButtonClick = () => {
    if (account) {
      const { address, meta } = account;

      api.claimValueFromMailbox.submit(id);
      web3FromSource(meta.source)
        .then(({ signer }) => api.claimValueFromMailbox.signAndSend(address, { signer }, showSuccessAlert))
        .catch((error: Error) => showErrorAlert(error.message));
    }
  };

  return (
    <div className={styles.message}>
      <pre className={styles.pre}>{getPreformattedText(message)}</pre>
      <div>
        <ReplyLink to={id} />
        <Button text="Claim value" icon={claimIcon} color="secondary" size="small" onClick={handleClaimButtonClick} />
      </div>
    </div>
  );
}
Example #4
Source File: CopiedInfo.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
CopiedInfo = ({ title, info }: Props) => {
  const alert = useAlert();

  const handleClick = () => copyToClipboard(info, alert);

  return (
    <div>
      <p>{title}:</p>
      <p className={styles.info}>{info}</p>
      <Button icon={copySVG} color="transparent" className={styles.copyButton} onClick={handleClick} />
    </div>
  );
}
Example #5
Source File: MetaFile.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
MetaFile = (props: Props) => {
  const alert = useAlert();
  const metaFieldRef = useRef<HTMLInputElement>(null);

  const { file, className, onUpload, onDelete } = props;

  const uploadMetaFile = () => {
    metaFieldRef.current?.click();
  };

  const handleChangeMetaFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files && event.target.files.length) {
      const isCorrectFormat = checkFileFormat(event.target.files[0]);

      if (isCorrectFormat) {
        onUpload(event.target.files[0]);
      } else {
        alert.error('Wrong file format');
      }

      event.target.value = '';
    }
  };

  return (
    <div className={clsx(styles.upload, className)}>
      <label htmlFor="meta" className={styles.caption}>
        Metadata file:
      </label>
      <div className={styles.block}>
        <Field
          id="meta"
          name="meta"
          className={styles.hidden}
          type="file"
          innerRef={metaFieldRef}
          onChange={handleChangeMetaFile}
        />
        {file ? (
          <div className={clsx(styles.value, styles.filename)}>
            {file.name}
            <button type="button" onClick={onDelete}>
              <Trash2 color="#ffffff" size="20" strokeWidth="1" />
            </button>
          </div>
        ) : (
          <Button
            text="Select file"
            type="button"
            color="secondary"
            className={styles.button}
            onClick={uploadMetaFile}
          />
        )}
      </div>
    </div>
  );
}
Example #6
Source File: Provider.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
useEditor = () => {
  const alert = useAlert();
  const [isBuildDone, setIsBuildDone] = useState(false);

  useEffect(() => {
    let timerId: any;

    if (localStorage.getItem(LOCAL_STORAGE.PROGRAM_COMPILE_ID)) {
      const id = localStorage.getItem(LOCAL_STORAGE.PROGRAM_COMPILE_ID);

      timerId = setInterval(() => {
        fetch(WASM_COMPILER_GET, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json;charset=utf-8',
          },
          body: JSON.stringify({ id }),
        })
          .then((data) => data.json())
          .then((json) => {
            const zip = new JSZip();

            zip.loadAsync(json.file.data).then((data) => {
              data.generateAsync({ type: 'blob' }).then((val) => {
                saveAs(val, `program.zip`);
                setIsBuildDone(false);
                localStorage.removeItem(LOCAL_STORAGE.PROGRAM_COMPILE_ID);
                clearInterval(timerId);
                alert.success('Program is ready!');
              });
            });
          })
          .catch((err) => console.error(err));
      }, 20000);
    }

    return () => {
      clearInterval(timerId);
    };
  }, [isBuildDone, alert]);

  return { isBuildDone, setIsBuildDone };
}
Example #7
Source File: UserProgram.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
UserProgram = (props: Props) => {
  const alert = useAlert();

  const { program, isMetaLinkActive = true } = props;

  return (
    <div className={styles.programsListItem} key={program.id}>
      <div className={styles.programWrapper}>
        <span
          className={clsx(
            styles.programsListIndicator,
            program.initStatus === ProgramStatus.Success && styles['program-item-success'],
            program.initStatus === ProgramStatus.Failed && styles['program-item-failure'],
            program.initStatus === ProgramStatus.InProgress && styles['program-item-loading']
          )}
        />
        <div className={styles.programWrapperName}>
          <div className={styles.programsListName}>
            <Link className={styles.programLink} to={`/program/${program.id}`}>
              {program.name && fileNameHandler(program.name)}
            </Link>
          </div>
        </div>
        <div className={styles.programsCopyId}>
          <button type="button" onClick={() => copyToClipboard(program.id, alert, 'Program ID copied')}>
            <img src={Copy} alt="copy program ID" />
          </button>
        </div>
      </div>
      <div className={styles.programWrapperData}>
        <div className={styles.programsListInfo}>
          Timestamp:
          <span className={styles.programsListInfoData}>{program.timestamp && formatDate(program.timestamp)}</span>
        </div>
      </div>

      <div className={styles.programsListBtns}>
        <Link to={`/send/message/${program.id}`} className={styles.allProgramsItemSendMessage}>
          <img src={MessageIllustration} alt="Send message to program" />
        </Link>
        <Link
          to={generatePath(routes.meta, { programId: program.id })}
          tabIndex={Number(isMetaLinkActive)}
          className={clsx(styles.allProgramsItemUpload, !isMetaLinkActive && styles.linkInactive)}
        >
          <img src={UploadIcon} alt="Upload metadata" />
        </Link>
      </div>
    </div>
  );
}
Example #8
Source File: App.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
Component = () => {
  globalStyles();
  const alert = useAlert();
  const events = useEvents();
  const { isApiReady } = useApi();
  const [searchParams, setSearchParams] = useSearchParams();
  useLoggedInAccount();

  useEffect(() => {
    if (isApiReady) {
      subscribeToEvents(alert);
    }
  }, [isApiReady, alert]);

  useEffect(() => {
    const urlNodeAddress = searchParams.get(NODE_ADRESS_URL_PARAM);

    if (!urlNodeAddress) {
      searchParams.set(NODE_ADRESS_URL_PARAM, nodeApi.address);
      setSearchParams(searchParams, { replace: true });
    }
  }, [searchParams, setSearchParams]);

  const isFooterHidden = () => {
    const locationPath = window.location.pathname.replaceAll('/', '');
    const privacyPath = routes.privacyPolicy.replaceAll('/', '');
    const termsOfUsePath = routes.termsOfUse.replaceAll('/', '');
    return locationPath === privacyPath || locationPath === termsOfUsePath;
  };

  // we'll get rid of multiple paths in one route anyway, so temp solution
  const getMultipleRoutes = (paths: string[], element: JSX.Element) =>
    paths.map((path) => <Route key={path} path={path} element={element} />);

  return (
    <div className="app">
      <Header />
      <Main>
        {isApiReady ? (
          <Routes>
            {getMultipleRoutes(mainRoutes, <Programs />)}
            {getMultipleRoutes(utilRoutes, <Document />)}

            {/* temp solution since in react-router v6 optional parameters are gone */}
            <Route path={routes.explorer}>
              <Route path="" element={<Explorer events={events} />} />
              <Route path=":blockId" element={<Explorer events={events} />} />
            </Route>
            <Route path={routes.program} element={<Program />} />
            <Route path={routes.message} element={<Message />} />
            <Route path={routes.state} element={<State />} />
            <Route path={routes.send}>
              <Route path={routes.sendMessage} element={<Send />} />
              <Route path={routes.reply} element={<Send />} />
            </Route>
            <Route path={routes.meta} element={<Meta />} />
            <Route path={routes.editor} element={<EditorPage />} />
            <Route path={routes.mailbox} element={<Mailbox />} />
            <Route path="*" element={<PageNotFound />} />
          </Routes>
        ) : (
          <Loader />
        )}
      </Main>
      {isFooterHidden() || <Footer />}
    </div>
  );
}
Example #9
Source File: SelectAccountModal.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
SelectAccountModal = (props: Props) => {
  const alert = useAlert();
  const { logout, switchAccount } = useAccount();

  const { isOpen, accounts, onClose } = props;

  const selectAccount = (account: InjectedAccountWithMeta) => {
    switchAccount(account);

    localStorage.setItem(LOCAL_STORAGE.ACCOUNT, account.address);
    localStorage.setItem(LOCAL_STORAGE.PUBLIC_KEY_RAW, GearKeyring.decodeAddress(account.address));

    onClose();

    alert.success('Account successfully changed');
  };

  const handleLogout = () => {
    logout()

    localStorage.removeItem(LOCAL_STORAGE.ACCOUNT);
    localStorage.removeItem(LOCAL_STORAGE.PUBLIC_KEY_RAW);

    onClose();
  };

  return (
    <Modal
      open={isOpen}
      title="Connect"
      content={
        accounts ? (
          <>
            <AccountList list={accounts} toggleAccount={selectAccount} />
            <Button
              aria-label="Logout"
              icon={logoutSVG}
              color="transparent"
              className={styles.logoutButton}
              onClick={handleLogout}
            />
          </>
        ) : (
          <p className={styles.message}>
            Polkadot extension was not found or disabled. Please{' '}
            <a href="https://polkadot.js.org/extension/" target="_blank" rel="noreferrer">
              install it
            </a>
          </p>
        )
      }
      handleClose={onClose}
    />
  );
}
Example #10
Source File: MessagesList.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
MessagesList: VFC<Props> = ({ messages }) => {
  const alert = useAlert();

  return (
    <div className="messages__list">
      <div className={clsx('messages__list-block', 'messages__list-block__header')}>
        <div className="messages__list-item">
          <img className="messages__list-icon" src={codeIcon} alt="program name" />
          <p className="messages__list-caption">Program name</p>
        </div>
        <div className="messages__list-item">
          <img className="messages__list-icon" src={idIcon} alt="program id" />
          <p>Message Id</p>
        </div>
        <div className="messages__list-item">
          <img className="messages__list-icon" src={timestampIcon} alt="program date" />
          <p>Timestamp</p>
        </div>
      </div>
      {messages &&
        messages.length > 0 &&
        messages.map((message: MessageModel) => (
          <div
            key={message.id}
            className={clsx(
              'messages__list-block',
              message.replyError === '0' || message.replyError === null
                ? 'messages__list-block_success'
                : 'messages__list-block_error'
            )}
          >
            <div className="messages__list-item">
              <span className="messages__list-status" />
              <p className="messages__list-caption">{message.destination && fileNameHandler(message.destination)}</p>
            </div>
            <div className="messages__list-item">
              <Link className="messages__list-link" to={`/message/${message.id}`}>
                {message.id}
              </Link>
              <div className="programsCopyId">
                <button type="button" onClick={() => copyToClipboard(message.id, alert, 'Message ID copied')}>
                  <img src={copyIcon} alt="copy message ID" />
                </button>
              </div>
            </div>
            <div className="messages__list-item">
              <p>{message.timestamp && formatDate(message.timestamp)}</p>
            </div>
          </div>
        ))}
    </div>
  );
}
Example #11
Source File: Node.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
Node = ({ address, isCustom, setLocalNodes, selectedNode, setSelectedNode }: Props) => {
  const alert = useAlert();

  const handleChange = () => {
    setSelectedNode(address);
  };

  const handleCopy = () => {
    copyToClipboard(address, alert, 'Node address copied');
  };

  const removeNode = () => {
    setLocalNodes((prevNodes) => prevNodes.filter((prevNode) => prevNode.address !== address));

    if (selectedNode === address) {
      setSelectedNode(nodeApi.address);
    }
  };

  return (
    <li className={styles.node}>
      <Radio
        label={address}
        name="node"
        className={styles.radio}
        checked={selectedNode === address}
        onChange={handleChange}
      />
      <div className={styles.buttons}>
        <Button aria-label="Copy node address" icon={copy} color="transparent" onClick={handleCopy} />
        {isCustom && (
          <Button
            aria-label="Remove node address"
            icon={trash}
            color="transparent"
            onClick={removeNode}
            disabled={address === nodeApi.address}
          />
        )}
      </div>
    </li>
  );
}
Example #12
Source File: FormPayload.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
FormPayload = (props: Props) => {
  const { name, values } = props;

  const alert = useAlert();

  const [field, meta, { setValue }] = useField<PayloadValue>(name);

  const jsonManualPayload = useRef<string>();

  const [isManualView, setIsManualView] = useState(!values);
  const [manualPayloadFile, setManualPayloadFile] = useState<File>();

  const handleViewChange = () => setIsManualView((prevState) => !prevState);

  const resetFileData = () => {
    setManualPayloadFile(void 0);
    jsonManualPayload.current = void 0;
  };

  const dropManualPayloadFile = () => {
    resetFileData();

    if (values) {
      setValue(values.manualPayload, false);
    }
  };

  const handleUploadManualPayload = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];

    if (!file) {
      return dropManualPayloadFile();
    }

    try {
      if (!checkFileFormat(file, FILE_TYPES.JSON)) {
        throw new Error('Wrong file format');
      }

      setManualPayloadFile(file);

      const fileText = (await readTextFileAsync(file)) ?? '';

      setValue(fileText);
      jsonManualPayload.current = fileText;
    } catch (error: unknown) {
      alert.error((error as Error).message);
    }
  };

  useEffect(() => {
    if (!values) {
      return;
    }

    const payloadValue = isManualView ? jsonManualPayload.current ?? values.manualPayload : values.payload;

    setValue(payloadValue, false);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isManualView]);

  useChangeEffect(() => {
    if (!values && manualPayloadFile) {
      resetFileData();
    }

    setIsManualView(!values);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [values]);

  return (
    <>
      <div className={styles.formPayload}>
        {values && (
          <Checkbox
            type="switch"
            label="Manual input"
            checked={isManualView}
            className={styles.viewCheckbox}
            onChange={handleViewChange}
          />
        )}
        {!isManualView && values ? (
          <PayloadStructure levelName={name} typeStructure={values.typeStructure} />
        ) : (
          <>
            <Textarea
              {...field}
              id={name}
              rows={15}
              value={field.value as string}
              placeholder="// Enter your payload here"
            />
            {values && (
              <FileInput
                value={manualPayloadFile}
                accept={FILE_TYPES.JSON}
                className={styles.fileInput}
                onChange={handleUploadManualPayload}
              />
            )}
          </>
        )}
      </div>
      {meta.error && <div className={styles.error}>{meta.error}</div>}
    </>
  );
}
Example #13
Source File: UploadMetaForm.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
UploadMetaForm = ({ programId, programName }: Props) => {
  const alert = useAlert();
  const { account } = useAccount();

  const [isFileUpload, setFileUpload] = useState(true);

  const [meta, setMeta] = useState<Metadata | null>(null);
  const [metaFile, setMetaFile] = useState<File | null>(null);
  const [metaBuffer, setMetaBuffer] = useState<string | null>(null);
  const [fieldsFromFile, setFieldFromFile] = useState<string[] | null>(null);
  const [initialValues, setInitialValues] = useState<FormValues>({
    name: programName,
    ...INITIAL_VALUES,
  });

  const handleUploadMetaFile = async (file: File) => {
    try {
      const fileBuffer = (await readFileAsync(file)) as Buffer;
      const currentMetaWasm = await getWasmMetadata(fileBuffer);

      if (!currentMetaWasm) {
        return;
      }

      const valuesFromFile = getMetaValues(currentMetaWasm);
      const currentMetaBuffer = Buffer.from(new Uint8Array(fileBuffer)).toString('base64');

      setMeta(currentMetaWasm);
      setMetaBuffer(currentMetaBuffer);
      setFieldFromFile(Object.keys(valuesFromFile));
      setInitialValues({
        ...INITIAL_VALUES,
        ...valuesFromFile,
        name: currentMetaWasm.title ?? programName,
      });
    } catch (error) {
      alert.error(`${error}`);
    } finally {
      setMetaFile(file);
    }
  };

  const resetForm = () => {
    setMetaFile(null);
    setMeta(null);
    setMetaBuffer(null);
    setFieldFromFile(null);
    setInitialValues({
      name: programName,
      ...INITIAL_VALUES,
    });
  };

  const handleSubmit = (values: FormValues, actions: FormikHelpers<FormValues>) => {
    if (!account) {
      alert.error(`WALLET NOT CONNECTED`);
      return;
    }

    const { name, ...formMeta } = values;

    if (isFileUpload) {
      if (meta) {
        addMetadata(meta, metaBuffer, account, programId, name, alert);
      } else {
        alert.error(`ERROR: metadata not loaded`);
      }
    } else {
      addMetadata(formMeta, null, account, programId, name, alert);
    }

    actions.setSubmitting(false);
    resetForm();
  };

  const fields = isFileUpload ? fieldsFromFile : META_FIELDS;

  return (
    <Formik
      initialValues={initialValues}
      validateOnBlur
      validationSchema={Schema}
      enableReinitialize
      onSubmit={handleSubmit}
    >
      {({ isValid, isSubmitting }: FormikProps<FormValues>) => {
        const emptyFile = isFileUpload && !meta;
        const disabledBtn = emptyFile || !isValid || isSubmitting;

        return (
          <Form className={styles.uploadMetaForm}>
            <MetaSwitch isMetaFromFile={isFileUpload} onChange={setFileUpload} className={styles.formField} />
            <FormInput name="name" label="Program name:" className={styles.formField} />
            {fields?.map((field) => {
              const MetaField = field === 'types' ? FormTextarea : FormInput;

              return (
                <MetaField
                  key={field}
                  name={field}
                  label={`${field}:`}
                  disabled={isFileUpload}
                  className={styles.formField}
                />
              );
            })}
            {isFileUpload && (
              <MetaFile
                file={metaFile}
                className={styles.formField}
                onUpload={handleUploadMetaFile}
                onDelete={resetForm}
              />
            )}
            <div className={styles.formBtnWrapper}>
              <Button type="submit" text="Upload metadata" className={styles.formSubmitBtn} disabled={disabledBtn} />
            </div>
          </Form>
        );
      }}
    </Formik>
  );
}
Example #14
Source File: DropTarget.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
DropTarget = ({ type, setDroppedFile }: Props) => {
  const alert = useAlert();

  const [wrongFormat, setWrongFormat] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);

  if (wrongFormat) {
    setTimeout(() => setWrongFormat(false), 3000);
  }

  const checkFileFormat = useCallback((files: any) => {
    if (typeof files[0]?.name === 'string') {
      const fileExt: string = files[0].name.split('.').pop().toLowerCase();
      return fileExt !== 'wasm';
    }
    return true;
  }, []);

  const handleFilesUpload = useCallback(
    (file: File) => {
      setDroppedFile({ file, type });
    },
    [setDroppedFile, type]
  );

  const emulateInputClick = () => {
    inputRef.current?.click();
  };

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { files },
    } = event;
    if (files?.length) {
      const isCorrectFormat = checkFileFormat(files);
      setWrongFormat(isCorrectFormat);
      if (!isCorrectFormat) {
        handleFilesUpload(files[0]);
        // since type='file' input can't be controlled,
        // reset it's value to trigger onChange again in case the same file selected twice
        event.target.value = '';
      } else {
        alert.error('Wrong file format');
        setWrongFormat(false);
      }
    }
  };

  const handleFileDrop = useCallback(
    (item) => {
      if (item) {
        const { files } = item;
        const isCorrectFormat = checkFileFormat(files);
        setWrongFormat(isCorrectFormat);
        if (!isCorrectFormat) {
          handleFilesUpload(files[0]);
        } else {
          alert.error('Wrong file format');
          setWrongFormat(false);
        }
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [checkFileFormat, handleFilesUpload]
  );

  const [{ canDrop, isOver }, drop] = useDrop(
    () => ({
      accept: [NativeTypes.FILE],
      drop(item: { files: any[] }) {
        if (handleFileDrop) {
          handleFileDrop(item);
        }
      },
      collect: (monitor: DropTargetMonitor) => ({
        isOver: monitor.isOver(),
        canDrop: monitor.canDrop(),
      }),
    }),
    [handleFileDrop]
  );

  const isActive = canDrop && isOver;
  const className = clsx(styles.drop, isActive && styles.active);
  const isProgramUpload = type === UploadTypes.PROGRAM;
  const buttonText = `Upload ${type}`;

  return (
    <div className={className} ref={drop}>
      {isActive ? (
        <div className={styles.file}>
          <span className={styles.text}>Drop your .wasm files here to upload</span>
        </div>
      ) : (
        <div className={styles.noFile}>
          <input className={styles.input} ref={inputRef} type="file" onChange={handleChange} />
          <Button
            text={buttonText}
            icon={isProgramUpload ? upload : editor}
            color={isProgramUpload ? 'primary' : 'secondary'}
            onClick={emulateInputClick}
          />
          <div className={styles.text}>{`Click “${buttonText}” to browse or drag and drop your .wasm files here`}</div>
        </div>
      )}
    </div>
  );
}
Example #15
Source File: UploadForm.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
UploadForm: VFC<Props> = ({ setDroppedFile, droppedFile }) => {
  const { api } = useApi();
  const alert = useAlert();
  const { account } = useAccount();

  const [fieldFromFile, setFieldFromFile] = useState<string[] | null>(null);
  const [meta, setMeta] = useState<Metadata | null>(null);
  const [metaFile, setMetaFile] = useState<string | null>(null);
  const [droppedMetaFile, setDroppedMetaFile] = useState<File>();
  const [isMetaFromFile, setIsMetaFromFile] = useState<boolean>(true);

  const handleResetForm = () => {
    setDroppedFile(null);
  };

  const handleResetMetaForm = (setValues: SetValues) => {
    setMeta(null);
    setMetaFile(null);
    setDroppedMetaFile(void 0);
    setFieldFromFile(null);
    setValues(INITIAL_VALUES, false);
  };

  const handleUploadMetaFile = (setValues: SetValues) => async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];

    if (!file) {
      return handleResetMetaForm(setValues);
    }

    try {
      if (!checkFileFormat(file)) {
        throw new Error('Wrong file format');
      }

      setDroppedMetaFile(file);

      const readedFile = (await readFileAsync(file)) as Buffer;
      const metadata: Metadata = await getWasmMetadata(readedFile);

      if (!metadata) {
        throw new Error('Failed to load metadata');
      }

      const metaBufferString = Buffer.from(new Uint8Array(readedFile)).toString('base64');
      const valuesFromFile = getMetaValues(metadata);

      setMeta(metadata);
      setMetaFile(metaBufferString);
      setFieldFromFile(Object.keys(valuesFromFile));
      setValues(
        ({ programValues }) => ({
          metaValues: valuesFromFile,
          programValues: {
            ...programValues,
            programName: metadata?.title || '',
          },
        }),
        false
      );
    } catch (error) {
      alert.error(`${error}`);
    }
  };

  const handleSubmitForm = (values: FormValues) => {
    if (!account) {
      alert.error(`Wallet not connected`);
      return;
    }

    const { value, payload, gasLimit, programName } = values.programValues;

    const programOptions: UploadProgramModel = {
      meta: void 0,
      value,
      title: '',
      gasLimit,
      programName,
      initPayload: meta ? getSubmitPayload(payload) : payload,
    };

    if (meta) {
      programOptions.meta = isMetaFromFile ? meta : values.metaValues;
    }

    UploadProgram(api, account, droppedFile, programOptions, metaFile, alert, handleResetForm).catch(() => {
      alert.error(`Invalid JSON format`);
    });
  };

  const handleCalculateGas = async (values: ProgramValues, setFieldValue: SetFieldValue) => {
    const fileBuffer = (await readFileAsync(droppedFile)) as ArrayBuffer;
    const code = Buffer.from(new Uint8Array(fileBuffer));

    calculateGas('init', api, values, alert, meta, code).then((gasLimit) =>
      setFieldValue('programValues.gasLimit', gasLimit)
    );
  };

  const payloadFormValues = useMemo(() => getPayloadFormValues(meta?.types, meta?.init_input), [meta]);

  const metaFields = isMetaFromFile ? fieldFromFile : META_FIELDS;
  const isUploadAvailable = !(account && parseInt(account.balance.value, 10) > 0);

  return (
    <Box className={styles.uploadFormWrapper}>
      <h3 className={styles.heading}>UPLOAD NEW PROGRAM</h3>
      <Formik initialValues={INITIAL_VALUES} validateOnBlur validationSchema={Schema} onSubmit={handleSubmitForm}>
        {({ values, setFieldValue, setValues }) => (
          <Form className={styles.uploadForm}>
            <div className={styles.formContent}>
              <div className={styles.program}>
                <div className={styles.fieldWrapper}>
                  <span className={styles.caption}>File:</span>
                  <span className={styles.fileName}>{droppedFile.name}</span>
                </div>
                <FormInput
                  name="programValues.programName"
                  label="Name:"
                  placeholder="Name"
                  className={styles.formField}
                />
                <FormNumberFormat
                  name="programValues.gasLimit"
                  label="Gas limit:"
                  placeholder="20,000,000"
                  thousandSeparator
                  allowNegative={false}
                  className={styles.formField}
                />
                <FormInput
                  type="number"
                  name="programValues.value"
                  label="Initial value:"
                  placeholder="0"
                  className={styles.formField}
                />
                <div className={styles.fieldWrapper}>
                  <label htmlFor="programValues.payload" className={clsx(styles.caption, styles.top)}>
                    Initial payload:
                  </label>
                  <FormPayload name="programValues.payload" values={payloadFormValues} />
                </div>
              </div>

              <fieldset className={styles.meta}>
                <legend className={styles.metaLegend}>Metadata:</legend>
                <MetaSwitch isMetaFromFile={isMetaFromFile} onChange={setIsMetaFromFile} className={styles.formField} />
                {isMetaFromFile && (
                  <div className={styles.fieldWrapper}>
                    <FileInput
                      label="Metadata file:"
                      value={droppedMetaFile}
                      className={clsx(styles.formField, styles.fileInput)}
                      onChange={handleUploadMetaFile(setValues)}
                    />
                  </div>
                )}
                {metaFields?.map((field) => {
                  const FormField = field === 'types' ? FormTextarea : FormInput;

                  return (
                    <FormField
                      key={field}
                      name={`metaValues.${field}`}
                      label={`${field}:`}
                      disabled={isMetaFromFile}
                      className={styles.formField}
                    />
                  );
                })}
              </fieldset>
            </div>

            <div className={styles.buttons}>
              <Button type="submit" text="Upload program" disabled={isUploadAvailable} />
              <Button
                text="Calculate Gas"
                onClick={() => {
                  handleCalculateGas(values.programValues, setFieldValue);
                }}
              />
              <Button
                type="submit"
                text="Cancel upload"
                color="transparent"
                aria-label="closeUploadForm"
                onClick={handleResetForm}
              />
            </div>
          </Form>
        )}
      </Formik>
    </Box>
  );
}
Example #16
Source File: ProgramSwitch.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
ProgramSwitch: VFC<Props> = ({ pageType }) => {
  const { api } = useApi();
  const alert = useAlert();
  const { account: currentAccount } = useAccount();

  const [captchaToken, setCaptchaToken] = useState('');
  const captchaRef = useRef<HCaptcha>(null);

  const handleTransferBalance = async () => {
    try {
      if (!currentAccount) {
        throw new Error(`WALLET NOT CONNECTED`);
      }

      const apiRequest = new ServerRPCRequestService();
      const response = await apiRequest.callRPC(RPC_METHODS.GET_TEST_BALANCE, {
        address: `${currentAccount.address}`,
        token: captchaToken,
      });

      if (response.error) {
        alert.error(`${response.error.message}`);
      }
    } catch (error) {
      alert.error(`${error}`);
    }
  };

  useEffect(() => {
    if (captchaToken) {
      handleTransferBalance();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [captchaToken]);

  const handleTestBalanceClick = () => {
    if (captchaToken) {
      handleTransferBalance();
    } else {
      captchaRef.current?.execute();
    }
  };

  const handleTransferBalanceFromAlice = () => {
    try {
      if (!currentAccount) {
        throw new Error(`WALLET NOT CONNECTED`);
      }

      if (api) {
        api.balance.transferFromAlice(currentAccount.address, GEAR_BALANCE_TRANSFER_VALUE);
      }
    } catch (error) {
      alert.error(`${error}`);
    }
  };

  return (
    <div className="switch-block">
      <div className="switch-block--wrapper">
        <div className="switch-buttons">
          <Link
            to={routes.main}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.UPLOAD_PROGRAM && 'switch-buttons__item--active'
            )}
          >
            Upload program
          </Link>
          <Link
            to={routes.uploadedPrograms}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.UPLOADED_PROGRAMS && 'switch-buttons__item--active'
            )}
          >
            My programs
          </Link>
          <Link
            to={routes.allPrograms}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.ALL_PROGRAMS && 'switch-buttons__item--active'
            )}
          >
            All programs
          </Link>
          <Link
            to={routes.messages}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.ALL_MESSAGES && 'switch-buttons__item--active'
            )}
          >
            Messages
          </Link>
        </div>
        {currentAccount && (
          <>
            <Button
              className="test-balance-button"
              text="Get test balance"
              onClick={isDevChain() ? handleTransferBalanceFromAlice : handleTestBalanceClick}
            />
            <HCaptcha
              sitekey={HCAPTCHA_SITE_KEY}
              onVerify={setCaptchaToken}
              onExpire={() => setCaptchaToken('')}
              ref={captchaRef}
              theme="dark"
              size="invisible"
            />
          </>
        )}
      </div>
      <BlocksSummary />
    </div>
  );
}
Example #17
Source File: MessageForm.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
MessageForm: VFC<Props> = ({ id, metadata, replyErrorCode }) => {
  const { api } = useApi();
  const alert = useAlert();
  const { account: currentAccount } = useAccount();

  const initialValues = useRef<FormValues>({
    value: 0,
    payload: '',
    gasLimit: 20000000,
    payloadType: 'Bytes',
    destination: id,
  });

  const isReply = !!replyErrorCode;
  const isMeta = useMemo(() => metadata && Object.keys(metadata).length > 0, [metadata]);

  const handleSubmit = (values: FormValues, { resetForm }: FormikHelpers<FormValues>) => {
    if (!currentAccount) {
      alert.error(`WALLET NOT CONNECTED`);
      return;
    }

    const payload = getSubmitPayload(values.payload);
    const apiMethod = isReply ? api.reply : api.message;
    const payloadType = isMeta ? void 0 : values.payloadType;

    const message = {
      value: values.value.toString(),
      payload,
      gasLimit: values.gasLimit.toString(),
      replyToId: values.destination,
      destination: values.destination,
    };

    sendMessage(apiMethod, currentAccount, message, alert, resetForm, metadata, payloadType);
  };

  const handleCalculateGas = (values: FormValues, setFieldValue: SetFieldValue) => () => {
    const method = isReply ? 'reply' : 'handle';

    calculateGas(method, api, values, alert, metadata, null, id, replyErrorCode).then((gasLimit) =>
      setFieldValue('gasLimit', gasLimit)
    );
  };

  const payloadFormValues = useMemo(() => getPayloadFormValues(metadata?.types, metadata?.handle_input), [metadata]);

  return (
    <Formik initialValues={initialValues.current} validateOnBlur validationSchema={Schema} onSubmit={handleSubmit}>
      {({ errors, touched, values, setFieldValue }) => (
        <Form id="message-form">
          <div className="message-form--wrapper">
            <div className="message-form--col">
              <div className="message-form--info">
                <label htmlFor="destination" className="message-form__field">
                  {isReply ? 'Message Id:' : 'Destination:'}
                </label>
                <div className="message-form__field-wrapper">
                  <Field
                    id="destination"
                    name="destination"
                    type="text"
                    className={clsx(
                      'inputField',
                      errors.destination && touched.destination && 'message-form__input-error'
                    )}
                  />
                  {errors.destination && touched.destination && (
                    <div className="message-form__error">{errors.destination}</div>
                  )}
                </div>
              </div>

              <div className="message-form--info">
                <label htmlFor="payload" className="message-form__field">
                  Payload:
                </label>
                <FormPayload name="payload" values={payloadFormValues} />
              </div>

              {!isMeta && (
                <div className="message-form--info">
                  <label htmlFor="payloadType" className="message-form__field">
                    Payload type:
                  </label>
                  <PayloadType />
                </div>
              )}

              <div className="message-form--info">
                <label htmlFor="gasLimit" className="message-form__field">
                  Gas limit:
                </label>
                <div className="message-form__field-wrapper">
                  <NumberFormat
                    name="gasLimit"
                    placeholder="20,000,000"
                    value={values.gasLimit}
                    thousandSeparator
                    allowNegative={false}
                    className={clsx('inputField', errors.gasLimit && touched.gasLimit && 'message-form__input-error')}
                    onValueChange={(val) => {
                      const { floatValue } = val;
                      setFieldValue('gasLimit', floatValue);
                    }}
                  />
                  {errors.gasLimit && touched.gasLimit ? (
                    <div className="message-form__error">{errors.gasLimit}</div>
                  ) : null}
                </div>
              </div>

              <div className="message-form--info">
                <label htmlFor="value" className="message-form__field">
                  Value:
                </label>
                <div className="message-form__field-wrapper">
                  <Field
                    id="value"
                    name="value"
                    placeholder="20000"
                    type="number"
                    className={clsx('inputField', errors.value && touched.value && 'message-form__input-error')}
                  />
                  {errors.value && touched.value ? <div className="message-form__error">{errors.value}</div> : null}
                </div>
              </div>
              <div className="message-form--btns">
                <button
                  className="message-form__button"
                  type="button"
                  onClick={handleCalculateGas(values, setFieldValue)}
                >
                  Calculate Gas
                </button>
                <button className="message-form__button" type="submit">
                  <img src={MessageIllustration} alt="message" />
                  {isReply ? 'Send reply' : 'Send message'}
                </button>
              </div>
            </div>
          </div>
        </Form>
      )}
    </Formik>
  );
}
Example #18
Source File: EditorPage.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
EditorPage = () => {
  const navigate = useNavigate();
  const alert = useAlert();

  const { isBuildDone, setIsBuildDone } = useEditor();

  const [state, dispatch] = useReducer(reducer, { tree: null });
  const [currentFile, setCurrentFile] = useState<string[] | null>(null);
  const [programName, setProgramName] = useState('');
  const [isProgramNameError, setIsProgramNameError] = useState(false);

  const options = {
    selectOnLineNumbers: true,
    fontSize: 14,
    fontFamily: 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
    theme: 'vs-dark',
    language: 'rust',
  };

  useEffect(() => {
    dispatch({ type: 'SET_DATA', payload: addParentToNode(SimpleExample) });
  }, []);

  function createStructure(zip: any, path: string | null, filesList: any) {
    for (const key in filesList) {
      if (Object.prototype.hasOwnProperty.call(filesList, key)) {
        const file = filesList[key];
        let newPath = '';

        if (file.type === 'file') {
          if (path) {
            zip.folder(path).file(`${file.name}`, file.value);
          } else {
            zip.file(`${file.name}`, file.value);
          }
        } else {
          if (path) {
            newPath = `${path}/${file.name}`;
            zip.folder(newPath);
          } else {
            newPath = file.name;
            zip.folder(file.name);
          }

          createStructure(zip, newPath, file.children);
        }
      }
    }
  }

  async function createArchive() {
    const zip = new JSZip();

    if (state.tree) {
      createStructure(zip, null, state.tree.root.children);
    }

    return zip.generateAsync({ type: 'blob' });
  }

  function buildWasmProgram(val: any) {
    const formData = new FormData();

    formData.append('file', val);

    fetch(WASM_COMPILER_BUILD, {
      method: 'POST',
      body: formData,
    })
      .then((data) => data.json())
      .then((json) => {
        localStorage.setItem(LOCAL_STORAGE.PROGRAM_COMPILE_ID, json.id);
        setIsBuildDone(true);
        alert.success('Compiling, please wait!');
      });
  }

  function handleDownload() {
    createArchive()
      .then((val: any) => {
        saveAs(val, `${programName.trim()}.zip`);
      })
      .catch((err) => {
        console.error(err);
      });
  }

  function handleBuild() {
    createArchive()
      .then((val: any) => {
        buildWasmProgram(val);
      })
      .catch((err) => {
        console.error(err);
      });
  }

  function handleClose() {
    navigate(-1);
  }

  function onNodeClick(node: EditorItem) {
    setCurrentFile(node.path);
  }

  function handleProgramNameChange(event: ChangeEvent) {
    const target = event.target as HTMLInputElement;
    const { value } = target;
    if ((value.trim().length && isProgramNameError) || (!value.trim().length && !isProgramNameError)) {
      setIsProgramNameError(!isProgramNameError);
    }
    setProgramName(target.value);
  }

  function handleEditorChange(value: string | undefined) {
    if (currentFile) {
      const file = get(state.tree, currentFile);
      dispatch({ type: 'UPDATE_VALUE', payload: { nodeId: file.id, value } });
    }
  }

  function handlePanelBtnClick(type: string) {
    if (!programName.trim().length) {
      setIsProgramNameError(true);
      return;
    }
    if (type === EDITOR_BTNS.DOWNLOAD) {
      handleDownload();
    } else if (type === EDITOR_BTNS.BUILD) {
      handleBuild();
    }
  }

  function getCurrFileName() {
    let value = '';

    if (currentFile) {
      value = get(state.tree, currentFile).value;
    }

    return value;
  }

  function getCurrFileLang() {
    let lang = '';

    if (currentFile) {
      lang = get(state.tree, currentFile).lang;
    }

    return lang;
  }

  // @ts-ignore
  /* eslint-disable react/jsx-no-bind */
  return (
    <EditorTreeContext.Provider
      value={{
        state,
        dispatch,
        onNodeClick,
        setCurrentFile,
      }}
    >
      <div className="editor-page">
        <PageHeader programName={programName} pageType={PAGE_TYPES.EDITOR_PAGE} handleClose={handleClose} />
        <div className="editor-content">
          <div className="editor-panel">
            <div className="editor-panel--form">
              <span className="editor-panel--form__label">Program name:</span>
              <input
                type="text"
                className={clsx('editor-panel--form__input', isProgramNameError && 'error')}
                value={programName}
                onChange={handleProgramNameChange}
              />
            </div>
            {isBuildDone && <div className="editor-panel--text">Compiling ...</div>}
            <div className="editor-panel--actions">
              <button
                className="editor-panel--actions__btn"
                type="button"
                onClick={() => handlePanelBtnClick(EDITOR_BTNS.DOWNLOAD)}
              >
                <img src={EditorDownload} alt="editor-download" />
                Download
              </button>
              <button
                className="editor-panel--actions__btn"
                type="button"
                onClick={() => handlePanelBtnClick(EDITOR_BTNS.BUILD)}
                disabled={isBuildDone}
              >
                <img src={EditorBuild} alt="editor-build" />
                Compile
              </button>
            </div>
          </div>
          <div className="editor-container">
            <EditorTree />
            <div className="editor-container__editor">
              {currentFile ? (
                <>
                  <Editor
                    theme="vs-dark"
                    options={options}
                    value={getCurrFileName()}
                    language={getCurrFileLang()}
                    onChange={handleEditorChange}
                  />
                </>
              ) : (
                <div className="editor-empty">Please select at least one file</div>
              )}
            </div>
          </div>
        </div>
        <span className="editor-page__footer-text">2022. All rights reserved.</span>
      </div>
    </EditorTreeContext.Provider>
  );
}
Example #19
Source File: useCodeUpload.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
useCodeUpload = () => {
  const { api } = useApi();
  const alert = useAlert();
  const { account } = useAccount();

  const submit = async (file: File) => {
    const arrayBuffer = (await readFileAsync(file)) as ArrayBuffer;
    const buffer = Buffer.from(arrayBuffer);

    return api.code.submit(buffer);
  };

  const getErrorMessage = (event: Event) => {
    const { docs, method: errorMethod } = api.getExtrinsicFailedError(event);
    const formattedDocs = docs.filter(Boolean).join('. ');

    return `${errorMethod}: ${formattedDocs}`;
  };

  const uploadCode = async (file: File) => {
    if (!account) {
      alert.error('Wallet not connected');

      return;
    }

    const { address, meta } = account;

    const alertTitle = 'gear.submitCode';
    const alertId = alert.loading('SignIn', { title: alertTitle });

    try {
      const { signer } = await web3FromSource(meta.source);
      const { codeHash } = await submit(file);

      await api.code.signAndSend(address, { signer }, ({ events, status }) => {
        if (status.isReady) {
          alert.update(alertId, 'Ready');

          return;
        }

        if (status.isInBlock) {
          alert.update(alertId, 'InBlock');

          events.forEach(({ event }) => {
            const { method, section } = event;

            if (method === 'CodeSaved') {
              alert.success(<CopiedInfo title="Code hash" info={codeHash} />, {
                title: `${section}.CodeSaved`,
                timeout: 0,
              });

              return;
            }

            if (method === 'ExtrinsicFailed') {
              alert.error(getErrorMessage(event), { title: `${section}.ExtrinsicFailed` });

              return;
            }
          });

          return;
        }

        if (status.isFinalized) {
          alert.update(alertId, 'Finalized', DEFAULT_SUCCESS_OPTIONS);

          return;
        }

        if (status.isInvalid) {
          alert.update(alertId, PROGRAM_ERRORS.INVALID_TRANSACTION, DEFAULT_ERROR_OPTIONS);
        }
      });
    } catch (error) {
      alert.update(alertId, `${error}`, DEFAULT_ERROR_OPTIONS);
      console.error(error);
    }
  };

  return uploadCode;
}