@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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:
<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>
);
}