Add password validation hints
This commit is contained in:
parent
faa597726b
commit
c74ecd58ce
4 changed files with 190 additions and 8 deletions
|
@ -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"
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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
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