diff --git a/package.json b/package.json index 6cdaabba..6f711fb6 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,8 @@ "styled-components": "^3.2.5", "styled-system": "^2.2.1", "tippy.js": "^2.5.2", - "ws": "^5.1.1" + "ws": "^5.1.1", + "zxcvbn": "^4.4.2" }, "devDependencies": { "@babel/core": "7.0.0-beta.42", diff --git a/src/components/base/InputPassword/index.js b/src/components/base/InputPassword/index.js new file mode 100644 index 00000000..a387a9c7 --- /dev/null +++ b/src/components/base/InputPassword/index.js @@ -0,0 +1,118 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' +import { translate } from 'react-i18next' +import zxcvbn from 'zxcvbn' + +import type { T } from 'types/common' + +import debounce from 'lodash/debounce' +import noop from 'lodash/noop' + +import Box from 'components/base/Box' +import Input from 'components/base/Input' + +import IconEye from 'icons/Eye' + +const InputRight = styled(Box).attrs({ + color: 'grey', + justifyContent: 'center', + pr: 3, +})` + cursor: pointer; +` + +const Strength = styled(Box).attrs({ + bg: p => (p.activated ? (p.warning ? 'alertRed' : 'positiveGreen') : 'fog'), + grow: true, +})` + border-radius: 13px; + height: 4px; +` + +const Warning = styled(Box).attrs({ + alignItems: 'flex-end', + color: p => (p.passwordStrength <= 1 ? 'alertRed' : 'positiveGreen'), + ff: 'Open Sans|SemiBold', + fontSize: 3, +})`` + +const getPasswordStrength = (v: string) => zxcvbn(v).score + +type State = { + inputType: 'text' | 'password', + passwordStrength: number, +} + +type Props = { + maxLength: number, + onChange: Function, + t: T, + value: string, +} + +class InputPassword extends PureComponent { + static defaultProps = { + onChange: noop, + value: '', + maxLength: 20, + } + + state = { + passwordStrength: getPasswordStrength(this.props.value), + inputType: 'password', + } + + toggleInputType = () => + this.setState(prev => ({ + inputType: prev.inputType === 'text' ? 'password' : 'text', + })) + + debouncePasswordStrength = debounce( + v => + this.setState({ + passwordStrength: getPasswordStrength(v), + }), + 150, + ) + + handleChange = (v: string) => { + const { onChange } = this.props + onChange(v) + this.debouncePasswordStrength(v) + } + + render() { + const { t, maxLength } = this.props + const { passwordStrength, inputType } = this.state + + return ( + + + + + } + /> + + {[1, 2, 3, 4].map(v => ( + = v} /> + ))} + + {passwordStrength > 0 && ( + + {t(`password:warning_${passwordStrength}`)} + + )} + + ) + } +} + +export default translate()(InputPassword) diff --git a/src/components/base/InputPassword/stories.js b/src/components/base/InputPassword/stories.js new file mode 100644 index 00000000..807bca76 --- /dev/null +++ b/src/components/base/InputPassword/stories.js @@ -0,0 +1,34 @@ +// @flow + +import React, { Component } from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' + +import InputPassword from 'components/base/InputPassword' + +const stories = storiesOf('Components', module) + +class Wrapper extends Component { + state = { + value: 'p@ssw0rd!', + } + + handleChange = value => { + action('onChange')(value) + this.setState({ value }) + } + + render() { + const { render } = this.props + const { value } = this.state + + return render({ + onChange: this.handleChange, + value, + }) + } +} + +stories.add('InputPassword', () => ( + } /> +)) diff --git a/src/icons/Eye.js b/src/icons/Eye.js new file mode 100644 index 00000000..be34ea19 --- /dev/null +++ b/src/icons/Eye.js @@ -0,0 +1,12 @@ +// @flow + +import React from 'react' + +export default ({ size, ...p }: { size: number }) => ( + + + +) diff --git a/static/i18n/en/password.yml b/static/i18n/en/password.yml new file mode 100644 index 00000000..2442233e --- /dev/null +++ b/static/i18n/en/password.yml @@ -0,0 +1,4 @@ +warning_1: Warning 1 +warning_2: Warning 2 +warning_3: Warning 3 +warning_4: Warning 4 diff --git a/yarn.lock b/yarn.lock index bb774a7c..7448044d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13222,3 +13222,7 @@ yeoman-generator@^2.0.3: text-table "^0.2.0" through2 "^2.0.0" yeoman-environment "^2.0.5" + +zxcvbn@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"