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, Container,
FormControl, FormControl,
InputLabel, InputLabel,
LinearProgress,
Link, Link,
List,
ListItem,
ListItemIcon,
ListItemText,
MenuItem, MenuItem,
Paper, Paper,
Select, Select,
@ -13,6 +18,7 @@ import {
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import React from 'react';
import SnackbarIndicator from '../../components/SnackbarIndicator'; import SnackbarIndicator from '../../components/SnackbarIndicator';
import LoadingIndicator from '../../components/LoadingIndicator'; import LoadingIndicator from '../../components/LoadingIndicator';
@ -23,20 +29,31 @@ import logoImage from '../../assets/if-salas-logo.svg';
import styles from './styles'; import styles from './styles';
import { createArrayFrom1ToN } from '../../utils/createArrayFrom1ToN'; import { createArrayFrom1ToN } from '../../utils/createArrayFrom1ToN';
import { COURSES } from '../../utils/constants'; import { COURSES } from '../../utils/constants';
import { Done } from '@mui/icons-material';
function View({ function View({
isPending, isPending,
isError, isError,
error, error,
layoutType, layoutType,
isPasswordFocusedForTheFirstTime,
data, data,
onChangeInput, onChangeInput,
onChangePasswordInput,
onChangeCheckbox, onChangeCheckbox,
onFocusInput,
onTryRegister, onTryRegister,
currentYear, currentYear,
}) { }) {
const { container, paper, boxLogo, boxForm, logoContainer } = const {
styles[layoutType]; container,
paper,
boxLogo,
boxForm,
logoContainer,
passwordRulesBox,
passwordRulesStrength,
} = styles[layoutType];
return ( return (
<Container sx={container} disableGutters> <Container sx={container} disableGutters>
@ -72,7 +89,7 @@ function View({
type="text" type="text"
value={data.ra} value={data.ra}
onChange={onChangeInput} onChange={onChangeInput}
placeholder="00#####" placeholder="#######"
InputProps={{ InputProps={{
inputComponent: InputMask, inputComponent: InputMask,
}} }}
@ -117,7 +134,6 @@ function View({
))} ))}
</Select> </Select>
</FormControl> </FormControl>
{/* TODO: Add field mask */}
<TextField <TextField
id="phone" id="phone"
name="phone" name="phone"
@ -146,9 +162,50 @@ function View({
label="Senha" label="Senha"
variant="standard" variant="standard"
type="password" type="password"
value={data.password} value={data.password.value}
onChange={onChangeInput} 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"> <Stack flexDirection="row" alignItems="center">
<Checkbox <Checkbox
name="termsAgreed" name="termsAgreed"

View file

@ -5,12 +5,23 @@ import { useDocumentTitle } from '../../hooks/useDocumentTitle';
import useLayoutType from '../../hooks/useLayoutType'; import useLayoutType from '../../hooks/useLayoutType';
import View from './View'; import View from './View';
import {
hasAtLeastLength,
hasLowerCase,
hasNumber,
hasSpecialChars,
hasUpperCase,
} from '../../utils/validations';
function Register() { function Register() {
useDocumentTitle('Criar conta'); useDocumentTitle('Criar conta');
const currentYear = dayjs().year(); const currentYear = dayjs().year();
const { register, isPending, isError, error } = useAuthState(); const { register, isPending, isError, error } = useAuthState();
const layoutType = useLayoutType(); const layoutType = useLayoutType();
const [
isPasswordFocusedForTheFirstTime,
setIsPasswordFocusedForTheFirstTime,
] = useState(false);
const [data, setData] = useState({ const [data, setData] = useState({
firstName: '', firstName: '',
lastName: '', lastName: '',
@ -19,12 +30,68 @@ function Register() {
year: currentYear, year: currentYear,
phone: '', phone: '',
email: '', 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, termsAgreed: false,
}); });
const onTryRegister = () => { 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 => { const onChangeInput = e => {
@ -47,9 +114,12 @@ function Register() {
isError={isError} isError={isError}
error={error} error={error}
layoutType={layoutType} layoutType={layoutType}
isPasswordFocusedForTheFirstTime={isPasswordFocusedForTheFirstTime}
data={data} data={data}
onChangeInput={onChangeInput} onChangeInput={onChangeInput}
onChangePasswordInput={onChangePasswordInput}
onChangeCheckbox={onChangeCheckbox} onChangeCheckbox={onChangeCheckbox}
onFocusInput={onFocusInput}
onTryRegister={onTryRegister} onTryRegister={onTryRegister}
currentYear={currentYear} currentYear={currentYear}
/> />

View file

@ -47,12 +47,27 @@ const desktopBoxForm = {
const logoContainerDesktop = {}; 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 = { const desktop = {
container: desktopContainer, container: desktopContainer,
paper: desktopPaper, paper: desktopPaper,
boxLogo: desktopBoxLogo, boxLogo: desktopBoxLogo,
boxForm: desktopBoxForm, boxForm: desktopBoxForm,
logoContainer: logoContainerDesktop, logoContainer: logoContainerDesktop,
passwordRulesBox: passwordRulesBoxDesktop,
passwordRulesStrength: passwordRulesStrengthDesktop,
}; };
// ========== Mobile ========== // ========== Mobile ==========
@ -83,12 +98,22 @@ const logoContainerMobile = {
padding: '20px 16px', padding: '20px 16px',
}; };
const passwordRulesBoxMobile = {
...passwordRulesBoxDesktop,
};
const passwordRulesStrengthMobile = {
...passwordRulesStrengthDesktop,
};
const mobile = { const mobile = {
container: mobileContainer, container: mobileContainer,
paper: mobilePaper, paper: mobilePaper,
boxLogo: mobileBoxLogo, boxLogo: mobileBoxLogo,
boxForm: mobileBoxForm, boxForm: mobileBoxForm,
logoContainer: logoContainerMobile, logoContainer: logoContainerMobile,
passwordRulesBox: passwordRulesBoxMobile,
passwordRulesStrength: passwordRulesStrengthMobile,
}; };
// ========== Unset ========== // ========== Unset ==========
@ -98,6 +123,8 @@ const unset = {
boxLogo: null, boxLogo: null,
boxForm: null, boxForm: null,
logoContainer: null, logoContainer: null,
passwordRulesBox: null,
passwordRulesStrength: null,
}; };
const styles = { desktop, mobile, unset }; 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,
};