@chakra-ui/react#FormErrorMessage TypeScript Examples

The following examples show how to use @chakra-ui/react#FormErrorMessage. 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: UserForm.tsx    From next-crud with MIT License 6 votes vote down vote up
UserForm = ({ initialValues, onSubmit }: IProps) => {
  const { register, formState, handleSubmit } = useForm<IFormValues>({
    defaultValues: initialValues,
    resolver: yupResolver(schema),
    mode: 'onChange',
  })

  return (
    <VStack
      as="form"
      onSubmit={handleSubmit(onSubmit)}
      spacing={4}
      width="100%"
    >
      <FormControl
        id="username"
        isInvalid={!!formState.errors.username?.message}
      >
        <FormLabel>Username</FormLabel>
        <Input name="username" ref={register} />
        <FormErrorMessage>
          {formState.errors.username?.message}
        </FormErrorMessage>
      </FormControl>
      <Button
        type="submit"
        colorScheme="blue"
        isLoading={formState.isSubmitting}
        disabled={!formState.isValid}
      >
        Submit
      </Button>
    </VStack>
  )
}
Example #2
Source File: index.tsx    From formik-chakra-ui with MIT License 6 votes vote down vote up
FormControl: FC<BaseProps> = (props: BaseProps) => {
  const {
    children,
    name,
    label,
    labelProps,
    helperText,
    helperTextProps,
    errorMessageProps,
    ...rest
  } = props;
  const [, { error, touched }] = useField(name);

  return (
    <ChakraFormControl isInvalid={!!error && touched} {...rest}>
      {label && (
        <FormLabel htmlFor={name} {...labelProps}>
          {label}
        </FormLabel>
      )}
      {children}
      {error && (
        <FormErrorMessage {...errorMessageProps}>{error}</FormErrorMessage>
      )}
      {helperText && (
        <FormHelperText {...helperTextProps}>{helperText}</FormHelperText>
      )}
    </ChakraFormControl>
  );
}
Example #3
Source File: Content.tsx    From calories-in with MIT License 5 votes vote down vote up
function Content({ title, onClose, initialRef, variantFormIndex }: Props) {
  const { register } = useFormContext()
  const nameRegister = register('name')
  const nameInputRef = useMergeRefs(nameRegister.ref, initialRef)

  const onSubmit = useSubmitVariantNameForm({
    variantFormIndex,
    onComplete: onClose,
  })

  const { errorMessage, isInvalid } = useFormError('name')

  useSelectInputText(initialRef)

  return (
    <form onSubmit={onSubmit}>
      <ModalContent>
        <ModalHeader>{title}</ModalHeader>
        <ModalCloseButton />

        <ModalBody>
          <FormControl isInvalid={isInvalid}>
            <FormLabel>Name</FormLabel>
            <Input
              autoComplete="off"
              {...nameRegister}
              ref={nameInputRef}
              focusBorderColor={isInvalid ? 'red.500' : undefined}
              placeholder="Enter name"
            />
            <Collapse animateOpacity={true} in={Boolean(errorMessage)}>
              <Box minHeight="21px">
                <FormErrorMessage>{errorMessage}</FormErrorMessage>
              </Box>
            </Collapse>
          </FormControl>
        </ModalBody>

        <ModalFooter>
          <Button mr={3} onClick={onClose}>
            Close
          </Button>
          <Button
            type="submit"
            colorScheme="teal"
            variant="solid"
            onClick={onSubmit}
          >
            Rename
          </Button>
        </ModalFooter>
      </ModalContent>
    </form>
  )
}
Example #4
Source File: PollIntervalField.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
PollIntervalField = ({ helpText }: PollIntervalFieldProps): JSX.Element => {
    const dispatch = useAppDispatch();

    const pollInterval: number = useAppSelector(state => state.config.db_poll_interval) ?? 1000;
    const [newInterval, setNewInterval] = useState(pollInterval);
    const [intervalError, setIntervalError] = useState('');
    const hasIntervalError: boolean = (intervalError?? '').length > 0;

    useEffect(() => { setNewInterval(pollInterval); }, [pollInterval]);

    /**
     * A handler & validator for saving a new poll interval
     *
     * @param theNewInterval - The new interval to save
     */
    const saveInterval = (theNewInterval: number): void => {
        // Validate the interval
        if (theNewInterval < 500) {
            setIntervalError('The interval must be at least 500ms or else database locks can occur');
            return;
        }

        dispatch(setConfig({ name: 'db_poll_interval', value: theNewInterval }));
        if (hasIntervalError) setIntervalError('');
        showSuccessToast({
            id: 'settings',
            duration: 4000,
            description: 'Successfully saved new poll interval! Restarting DB listeners...'
        });
    };

    return (
        <FormControl isInvalid={hasIntervalError}>
            <FormLabel htmlFor='db_poll_interval'>Database Poll Interval (ms)</FormLabel>
            <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                <Input
                    id='db_poll_interval'
                    type='number'
                    maxWidth="5em"
                    value={newInterval}
                    onChange={(e) => {
                        if (hasIntervalError) setIntervalError('');
                        setNewInterval(Number.parseInt(e.target.value));
                    }}
                />
                <IconButton
                    ml={3}
                    verticalAlign='top'
                    aria-label='Save poll interval'
                    icon={<AiOutlineSave />}
                    onClick={() => saveInterval(newInterval)}
                />
            </Flex>
            {!hasIntervalError ? (
                <FormHelperText>
                    {helpText ?? 'Enter how often (in milliseconds) you want the server to check for new messages in the database'}
                </FormHelperText>
            ) : (
                <FormErrorMessage>{intervalError}</FormErrorMessage>
            )}
        </FormControl>
    );
}
Example #5
Source File: ServerPasswordField.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
ServerPasswordField = ({ helpText }: ServerPasswordFieldProps): JSX.Element => {
    const dispatch = useAppDispatch();

    const password: string = (useAppSelector(state => state.config.password) ?? '');
    const [showPassword, setShowPassword] = useBoolean();
    const [newPassword, setNewPassword] = useState(password);
    const [passwordError, setPasswordError] = useState('');
    const hasPasswordError: boolean = (passwordError?? '').length > 0;

    useEffect(() => { setNewPassword(password); }, [password]);

    /**
     * A handler & validator for saving a new password.
     *
     * @param theNewPassword - The new password to save
     */
    const savePassword = (theNewPassword: string): void => {
        // Validate the port
        if (theNewPassword.length < 8) {
            setPasswordError('Your password must be at least 8 characters!');
            return;
        } else if (theNewPassword === password) {
            setPasswordError('You have not changed the password since your last save!');
            return;
        }

        dispatch(setConfig({ name: 'password', value: theNewPassword }));
        if (hasPasswordError) setPasswordError('');
        showSuccessToast({
            id: 'settings',
            description: 'Successfully saved new password!'
        });
    };

    return (
        <FormControl isInvalid={hasPasswordError}>
            <FormLabel htmlFor='password'>Server Password</FormLabel>
            <Input
                id='password'
                type={showPassword ? 'text' : 'password'}
                maxWidth="20em"
                value={newPassword}
                onChange={(e) => {
                    if (hasPasswordError) setPasswordError('');
                    setNewPassword(e.target.value);
                }}
            />
            <IconButton
                ml={3}
                verticalAlign='top'
                aria-label='View password'
                icon={showPassword ? <AiFillEye /> : <AiFillEyeInvisible />}
                onClick={() => setShowPassword.toggle()}
            />
            <IconButton
                ml={3}
                verticalAlign='top'
                aria-label='Save password'
                icon={<AiOutlineSave />}
                onClick={() => savePassword(newPassword)}
            />
            {!hasPasswordError ? (
                <FormHelperText>
                    {helpText ?? 'Enter a password to use for clients to authenticate with the server'}
                </FormHelperText>
            ) : (
                <FormErrorMessage>{passwordError}</FormErrorMessage>
            )}
        </FormControl>
    );
}
Example #6
Source File: index.tsx    From calories-in with MIT License 5 votes vote down vote up
function Form({
  ownerName,
  notes,
  onClose,
  initialRef,
  onEditNotes,
  fieldId,
  textAreaHeight,
}: Props) {
  const { register, handleSubmit } = useFormContext()
  const notesRegister = register('notes')
  const notesInputRef = useMergeRefs(notesRegister.ref, initialRef)
  const oneTimeCheckActions = useOneTimeCheckActions()

  const onSubmit = handleSubmit((form: NotesForm) => {
    oneTimeCheckActions.set(`notes-${fieldId}`)
    onEditNotes(form.notes || undefined)
    onClose()
  })

  const { errorMessage, isInvalid } = useFormError('name')

  return (
    <form onSubmit={onSubmit}>
      <ModalContent>
        <Header ownerName={ownerName} notes={notes} />
        <ModalCloseButton />

        <ModalBody>
          <FormControl isInvalid={isInvalid}>
            <FormLabel>Notes</FormLabel>
            <Textarea
              autoComplete="off"
              {...notesRegister}
              ref={notesInputRef}
              focusBorderColor={isInvalid ? 'red.500' : undefined}
              placeholder="Enter notes"
              height={textAreaHeight}
            />
            <Collapse animateOpacity={true} in={Boolean(errorMessage)}>
              <Box minHeight="21px">
                <FormErrorMessage>{errorMessage}</FormErrorMessage>
              </Box>
            </Collapse>
          </FormControl>
        </ModalBody>

        <ModalFooter>
          <Button mr={3} onClick={onClose}>
            Close
          </Button>
          <Button
            type="submit"
            colorScheme="teal"
            variant="solid"
            onClick={onSubmit}
          >
            Save
          </Button>
        </ModalFooter>
      </ModalContent>
    </form>
  )
}
Example #7
Source File: LocalPortField.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
LocalPortField = ({ helpText }: LocalPortFieldProps): JSX.Element => {
    const dispatch = useAppDispatch();

    const port: number = useAppSelector(state => state.config.socket_port) ?? 1234;
    const [newPort, setNewPort] = useState(port);
    const [portError, setPortError] = useState('');
    const hasPortError: boolean = (portError?? '').length > 0;

    useEffect(() => { setNewPort(port); }, [port]);

    /**
     * A handler & validator for saving a new port.
     *
     * @param theNewPort - The new port to save
     */
    const savePort = (theNewPort: number): void => {
        // Validate the port
        if (theNewPort < 1024 || theNewPort > 65635) {
            setPortError('Port must be between 1,024 and 65,635');
            return;
        } else if (theNewPort === port) {
            setPortError('You have not changed the port since your last save!');
            return;
        }

        dispatch(setConfig({ name: 'socket_port', value: theNewPort }));
        if (hasPortError) setPortError('');
        showSuccessToast({
            id: 'settings',
            duration: 4000,
            description: 'Successfully saved new port! Restarting Proxy & HTTP services...'
        });
    };

    return (
        <FormControl isInvalid={hasPortError}>
            <FormLabel htmlFor='socket_port'>Local Port</FormLabel>
            <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                <Input
                    id='socket_port'
                    type='number'
                    maxWidth="5em"
                    value={newPort}
                    onChange={(e) => {
                        if (hasPortError) setPortError('');
                        setNewPort(Number.parseInt(e.target.value));
                    }}
                />
                <IconButton
                    ml={3}
                    verticalAlign='top'
                    aria-label='Save port'
                    icon={<AiOutlineSave />}
                    onClick={() => savePort(newPort)}
                />
            </Flex>
            {!hasPortError ? (
                <FormHelperText>
                    {helpText ?? 'Enter the local port for the socket server to run on'}
                </FormHelperText>
            ) : (
                <FormErrorMessage>{portError}</FormErrorMessage>
            )}
        </FormControl>
    );
}
Example #8
Source File: AddAppDomain.tsx    From ledokku with MIT License 5 votes vote down vote up
AddAppDomain = ({ appId, appDomainsRefetch }: AddDomainProps) => {
  const toast = useToast();
  const [addDomainMutation] = useAddDomainMutation();
  const [showAddForm, setShowAddForm] = useState(false);

  const formik = useFormik<{ domainName: string }>({
    initialValues: {
      domainName: '',
    },
    validateOnChange: true,
    validationSchema: addAppDomainYupSchema,
    onSubmit: async (values) => {
      try {
        await addDomainMutation({
          variables: {
            input: {
              appId,
              domainName: values.domainName,
            },
          },
        });

        await appDomainsRefetch();
        toast.success('Domain added successfully');

        formik.resetForm();
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  return (
    <>
      {!showAddForm ? (
        <Button variant="outline" onClick={() => setShowAddForm(true)}>
          Add custom domain
        </Button>
      ) : (
        <form onSubmit={formik.handleSubmit}>
          <FormControl
            id="domainName"
            isInvalid={Boolean(
              formik.errors.domainName && formik.touched.domainName
            )}
          >
            <Input
              placeholder="www.mydomain.com"
              name="domainName"
              value={formik.values.domainName}
              onChange={formik.handleChange}
            />
            <FormErrorMessage>{formik.errors.domainName}</FormErrorMessage>
          </FormControl>

          <ButtonGroup mt="4" spacing="2">
            <Button isLoading={formik.isSubmitting} type="submit">
              Save
            </Button>
            <Button
              variant="outline"
              disabled={formik.isSubmitting}
              onClick={() => setShowAddForm(false)}
            >
              Cancel
            </Button>
          </ButtonGroup>
        </form>
      )}
    </>
  );
}
Example #9
Source File: AddAppProxyPorts.tsx    From ledokku with MIT License 4 votes vote down vote up
AddAppProxyPorts = ({
  appId,
  appProxyPortsRefetch,
  open,
  onClose,
}: AddAppProxyPortsProps) => {
  const [addAppProxyPortMutation] = useAddAppProxyPortMutation();
  const toast = useToast();
  const formik = useFormik<{ host: string; container: string }>({
    initialValues: {
      host: '',
      container: '',
    },
    validateOnChange: true,
    validationSchema: createAppProxyPortSchema,
    onSubmit: async (values) => {
      try {
        await addAppProxyPortMutation({
          variables: {
            input: {
              appId,
              host: values.host,
              container: values.container,
            },
          },
        });
        await appProxyPortsRefetch();
        toast.success('Port mapping created successfully');

        onClose();
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  if (!open) {
    return null;
  }

  return (
    <Modal isOpen={open} onClose={onClose} isCentered size="xl">
      <ModalOverlay />
      <ModalContent>
        <ModalHeader>Add port mapping</ModalHeader>
        <ModalCloseButton />
        <ModalBody>
          <SimpleGrid columns={{ sm: 1, md: 2 }} spacing={3}>
            <FormControl
              id="host"
              isInvalid={Boolean(formik.errors.host && formik.touched.host)}
            >
              <FormLabel>Host port:</FormLabel>
              <Input
                name="host"
                value={formik.values.host}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
              />
              <FormErrorMessage>{formik.errors.host}</FormErrorMessage>
            </FormControl>
            <FormControl
              id="container"
              isInvalid={Boolean(
                formik.errors.container && formik.touched.container
              )}
            >
              <FormLabel>Container port:</FormLabel>
              <Input
                name="container"
                value={formik.values.container}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
              />
              <FormErrorMessage>{formik.errors.container}</FormErrorMessage>
            </FormControl>
          </SimpleGrid>
        </ModalBody>

        <ModalFooter>
          <Button mr={3} onClick={onClose}>
            Cancel
          </Button>
          <Button
            colorScheme="red"
            isLoading={formik.isSubmitting}
            onClick={() => formik.handleSubmit()}
          >
            Create
          </Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}
Example #10
Source File: index.tsx    From calories-in with MIT License 4 votes vote down vote up
function StatFormField(props: Props) {
  const {
    name,
    label,
    inputType,
    isIdented = false,
    nutritionValueUnit = 'g',
    textInputRef,
    isReadOnly = false,
    isEmphasized = false,
    isValueBold = false,
    isCaption = false,
    isRequired,
    children,
    labelDetail,
    dividerProps = {},
    hasDivider = true,
    dailyValuePercent,
    labelElement,
    formLabelProps,
    ...rest
  } = props
  const { errorMessage, isInvalid } = useFormError(name)

  const inputElement = useGetInputElement({
    isInvalid,
    name,
    inputType,
    textInputRef,
    isReadOnly,
    nutritionValueUnit,
    isBold: isValueBold,
  })

  const labelDetailElement = labelDetail ? (
    <Text
      as={isReadOnly ? 'span' : undefined}
      fontSize="sm"
      fontWeight="thin"
      ml={1}
    >
      {labelDetail}
    </Text>
  ) : null

  const isValueNextToLabel = isReadOnly && !(isCaption || isEmphasized)

  return (
    <FormControl
      isInvalid={isInvalid}
      id={name}
      pl={isIdented ? 10 : 0}
      isRequired={!isReadOnly && isRequired}
      {...rest}
    >
      <VStack spacing={2} alignItems="stretch">
        {hasDivider && <Divider {...dividerProps} />}
        <Flex justifyContent={'space-between'} alignItems="center">
          <Flex>
            <FormLabel
              fontWeight={
                isIdented ? 'normal' : isEmphasized ? 'semibold' : 'medium'
              }
              flexShrink={0}
              fontSize={isCaption ? 'lg' : 'md'}
              m={0}
              {...formLabelProps}
            >
              {label || labelElement}
              {isReadOnly && labelDetailElement}
            </FormLabel>
            {!isReadOnly && labelDetailElement}
            {isValueNextToLabel && <Box ml={2}>{inputElement}</Box>}
          </Flex>

          <Flex ml={2} justifyContent="flex-end">
            {!isValueNextToLabel && inputElement}

            {dailyValuePercent !== undefined && isValueNextToLabel && (
              <Text fontWeight="medium">{`${dailyValuePercent}%`}</Text>
            )}

            {!isReadOnly && inputType === 'nutritionValue' && (
              <Flex
                width={9}
                flexShrink={0}
                justifyContent="flex-start"
                alignItems="center"
                ml={1}
              >
                <Text textColor="gray.500">{nutritionValueUnit}</Text>
              </Flex>
            )}
          </Flex>
        </Flex>
      </VStack>

      <Collapse animateOpacity={true} in={Boolean(errorMessage)}>
        <Box minHeight="21px">
          <FormErrorMessage>{errorMessage}</FormErrorMessage>
        </Box>
      </Collapse>

      {children}
    </FormControl>
  )
}
Example #11
Source File: Inspector.tsx    From openchakra with MIT License 4 votes vote down vote up
Inspector = () => {
  const dispatch = useDispatch()
  const component = useSelector(getSelectedComponent)
  const { isOpen, onOpen, onClose } = useDisclosure()
  const [componentName, onChangeComponentName] = useState('')
  const componentsNames = useSelector(getComponentNames)

  const { clearActiveProps } = useInspectorUpdate()

  const saveComponent = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    dispatch.components.setComponentName({
      componentId: component.id,
      name: componentName,
    })
    onClose()
    onChangeComponentName('')
  }
  const isValidComponentName = useMemo(() => {
    return (
      !!componentName.match(/^[A-Z]\w*$/g) &&
      !componentsNames.includes(componentName) &&
      // @ts-ignore
      !componentsList.includes(componentName)
    )
  }, [componentName, componentsNames])

  const { type, rootParentType, id, children } = component

  const isRoot = id === 'root'
  const parentIsRoot = component.parent === 'root'

  const docType = rootParentType || type
  const componentHasChildren = children.length > 0

  useEffect(() => {
    clearActiveProps()
  }, [clearActiveProps])

  return (
    <>
      <Box bg="white">
        <Box
          fontWeight="semibold"
          fontSize="md"
          color="yellow.900"
          py={2}
          px={2}
          boxShadow="sm"
          bg="yellow.100"
          display="flex"
          justifyContent="space-between"
          flexDir="column"
        >
          {isRoot ? 'Document' : type}
          {!!component.componentName && (
            <Text fontSize="xs" fontWeight="light">
              {component.componentName}
            </Text>
          )}
        </Box>
        {!isRoot && (
          <Stack
            direction="row"
            py={2}
            spacing={2}
            align="center"
            zIndex={99}
            px={2}
            flexWrap="wrap"
            justify="flex-end"
          >
            <CodeActionButton />
            {!component.componentName && (
              <ActionButton
                label="Name component"
                icon={<EditIcon path="" />}
                onClick={onOpen}
              />
            )}
            <ActionButton
              label="Duplicate"
              onClick={() => dispatch.components.duplicate()}
              icon={<CopyIcon path="" />}
            />
            <ActionButton
              label="Reset props"
              icon={<IoMdRefresh />}
              onClick={() => dispatch.components.resetProps(component.id)}
            />
            <ActionButton
              label="Chakra UI Doc"
              as={Link}
              onClick={() => {
                window.open(
                  `https://chakra-ui.com/${docType.toLowerCase()}`,
                  '_blank',
                )
              }}
              icon={<GoRepo />}
            />
            <ActionButton
              bg="red.500"
              label="Remove"
              onClick={() => dispatch.components.deleteComponent(component.id)}
              icon={<FiTrash2 />}
            />
          </Stack>
        )}
      </Box>

      <Box pb={1} bg="white" px={3}>
        <Panels component={component} isRoot={isRoot} />
      </Box>

      <StylesPanel
        isRoot={isRoot}
        showChildren={componentHasChildren}
        parentIsRoot={parentIsRoot}
      />
      <Modal onClose={onClose} isOpen={isOpen} isCentered>
        <ModalOverlay>
          <ModalContent>
            <form onSubmit={saveComponent}>
              <ModalHeader>Save this component</ModalHeader>
              <ModalCloseButton />
              <ModalBody>
                <FormControl isInvalid={!isValidComponentName}>
                  <FormLabel>Component name</FormLabel>
                  <Input
                    size="md"
                    autoFocus
                    variant="outline"
                    isFullWidth
                    focusBorderColor="blue.500"
                    errorBorderColor="red.500"
                    value={componentName}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      onChangeComponentName(e.target.value)
                    }
                  />
                  {!isValidComponentName && (
                    <FormErrorMessage>
                      Component name must start with a capital character and
                      must not contain space or special character, and name
                      should not be already taken (including existing chakra-ui
                      components).
                    </FormErrorMessage>
                  )}
                  <FormHelperText>
                    This will name your component that you will see in the code
                    panel as a separated component.
                  </FormHelperText>
                </FormControl>
              </ModalBody>
              <ModalFooter>
                <Button
                  colorScheme="blue"
                  mr={3}
                  type="submit"
                  isDisabled={!isValidComponentName}
                >
                  Save
                </Button>
                <Button onClick={onClose}>Cancel</Button>
              </ModalFooter>
            </form>
          </ModalContent>
        </ModalOverlay>
      </Modal>
    </>
  )
}
Example #12
Source File: create-database.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateDatabase = () => {
  const location = useLocation();
  const history = useHistory();
  const toast = useToast();

  const { data: dataDb } = useDatabaseQuery();
  const [arrayOfCreateDbLogs, setArrayofCreateDbLogs] = useState<RealTimeLog[]>(
    []
  );
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [createDatabaseMutation] = useCreateDatabaseMutation();
  const [
    isDbCreationSuccess,
    setIsDbCreationSuccess,
  ] = useState<DbCreationStatus>();

  useCreateDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.createDatabaseLogs;

      if (logsExist) {
        setArrayofCreateDbLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (logsExist.type === 'end:success') {
          setIsDbCreationSuccess(DbCreationStatus.SUCCESS);
        } else if (logsExist.type === 'end:failure') {
          setIsDbCreationSuccess(DbCreationStatus.FAILURE);
        }
      }
    },
  });

  const createDatabaseSchema = yup.object({
    type: yup
      .string()
      .oneOf(['POSTGRESQL', 'MYSQL', 'MONGODB', 'REDIS'])
      .required(),
    name: yup.string().when('type', (type: DatabaseTypes) => {
      return yup
        .string()
        .required('Database name is required')
        .matches(/^[a-z0-9-]+$/)
        .test(
          'Name already exists',
          `You already have created ${type} database with this name`,
          (name) =>
            !dataDb?.databases.find(
              (db) => db.name === name && type === db.type
            )
        );
    }),
  });

  const [
    isDokkuPluginInstalled,
    { data, loading, error: isDokkuPluginInstalledError },
  ] = useIsPluginInstalledLazyQuery({
    // we poll every 5 sec
    pollInterval: 5000,
  });
  const formik = useFormik<{ name: string; type: DatabaseTypes }>({
    initialValues: {
      name: location.state ? (location.state as string) : '',
      type: 'POSTGRESQL',
    },
    validateOnChange: true,
    validationSchema: createDatabaseSchema,
    onSubmit: async (values) => {
      try {
        await createDatabaseMutation({
          variables: {
            input: { name: values.name, type: values.type },
          },
        });
        setIsTerminalVisible(true);

        trackGoal(trackingGoals.createDatabase, 0);
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  const isPluginInstalled = data?.isPluginInstalled.isPluginInstalled;

  const handleNext = () => {
    setIsTerminalVisible(false);
    const dbId = arrayOfCreateDbLogs[arrayOfCreateDbLogs.length - 1].message;
    history.push(`database/${dbId}`);
  };

  // Effect for checking whether plugin is installed
  useEffect(() => {
    isDokkuPluginInstalled({
      variables: {
        pluginName: dbTypeToDokkuPlugin(formik.values.type),
      },
    });
  }, [formik.values.type, isPluginInstalled, isDokkuPluginInstalled]);

  // Effect for db creation
  useEffect(() => {
    isDbCreationSuccess === DbCreationStatus.FAILURE
      ? toast.error('Failed to create database')
      : isDbCreationSuccess === DbCreationStatus.SUCCESS &&
        toast.success('Database created successfully');
  }, [isDbCreationSuccess, toast]);

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" my="4">
        <Heading as="h2" size="md">
          Create a new database
        </Heading>
        <Box mt="12">
          {isTerminalVisible ? (
            <>
              <Text mb="2">
                Creating <b>{formik.values.type}</b> database{' '}
                <b>{formik.values.name}</b>
              </Text>
              <Text mb="2" color="gray.500">
                Creating database usually takes a couple of minutes. Breathe in,
                breathe out, logs are about to appear below:
              </Text>
              <Terminal>
                {arrayOfCreateDbLogs.map((log) => (
                  <Text key={arrayOfCreateDbLogs.indexOf(log)} size="small">
                    {log.message}
                  </Text>
                ))}
              </Terminal>

              {!!isDbCreationSuccess &&
              isDbCreationSuccess === DbCreationStatus.SUCCESS ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => handleNext()}
                    rightIcon={<FiArrowRight size={20} />}
                  >
                    Next
                  </Button>
                </Box>
              ) : !!isDbCreationSuccess &&
                isDbCreationSuccess === DbCreationStatus.FAILURE ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => {
                      setIsTerminalVisible(false);
                      formik.resetForm();
                    }}
                    rightIcon={<FiArrowLeft size={20} />}
                  >
                    Back
                  </Button>
                </Box>
              ) : null}
            </>
          ) : (
            <Box mt="8">
              <form onSubmit={formik.handleSubmit}>
                <Box mt="12">
                  {loading && (
                    <Center>
                      <Spinner />
                    </Center>
                  )}
                  {isDokkuPluginInstalledError ? (
                    <Alert
                      status="error"
                      variant="top-accent"
                      flexDirection="column"
                      alignItems="flex-start"
                      borderBottomRadius="base"
                      boxShadow="md"
                    >
                      <AlertTitle mr={2}>Request failed</AlertTitle>
                      <AlertDescription>
                        {isDokkuPluginInstalledError.message}
                      </AlertDescription>
                    </Alert>
                  ) : null}
                  {data?.isPluginInstalled.isPluginInstalled === false &&
                    !loading && (
                      <>
                        <Text mt="3">
                          Before creating a{' '}
                          <b>{formik.values.type.toLowerCase()}</b> database,
                          you will need to run this command on your dokku
                          server.
                        </Text>
                        <Terminal>{`sudo dokku plugin:install https://github.com/dokku/dokku-${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}.git ${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}`}</Terminal>
                        <Text mt="3">
                          Couple of seconds later you will be able to proceed
                          further.
                        </Text>
                      </>
                    )}
                  {data?.isPluginInstalled.isPluginInstalled === true &&
                    !loading && (
                      <SimpleGrid columns={{ sm: 1, md: 3 }}>
                        <FormControl
                          id="name"
                          isInvalid={Boolean(
                            formik.errors.name && formik.touched.name
                          )}
                        >
                          <FormLabel>Database name</FormLabel>
                          <Input
                            autoComplete="off"
                            id="name"
                            name="name"
                            value={formik.values.name}
                            onChange={formik.handleChange}
                            onBlur={formik.handleBlur}
                          />
                          <FormErrorMessage>
                            {formik.errors.name}
                          </FormErrorMessage>
                        </FormControl>
                      </SimpleGrid>
                    )}
                </Box>

                <Box mt="12">
                  <Text mb="2">Choose your database</Text>
                  <Grid
                    templateColumns={{
                      base: 'repeat(2, minmax(0, 1fr))',
                      md: 'repeat(4, minmax(0, 1fr))',
                    }}
                    gap="4"
                  >
                    <DatabaseBox
                      selected={formik.values.type === 'POSTGRESQL'}
                      label="PostgreSQL"
                      icon={<PostgreSQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'POSTGRESQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MYSQL'}
                      label="MySQL"
                      icon={<MySQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MYSQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MONGODB'}
                      label="Mongo"
                      icon={<MongoIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MONGODB')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'REDIS'}
                      label="Redis"
                      icon={<RedisIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'REDIS')}
                    />
                  </Grid>
                </Box>

                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    isLoading={formik.isSubmitting}
                    disabled={
                      data?.isPluginInstalled.isPluginInstalled === false ||
                      !formik.values.name ||
                      !!formik.errors.name ||
                      !dataDb?.databases
                    }
                    rightIcon={<FiArrowRight size={20} />}
                    type="submit"
                  >
                    Create
                  </Button>
                </Box>
              </form>
            </Box>
          )}
        </Box>
      </Container>
    </>
  );
}
Example #13
Source File: create-app-dokku.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateAppDokku = () => {
  const history = useHistory();
  const toast = useToast();
  const { data: dataApps } = useAppsQuery();
  const [createAppDokkuMutation, { loading }] = useCreateAppDokkuMutation();

  const createAppSchema = yup.object().shape({
    name: yup
      .string()
      .required('App name is required')
      .matches(/^[a-z0-9-]+$/)
      .test(
        'Name exists',
        'App with this name already exists',
        (val) => !dataApps?.apps.find((app) => app.name === val)
      ),
  });

  const formik = useFormik<{
    name: string;
  }>({
    initialValues: {
      name: '',
    },

    validateOnChange: true,
    validationSchema: createAppSchema,
    onSubmit: async (values) => {
      try {
        const res = await createAppDokkuMutation({
          variables: {
            input: {
              name: values.name,
            },
          },
        });

        trackGoal(trackingGoals.createAppDokku, 0);

        if (res.data) {
          history.push(`app/${res.data?.createAppDokku.appId}`);
          toast.success('App created successfully');
        }
      } catch (error) {
        toast.error(error);
      }
    },
  });

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <Heading as="h2" size="md">
          Create a new app
        </Heading>

        <Text mt="12" mb="4" color="gray.400">
          Enter app name, click create and voila!
        </Text>

        <Grid templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}>
          <GridItem colSpan={2}>
            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="v"
                isInvalid={Boolean(formik.errors.name && formik.touched.name)}
              >
                <FormLabel>App name:</FormLabel>
                <Input
                  autoComplete="off"
                  id="name"
                  name="name"
                  placeholder="Name"
                  value={formik.values.name}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.name}</FormErrorMessage>
              </FormControl>

              <Box mt="4" display="flex" justifyContent="flex-end">
                <Button
                  type="submit"
                  color="grey"
                  disabled={
                    loading || !formik.values.name || !!formik.errors.name
                  }
                  isLoading={loading}
                >
                  Create
                </Button>
              </Box>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #14
Source File: advanced.tsx    From ledokku with MIT License 4 votes vote down vote up
AppSettingsAdvanced = () => {
  const { id: appId } = useParams<{ id: string }>();
  const toast = useToast();
  const history = useHistory();

  const { data, loading } = useAppByIdQuery({
    variables: {
      appId,
    },
  });

  const [
    destroyAppMutation,
    { loading: destroyAppMutationLoading },
  ] = useDestroyAppMutation();

  const DeleteAppNameSchema = yup.object().shape({
    appName: yup
      .string()
      .required('Required')
      .test(
        'Equals app name',
        'Must match app name',
        (val) => val === data?.app?.name
      ),
  });

  const formik = useFormik<{ appName: string }>({
    initialValues: {
      appName: '',
    },

    validateOnChange: true,
    validationSchema: DeleteAppNameSchema,

    onSubmit: async (values) => {
      try {
        await destroyAppMutation({
          variables: {
            input: { appId },
          },
          refetchQueries: [
            {
              query: DashboardDocument,
            },
          ],
        });
        toast.success('App deleted successfully');

        history.push('/dashboard');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  // TODO display error

  if (loading) {
    // TODO nice loading
    return <p>Loading...</p>;
  }

  if (!data?.app) {
    // TODO nice 404
    return <p>App not found.</p>;
  }

  const { app } = data;

  return (
    <>
      <HeaderContainer>
        <Header />
        <AppHeaderInfo app={app} />
        <AppHeaderTabNav app={app} />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <Grid
          templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(6, 1fr)' }}
          gap={{ sm: 0, md: 16 }}
        >
          <GridItem colSpan={2} py={5}>
            <AppSettingsMenu app={app} />
          </GridItem>
          <GridItem colSpan={4}>
            <AppRestart appId={app.id} />
            <AppRebuild appId={app.id} />

            <Box py="5">
              <Heading as="h2" size="md">
                Delete app
              </Heading>
              <Text fontSize="sm" color="gray.400">
                This action cannot be undone. This will permanently delete{' '}
                {app.name} app and everything related to it. Please type{' '}
                <b>{app.name}</b> to confirm deletion.
              </Text>
            </Box>

            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="appName"
                isInvalid={Boolean(
                  formik.errors.appName && formik.touched.appName
                )}
              >
                <Input
                  autoComplete="off"
                  id="appNme"
                  name="appName"
                  placeholder="App name"
                  value={formik.values.appName}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.appName}</FormErrorMessage>
              </FormControl>

              <Button
                my={4}
                type="submit"
                colorScheme="red"
                isLoading={destroyAppMutationLoading}
              >
                Delete
              </Button>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #15
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 #16
Source File: DynamicDnsDialog.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
DynamicDnsDialog = ({
    onCancel,
    onConfirm,
    isOpen,
    modalRef,
    onClose,
    port = 1234
}: DynamicDnsDialogProps): JSX.Element => {
    const [address, setAddress] = useState('');
    const [error, setError] = useState('');
    const isInvalid = (error ?? '').length > 0;

    return (
        <AlertDialog
            isOpen={isOpen}
            leastDestructiveRef={modalRef}
            onClose={() => onClose()}
        >
            <AlertDialogOverlay>
                <AlertDialogContent>
                    <AlertDialogHeader fontSize='lg' fontWeight='bold'>
                        Set Dynamic DNS
                    </AlertDialogHeader>

                    <AlertDialogBody>
                        <Text>Enter your Dynamic DNS URL, including the schema and port. Here are some examples:</Text>
                        <br />
                        <UnorderedList>
                            <ListItem>http://thequickbrownfox.ddns.net:{port}</ListItem>
                            <ListItem>http://bluebubbles.no-ip.org:{port}</ListItem>
                        </UnorderedList>
                        <br />
                        <Text>If you plan to use your own custom certificate, please remember to use <strong>"https://"</strong> as your URL scheme</Text>
                        <br />
                        <FormControl isInvalid={isInvalid}>
                            <FormLabel htmlFor='address'>Dynamic DNS</FormLabel>
                            <Input
                                id='address'
                                type='text'
                                maxWidth="20em"
                                value={address}
                                placeholder={`http://<your DNS>:${port}`}
                                onChange={(e) => {
                                    setError('');
                                    setAddress(e.target.value);
                                }}
                            />
                            {isInvalid ? (
                                <FormErrorMessage>{error}</FormErrorMessage>
                            ) : null}
                        </FormControl>
                        
                    </AlertDialogBody>

                    <AlertDialogFooter>
                        <Button
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (onCancel) onCancel();
                                onClose();
                            }}
                        >
                            Cancel
                        </Button>
                        <Button
                            ml={3}
                            bg='brand.primary'
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (address.length === 0) {
                                    setError('Please enter a Dynamic DNS address!');
                                    return;
                                }

                                if (onConfirm) onConfirm(address);
                                onClose();
                            }}
                        >
                            Save
                        </Button>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialogOverlay>
        </AlertDialog>
    );
}
Example #17
Source File: ContactDialog.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
ContactDialog = ({
    onCancel,
    onDelete,
    onCreate,
    onUpdate,
    onClose,
    onAddressAdd,
    onAddressDelete,
    isOpen,
    modalRef,
    existingContact,
}: ContactDialogProps): JSX.Element => {
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const [displayName, setDisplayName] = useState('');
    const [currentAddress, setCurrentAddress] = useState('');
    const [hasEdited, setHasEdited] = useBoolean(false);
    const [phones, setPhones] = useState([] as ContactAddress[]);
    const [emails, setEmails] = useState([] as ContactAddress[]);
    const [firstNameError, setFirstNameError] = useState('');
    const isNameValid = (firstNameError ?? '').length > 0;

    useEffect(() => {
        if (!existingContact) return;
        if (existingContact.firstName) setFirstName(existingContact.firstName);
        if (existingContact.lastName) setLastName(existingContact.lastName);
        if (existingContact.displayName) setDisplayName(existingContact.displayName);
        if (existingContact.phoneNumbers) setPhones(existingContact.phoneNumbers);
        if (existingContact.emails) setEmails(existingContact.emails);
    }, [existingContact]);

    const addAddress = (address: string) => {
        const existsPhone = phones.map((e: ContactAddress) => e.address).includes(address);
        const existsEmail = emails.map((e: ContactAddress) => e.address).includes(address);
        if (existsPhone || existsEmail) {
            return showErrorToast({
                id: 'contacts',
                description: 'Address already exists!'
            });
        }

        if (address.includes('@')) {
            setEmails([{ address }, ...emails]);
        } else {
            setPhones([{ address }, ...phones]);
        }

        if (onAddressAdd && existingContact) {
            onAddressAdd(existingContact.id, address);
        }
    };

    const removeAddress = (address: string, addressId: number | null) => {
        if (address.includes('@')) {
            setEmails(emails.filter((e: NodeJS.Dict<any>) => e.address !== address));
        } else {
            setPhones(phones.filter((e: NodeJS.Dict<any>) => e.address !== address));
        }

        if (onAddressDelete && addressId) {
            onAddressDelete(addressId);
        }
    };

    const _onClose = () => {
        setPhones([]);
        setEmails([]);
        setFirstName('');
        setLastName('');
        setDisplayName('');
        setCurrentAddress('');
        setHasEdited.off();

        if (onClose) onClose();
    };

    return (
        <AlertDialog
            isOpen={isOpen}
            leastDestructiveRef={modalRef}
            onClose={() => onClose()}
        >
            <AlertDialogOverlay>
                <AlertDialogContent>
                    <AlertDialogHeader fontSize='lg' fontWeight='bold'>
                        {(existingContact) ? 'Edit Contact' : 'Add a new Contact'}
                    </AlertDialogHeader>

                    <AlertDialogBody>
                        <Text>Add a custom contact to the server's database</Text>
                        <FormControl isInvalid={isNameValid} mt={5}>
                            <FormLabel htmlFor='firstName'>First Name</FormLabel>
                            <Input
                                id='firstName'
                                type='text'
                                value={firstName}
                                placeholder='Tim'
                                onChange={(e) => {
                                    setFirstNameError('');
                                    setFirstName(e.target.value);
                                    if (!hasEdited) {
                                        setDisplayName(`${e.target.value} ${lastName}`.trim());
                                    }
                                }}
                            />
                            {isNameValid ? (
                                <FormErrorMessage>{firstNameError}</FormErrorMessage>
                            ) : null}
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='lastName'>Last Name</FormLabel>
                            <Input
                                id='lastName'
                                type='text'
                                value={lastName}
                                placeholder='Apple'
                                onChange={(e) => {
                                    setLastName(e.target.value);
                                    if (!hasEdited) {
                                        setDisplayName(`${firstName} ${e.target.value}`.trim());
                                    }
                                }}
                            />
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='lastName'>Display Name</FormLabel>
                            <Input
                                id='displayName'
                                type='text'
                                value={displayName}
                                placeholder='Tim Apple'
                                onChange={(e) => {
                                    setHasEdited.on();
                                    setDisplayName(e.target.value);
                                }}
                            />
                        </FormControl>
                        <FormControl mt={5}>
                            <FormLabel htmlFor='address'>Addresses</FormLabel>
                            <HStack>
                                <Input
                                    id='address'
                                    type='text'
                                    value={currentAddress}
                                    placeholder='Add Address'
                                    onChange={(e) => {
                                        setCurrentAddress(e.target.value);
                                    }}
                                />
                                <IconButton
                                    onClick={() => {
                                        if (!currentAddress || currentAddress.length === 0) return;
                                        addAddress(currentAddress);
                                        setCurrentAddress('');
                                    }}
                                    aria-label='Add'
                                    icon={<AiOutlinePlus />}
                                />
                            </HStack>
                            <Flex flexDirection="row" alignItems="center" justifyContent="flex-start" flexWrap="wrap" mt={2}>
                                {[...phones, ...emails].map(((e: ContactAddress) => {
                                    return (
                                        <Tag
                                            mt={1}
                                            mx={1}
                                            size={'md'}
                                            key={e.address}
                                            borderRadius='full'
                                            variant='solid'
                                        >
                                            <TagLabel>{e.address}</TagLabel>
                                            <TagCloseButton
                                                onClick={() => {
                                                    removeAddress(e.address, (e.id) ? e.id : null);
                                                }}
                                            />
                                        </Tag>
                                    );
                                }))}
                            </Flex>
                        </FormControl>
                    </AlertDialogBody>

                    <AlertDialogFooter>
                        <Button
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (!existingContact && onCancel) onCancel();
                                if (existingContact && onUpdate) {
                                    existingContact.firstName = firstName;
                                    existingContact.lastName = lastName;
                                    existingContact.displayName = displayName;
                                    onUpdate(existingContact);
                                }
                                _onClose();
                            }}
                        >
                            {(existingContact) ? 'Save & Close' : 'Cancel'}
                        </Button>
                        {(existingContact) ? (
                            <Button
                                ml={3}
                                bg='red'
                                ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                                onClick={() => {
                                    if (onDelete) {
                                        onDelete(Number.parseInt(existingContact.id));
                                    }

                                    _onClose();
                                }}
                            >
                                Delete
                            </Button>
                        ) : null}
                        {(!existingContact) ? (
                            <Button
                                ml={3}
                                bg='brand.primary'
                                ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                                onClick={() => {
                                    if (firstName.length === 0) {
                                        setFirstNameError('Please enter a first name for the contact!');
                                        return;
                                    }

                                    if (onCreate) {
                                        onCreate({
                                            firstName,
                                            lastName,
                                            phoneNumbers: phones,
                                            emails: emails,
                                            displayName,
                                            birthday: '',
                                            avatar: '',
                                            id: '',
                                            sourceType: 'db'
                                        });
                                    }

                                    _onClose();
                                }}
                            >
                                Create
                            </Button>
                        ) : null}
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialogOverlay>
        </AlertDialog>
    );
}
Example #18
Source File: AddWebhookDialog.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
AddWebhookDialog = ({
    onCancel,
    isOpen,
    modalRef,
    onClose,
    existingId,
}: AddWebhookDialogProps): JSX.Element => {
    const [url, setUrl] = useState('');
    const dispatch = useAppDispatch();
    const webhooks = useAppSelector(state => state.webhookStore.webhooks) ?? [];
    const [selectedEvents, setSelectedEvents] = useState(
        webhookEventOptions.filter((option: any) => option.label === 'All Events') as Array<MultiSelectValue>);
    const [urlError, setUrlError] = useState('');
    const isUrlInvalid = (urlError ?? '').length > 0;
    const [eventsError, setEventsError] = useState('');
    const isEventsError = (eventsError ?? '').length > 0;

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

        const webhook = webhooks.find(e => e.id === existingId);
        if (webhook) {
            setUrl(webhook.url);
            setSelectedEvents(convertMultiSelectValues(JSON.parse(webhook.events)));
        }
    }, [existingId]);

    return (
        <AlertDialog
            isOpen={isOpen}
            leastDestructiveRef={modalRef}
            onClose={() => onClose()}
        >
            <AlertDialogOverlay>
                <AlertDialogContent>
                    <AlertDialogHeader fontSize='lg' fontWeight='bold'>
                        Add a new Webhook
                    </AlertDialogHeader>

                    <AlertDialogBody>
                        <Text>Enter a URL to receive a POST request callback when an event occurs</Text>
                        <FormControl isInvalid={isUrlInvalid} mt={5}>
                            <FormLabel htmlFor='url'>URL</FormLabel>
                            <Input
                                id='url'
                                type='text'
                                value={url}
                                placeholder='https://<your URL path>'
                                onChange={(e) => {
                                    setUrlError('');
                                    setUrl(e.target.value);
                                }}
                            />
                            {isUrlInvalid ? (
                                <FormErrorMessage>{urlError}</FormErrorMessage>
                            ) : null}
                        </FormControl>
                        <FormControl isInvalid={isEventsError} mt={5}>
                            <FormLabel htmlFor='permissions'>Event Subscriptions</FormLabel>
                            <MultiSelect
                                size='md'
                                isMulti={true}
                                options={webhookEventOptions}
                                value={selectedEvents}
                                onChange={(newValues) => {
                                    setEventsError('');
                                    setSelectedEvents(newValues as Array<MultiSelectValue>);
                                }}
                            />
                            {isEventsError ? (
                                <FormErrorMessage>{eventsError}</FormErrorMessage>
                            ) : null}
                        </FormControl>
                        
                    </AlertDialogBody>

                    <AlertDialogFooter>
                        <Button
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (onCancel) onCancel();
                                setUrl('');
                                onClose();
                            }}
                        >
                            Cancel
                        </Button>
                        <Button
                            ml={3}
                            bg='brand.primary'
                            ref={modalRef as React.LegacyRef<HTMLButtonElement> | undefined}
                            onClick={() => {
                                if (url.length === 0) {
                                    setUrlError('Please enter a webhook URL!');
                                    return;
                                }

                                if (selectedEvents.length === 0) {
                                    setEventsError('Please select at least 1 event to subscribe to!');
                                    return;
                                }

                                if (existingId) {
                                    dispatch(update({ id: existingId, url, events: selectedEvents }));
                                } else {
                                    dispatch(create({ url, events: selectedEvents }));
                                }

                                setUrl('');
                                onClose();
                            }}
                        >
                            Save
                        </Button>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialogOverlay>
        </AlertDialog>
    );
}
Example #19
Source File: NgrokAuthTokenField.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
NgrokAuthTokenField = ({ helpText }: NgrokAuthTokenFieldProps): JSX.Element => {
    const dispatch = useAppDispatch();
    const ngrokToken: string = (useAppSelector(state => state.config.ngrok_key) ?? '');
    const [showNgrokToken, setShowNgrokToken] = useBoolean();
    const [newNgrokToken, setNewNgrokToken] = useState(ngrokToken);
    const [ngrokTokenError, setNgrokTokenError] = useState('');
    const hasNgrokTokenError: boolean = (ngrokTokenError ?? '').length > 0;

    useEffect(() => { setNewNgrokToken(ngrokToken); }, [ngrokToken]);

    /**
     * A handler & validator for saving a new Ngrok auth token.
     *
     * @param theNewNgrokToken - The new auth token to save
     */
    const saveNgrokToken = (theNewNgrokToken: string): void => {
        theNewNgrokToken = theNewNgrokToken.trim();

        // Validate the port
        if (theNewNgrokToken === ngrokToken) {
            setNgrokTokenError('You have not changed the token since your last save!');
            return;
        } else if (theNewNgrokToken.includes(' ')) {
            setNgrokTokenError('Invalid Ngrok Auth Token! Please check that you have copied it correctly.');
            return;
        }

        dispatch(setConfig({ name: 'ngrok_key', value: theNewNgrokToken }));
        setNgrokTokenError('');
        showSuccessToast({
            id: 'settings',
            duration: 4000,
            description: 'Successfully saved new Ngrok Auth Token! Restarting Proxy service...'
        });
    };

    return (
        <FormControl isInvalid={hasNgrokTokenError}>
            <FormLabel htmlFor='ngrok_key'>Ngrok Auth Token (Optional)</FormLabel>
            <Input
                id='password'
                type={showNgrokToken ? 'text' : 'password'}
                maxWidth="20em"
                value={newNgrokToken}
                onChange={(e) => {
                    if (hasNgrokTokenError) setNgrokTokenError('');
                    setNewNgrokToken(e.target.value);
                }}
            />
            <IconButton
                ml={3}
                verticalAlign='top'
                aria-label='View Ngrok token'
                icon={showNgrokToken ? <AiFillEye /> : <AiFillEyeInvisible />}
                onClick={() => setShowNgrokToken.toggle()}
            />
            <IconButton
                ml={3}
                verticalAlign='top'
                aria-label='Save Ngrok token'
                icon={<AiOutlineSave />}
                onClick={() => saveNgrokToken(newNgrokToken)}
            />
            {!hasNgrokTokenError ? (
                <FormHelperText>
                    {helpText ?? (
                        <Text>
                            Using an Auth Token will allow you to use the benefits of the upgraded Ngrok
                            service. This can improve connection stability and reliability. If you do not have
                            an Ngrok Account, sign up for free here:&nbsp;
                            <Box as='span' color={baseTheme.colors.brand.primary}>
                                <Link href='https://dashboard.ngrok.com/get-started/your-authtoken' target='_blank'>ngrok.com</Link>
                            </Box>
                        </Text>
                    )}
                </FormHelperText>
            ) : (
                <FormErrorMessage>{ngrokTokenError}</FormErrorMessage>
            )}
        </FormControl>
    );
}