Merge pull request #24 from leomurca/enhancement/password_validation
Add password validation hints
This commit is contained in:
commit
4f3d6e8386
4 changed files with 190 additions and 8 deletions
|
@ -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"
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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
28
src/utils/validations.js
Normal 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,
|
||||||
|
};
|
Loading…
Reference in a new issue