import React, { useCallback, useRef, useState, KeyboardEvent, ChangeEvent, FocusEvent, MouseEvent, } from "react"; import { TooltipRefProps } from "react-tooltip"; // import equal from "fast-deep-equal/react"; import EyeReactSvgUrl from "PUBLIC_DIR/images/eye.react.svg?url"; import EyeOffReactSvgUrl from "PUBLIC_DIR/images/eye.off.react.svg?url"; import { InputBlock } from "../input-block"; import { Link, LinkType } from "../link"; import { Text } from "../text"; import { Tooltip } from "../tooltip"; import { InputType } from "../text-input"; import { PasswordProgress, StyledInput, TooltipStyle, StyledTooltipContainer, StyledTooltipItem, } from "./PasswordInput.styled"; import { PasswordInputProps, TPasswordSettings, TPasswordValidation, } from "./PasswordInput.types"; const PasswordInput = React.forwardRef( ( { inputType = InputType.password, inputValue, clipActionResource, emailInputName, onBlur, onKeyDown, onValidateInput, onChange, isDisabled, simpleView, passwordSettings, generatorSpecial, clipCopiedResource, tooltipPasswordTitle, tooltipPasswordLength, tooltipPasswordDigits, tooltipPasswordCapital, tooltipPasswordSpecial, generatePasswordTitle, inputName, scale, size, hasError, hasWarning, placeholder, tabIndex, maxLength, id, autoComplete, forwardedRef, isDisableTooltip, inputWidth, className, style, isFullWidth, tooltipOffsetLeft, tooltipOffsetTop, isAutoFocussed, }: PasswordInputProps, ref, ) => { const [state, setState] = useState({ type: inputType, value: inputValue, copyLabel: clipActionResource, disableCopyAction: !emailInputName, displayTooltip: false, validLength: false, validDigits: false, validCapital: false, validSpecial: false, }); const refProgress = useRef(null); const refTooltip = useRef(null); const refTooltipContent = useRef(null); const onBlurAction = useCallback( (e: FocusEvent) => { e.persist(); if (onBlur) onBlur(e); }, [onBlur], ); const onFocusAction = () => { const length = state.value?.length ?? 0; const minLength = passwordSettings?.minLength; if ((minLength && length < minLength) || hasError || hasWarning) { if (refTooltip.current) { const tooltip = refTooltip.current as TooltipRefProps; tooltip?.open?.(); } } }; const handleClickOutside = React.useCallback((event: Event) => { if (refTooltip.current && refTooltipContent.current) { const target = event.target as HTMLElement; const tooltip = refTooltip.current as TooltipRefProps; const tooltipContent = refTooltipContent.current as HTMLElement; if ( !tooltip || !tooltip.isOpen || tooltip.activeAnchor?.contains(target) || tooltipContent?.parentElement?.contains(target) ) return; tooltip.close(); } }, []); React.useEffect(() => { document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [handleClickOutside]); const onKeyDownAction = useCallback( (e: KeyboardEvent) => { e.persist(); if (onKeyDown) onKeyDown(e); }, [onKeyDown], ); const changeInputType = () => { const newType = state.type === InputType.text ? InputType.password : InputType.text; setState((s) => ({ ...s, type: newType, })); }; const testStrength = useCallback( (value: string) => { if (passwordSettings) { const capitalRegExp = new RegExp( passwordSettings.upperCaseRegexStr || "", ); const digitalRegExp = new RegExp( passwordSettings.digitsRegexStr || "", ); const specSymbolsRegExp = new RegExp( passwordSettings.specSymbolsRegexStr || "", ); let capital = true; let digits = true; let special = true; let allowed = true; let length = true; if (passwordSettings.upperCase) { capital = capitalRegExp.test(value); } if (passwordSettings.digits) { digits = digitalRegExp.test(value); } if (passwordSettings.specSymbols) { special = specSymbolsRegExp.test(value); } if (passwordSettings.allowedCharactersRegexStr) { const allowedRegExp = new RegExp( `^${passwordSettings.allowedCharactersRegexStr}{1,}$`, ); allowed = allowedRegExp.test(value); } if (passwordSettings?.minLength !== undefined) { length = value.trim().length >= passwordSettings.minLength; } return { allowed, digits, capital, special, length, }; } return {} as TPasswordValidation; }, [passwordSettings], ); const checkPassword = useCallback( (value: string) => { const passwordValidation = testStrength(value); const progressScore = (passwordValidation?.digits && passwordValidation?.capital && passwordValidation?.special && passwordValidation?.length && passwordValidation?.allowed) || false; onValidateInput?.(progressScore, passwordValidation); setState((s) => ({ ...s, value, validLength: passwordValidation.length || false, validDigits: passwordValidation.digits || false, validCapital: passwordValidation.capital || false, validSpecial: passwordValidation.special || false, })); }, [onValidateInput, testStrength], ); const onChangeAction = useCallback( (e: ChangeEvent) => { onChange?.(e); if (refTooltip.current) { const tooltip = refTooltip.current as TooltipRefProps; if (tooltip?.isOpen) { tooltip?.close?.(); } } if (simpleView) { setState((s) => ({ ...s, value: e.target.value, })); return; } checkPassword(e.target.value); }, [onChange, simpleView, checkPassword], ); const getNewPassword = React.useCallback(() => { const length = passwordSettings?.minLength || 0; const string = "abcdefghijklmnopqrstuvwxyz"; const numeric = "0123456789"; const special = generatorSpecial || ""; let password = ""; let character = ""; while (password.length < length) { const a = Math.ceil(string.length * Math.random() * Math.random()); const b = Math.ceil(numeric.length * Math.random() * Math.random()); const c = Math.ceil(special.length * Math.random() * Math.random()); let hold = string.charAt(a); if (passwordSettings?.upperCase) { hold = password.length % 2 === 0 ? hold.toUpperCase() : hold; } character += hold; if (passwordSettings?.digits) { character += numeric.charAt(b); } if (passwordSettings?.specSymbols) { character += special.charAt(c); } password = character; } password = password .split("") .sort(() => 0.5 - Math.random()) .join(""); return password.substring(0, length); }, [ generatorSpecial, passwordSettings?.digits, passwordSettings?.minLength, passwordSettings?.specSymbols, passwordSettings?.upperCase, ]); const onGeneratePassword = React.useCallback( (e: MouseEvent) => { if (isDisabled) return e.preventDefault(); const newPassword = getNewPassword(); if (state.type !== InputType.text) { setState((s) => ({ ...s, type: InputType.text, })); } checkPassword(newPassword); onChangeAction?.({ target: { value: newPassword }, } as ChangeEvent); }, [checkPassword, getNewPassword, isDisabled, onChangeAction, state.type], ); // const copyToClipboard = (emailName: string) => { // const { disableCopyAction, value } = state; // if (isDisabled || disableCopyAction) return; // setState((s) => ({ // ...s, // disableCopyAction: true, // copyLabel: clipCopiedResource, // })); // const textField = document.createElement("textarea"); // const emailValue = ( // document.getElementsByName(emailName)[0] as HTMLInputElement // ).value; // const formattedText = `${clipEmailResource}${emailValue} | ${clipPasswordResource}${value}`; // textField.innerText = formattedText; // document.body.appendChild(textField); // textField.select(); // document.execCommand("copy"); // textField.remove(); // onCopyToClipboard?.(formattedText); // setTimeout(() => { // setState({ // ...state, // disableCopyAction: false, // copyLabel: clipActionResource, // }); // }, 2000); // }; React.useEffect(() => { setState((s) => ({ ...s, copyLabel: clipActionResource })); }, [clipActionResource, clipCopiedResource]); React.useImperativeHandle( ref, () => { return { onGeneratePassword, setState, value: state.value }; }, [onGeneratePassword, setState, state.value], ); const renderTextTooltip = ( settings?: TPasswordSettings, length?: number, digits?: string, capital?: string, special?: string, ) => { return ( <>
{settings?.minLength ? length : null}{" "} {settings?.digits ? `, ${digits}` : null}{" "} {settings?.upperCase ? `, ${capital}` : null}{" "} {settings?.specSymbols ? `, ${special}` : null}
); }; const renderTooltipContent = () => ( {tooltipPasswordTitle} {tooltipPasswordLength} {passwordSettings?.digits && ( {tooltipPasswordDigits} )} {passwordSettings?.upperCase && ( {tooltipPasswordCapital} )} {passwordSettings?.specSymbols && ( {tooltipPasswordSpecial} )} {generatePasswordTitle && (
{generatePasswordTitle}
)}
); const renderInputGroup = () => { const { type, value } = state; const iconName = type === "password" ? EyeOffReactSvgUrl : EyeReactSvgUrl; const iconButtonClassName = `password_eye--${ type === "password" ? "close" : "open" }`; return ( <> {!isDisableTooltip && !isDisabled && ( {renderTooltipContent()} )} ); }; return ( {simpleView ? ( <> {renderInputGroup()} {renderTextTooltip()} ) : ( <>
{renderInputGroup()}
{renderTextTooltip()} )}
); }, ); PasswordInput.displayName = "PasswordInput"; PasswordInput.defaultProps = { inputName: "passwordInput", autoComplete: "new-password", isDisabled: false, scale: true, isDisableTooltip: false, isTextTooltipVisible: false, clipEmailResource: "E-mail ", clipPasswordResource: "Password ", clipCopiedResource: "Copied", generatorSpecial: "!@#$%^&*", className: "", simpleView: false, passwordSettings: { minLength: 8, upperCase: false, digits: false, specSymbols: false, digitsRegexStr: "(?=.*\\d)", upperCaseRegexStr: "(?=.*[A-Z])", specSymbolsRegexStr: "(?=.*[\\x21-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E])", }, isFullWidth: false, }; export { PasswordInput }; // const compare = ( // prevProps: Readonly, // nextProps: Readonly, // ) => { // return equal(prevProps, nextProps); // }; // const PasswordInput = React.memo( // (props) => , // compare, // ); // PasswordInput.displayName = "PasswordInput"; // export { PasswordInput };