Skip to content

Commit

Permalink
fix: user creation/edition validation
Browse files Browse the repository at this point in the history
Signed-off-by: Jérémy Morel <jeremy.morel@owkin.com>
  • Loading branch information
jmorel authored and Milouu committed Dec 19, 2022
1 parent de1bf9c commit fb149ac
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- UI and markup glitches on login page (#123)
- Check for "last admin" when editing a user (#131)
- Warnings during npm install (#126)
- User creation/edition validation (#133)

## [0.36.0] - 2022-10-03

Expand Down
14 changes: 7 additions & 7 deletions src/modules/users/UsersUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const isDifferentFromUsername = (
return password !== username;
};

export const hasCorrectLenght = (password: string): boolean => {
export const hasCorrectLength = (password: string): boolean => {
return password.length >= 20 && password.length <= 64;
};

Expand All @@ -33,15 +33,15 @@ export const hasLowerAndUpperChar = (password: string): boolean => {
return !!password.match(regexLowerChar) && !!password.match(regexUpperChar);
};

export const checkPasswordErrors = (
export const isPasswordValid = (
password: string,
username: string
): boolean => {
return (
!isDifferentFromUsername(password, username) &&
!hasCorrectLenght(password) &&
!hasSpecialChar(password) &&
!hasNumber(password) &&
!hasLowerAndUpperChar(password)
isDifferentFromUsername(password, username) &&
hasCorrectLength(password) &&
hasSpecialChar(password) &&
hasNumber(password) &&
hasLowerAndUpperChar(password)
);
};
8 changes: 4 additions & 4 deletions src/routes/resetPassword/components/ResetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import useKeyFromPath from '@/hooks/useKeyFromPath';
import { getUrlSearchParams } from '@/hooks/useLocationWithParams';
import * as UsersApi from '@/modules/users/UsersApi';
import {
checkPasswordErrors,
hasCorrectLenght,
isPasswordValid,
hasCorrectLength,
hasLowerAndUpperChar,
hasNumber,
hasSpecialChar,
Expand Down Expand Up @@ -123,7 +123,7 @@ const ResetForm = (): JSX.Element => {
setIsDirty(true);
setPassword(newValue);
setPasswordHasErrors(
checkPasswordErrors(newValue, username)
!isPasswordValid(newValue, username)
);
}}
/>
Expand All @@ -136,7 +136,7 @@ const ResetForm = (): JSX.Element => {
/>
<PasswordValidationMessage
isEmpty={isEmpty}
isValid={hasCorrectLenght(password)}
isValid={hasCorrectLength(password)}
message="Length must be between 20 and 64 characters"
/>
<PasswordValidationMessage
Expand Down
63 changes: 50 additions & 13 deletions src/routes/users/components/CreateUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,41 @@ const CreateUserForm = ({
const [password, setPassword] = useState('');
const [passwordHasErrors, setPasswordHasErrors] = useState(false);

const [creating, setCreating] = useState(false);

const onSave = async () => {
if (!usernameHasError && !passwordHasErrors) {
await dispatch(
setCreating(true);
dispatch(
createUser({
username: username,
password: password,
role: role,
})
);

toast({
title: 'User created',
descriptionComponent: `${username} was successfully created!`,
status: 'success',
isClosable: true,
});
closeHandler();
)
.unwrap()
.then(
() => {
toast({
title: 'User created',
descriptionComponent: `${username} was successfully created!`,
status: 'success',
isClosable: true,
});
setCreating(false);
closeHandler();
},
(error) => {
toast({
title: 'User creation failed',
descriptionComponent:
error?.message ?? 'Could not create user',
status: 'error',
isClosable: true,
});
setCreating(false);
}
);
}
};

Expand All @@ -76,23 +94,42 @@ const CreateUserForm = ({
onChange={setUsername}
hasErrors={usernameHasError}
setHasErrors={setUsernameHasError}
isDisabled={creating}
/>
<RoleInput
value={role}
onChange={setRole}
isDisabled={creating}
/>
<RoleInput value={role} onChange={setRole} />
<PasswordInput
value={password}
username={username}
onChange={setPassword}
hasErrors={passwordHasErrors}
setHasErrors={setPasswordHasErrors}
isDisabled={creating}
/>
</VStack>
</DrawerBody>
<DrawerFooter>
<HStack spacing="2">
<Button size="sm" variant="outline" onClick={closeHandler}>
<Button
size="sm"
variant="outline"
onClick={closeHandler}
isDisabled={creating}
>
Cancel
</Button>
<Button size="sm" colorScheme="primary" onClick={onSave}>
<Button
size="sm"
colorScheme="primary"
onClick={onSave}
isDisabled={
creating || usernameHasError || passwordHasErrors
}
isLoading={creating}
>
Save
</Button>
</HStack>
Expand Down
18 changes: 10 additions & 8 deletions src/routes/users/components/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import { RiEyeLine } from 'react-icons/ri';

import {
checkPasswordErrors,
hasCorrectLenght,
isPasswordValid,
hasCorrectLength,
hasLowerAndUpperChar,
hasNumber,
hasSpecialChar,
Expand All @@ -28,6 +28,7 @@ type PasswordInputProps = {
onChange: (value: string) => void;
hasErrors: boolean;
setHasErrors: (setHasErrors: boolean) => void;
isDisabled: boolean;
};

const PasswordInput = ({
Expand All @@ -36,19 +37,23 @@ const PasswordInput = ({
onChange,
hasErrors,
setHasErrors,
isDisabled,
}: PasswordInputProps): JSX.Element => {
const [isDirty, setIsDirty] = useState(false);
const [showPassword, setShowPassword] = useState(false);

const isEmpty = !value.length;

useEffect(() => {
setHasErrors(checkPasswordErrors(value, username));
setHasErrors(!isPasswordValid(value, username));
}, [value, username, setHasErrors]);

return (
<DrawerSectionEntry title="Password" alignItems="baseline">
<FormControl isInvalid={hasErrors && isDirty}>
<FormControl
isInvalid={hasErrors && isDirty}
isDisabled={isDisabled}
>
<InputGroup size="sm">
<Input
id="password"
Expand All @@ -60,9 +65,6 @@ const PasswordInput = ({
const newValue = e.target.value;
setIsDirty(true);
onChange(newValue);
setHasErrors(
checkPasswordErrors(newValue, username)
);
}}
/>
<InputRightElement>
Expand All @@ -86,7 +88,7 @@ const PasswordInput = ({
/>
<PasswordValidationMessage
isEmpty={isEmpty}
isValid={hasCorrectLenght(value)}
isValid={hasCorrectLength(value)}
message="Length must be between 20 and 64 characters"
/>
<PasswordValidationMessage
Expand Down
8 changes: 7 additions & 1 deletion src/routes/users/components/RoleInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ import { DrawerSectionEntry } from '@/components/DrawerSection';
type RoleInputProps = {
value: UserRolesT;
onChange: (value: UserRolesT) => void;
isDisabled?: boolean;
};

const RoleInput = ({ value, onChange }: RoleInputProps): JSX.Element => {
const RoleInput = ({
value,
onChange,
isDisabled,
}: RoleInputProps): JSX.Element => {
return (
<DrawerSectionEntry title="Role">
<Select
size="sm"
id="role"
value={value}
onChange={(e) => onChange(e.target.value as UserRolesT)}
isDisabled={isDisabled}
>
{Object.entries(UserRolesT).map(([key, type]) => (
<option key={key} value={type}>
Expand Down
50 changes: 28 additions & 22 deletions src/routes/users/components/UpdateUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,34 +71,40 @@ const UpdateUserForm = ({

const onUpdate = () => {
if (user && role !== user.role) {
dispatch(updateUser({ key: user.username, payload: { role } }));
dispatch(updateUser({ key: user.username, payload: { role } }))
.unwrap()
.then(() => {
toast({
title: `User updated`,
descriptionComponent: () => (
<Text>
{user.username} was successfully updated!
</Text>
),
status: 'success',
isClosable: true,
});

toast({
title: `User updated`,
descriptionComponent: () => (
<Text>{user.username} was successfully updated!</Text>
),
status: 'success',
isClosable: true,
});

closeHandler();
closeHandler();
});
}
};

const onDelete = () => {
dispatch(deleteUser(username));

toast({
title: `User deleted`,
descriptionComponent: () => (
<Text>{username} was successfully deleted</Text>
),
status: 'success',
isClosable: true,
});
dispatch(deleteUser(username))
.unwrap()
.then(() => {
toast({
title: `User deleted`,
descriptionComponent: () => (
<Text>{username} was successfully deleted</Text>
),
status: 'success',
isClosable: true,
});

closeHandler();
closeHandler();
});
};

const onReset = async () => {
Expand Down
9 changes: 7 additions & 2 deletions src/routes/users/components/UsernameInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ type UsernameInputProps = {
onChange: (value: string) => void;
hasErrors: boolean;
setHasErrors: (hasErrors: boolean) => void;
isDisabled?: boolean;
};

const UsernameInput = ({
value,
onChange,
hasErrors,
setHasErrors,
isDisabled,
}: UsernameInputProps): JSX.Element => {
const [isDirty, setIsDirty] = useState(false);

return (
<DrawerSectionEntry title="Username">
<FormControl isInvalid={hasErrors && isDirty}>
<DrawerSectionEntry title="Username" alignItems="baseline">
<FormControl
isInvalid={hasErrors && isDirty}
isDisabled={isDisabled}
>
<Input
size="sm"
id="username"
Expand Down

0 comments on commit fb149ac

Please sign in to comment.