Created
September 19, 2024 00:19
-
-
Save jonathasborges1/897bf32a410dca7fddadfda8ff1b892c to your computer and use it in GitHub Desktop.
Componentes Reutilizáveis em ReactJS com Material UI e TypeScript - Barra de Força de Senha
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useState } from "react"; | |
import { Grid, LinearProgress, Typography, useTheme } from "@mui/material"; | |
import CheckIcon from "@mui/icons-material/Check"; | |
import CloseIcon from "@mui/icons-material/Close"; | |
interface StrengthOptions { | |
isStrong: boolean; | |
label: string; | |
} | |
const passwordCriteria = { | |
hasNumber: /\d/, | |
hasUppercase: /[A-Z]/, | |
hasLowercase: /[a-z]/, | |
hasSymbol: /[!@#$%^&*(),.?":{}|<>_]/, | |
minLength: 8, | |
strongLenght: 12, | |
}; | |
const getPasswordStrength = (password: string): number => { | |
const regExps = [ | |
passwordCriteria.hasNumber, | |
passwordCriteria.hasUppercase, | |
passwordCriteria.hasLowercase, | |
passwordCriteria.hasSymbol, | |
]; | |
const lengthTest = password.length >= passwordCriteria.minLength; | |
const stronglengthTest = password.length >= passwordCriteria.strongLenght; | |
const regexTests = regExps.map((regex) => regex.test(password)); | |
// Conta quantos critérios são verdadeiros | |
const numCriteriaMet = regexTests.filter(Boolean).length; | |
// Adiciona um ponto de força se a senha atender a pelo menos três critérios de caracter e o comprimento mínimo | |
const allCriteriaBonus = (numCriteriaMet > 3 && lengthTest) ? 1 : 0; | |
return numCriteriaMet + allCriteriaBonus + Number(lengthTest) + Number(stronglengthTest); | |
}; | |
const updateStrongOptions = ( | |
password: string, | |
confirmPassword: string | |
): StrengthOptions[] => { | |
return [ | |
{ | |
isStrong: passwordCriteria.hasNumber.test(password), | |
label: "Número", | |
}, | |
{ | |
isStrong: passwordCriteria.hasUppercase.test(password), | |
label: "Letra Maiúscula", | |
}, | |
{ | |
isStrong: passwordCriteria.hasLowercase.test(password), | |
label: "Letra Minúscula", | |
}, | |
{ | |
isStrong: passwordCriteria.hasSymbol.test(password), | |
label: "Símbolo", | |
}, | |
{ | |
isStrong: password.length >= passwordCriteria.minLength, | |
label: "Pelo menos 8 caracteres", | |
}, | |
{ | |
isStrong: password === confirmPassword && confirmPassword.length > 0, | |
label: "Senhas conferem", | |
}, | |
]; | |
}; | |
enum strengthLevels { | |
lowPass = "Senha Fraca", | |
middlePass = "Senha Média", | |
strongPass = "Senha Forte", | |
} | |
const PasswordStrengthBar: React.FC<{ | |
newPass: string; | |
newPassConfirmation: string; | |
}> = ({ newPass, newPassConfirmation }) => { | |
const theme = useTheme(); | |
const vermelho = theme.palette.tagsAndStatus[2]; // #ff6666 | |
const amarelo = theme.palette.tagsAndStatus[1]; // #FDC742 | |
const verde = theme.palette.tagsAndStatus[0]; // #0BB873 | |
const cinza = theme.palette.tagsAndStatus[6]; // #D5D1CB | |
const [strength, setStrength] = useState<number>(0); | |
const [strongOptions, setStrongOptions] = useState<StrengthOptions[] | null>(null); | |
useEffect(() => { | |
const handleSetStrength = () => { | |
setStrength(getPasswordStrength(newPass)); | |
}; | |
const handleSetOptions = () => { | |
setStrongOptions(updateStrongOptions(newPass, newPassConfirmation)); | |
}; | |
handleSetStrength(); | |
handleSetOptions(); | |
}, [newPass, newPassConfirmation]); | |
const applyColor = (strength: number) => { | |
if (strength <= 2) return vermelho; // 0,1,2 | |
if (strength >= 3 && strength <= 4) return amarelo; // 3,4 | |
if (strength >= 5) return verde; // 5,6 | |
}; | |
const getStrengthLevels = (strength: number) => { | |
if (strength <= 2) return strengthLevels.lowPass; // 0,1,2 | |
if (strength >= 3 && strength <= 4) return strengthLevels.middlePass; // 3,4 | |
if (strength >= 5) return strengthLevels.strongPass; // 5 | |
}; | |
return ( | |
<Grid container gap={1}> | |
<Grid item xs={12}> | |
<Grid container gap={1.45} justifyContent={"center"} sx={{ opacity: 0.6 }}> | |
{Array.from({ length: 7 }).map((_, index) => { | |
return ( | |
<Grid item key={index} xs={1.43} > | |
<LinearProgress | |
variant="determinate" | |
value={100} | |
sx={{ | |
borderRadius: 2, | |
height: "6px", | |
"& .MuiLinearProgress-bar": { | |
backgroundColor: | |
index < strength ? applyColor(index) : cinza, | |
}, | |
}} | |
/> | |
</Grid> | |
); | |
})} | |
</Grid> | |
</Grid> | |
<Grid | |
item | |
xs={12} | |
sx={{ display: "flex", justifyContent: "space-between" }} | |
> | |
<Typography variant={"caption"} sx={{ color: applyColor(strength-1) }}> | |
{getStrengthLevels(strength-1)} | |
</Typography> | |
<Typography variant={"caption"}> Min. 8 Caracteres </Typography> | |
</Grid> | |
<Grid item xs={12} > | |
{strongOptions?.map((option, index) => { | |
return ( | |
<Grid container key={index} alignItems={"center"} gap={0.5}> | |
<Grid item sx={{ pt: 1 }}> | |
{option.isStrong ? ( | |
<CheckIcon sx={{ color: verde }} /> | |
) : ( | |
<CloseIcon sx={{ color: vermelho }} /> | |
)} | |
</Grid> | |
<Grid item> | |
<Typography variant={"caption"} sx={{ fontWeight:500, color: theme.palette.subTitles.main }} > {option.label} </Typography> | |
</Grid> | |
</Grid> | |
); | |
})} | |
</Grid> | |
</Grid> | |
); | |
}; | |
export default PasswordStrengthBar; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment