Merge pull request #24 from leomurca/enhancement/password_validation

Add password validation hints
This commit is contained in:
Leonardo Murça 2023-10-01 21:30:11 -03:00 committed by GitHub
commit 4f3d6e8386
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 190 additions and 8 deletions

View file

@ -5,7 +5,12 @@ import {
Container,
FormControl,
InputLabel,
LinearProgress,
Link,
List,
ListItem,
ListItemIcon,
ListItemText,
MenuItem,
Paper,
Select,
@ -13,6 +18,7 @@ import {
TextField,
Typography,
} from '@mui/material';
import React from 'react';
import SnackbarIndicator from '../../components/SnackbarIndicator';
import LoadingIndicator from '../../components/LoadingIndicator';
@ -23,20 +29,31 @@ import logoImage from '../../assets/if-salas-logo.svg';
import styles from './styles';
import { createArrayFrom1ToN } from '../../utils/createArrayFrom1ToN';
import { COURSES } from '../../utils/constants';
import { Done } from '@mui/icons-material';
function View({
isPending,
isError,
error,
layoutType,
isPasswordFocusedForTheFirstTime,
data,
onChangeInput,
onChangePasswordInput,
onChangeCheckbox,
onFocusInput,
onTryRegister,
currentYear,
}) {
const { container, paper, boxLogo, boxForm, logoContainer } =
styles[layoutType];
const {
container,
paper,
boxLogo,
boxForm,
logoContainer,
passwordRulesBox,
passwordRulesStrength,
} = styles[layoutType];
return (
<Container sx={container} disableGutters>
@ -72,7 +89,7 @@ function View({
type="text"
value={data.ra}
onChange={onChangeInput}
placeholder="00#####"
placeholder="#######"
InputProps={{
inputComponent: InputMask,
}}
@ -117,7 +134,6 @@ function View({
))}
</Select>
</FormControl>
{/* TODO: Add field mask */}
<TextField
id="phone"
name="phone"
@ -146,9 +162,50 @@ function View({
label="Senha"
variant="standard"
type="password"
value={data.password}
onChange={onChangeInput}
value={data.password.value}
onChange={onChangePasswordInput}
onFocus={onFocusInput}
/>
{isPasswordFocusedForTheFirstTime && (
<Box sx={passwordRulesBox}>
<p style={passwordRulesStrength}>
Força da senha: {data.password.strength}%
</p>
<LinearProgress
color={data.password.strength === 100 ? 'success' : 'error'}
variant="determinate"
value={data.password.strength}
/>
<List dense>
{data.password.rules.map(rule => (
<ListItem
key={rule.label}
style={{
padding: '0',
color: rule.applied ? 'green' : 'black',
}}
>
<ListItemIcon
style={{
minWidth: '40px',
color: rule.applied ? 'green' : 'black',
}}
>
<Done />
</ListItemIcon>
<ListItemText
primaryTypographyProps={{
fontWeight: rule.applied ? 'bolder' : 'inherit',
}}
primary={rule.label}
/>
</ListItem>
))}
</List>
</Box>
)}
<Stack flexDirection="row" alignItems="center">
<Checkbox
name="termsAgreed"

View file

@ -5,12 +5,23 @@ import { useDocumentTitle } from '../../hooks/useDocumentTitle';
import useLayoutType from '../../hooks/useLayoutType';
import View from './View';
import {
hasAtLeastLength,
hasLowerCase,
hasNumber,
hasSpecialChars,
hasUpperCase,
} from '../../utils/validations';
function Register() {
useDocumentTitle('Criar conta');
const currentYear = dayjs().year();
const { register, isPending, isError, error } = useAuthState();
const layoutType = useLayoutType();
const [
isPasswordFocusedForTheFirstTime,
setIsPasswordFocusedForTheFirstTime,
] = useState(false);
const [data, setData] = useState({
firstName: '',
lastName: '',
@ -19,12 +30,68 @@ function Register() {
year: currentYear,
phone: '',
email: '',
password: '',
password: {
value: '',
strength: 0,
rules: [
{
applied: false,
label: 'Pelo menos 8 caracteres.',
},
{
applied: false,
label: 'Pelo menos uma letra minúscula.',
},
{
applied: false,
label: 'Pelo menos uma letra maiúscula.',
},
{
applied: false,
label: 'Pelo menos um caractere especial (Exemplo: @, #, $, %, etc).',
},
{
applied: false,
label: 'Pelo menos um número.',
},
],
},
termsAgreed: false,
});
const onTryRegister = () => {
register(data);
register({ ...data, password: data.password.value });
};
const onChangePasswordInput = e => {
const value = e.target.value;
const appliedRules = [
hasAtLeastLength(value, 8),
hasLowerCase(value),
hasUpperCase(value),
hasSpecialChars(value),
hasNumber(value),
];
setData(prev => ({
...prev,
password: {
value,
strength:
(appliedRules.filter(r => r === true).length * 100) /
appliedRules.length,
rules: prev.password.rules.map((rule, i) => ({
...rule,
applied: appliedRules[i],
})),
},
}));
};
const onFocusInput = e => {
if (isPasswordFocusedForTheFirstTime) return;
const name = e.target.name;
setIsPasswordFocusedForTheFirstTime(name === 'password');
};
const onChangeInput = e => {
@ -47,9 +114,12 @@ function Register() {
isError={isError}
error={error}
layoutType={layoutType}
isPasswordFocusedForTheFirstTime={isPasswordFocusedForTheFirstTime}
data={data}
onChangeInput={onChangeInput}
onChangePasswordInput={onChangePasswordInput}
onChangeCheckbox={onChangeCheckbox}
onFocusInput={onFocusInput}
onTryRegister={onTryRegister}
currentYear={currentYear}
/>

View file

@ -47,12 +47,27 @@ const desktopBoxForm = {
const logoContainerDesktop = {};
const passwordRulesBoxDesktop = {
width: '100%',
backgroundColor: '#f2f2f2',
border: '1px solid black',
padding: '16px',
};
const passwordRulesStrengthDesktop = {
color: 'black',
fontSize: '0.9em',
marginBottom: '10px',
};
const desktop = {
container: desktopContainer,
paper: desktopPaper,
boxLogo: desktopBoxLogo,
boxForm: desktopBoxForm,
logoContainer: logoContainerDesktop,
passwordRulesBox: passwordRulesBoxDesktop,
passwordRulesStrength: passwordRulesStrengthDesktop,
};
// ========== Mobile ==========
@ -83,12 +98,22 @@ const logoContainerMobile = {
padding: '20px 16px',
};
const passwordRulesBoxMobile = {
...passwordRulesBoxDesktop,
};
const passwordRulesStrengthMobile = {
...passwordRulesStrengthDesktop,
};
const mobile = {
container: mobileContainer,
paper: mobilePaper,
boxLogo: mobileBoxLogo,
boxForm: mobileBoxForm,
logoContainer: logoContainerMobile,
passwordRulesBox: passwordRulesBoxMobile,
passwordRulesStrength: passwordRulesStrengthMobile,
};
// ========== Unset ==========
@ -98,6 +123,8 @@ const unset = {
boxLogo: null,
boxForm: null,
logoContainer: null,
passwordRulesBox: null,
passwordRulesStrength: null,
};
const styles = { desktop, mobile, unset };

28
src/utils/validations.js Normal file
View file

@ -0,0 +1,28 @@
function hasLowerCase(str) {
return str.toUpperCase() !== str;
}
function hasUpperCase(str) {
return str.toLowerCase() !== str;
}
function hasSpecialChars(str) {
const specialChars = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/;
return specialChars.test(str);
}
function hasAtLeastLength(str, length) {
return str.length >= length;
}
function hasNumber(myString) {
return /\d/.test(myString);
}
export {
hasLowerCase,
hasUpperCase,
hasSpecialChars,
hasAtLeastLength,
hasNumber,
};