1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-18 11:14:57 +02:00
unleash.unleash/frontend/src/component/user/common/ResetPasswordForm/PasswordChecker.tsx
NicolaeUnleash 705462f0cf
feat: dark theme v1 (#3298)
## About the changes

Creating the first version of the Dark theme

Refactor: colors variables
Refactor: use theme variable instead 
- this change will help us to use MuiCssBaseline, and we can use classes
directly for easy customization when we can't identify MUI classes

Refactor: adjusting some files components
- i’ve touched also the structure of some files, not only the colors
variables (but only to adjust the style, not functionality)

Fix: dark mode persistence on refresh (by Nuno)

Feat: dark mode sees light logos, and light mode sees dark logos (by
Nuno)

---------

Co-authored-by: Nuno Góis <github@nunogois.com>
2023-03-22 16:37:40 +02:00

259 lines
8.2 KiB
TypeScript

import { styled, Typography } from '@mui/material';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { BAD_REQUEST, OK } from 'constants/statusCodes';
import { useCallback } from 'react';
import { formatApiPath } from 'utils/formatPath';
import { Alert } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
interface IPasswordCheckerProps {
password: string;
callback: Dispatch<SetStateAction<boolean>>;
style?: object;
hideOnCompletion?: boolean;
}
interface IErrorResponse {
details: IErrorDetails[];
}
interface IErrorDetails {
message: string;
validationErrors: string[];
}
const LENGTH_ERROR = 'The password must be at least 10 characters long.';
const NUMBER_ERROR = 'The password must contain at least one number.';
const SYMBOL_ERROR =
'The password must contain at least one special character.';
const UPPERCASE_ERROR =
'The password must contain at least one uppercase letter.';
const LOWERCASE_ERROR =
'The password must contain at least one lowercase letter.';
const REPEATING_CHARACTER_ERROR =
'The password may not contain sequences of three or more repeated characters.';
export const PASSWORD_FORMAT_MESSAGE =
'The password must be at least 10 characters long and must include an uppercase letter, a lowercase letter, a number, and a symbol.';
const StyledTitle = styled(Typography)(({ theme }) => ({
marginBottom: '0',
display: 'flex',
alignItems: 'center',
gap: '1ch',
}));
const StyledContainer = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.elevation2,
borderRadius: theme.shape.borderRadius,
position: 'relative',
maxWidth: '350px',
color: theme.palette.text.secondary,
padding: theme.spacing(0.5, 0, 1.5),
}));
const StyledHeaderContainer = styled('div')(({ theme }) => ({
display: 'flex',
padding: theme.spacing(1),
}));
const StyledCheckContainer = styled('div')(({ theme }) => ({
width: '95px',
margin: theme.spacing(0, 0.5),
display: 'flex',
justifyContent: 'center',
}));
const StyledDivider = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.neutral.light,
height: '1px',
width: '100%',
}));
const StyledStatusBarContainer = styled('div')(({ theme }) => ({
display: 'flex',
padding: theme.spacing(1),
}));
const StyledError = styled(Alert)(({ theme }) => ({
marginTop: theme.spacing(1),
bottom: '0',
position: 'absolute',
}));
const StyledStatusBar = styled('div', {
shouldForwardProp: prop => prop !== 'error',
})<{ error: boolean }>(({ theme, error }) => ({
width: '50px',
borderRadius: theme.shape.borderRadius,
height: '6px',
backgroundColor: error
? theme.palette.error.main
: theme.palette.primary.main,
}));
const PasswordChecker = ({
password,
callback,
style = {},
}: IPasswordCheckerProps) => {
const [casingError, setCasingError] = useState(true);
const [numberError, setNumberError] = useState(true);
const [symbolError, setSymbolError] = useState(true);
const [lengthError, setLengthError] = useState(true);
const [repeatingCharError, setRepeatingCharError] = useState(false);
const makeValidatePassReq = useCallback(() => {
const path = formatApiPath('auth/reset/validate-password');
return fetch(path, {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({ password }),
});
}, [password]);
const clearCheckerErrors = useCallback(() => {
setAllErrors(false);
}, []);
const checkPassword = useCallback(async () => {
if (!password) {
setAllErrors(true);
return;
}
if (password.length < 3) {
setLengthError(true);
return;
}
try {
const res = await makeValidatePassReq();
if (res.status === BAD_REQUEST) {
const data = await res.json();
handleErrorResponse(data);
callback(false);
}
if (res.status === OK) {
clearCheckerErrors();
setRepeatingCharError(false);
callback(true);
}
} catch (e) {
// ResetPasswordForm handles errors related to submitting the form.
console.log('An exception was caught and handled');
}
}, [makeValidatePassReq, callback, password, clearCheckerErrors]);
useEffect(() => {
checkPassword();
}, [password, checkPassword]);
const setAllErrors = (flag: boolean) => {
setCasingError(flag);
setNumberError(flag);
setSymbolError(flag);
setLengthError(flag);
};
const handleErrorResponse = (data: IErrorResponse) => {
const errors = data.details[0].validationErrors;
if (errors.includes(NUMBER_ERROR)) {
setNumberError(true);
} else {
setNumberError(false);
}
if (errors.includes(SYMBOL_ERROR)) {
setSymbolError(true);
} else {
setSymbolError(false);
}
if (errors.includes(LENGTH_ERROR)) {
setLengthError(true);
} else {
setLengthError(false);
}
if (
errors.includes(LOWERCASE_ERROR) ||
errors.includes(UPPERCASE_ERROR)
) {
setCasingError(true);
} else {
setCasingError(false);
}
if (errors.includes(REPEATING_CHARACTER_ERROR)) {
setRepeatingCharError(true);
} else {
setRepeatingCharError(false);
}
};
return (
<>
<StyledTitle variant="body2" data-loading>
Please set a strong password
<HelpIcon tooltip={PASSWORD_FORMAT_MESSAGE} />
</StyledTitle>
<StyledContainer
style={{
...style,
}}
>
<StyledHeaderContainer>
<StyledCheckContainer>
<Typography variant="body2" data-loading>
Length
</Typography>
</StyledCheckContainer>
<StyledCheckContainer>
<Typography variant="body2" data-loading>
Casing
</Typography>
</StyledCheckContainer>
<StyledCheckContainer>
<Typography variant="body2" data-loading>
Number
</Typography>
</StyledCheckContainer>
<StyledCheckContainer>
<Typography variant="body2" data-loading>
Symbol
</Typography>
</StyledCheckContainer>
</StyledHeaderContainer>
<StyledDivider />
<StyledStatusBarContainer>
<StyledCheckContainer>
<StyledStatusBar error={lengthError} data-loading />
</StyledCheckContainer>
<StyledCheckContainer>
<StyledStatusBar error={casingError} data-loading />
</StyledCheckContainer>{' '}
<StyledCheckContainer>
<StyledStatusBar error={numberError} data-loading />
</StyledCheckContainer>
<StyledCheckContainer>
<StyledStatusBar error={symbolError} data-loading />
</StyledCheckContainer>
</StyledStatusBarContainer>
<ConditionallyRender
condition={repeatingCharError}
show={
<StyledError severity="error">
You may not repeat three characters in a row.
</StyledError>
}
/>
</StyledContainer>
</>
);
};
export default PasswordChecker;