import React from "react"; import PropTypes from "prop-types"; import equal from "fast-deep-equal/react"; import InputBlock from "../input-block"; import Link from "../link"; import Text from "../text"; import Tooltip from "../tooltip"; import EyeReactSvgUrl from "PUBLIC_DIR/images/eye.react.svg?url"; import EyeOffReactSvgUrl from "PUBLIC_DIR/images/eye.off.react.svg?url"; import { PasswordProgress, StyledInput, TooltipStyle, StyledTooltipContainer, StyledTooltipItem, } from "./styled-password-input"; class PasswordInput extends React.Component { constructor(props) { super(props); const { inputValue, inputType, clipActionResource, emailInputName } = props; this.ref = React.createRef(); this.state = { type: inputType, inputValue: inputValue, copyLabel: clipActionResource, disableCopyAction: emailInputName ? false : true, displayTooltip: false, validLength: false, validDigits: false, validCapital: false, validSpecial: false, }; } onBlur = (e) => { e.persist(); if (this.props.onBlur) this.props.onBlur(e); }; onKeyDown = (e) => { e.persist(); if (this.props.onKeyDown) this.props.onKeyDown(e); }; changeInputType = () => { const newType = this.state.type === "text" ? "password" : "text"; this.setState({ type: newType, }); }; testStrength = (value) => { const { passwordSettings } = this.props; const capitalRegExp = new RegExp(passwordSettings.upperCaseRegexStr); const digitalRegExp = new RegExp(passwordSettings.digitsRegexStr); const specSymbolsRegExp = new RegExp(passwordSettings.specSymbolsRegexStr); const allowedRegExp = new RegExp( "^" + passwordSettings.allowedCharactersRegexStr + "{1,}$" ); let capital; let digits; let special; passwordSettings.upperCase ? (capital = capitalRegExp.test(value)) : (capital = true); passwordSettings.digits ? (digits = digitalRegExp.test(value)) : (digits = true); passwordSettings.specSymbols ? (special = specSymbolsRegExp.test(value)) : (special = true); const allowedCharacters = allowedRegExp.test(value); return { allowed: allowedCharacters, digits: digits, capital: capital, special: special, length: value.trim().length >= passwordSettings.minLength, }; }; checkPassword = (value) => { const passwordValidation = this.testStrength(value); const progressScore = passwordValidation.digits && passwordValidation.capital && passwordValidation.special && passwordValidation.length && passwordValidation.allowed; this.props.onValidateInput && this.props.onValidateInput(progressScore, passwordValidation); this.setState({ inputValue: value, validLength: passwordValidation.length, validDigits: passwordValidation.digits, validCapital: passwordValidation.capital, validSpecial: passwordValidation.special, }); }; onChangeAction = (e) => { this.props.onChange && this.props.onChange(e); if (this.props.simpleView) { this.setState({ inputValue: e.target.value, }); return; } this.checkPassword(e.target.value); }; onGeneratePassword = (e) => { if (this.props.isDisabled) return e.preventDefault(); const newPassword = this.getNewPassword(); if (this.state.type !== "text") { this.setState({ type: "text", }); } this.checkPassword(newPassword); this.props.onChange && this.props.onChange({ target: { value: newPassword } }); }; getNewPassword = () => { const { passwordSettings, generatorSpecial } = this.props; const length = passwordSettings.minLength; 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.substr(0, length); }; copyToClipboard = (emailInputName) => { const { clipEmailResource, clipPasswordResource, clipActionResource, clipCopiedResource, isDisabled, onCopyToClipboard, } = this.props; const { disableCopyAction, inputValue } = this.state; if (isDisabled || disableCopyAction) return event.preventDefault(); this.setState({ disableCopyAction: true, copyLabel: clipCopiedResource, }); const textField = document.createElement("textarea"); const emailValue = document.getElementsByName(emailInputName)[0].value; const formattedText = clipEmailResource + emailValue + " | " + clipPasswordResource + inputValue; textField.innerText = formattedText; document.body.appendChild(textField); textField.select(); document.execCommand("copy"); textField.remove(); onCopyToClipboard && onCopyToClipboard(formattedText); setTimeout(() => { this.setState({ disableCopyAction: false, copyLabel: clipActionResource, }); }, 2000); }; shouldComponentUpdate(nextProps, nextState) { return !equal(this.props, nextProps) || !equal(this.state, nextState); } componentDidUpdate(prevProps, prevState) { if ( prevProps.clipActionResource !== this.props.clipActionResource && this.state.copyLabel !== this.props.clipCopiedResource ) { this.setState({ copyLabel: this.props.clipActionResource }); } } renderTextTooltip = (settings, length, digits, capital, special) => { return ( <>
{settings.minLength ? length : null}{" "} {settings.digits ? `, ${digits}` : null}{" "} {settings.upperCase ? `, ${capital}` : null}{" "} {settings.specSymbols ? `, ${special}` : null}
); }; renderTextTooltip = () => { const { tooltipPasswordLength, tooltipPasswordDigits, tooltipPasswordCapital, tooltipPasswordSpecial, passwordSettings, isTextTooltipVisible, } = this.props; return isTextTooltipVisible ? ( <>
{passwordSettings.minLength ? tooltipPasswordLength : null}{" "} {passwordSettings.digits ? `, ${tooltipPasswordDigits}` : null}{" "} {passwordSettings.upperCase ? `, ${tooltipPasswordCapital}` : null}{" "} {passwordSettings.specSymbols ? `, ${tooltipPasswordSpecial}` : null}
) : null; }; renderTooltipContent = () => !this.props.isDisableTooltip && !this.props.isDisabled ? ( {this.props.tooltipPasswordTitle} {this.props.tooltipPasswordLength} {this.props.passwordSettings.digits && ( {this.props.tooltipPasswordDigits} )} {this.props.passwordSettings.upperCase && ( {this.props.tooltipPasswordCapital} )} {this.props.passwordSettings.specSymbols && ( {this.props.tooltipPasswordSpecial} )} {this.props.generatePasswordTitle && (
{this.props.generatePasswordTitle}
)}
) : null; renderInputGroup = () => { const { inputName, isDisabled, scale, size, hasError, hasWarning, placeholder, tabIndex, maxLength, id, autoComplete, forwardedRef, } = this.props; const { type, inputValue } = this.state; const iconName = type === "password" ? EyeOffReactSvgUrl : EyeReactSvgUrl; const iconButtonClassName = `password_eye--${ type === "password" ? "close" : "open" }`; return ( <> {this.renderTooltipContent()} ); }; render() { //console.log('PasswordInput render()'); const { inputWidth, onValidateInput, className, style, simpleView, isDisabled, isFullWidth, } = this.props; return ( {simpleView ? ( <> {this.renderInputGroup()} {this.renderTextTooltip()} ) : ( <>
{this.renderInputGroup()}
{this.renderTextTooltip()} )}
); } } PasswordInput.propTypes = { /** Allows setting the component id */ id: PropTypes.string, /** Allows setting the component auto-complete */ autoComplete: PropTypes.string, /** Facilitates the correct display of values inside the input*/ inputType: PropTypes.oneOf(["text", "password"]), /** Input name */ inputName: PropTypes.string, /** Required to associate the password field with the email field */ emailInputName: PropTypes.string, /** Input value */ inputValue: PropTypes.string, /** Sets a callback function that is triggered on PasswordInput */ onChange: PropTypes.func, /** Default event that is triggered when the button is already pressed but not released */ onKeyDown: PropTypes.func, /** Event that is triggered when the focused item is lost */ onBlur: PropTypes.func, /** Sets the input width manually */ inputWidth: PropTypes.string, /** Notifies if the error occurs */ hasError: PropTypes.bool, /** Notifies if the warning occurs */ hasWarning: PropTypes.bool, /** Default placeholder input */ placeholder: PropTypes.string, /** Tab index input */ tabIndex: PropTypes.number, /** Default maxLength input */ maxLength: PropTypes.number, /** Accepts class */ className: PropTypes.string, /** Accepts css style */ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), /** Sets the input disabled */ isDisabled: PropTypes.bool, size: PropTypes.oneOf(["base", "middle", "big", "huge", "large"]), /** Indicates that the input field has scale */ scale: PropTypes.bool, /** Allows to hide Tooltip */ isDisableTooltip: PropTypes.bool, /** Allows to show Tooltip text */ isTextTooltipVisible: PropTypes.bool, /** Prompts to copy the email and password data to clipboard */ clipActionResource: PropTypes.string, /** Prompts to copy the email data to clipboard */ clipEmailResource: PropTypes.string, /** Prompts to copy the password data to clipboard */ clipPasswordResource: PropTypes.string, /** Prompts that the data has been copied to clipboard */ clipCopiedResource: PropTypes.string, /** Title that indicates that the tooltip must contain a password */ tooltipPasswordTitle: PropTypes.string, /** Prompt that indicates the minimal password length */ tooltipPasswordLength: PropTypes.string, /** Prompt that instructs to include digits into the password */ tooltipPasswordDigits: PropTypes.string, /** Prompt that indicates that capital letters are allowed */ tooltipPasswordCapital: PropTypes.string, /** Prompt that indicates that special characters are allowed */ tooltipPasswordSpecial: PropTypes.string, /** Set of special characters for password generator and validator */ generatorSpecial: PropTypes.string, /** Set of settings for password generator and validator */ passwordSettings: PropTypes.object, /** Sets a callback function that is triggered on PasswordInput. Returns bool value */ onValidateInput: PropTypes.func, /** Sets a callback function that is triggered when the copy button is clicked. Returns formatted value */ onCopyToClipboard: PropTypes.func, /** Sets the tooltip offset to the left */ tooltipOffsetLeft: PropTypes.number, /** Sets the tooltip offset to the top */ tooltipOffsetTop: PropTypes.number, /** Sets the password input view to simple (without tooltips, password progress bar and several additional buttons (copy and generate password) */ simpleView: PropTypes.bool, /** Sets a title of the password generation button */ generatePasswordTitle: PropTypes.string, /** Setting display block to set element to full width*/ isfullwidth: PropTypes.bool, }; PasswordInput.defaultProps = { inputType: "password", inputName: "passwordInput", autoComplete: "new-password", isDisabled: false, size: "base", 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 default PasswordInput;