From 8ac2b6763712c47b25ac566b018bd1741aefb275 Mon Sep 17 00:00:00 2001 From: Oleg Chendighelean Date: Wed, 19 Feb 2025 16:19:14 +0000 Subject: [PATCH] test 2 --- .../src/hooks/usePasswordValidation.tsx | 224 ++++++------------ 1 file changed, 76 insertions(+), 148 deletions(-) diff --git a/packages/components/src/hooks/usePasswordValidation.tsx b/packages/components/src/hooks/usePasswordValidation.tsx index 15082327b..a678f851a 100644 --- a/packages/components/src/hooks/usePasswordValidation.tsx +++ b/packages/components/src/hooks/usePasswordValidation.tsx @@ -1,28 +1,22 @@ -import { Box, Collapse, Flex, List, ListItem, Text } from "@chakra-ui/react"; +import { CheckIcon, CloseIcon } from "@chakra-ui/icons"; +import { Flex, List, ListIcon, ListItem, Text } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; -import { type ZodIssue, z } from "zod"; +import { z } from "zod"; import zxcvbn from "zxcvbn"; export const DEFAULT_MIN_LENGTH = 12; -const DEFAULT_SCORE = 0; -const DEFAULT_COLOR = "gray.100"; -const DEFAULT_PASSWORD_FIELD_NAME = "password"; -const PASSWORD_REQUIREMENTS_COUNT = 4; -// Types -type ValidationPath = "minLength" | "uppercase" | "number" | "special" | "simple"; +type ValidationPath = "minLength" | "uppercase" | "number" | "special" | "simplicity"; type Requirement = { message: string; path: ValidationPath; + passed: boolean; }; type PasswordStrengthBarProps = { - score: number; - color: string; - errors: ZodIssue[]; - hasRequiredError: boolean; + requirements: Requirement[]; }; type UsePasswordValidationProps = { @@ -31,143 +25,89 @@ type UsePasswordValidationProps = { minLength?: number; }; -const REQUIREMENTS: Requirement[] = [ +const getPasswordSchema = (minLength: number) => + z + .string() + .refine(value => value.length >= minLength, { + path: ["minLength"], + }) + .refine(value => /[A-Z]/.test(value), { + path: ["uppercase"], + }) + .refine(value => /\d/.test(value), { + path: ["number"], + }) + .refine(value => /[!@#$%^&*(),.?":{}|<>]/.test(value), { + path: ["special"], + }) + .refine( + value => { + const score = zxcvbn(value).score; + + return score > 3; + }, + { + path: ["simplicity"], + } + ); + +const PasswordStrengthBar = ({ requirements }: PasswordStrengthBarProps) => ( + + + {requirements.map(({ message, path, passed }) => ( + + + + {message} + + + ))} + + +); + +const DEFAULT_REQUIREMENTS: Requirement[] = [ { message: `Password must be at least ${DEFAULT_MIN_LENGTH} characters long`, path: "minLength", + passed: false, }, { message: "Password must contain at least one uppercase letter", path: "uppercase", + passed: false, }, { message: "Password must contain at least one number", path: "number", + passed: false, }, { message: "Password must contain at least one special character", path: "special", + passed: false, }, { - message: "Avoid common passwords and simple patterns", - path: "simple", + message: "Avoid common passwords, simple patterns and repeated characters", + path: "simplicity", + passed: false, }, ]; -const RoundStatusDot = ({ background }: { background: string }) => ( - -); - -const getPasswordSchema = (minLength: number) => - z - .string() - .refine(value => value.length >= minLength, { - message: `Password must be at least ${minLength} characters long`, - path: ["minLength"], - }) - .refine(value => /[A-Z]/.test(value), { - message: "Password must contain at least one uppercase letter", - path: ["uppercase"], - }) - .refine(value => /\d/.test(value), { - message: "Password must contain at least one number", - path: ["number"], - }) - .refine(value => /[!@#$%^&*(),.?":{}|<>]/.test(value), { - message: "Password must contain at least one special character", - path: ["special"], - }); - -const PasswordStrengthBar = ({ - score, - color, - errors, - hasRequiredError, -}: PasswordStrengthBarProps) => { - console.log(errors); - - const isPasswordStrong = !errors.length && score === 4; - const shouldShowRequirements = !!errors.length && !hasRequiredError; - - const checkRequirement = (path: ValidationPath) => - !errors.some(error => error.path.includes(path)); - - const colors = [color, "red.500", "yellow.500", "green.500"]; - - const getSectionColor = (index: number) => { - switch (score) { - case 1: - case 2: - return index === 0 ? colors[1] : colors[0]; - case 3: - return index <= 1 ? colors[2] : colors[0]; - case 4: - return colors[3]; - default: - return colors[0]; - } - }; - - return ( - - - {Array.from({ length: 3 }).map((_, index) => ( - - ))} - - - {isPasswordStrong && ( - - Your password is strong - - )} - - - - {REQUIREMENTS.map(({ message, path }) => ( - - - - {message} - - - ))} - - - - ); -}; - export const usePasswordValidation = ({ - color = DEFAULT_COLOR, - inputName = DEFAULT_PASSWORD_FIELD_NAME, minLength = DEFAULT_MIN_LENGTH, + inputName = "password", }: UsePasswordValidationProps = {}) => { - const [passwordScore, setPasswordScore] = useState(DEFAULT_SCORE); - const [passwordErrors, setPasswordErrors] = useState([ - { - message: "Avoid common passwords and simple patterns", - path: ["simple"], - } as ZodIssue, - ]); + const [requirements, setRequirements] = useState(DEFAULT_REQUIREMENTS); const { formState: { errors, isDirty }, @@ -178,45 +118,33 @@ export const usePasswordValidation = ({ useEffect(() => { if (hasRequiredError || !isDirty) { - setPasswordScore(DEFAULT_SCORE); + setRequirements(DEFAULT_REQUIREMENTS); } }, [isDirty, hasRequiredError]); const validatePasswordStrength = (value: string) => { - const result = zxcvbn(value); - let schemaErrors = 0; - try { getPasswordSchema(minLength).parse(value); - setPasswordErrors([]); + setRequirements(requirements.map(requirement => ({ ...requirement, passed: true }))); } catch (e) { if (e instanceof z.ZodError) { - schemaErrors = e.errors.length; - setPasswordErrors(e.errors); + const errorPaths = new Set(e.errors.map(error => error.path[0])); + setRequirements(prev => + prev.map(requirement => ({ + ...requirement, + passed: !errorPaths.has(requirement.path), + })) + ); return false; } } - const requirementsMeetingPercentage = (PASSWORD_REQUIREMENTS_COUNT - schemaErrors) / 4; - setPasswordScore(Math.ceil(result.score * requirementsMeetingPercentage)); - - if (result.score < 4) { - return false; - } - return true; }; return { validatePasswordStrength, - PasswordStrengthBar: ( - - ), + PasswordStrengthBar: , }; };