react-hook-form#SubmitHandler TypeScript Examples

The following examples show how to use react-hook-form#SubmitHandler. 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: pricing-form-context.tsx    From admin with MIT License 6 votes vote down vote up
PriceListFormContext = React.createContext<{
  configFields: Record<ConfigurationField, unknown>
  handleConfigurationSwitch: (values: string[]) => void
  prices: CreatePriceListPricesFormValues | null
  setPrices: React.Dispatch<
    React.SetStateAction<CreatePriceListPricesFormValues | null>
  >
  handleSubmit: <T>(
    submitHandler: SubmitHandler<T>
  ) => (e?: React.BaseSyntheticEvent) => Promise<void>
} | null>(null)
Example #2
Source File: index.tsx    From github-explorer with MIT License 5 votes vote down vote up
export function Dashboard() {
  const {
    errors,
    setError,
    register,
    formState,
    handleSubmit,
  } = useForm({
    mode: "onChange",
    resolver: yupResolver(addRepositorySchema),
  });

  const { repositories, addRepository, isLoading } = useRepositories();

  const [t] = useTranslation();

  const onSubmit: SubmitHandler<RepositoryFormValues> = async (
    data,
  ) => {
    const { repositoryName } = data;

    try {
      await addRepository(repositoryName);
    } catch (error) {
      setError("repositoryName", {
        type: "manual",
        message: error.message,
      });
    }
  };

  const { isDirty, isValid } = formState;
  const { repositoryName: repositoryNameError } = errors;

  const isButtonDisabled = isLoading || !isValid || !isDirty;
  const hasError = Boolean(repositoryNameError?.message) && !isValid && isDirty;

  return (
    <Layout>
      <Title>{t("dashboard.title")}</Title>

      <Form onSubmit={handleSubmit(onSubmit)}>
        <Input
          id="repositoryName"
          ref={register}
          type="text"
          name="repositoryName"
          hasError={hasError}
          aria-label="Repository Name"
          placeholder={t("dashboard.repository_name_placeholder")}
        />

        <Button
          type="submit"
          isLoading={isLoading}
          disabled={isButtonDisabled}
        >
          {t("buttons.search")}
        </Button>
      </Form>

      {repositoryNameError && (
        <AddRepositoryInputError>
          {repositoryNameError.message}
        </AddRepositoryInputError>
      )}

      <RepositoriesList>
        {repositories.map(repository => (
          <li key={repository.full_name}>
            <Repository repository={repository} />
          </li>
        ))}
      </RepositoriesList>
    </Layout>
  );
}
Example #3
Source File: AddShortcut.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
AddShortcut = ({ onClose, anchorEl, api }: Props) => {
  const classes = useStyles();
  const alertApi = useApi(alertApiRef);
  const { pathname } = useLocation();
  const [formValues, setFormValues] = useState<FormValues>();
  const open = Boolean(anchorEl);

  const handleSave: SubmitHandler<FormValues> = async ({ url, title }) => {
    const shortcut: Omit<Shortcut, 'id'> = { url, title };

    try {
      await api.add(shortcut);
      alertApi.post({
        message: `Added shortcut '${title}' to your sidebar`,
        severity: 'success',
      });
    } catch (error) {
      alertApi.post({
        message: `Could not add shortcut: ${error.message}`,
        severity: 'error',
      });
    }

    onClose();
  };

  const handlePaste = () => {
    setFormValues({ url: pathname, title: document.title });
  };

  const handleClose = () => {
    setFormValues(undefined);
    onClose();
  };

  return (
    <Popover
      open={open}
      anchorEl={anchorEl}
      TransitionProps={{ onExit: handleClose }}
      onClose={onClose}
      anchorOrigin={{
        vertical: 'top',
        horizontal: 'right',
      }}
    >
      <Card className={classes.card}>
        <CardHeader
          className={classes.header}
          title="Add Shortcut"
          titleTypographyProps={{ variant: 'subtitle2' }}
          action={
            <Button
              className={classes.button}
              variant="text"
              size="small"
              color="primary"
              onClick={handlePaste}
            >
              Use current page
            </Button>
          }
        />
        <ShortcutForm
          onClose={handleClose}
          onSave={handleSave}
          formValues={formValues}
        />
      </Card>
    </Popover>
  );
}
Example #4
Source File: SendLN.tsx    From raspiblitz-web with MIT License 5 votes vote down vote up
SendLn: FC<Props> = ({
  loading,
  balanceDecorated,
  onConfirm,
  onChangeInvoice,
  error,
}) => {
  const { unit } = useContext(AppContext);
  const { t } = useTranslation();

  const {
    register,
    handleSubmit,
    formState: { errors, isValid, submitCount },
  } = useForm<IFormInputs>({
    mode: "onChange",
  });

  const onSubmit: SubmitHandler<IFormInputs> = (_) => onConfirm();

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <h3 className="text-xl font-bold">{t("wallet.send_lightning")}</h3>

      <p className="my-5">
        <span className="font-bold">{t("wallet.balance")}:&nbsp;</span>
        {balanceDecorated} {unit}
      </p>

      <InputField
        {...register("invoiceInput", {
          required: t("forms.validation.lnInvoice.required"),
          pattern: {
            value: /^(lnbc|lntb)\w+/i,
            message: t("forms.validation.lnInvoice.patternMismatch"),
          },
          onChange: onChangeInvoice,
        })}
        label={t("wallet.invoice")}
        errorMessage={errors.invoiceInput}
        placeholder="lnbc..."
        disabled={loading}
      />

      {error && <ErrorMessage errorMessage={error} />}

      <ButtonWithSpinner
        type="submit"
        className="bd-button my-8 p-3"
        loading={loading}
        disabled={(submitCount > 0 && !isValid) || loading}
        icon={<SendIcon className="mr-2 h-6 w-6" />}
      >
        {t("wallet.send")}
      </ButtonWithSpinner>
    </form>
  );
}
Example #5
Source File: UnlockModal.tsx    From raspiblitz-web with MIT License 5 votes vote down vote up
UnlockModal: FC<Props> = ({ onClose }) => {
  const { t } = useTranslation();
  const { setWalletLocked } = useContext(AppContext);
  const [isLoading, setIsLoading] = useState(false);
  const [passwordWrong, setPasswordWrong] = useState(false);

  const {
    register,
    handleSubmit,
    formState: { errors, isValid },
  } = useForm<IFormInputs>({ mode: "onChange" });

  const unlockHandler: SubmitHandler<IFormInputs> = (data: {
    passwordInput: string;
  }) => {
    setIsLoading(true);
    setPasswordWrong(false);
    instance
      .post("/lightning/unlock-wallet", { password: data.passwordInput })
      .then((res) => {
        if (res.data) {
          setWalletLocked(false);
          // disableScroll doesn't trigger on modal close
          disableScroll.off();
          onClose(true);
        }
      })
      .catch((_) => {
        setIsLoading(false);
        setPasswordWrong(true);
      });
  };

  return createPortal(
    <ModalDialog closeable={false} close={() => onClose(false)}>
      <h2 className="mt-5 text-lg font-bold">{t("wallet.unlock_title")}</h2>

      <div>
        <h3 className="p-2">{t("wallet.unlock_subtitle")}</h3>

        <form onSubmit={handleSubmit(unlockHandler)}>
          <InputField
            {...register("passwordInput", {
              required: t("forms.validation.unlock.required"),
            })}
            autoFocus
            errorMessage={errors.passwordInput}
            label={t("forms.validation.unlock.pass_c")}
            placeholder={t("forms.validation.unlock.pass_c")}
            type="password"
            disabled={isLoading}
          />
          <ButtonWithSpinner
            type="submit"
            className="bd-button my-5 p-3"
            loading={isLoading}
            disabled={!isValid}
            icon={<LockOpen className="mx-1 h-6 w-6" />}
          >
            {isLoading ? t("wallet.unlocking") : t("wallet.unlock")}
          </ButtonWithSpinner>
        </form>
      </div>

      {passwordWrong && (
        <p className="mb-5 text-red-500">{t("login.invalid_pass")}</p>
      )}
    </ModalDialog>,
    MODAL_ROOT
  );
}
Example #6
Source File: Onboarding.tsx    From revite with GNU Affero General Public License v3.0 5 votes vote down vote up
export function OnboardingModal({ onClose, callback }: Props) {
    const { handleSubmit, register } = useForm<FormInputs>();
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | undefined>(undefined);

    const onSubmit: SubmitHandler<FormInputs> = ({ username }) => {
        setLoading(true);
        callback(username, true)
            .then(() => onClose())
            .catch((err: unknown) => {
                setError(takeError(err));
                setLoading(false);
            });
    };

    return (
        <div className={styles.onboarding}>
            <div className={styles.header}>
                <h1>
                    <Text id="app.special.modals.onboarding.welcome" />
                    <br />
                    <img src={wideSVG} loading="eager" />
                </h1>
            </div>
            <div className={styles.form}>
                {loading ? (
                    <Preloader type="spinner" />
                ) : (
                    <>
                        <p>
                            <Text id="app.special.modals.onboarding.pick" />
                        </p>
                        <form
                            onSubmit={
                                handleSubmit(
                                    onSubmit,
                                ) as unknown as JSX.GenericEventHandler<HTMLFormElement>
                            }>
                            <div>
                                <FormField
                                    type="username"
                                    register={register}
                                    showOverline
                                    error={error}
                                />
                            </div>
                            <Button type="submit">
                                <Text id="app.special.modals.actions.continue" />
                            </Button>
                        </form>
                    </>
                )}
            </div>
            <div />
        </div>
    );
}
Example #7
Source File: CreateBot.tsx    From revite with GNU Affero General Public License v3.0 5 votes vote down vote up
export function CreateBotModal({ onClose, onCreate }: Props) {
    const client = useContext(AppContext);
    const { handleSubmit, register, errors } = useForm<FormInputs>();
    const [error, setError] = useState<string | undefined>(undefined);

    const onSubmit: SubmitHandler<FormInputs> = async ({ name }) => {
        try {
            const { bot } = await client.bots.create({ name });
            onCreate(bot);
            onClose();
        } catch (err) {
            setError(takeError(err));
        }
    };

    return (
        <Modal
            visible={true}
            onClose={onClose}
            title={<Text id="app.special.popovers.create_bot.title" />}
            actions={[
                {
                    confirmation: true,
                    palette: "accent",
                    onClick: handleSubmit(onSubmit),
                    children: <Text id="app.special.modals.actions.create" />,
                },
                {
                    palette: "plain",
                    onClick: onClose,
                    children: <Text id="app.special.modals.actions.cancel" />,
                },
            ]}>
            {/* Preact / React typing incompatabilities */}
            <form
                onSubmit={(e) => {
                    e.preventDefault();
                    handleSubmit(
                        onSubmit,
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    )(e as any);
                }}>
                <FormField
                    type="username"
                    name="name"
                    register={register}
                    showOverline
                    error={errors.name?.message}
                />
                {error && (
                    <Overline type="error" error={error}>
                        <Text id="app.special.popovers.create_bot.failed" />
                    </Overline>
                )}
            </form>
        </Modal>
    );
}
Example #8
Source File: FormContainer.tsx    From UsTaxes with GNU Affero General Public License v3.0 5 votes vote down vote up
OpenableFormContainer = <A,>(
  props: PropsWithChildren<OpenableFormContainerProps<A>>
): ReactElement => {
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
  const { isOpen = false, allowAdd = true, defaultValues } = props
  const classes = useStyles()

  // Note useFormContext here instead of useForm reuses the
  // existing form context from the parent.
  const { reset, handleSubmit } = useFormContext()

  const closeForm = (): void => {
    props.onOpenStateChange(false)
    reset(defaultValues)
  }

  const onClose = (): void => {
    if (props.onCancel !== undefined) props.onCancel()
    closeForm()
  }

  const onSave: SubmitHandler<A> = (formData): void => {
    props.onSave(formData)
    closeForm()
  }

  const openAddForm = () => {
    props.onOpenStateChange(true)
  }

  return (
    <>
      {(() => {
        if (isOpen) {
          return (
            <FormContainer
              onDone={intentionallyFloat(handleSubmit(onSave))}
              onCancel={onClose}
            >
              {props.children}
            </FormContainer>
          )
        } else if (allowAdd) {
          return (
            <div className={classes.buttonList}>
              <Button
                type="button"
                onClick={openAddForm}
                color={prefersDarkMode ? 'default' : 'secondary'}
                variant="contained"
              >
                Add
              </Button>
            </div>
          )
        }
      })()}
    </>
  )
}
Example #9
Source File: note-form.tsx    From notebook with MIT License 4 votes vote down vote up
NoteForm: React.SFC<NoteFormProps> = ({
  isOpen,
  onClose,
  selectedNote,
  handleNoteCreate,
  handleNoteUpdate
}) => {
  const { register, handleSubmit, formState, errors } = useForm<FormInputs>({
    mode: "onChange"
  });

  const onSubmit: SubmitHandler<FormInputs> = data => {
    let newNote: note = {
      id: "",
      title: data.title,
      body: data.body
    };
    if (handleNoteCreate) {
      newNote.id = nanoid();
      if (handleNoteCreate) handleNoteCreate(newNote);
    } else {
      newNote.id = selectedNote ? selectedNote.id : "";
      if (handleNoteUpdate) handleNoteUpdate(newNote);
    }
    onClose();
  };

  const validateTitle = (value: string) => {
    if (!value) {
      return "Title is required";
    } else return true;
  };

  const validateBody = (value: string) => {
    if (!value) {
      return "Body is required";
    } else return true;
  };

  return (
    <Modal
      isOpen={isOpen}
      onClose={onClose}
      size="lg"
      isCentered
      motionPreset="slideInBottom"
    >
      <ModalOverlay />
      <ModalContent>
        <form onSubmit={handleSubmit(onSubmit)}>
          <ModalHeader>{selectedNote ? "Edit" : "Create"} a Note</ModalHeader>
          <ModalCloseButton />
          <ModalBody pb={6}>
            <FormControl isInvalid={!!errors?.title} isRequired>
              <FormLabel>Title</FormLabel>
              <Input
                name="title"
                placeholder="Title"
                defaultValue={selectedNote?.title}
                ref={register({ validate: validateTitle })}
              />
              <FormErrorMessage>
                {!!errors?.title && errors?.title?.message}
              </FormErrorMessage>
            </FormControl>
            <FormControl size="lg" mt={4} isInvalid={!!errors?.body} isRequired>
              <FormLabel>Body</FormLabel>
              <Textarea
                name="body"
                placeholder="Body"
                size="md"
                borderRadius="5px"
                defaultValue={selectedNote?.body}
                ref={register({ validate: validateBody })}
              />
              <FormErrorMessage>
                {!!errors?.body && errors?.body?.message}
              </FormErrorMessage>
            </FormControl>
          </ModalBody>
          <ModalFooter>
            <Button
              type="submit"
              colorScheme="blue"
              isLoading={formState.isSubmitting}
              mr={3}
            >
              Save
            </Button>
            <Button onClick={onClose}>Cancel</Button>
          </ModalFooter>
        </form>
      </ModalContent>
    </Modal>
  );
}
Example #10
Source File: EditShortcut.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
EditShortcut = ({ shortcut, onClose, anchorEl, api }: Props) => {
  const classes = useStyles();
  const alertApi = useApi(alertApiRef);
  const open = Boolean(anchorEl);

  const handleSave: SubmitHandler<FormValues> = async ({ url, title }) => {
    const newShortcut: Shortcut = {
      ...shortcut,
      url,
      title,
    };

    try {
      await api.update(newShortcut);
      alertApi.post({
        message: `Updated shortcut '${title}'`,
        severity: 'success',
      });
    } catch (error) {
      alertApi.post({
        message: `Could not update shortcut: ${error.message}`,
        severity: 'error',
      });
    }

    onClose();
  };

  const handleRemove = async () => {
    try {
      await api.remove(shortcut.id);
      alertApi.post({
        message: `Removed shortcut '${shortcut.title}' from your sidebar`,
        severity: 'success',
      });
    } catch (error) {
      alertApi.post({
        message: `Could not delete shortcut: ${error.message}`,
        severity: 'error',
      });
    }
  };

  const handleClose = () => {
    onClose();
  };

  return (
    <Popover
      open={open}
      anchorEl={anchorEl}
      onClose={onClose}
      anchorOrigin={{
        vertical: 'top',
        horizontal: 'right',
      }}
    >
      <Card className={classes.card}>
        <CardHeader
          className={classes.header}
          title="Edit Shortcut"
          titleTypographyProps={{ variant: 'subtitle2' }}
          action={
            <Button
              className={classes.button}
              variant="text"
              size="small"
              color="secondary"
              startIcon={<DeleteIcon />}
              onClick={handleRemove}
            >
              Remove
            </Button>
          }
        />
        <ShortcutForm
          formValues={{ url: shortcut.url, title: shortcut.title }}
          onClose={handleClose}
          onSave={handleSave}
        />
      </Card>
    </Popover>
  );
}
Example #11
Source File: ReceiveModal.tsx    From raspiblitz-web with MIT License 4 votes vote down vote up
ReceiveModal: FC<Props> = ({ onClose }) => {
  const { unit } = useContext(AppContext);
  const { t } = useTranslation();
  const [invoiceType, setInvoiceType] = useState(TxType.LIGHTNING);
  const [address, setAddress] = useState("");
  const [amount, setAmount] = useState(0);
  const [comment, setComment] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [copyAddress, addressCopied] = useClipboard(address);
  const [error, setError] = useState("");

  const lnInvoice = invoiceType === TxType.LIGHTNING;

  const changeInvoiceHandler = async (txType: TxType) => {
    setAddress("");
    setAmount(0);
    setComment("");
    setError("");

    setInvoiceType(txType);

    if (txType === TxType.ONCHAIN) {
      setIsLoading(true);
      await instance
        .post("lightning/new-address", {
          type: "p2wkh",
        })
        .then((resp) => {
          setAddress(resp.data);
        })
        .catch((err) => {
          setError(`${t("login.error")}: ${err.response?.data?.detail}`);
        })
        .finally(() => {
          setIsLoading(false);
        });
    }
  };

  const commentChangeHandler = (event: ChangeEvent<HTMLInputElement>) => {
    setComment(event.target.value);
  };

  const amountChangeHandler = (event: ChangeEvent<HTMLInputElement>) => {
    setAmount(+event.target.value);
  };

  const generateInvoiceHandler = async () => {
    setIsLoading(true);
    const mSatAmount =
      unit === Unit.BTC ? convertBtcToSat(amount) * 1000 : amount * 1000;
    await instance
      .post(`lightning/add-invoice?value_msat=${mSatAmount}&memo=${comment}`)
      .then((resp) => {
        setAddress(resp.data.payment_request);
      })
      .catch((err) => {
        setError(`${t("login.error")}: ${err.response?.data?.detail}`);
      })
      .finally(() => {
        setIsLoading(false);
      });
  };

  const showLnInvoice = lnInvoice && !isLoading;

  const {
    register,
    handleSubmit,
    formState: { errors, isValid, submitCount },
  } = useForm<IFormInputs>({
    mode: "onChange",
  });

  const onSubmit: SubmitHandler<IFormInputs> = (_data) =>
    generateInvoiceHandler();

  return createPortal(
    <ModalDialog close={onClose}>
      {showLnInvoice && (
        <div className="text-xl font-bold">{t("wallet.create_invoice_ln")}</div>
      )}

      {!showLnInvoice && (
        <div className="text-xl font-bold">{t("wallet.fund")}</div>
      )}

      <div className="my-3">
        <SwitchTxType
          invoiceType={invoiceType}
          onTxTypeChange={changeInvoiceHandler}
        />
      </div>

      {address && (
        <>
          <div className="my-5 flex justify-center">
            <QRCodeSVG value={address} size={256} />
          </div>
          <p className="my-5 text-sm text-gray-500 dark:text-gray-300">
            {t("wallet.scan_qr")}
          </p>
        </>
      )}

      <form
        className="flex w-full flex-col items-center"
        onSubmit={handleSubmit(onSubmit)}
      >
        <fieldset className="mb-5 w-4/5">
          {isLoading && (
            <div className="p-5">
              <LoadingSpinner />
            </div>
          )}

          {showLnInvoice && !address && (
            <div className="flex flex-col justify-center pb-5 text-center">
              <AmountInput
                amount={amount}
                register={register("amountInput", {
                  required: t("forms.validation.chainAmount.required"),
                  validate: {
                    greaterThanZero: () =>
                      amount > 0 || t("forms.validation.chainAmount.required"),
                  },
                  onChange: amountChangeHandler,
                })}
                errorMessage={errors.amountInput}
              />

              <div className="mt-2 flex flex-col justify-center">
                <InputField
                  {...register("commentInput", {
                    onChange: commentChangeHandler,
                  })}
                  label={t("tx.comment")}
                  value={comment}
                  placeholder={t("tx.comment_placeholder")}
                />
              </div>
            </div>
          )}

          {error && <ErrorMessage errorMessage={error} />}

          {!address && showLnInvoice && (
            <button
              type="submit"
              className="bd-button my-3 p-3"
              disabled={submitCount > 0 && !isValid}
            >
              {t("wallet.create_invoice")}
            </button>
          )}
        </fieldset>
      </form>

      {address && (
        <>
          <article className="mb-5 flex flex-row items-center">
            <Tooltip
              overlay={
                <div>
                  {addressCopied
                    ? t("wallet.copied")
                    : t("wallet.copy_clipboard")}
                </div>
              }
              placement="top"
            >
              <p
                onClick={copyAddress}
                className="m-2 w-full break-all text-gray-600 dark:text-white"
              >
                {address}
              </p>
            </Tooltip>
          </article>
        </>
      )}
    </ModalDialog>,
    MODAL_ROOT
  );
}
Example #12
Source File: SendOnChain.tsx    From raspiblitz-web with MIT License 4 votes vote down vote up
SendOnChain: FC<Props> = ({
  amount,
  address,
  balance,
  comment,
  fee,
  onChangeAmount,
  onChangeAddress,
  onChangeComment,
  onChangeFee,
  onConfirm,
}) => {
  const { t } = useTranslation();
  const { unit } = useContext(AppContext);

  const convertedBalance =
    unit === Unit.BTC ? convertSatToBtc(balance) : balance;

  const balanceDecorated =
    unit === Unit.BTC
      ? convertToString(unit, convertedBalance)
      : convertToString(unit, balance);

  const {
    register,
    handleSubmit,
    formState: { errors, isValid, submitCount },
  } = useForm<IFormInputs>({
    mode: "onChange",
  });

  const onSubmit: SubmitHandler<IFormInputs> = (_data) => onConfirm();

  return (
    <form className="px-5" onSubmit={handleSubmit(onSubmit)}>
      <h3 className="text-xl font-bold">{t("wallet.send_onchain")}</h3>

      <p className="my-5">
        <span className="font-bold">{t("wallet.balance")}:&nbsp;</span>
        {balanceDecorated} {unit}
      </p>

      <fieldset className="my-5 flex flex-col items-center justify-center text-center">
        <div className="w-full py-1 md:w-10/12">
          <InputField
            {...register("addressInput", {
              required: t("forms.validation.chainAddress.required"),
              pattern: {
                value: /^(1|3|bc1|tb1|tpub|bcrt)\w+/i,
                message: t("forms.validation.chainAddress.patternMismatch"),
              },
              onChange: onChangeAddress,
            })}
            placeholder="bc1..."
            label={t("wallet.address")}
            errorMessage={errors.addressInput}
            value={address}
          />
        </div>

        <div className="w-full py-1 md:w-10/12">
          <AmountInput
            amount={amount}
            errorMessage={errors?.amountInput}
            register={register("amountInput", {
              required: t("forms.validation.chainAmount.required"),
              max: {
                value: convertedBalance || 0,
                message: t("forms.validation.chainAmount.max"),
              },
              validate: {
                greaterThanZero: () =>
                  amount > 0 || t("forms.validation.chainAmount.required"),
              },
              onChange: onChangeAmount,
            })}
          />
        </div>

        <div className="w-full py-1 md:w-10/12">
          <InputField
            {...register("feeInput", {
              required: t("forms.validation.chainFee.required"),
              onChange: onChangeFee,
            })}
            label={t("tx.fee")}
            errorMessage={errors.feeInput}
            value={fee}
            inputRightAddon="sat / vByte"
            type="number"
          />
        </div>

        <div className="w-full py-1 md:w-10/12">
          <InputField
            {...register("commentInput", {
              onChange: onChangeComment,
            })}
            label={t("tx.comment")}
            value={comment}
            placeholder={t("tx.comment_placeholder")}
          />
        </div>
      </fieldset>

      <div className="mb-5 inline-block w-4/5 align-top lg:w-3/12">
        <button
          type="submit"
          className="bd-button my-3 p-3"
          disabled={submitCount > 0 && !isValid}
        >
          {t("wallet.confirm")}
        </button>
      </div>
    </form>
  );
}
Example #13
Source File: useMessageDialog.tsx    From jobsgowhere with MIT License 4 votes vote down vote up
MessageDialogContainer: React.FC = () => {
  const messageDialogRef = React.useRef<HTMLDivElement | null>(null);
  const parameters = {} as MessageDialogParameters;
  const [messageDialogParameters, setMessageDialogParameters] = React.useState(parameters);
  const [shouldShowDialog, setShowDialog] = React.useState(false);
  const { handleSubmit, register, errors, setValue } = useForm();

  setMessageDialog = setMessageDialogParameters;
  showMessageDialog = setShowDialog;
  React.useEffect(() => {
    const node = document.createElement("div");
    document.body.appendChild(node);
    messageDialogRef.current = node;
    register("message", {
      required: "Please enter a message",
      minLength: {
        value: 3,
        message: "Please enter a message with a minimum of 3 characters",
      },
    });
  }, [register]);

  const onSubmit: SubmitHandler<FormValues> = (values) => {
    const receiverId = messageDialogParameters.job_poster.id;
    const subject = `${messageDialogParameters.current_user.first_name} ${messageDialogParameters.current_user.last_name} connected with you`;
    const body = values.message;
    const postTitle = messageDialogParameters.position.job_title;
    ApiClient.post(`${process.env.REACT_APP_API}/sendmessage`, {
      to: receiverId,
      subject,
      body,
      postTitle,
    })
      .then(() => {
        toast("? Good Job! Message Sent! Check your email for replies.");
        showMessageDialog(false);
      })
      .catch((err) => {
        toast(`❗️ Error: ${err.response.data.error_description}`);
      });
  };

  const handleTextAreaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
    setValue("message", event.target.value);
  };

  function markup() {
    if (!shouldShowDialog) return null;
    return (
      <StyledMessageDialogHolder>
        <HeaderContainer>
          <DialogTitle>{messageDialogParameters.title}</DialogTitle>
          <CloseButton onClick={() => setShowDialog(false)}>
            <svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path
                d="M16.064 8.315a.188.188 0 0 0-.188-.187l-1.546.007L12 10.912 9.673 8.137l-1.55-.007a.187.187 0 0 0-.187.188c0 .044.016.086.044.122l3.05 3.633-3.05 3.63a.188.188 0 0 0 .143.31l1.55-.008L12 13.228l2.327 2.775 1.547.007a.187.187 0 0 0 .188-.188.194.194 0 0 0-.045-.121l-3.044-3.63 3.049-3.634a.188.188 0 0 0 .042-.122z"
                fill="#3498DB"
              />
              <path
                d="M12 1.523c-5.798 0-10.5 4.702-10.5 10.5 0 5.799 4.702 10.5 10.5 10.5s10.5-4.701 10.5-10.5c0-5.798-4.702-10.5-10.5-10.5zm0 19.22a8.72 8.72 0 0 1-8.719-8.72A8.72 8.72 0 0 1 12 3.305a8.72 8.72 0 0 1 8.719 8.718A8.72 8.72 0 0 1 12 20.743z"
                fill="#3498DB"
              />
            </svg>
          </CloseButton>
        </HeaderContainer>
        <Container>
          <ContentContainerNested>
            <Avatar>
              <AvatarImage src={messageDialogParameters.job_poster.avatar_url} />
            </Avatar>
            <Info>
              <InfoHeader>
                <div>
                  <Name>
                    {messageDialogParameters.job_poster.first_name}{" "}
                    {messageDialogParameters.job_poster.last_name}
                  </Name>
                  <Headline>
                    {messageDialogParameters.job_poster.job_title} at{" "}
                    {messageDialogParameters.job_poster.company}
                  </Headline>
                </div>
              </InfoHeader>
              <Title>{messageDialogParameters.position.job_title}</Title>
            </Info>
          </ContentContainerNested>
          <form onSubmit={handleSubmit(onSubmit)}>
            <TextArea
              key={messageDialogParameters.id}
              placeholder={messageDialogParameters.position.placeholder}
              name="message"
              onChange={handleTextAreaChange}
              error={!!errors.message}
            />
            {errors.message ? (
              <InputErrorMessage>{errors.message.message}</InputErrorMessage>
            ) : null}
            <Button fullWidth primary>
              Send Message
            </Button>
          </form>
        </Container>
      </StyledMessageDialogHolder>
    );
  }

  if (!messageDialogRef.current) return null;
  return createPortal(markup(), messageDialogRef.current);
}
Example #14
Source File: Edit.tsx    From jobsgowhere with MIT License 4 votes vote down vote up
Edit: React.FC<ProfileEditProps> = ({ profile, newUser, handleCancelEdit }) => {
  const { firstName, lastName, email, picture } = profile;
  const profileType = ("profileType" in profile && profile.profileType) || SEEKER;
  const company = ("company" in profile && profile.company) || "";
  const [headline, setHeadline] = React.useState(("headline" in profile && profile.headline) || "");
  const [website, setWebsite] = React.useState(("website" in profile && profile.website) || "");

  const [selectedProfileType, setSelectedProfileType] = React.useState(profileType);
  const { register, handleSubmit, errors } = useForm<FormValues>();

  const onSubmit: SubmitHandler<FormValues> = async (values) => {
    try {
      await ApiClient({
        url: `${process.env.REACT_APP_API}/profile`,
        method: newUser ? "post" : "put",
        data: { ...values, email }, // add email value into request payload since email field is disabled
      });
      window.location.reload();
    } catch (error) {
      console.error(error.toJSON());
      throw error;
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Helmet>
        <title>Edit Profile</title>
      </Helmet>
      <Fieldset>
        <ProfileImage src={picture} width="128" height="128" alt="profile image" />
      </Fieldset>
      <TwoCol>
        <Col>
          <Fieldset>
            <Label htmlFor="first-name">First Name</Label>
            <TextInput
              id="first-name"
              name="first_name"
              defaultValue={firstName}
              ref={register({ required: true })}
              error={!!errors.first_name}
            />
          </Fieldset>
        </Col>
        <Col>
          <Fieldset>
            <Label htmlFor="last-name">Last Name</Label>
            <TextInput
              id="last-name"
              name="last_name"
              defaultValue={lastName}
              ref={register({ required: true })}
              error={!!errors.last_name}
            />
          </Fieldset>
        </Col>
      </TwoCol>
      {newUser ? (
        <Fieldset name="profile-type">
          <Label htmlFor="profile-type">Profile Type</Label>
          <RadiosHolder>
            <div className="radio-item">
              <Radio
                value={SEEKER}
                name="profile_type"
                defaultChecked={profileType === SEEKER}
                onChange={() => {
                  setSelectedProfileType(SEEKER);
                }}
                innerRef={register}
              >
                I&apos;m Seeking
              </Radio>
            </div>
            <div className="radio-item">
              <Radio
                value={RECRUITER}
                name="profile_type"
                defaultChecked={profileType === RECRUITER}
                onChange={() => {
                  setSelectedProfileType(RECRUITER);
                }}
                innerRef={register}
              >
                I&apos;m Hiring
              </Radio>
            </div>
          </RadiosHolder>
        </Fieldset>
      ) : (
        "profileType" in profile && (
          <input type="hidden" name="profile_type" value={profile.profileType} ref={register} />
        )
      )}
      {selectedProfileType === RECRUITER && (
        <>
          <Fieldset>
            <Label htmlFor="job-title">Your Title</Label>
            <TextInput
              id="job-title"
              name="headline"
              defaultValue={headline}
              ref={register}
              onChange={(e) => setHeadline(e.target.value)}
            />
          </Fieldset>
          <Fieldset>
            <Label htmlFor="company">Your Company</Label>
            <TextInput id="company" name="company" defaultValue={company} ref={register} />
          </Fieldset>
          <Fieldset>
            <Label htmlFor="company-website">Company Website</Label>
            <TextInput
              id="company-website"
              name="website"
              defaultValue={website}
              ref={register({
                required: `Please enter a website link in this format (e.g. ${process.env.REACT_APP_WEBSITE_URL})`,
                pattern: {
                  value: /(http(s)?):\/\/[(www.)?a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
                  message: `Please enter a valid website link (e.g. ${process.env.REACT_APP_WEBSITE_URL})`,
                },
              })}
              onChange={(e) => setWebsite(e.target.value)}
              error={!!errors.website}
            />
            {errors.website && <InputErrorMessage>{errors.website.message}</InputErrorMessage>}
            <Hint>
              Include a company link for potential candiates to learn more about your company
            </Hint>
          </Fieldset>
        </>
      )}
      {selectedProfileType === SEEKER && (
        <>
          <Fieldset>
            <Label htmlFor="headline">Headline</Label>
            <TextInput
              id="headline"
              name="headline"
              defaultValue={headline}
              ref={register}
              onChange={(e) => setHeadline(e.target.value)}
            />
            <Hint>Give a headline of what you want others to see you as.</Hint>
          </Fieldset>
          <Fieldset>
            <Label htmlFor="seeker-website">Website / Portfolio / GitHub</Label>
            <TextInput
              id="seeker-website"
              name="website"
              defaultValue={website}
              ref={register({
                required: `Please enter a website link in this format (e.g. ${process.env.REACT_APP_WEBSITE_URL})`,
                pattern: {
                  value: /(http(s)?):\/\/[(www.)?a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
                  message: `Please enter a valid website link (e.g. ${process.env.REACT_APP_WEBSITE_URL})`,
                },
              })}
              onChange={(e) => setWebsite(e.target.value)}
              error={!!errors.website}
            />
            {errors.website && <InputErrorMessage>{errors.website.message}</InputErrorMessage>}
            <Hint>Include a link for potential companies to learn more about you.</Hint>
          </Fieldset>
        </>
      )}
      <Fieldset>
        <Label htmlFor="email">Email</Label>
        <TextInput
          id="email"
          name="email"
          defaultValue={email}
          disabled
          ref={register({
            required: true,
            pattern: /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
          })}
          error={!!errors.email}
        />
        <Hint>This is for the emails you will receive when you connect with someone.</Hint>
      </Fieldset>
      {newUser ? (
        <Button type="submit" fullWidth primary>
          Continue
        </Button>
      ) : (
        <TwoCol>
          <Col>
            <Button type="button" onClick={handleCancelEdit} fullWidth>
              Cancel
            </Button>
          </Col>
          <Col>
            <Button type="submit" fullWidth primary>
              Save
            </Button>
          </Col>
        </TwoCol>
      )}
      <input type="hidden" name="avatar_url" value={picture} ref={register} />
    </form>
  );
}
Example #15
Source File: ModifyAccount.tsx    From revite with GNU Affero General Public License v3.0 4 votes vote down vote up
export function ModifyAccountModal({ onClose, field }: Props) {
    const client = useContext(AppContext);
    const [processing, setProcessing] = useState(false);
    const { handleSubmit, register, errors } = useForm<FormInputs>();
    const [error, setError] = useState<string | undefined>(undefined);

    const onSubmit: SubmitHandler<FormInputs> = async ({
        password,
        new_username,
        new_email,
        new_password,
    }) => {
        if (processing) return;
        setProcessing(true);

        try {
            if (field === "email") {
                await client.api.patch("/auth/account/change/email", {
                    current_password: password,
                    email: new_email,
                });
                onClose();
            } else if (field === "password") {
                await client.api.patch("/auth/account/change/password", {
                    current_password: password,
                    password: new_password,
                });
                onClose();
            } else if (field === "username") {
                await client.api.patch("/users/@me/username", {
                    username: new_username,
                    password,
                });
                onClose();
            }
        } catch (err) {
            setError(takeError(err));
            setProcessing(false);
        }
    };

    return (
        <Modal
            visible={true}
            onClose={onClose}
            title={<Text id={`app.special.modals.account.change.${field}`} />}
            disabled={processing}
            actions={[
                {
                    disabled: processing,
                    confirmation: true,
                    onClick: handleSubmit(onSubmit),
                    children:
                        field === "email" ? (
                            <Text id="app.special.modals.actions.send_email" />
                        ) : (
                            <Text id="app.special.modals.actions.update" />
                        ),
                },
                {
                    onClick: onClose,
                    children: <Text id="app.special.modals.actions.cancel" />,
                    palette: "plain",
                },
            ]}>
            {/* Preact / React typing incompatabilities */}
            <form
                onSubmit={(e) => {
                    e.preventDefault();
                    handleSubmit(
                        onSubmit,
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    )(e as any);
                }}>
                {field === "email" && (
                    <FormField
                        type="email"
                        name="new_email"
                        register={register}
                        showOverline
                        error={errors.new_email?.message}
                        disabled={processing}
                    />
                )}
                {field === "password" && (
                    <FormField
                        type="password"
                        name="new_password"
                        register={register}
                        showOverline
                        error={errors.new_password?.message}
                        autoComplete="new-password"
                        disabled={processing}
                    />
                )}
                {field === "username" && (
                    <FormField
                        type="username"
                        name="new_username"
                        register={register}
                        showOverline
                        error={errors.new_username?.message}
                        disabled={processing}
                    />
                )}
                <FormField
                    type="current_password"
                    register={register}
                    showOverline
                    error={errors.current_password?.message}
                    autoComplete="current-password"
                    disabled={processing}
                />
                {error && (
                    <Overline type="error" error={error}>
                        <Text id="app.special.modals.account.failed" />
                    </Overline>
                )}
            </form>
        </Modal>
    );
}
Example #16
Source File: FormContainer.tsx    From UsTaxes with GNU Affero General Public License v3.0 4 votes vote down vote up
FormListContainer = <A,>(
  props: PropsWithChildren<FormListContainerProps<A>>
): ReactElement => {
  const {
    children,
    items,
    icon,
    max,
    primary,
    defaultValues,
    secondary,
    disableEditing = false,
    removeItem,
    onSubmitAdd,
    onSubmitEdit,
    onCancel = () => {
      // default do nothing
    },
    grouping = () => 0,
    groupHeaders = []
  } = props
  const [isOpen, setOpen] = useState(false)
  const [editing, setEditing] = useState<number | undefined>(undefined)

  const allowAdd = max === undefined || items.length < max

  // Use the provided grouping function to split the input
  // array into an array of groups. Each group has a title
  // and a list of items, along with their original index.
  const groups: [ReactNode, [A, number][]][] = _.chain(items)
    .map<[A, number]>((x, n) => [x, n])
    .groupBy(([x]) => grouping(x))
    .toPairs()
    .map<[ReactNode, [A, number][]]>(([k, xs]) => [
      groupHeaders[parseInt(k)],
      xs
    ])
    .value()

  // Note useFormContext here instead of useForm reuses the
  // existing form context from the parent.
  const { reset } = useFormContext()

  const closeForm = (): void => {
    setEditing(undefined)
    setOpen(false)
    reset(defaultValues)
  }

  const cancel = (): void => {
    closeForm()
    onCancel()
  }

  const onSave: SubmitHandler<A> = (formData): void => {
    if (editing !== undefined) {
      onSubmitEdit(editing)(formData)
    } else {
      onSubmitAdd(formData)
    }
    closeForm()
  }

  const openEditForm = (n: number): (() => void) | undefined => {
    if (!disableEditing && editing === undefined) {
      return () => {
        setEditing(n)
        setOpen(true)
        reset(items[n])
      }
    }
  }

  const itemDisplay = (() => {
    if (items.length > 0) {
      return (
        <List dense={true}>
          {groups.map(([title, group], i) => (
            <div key={`group-${i}`}>
              {title}
              {group.map(([item, originalIndex], k) => (
                <MutableListItem
                  key={k}
                  primary={primary(item)}
                  secondary={
                    secondary !== undefined ? secondary(item) : undefined
                  }
                  onEdit={openEditForm(originalIndex)}
                  disableEdit={isOpen}
                  editing={editing === originalIndex}
                  remove={
                    removeItem !== undefined
                      ? () => removeItem(originalIndex)
                      : undefined
                  }
                  icon={icon !== undefined ? icon(item) : undefined}
                />
              ))}
            </div>
          ))}
        </List>
      )
    }
  })()

  return (
    <>
      {itemDisplay}
      <OpenableFormContainer
        allowAdd={allowAdd}
        defaultValues={defaultValues}
        onSave={onSave}
        isOpen={isOpen}
        onOpenStateChange={setOpen}
        onCancel={cancel}
      >
        {children}
      </OpenableFormContainer>
    </>
  )
}