aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@gmail.com>2018-05-13 11:53:40 +0800
committerAlexander Tseung <alextsg@gmail.com>2018-05-14 12:46:12 +0800
commit0bcfbc15446b01b3b87233715cd3ead42d2730e4 (patch)
tree8b39abffed694538464ff00c2471fdc0df25ecd8
parent6bd1b21d3b8f74c44214f814c3fe1c8770ab9e2d (diff)
downloadtangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar.gz
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar.bz2
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar.lz
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar.xz
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.tar.zst
tangerine-wallet-browser-0bcfbc15446b01b3b87233715cd3ead42d2730e4.zip
Add error message when passwords don't match in first time flow. Change input field styling in first time flow
-rw-r--r--app/_locales/en/messages.json2
-rw-r--r--mascara/src/app/first-time/create-password-screen.js133
-rw-r--r--mascara/src/app/first-time/import-seed-phrase-screen.js146
-rw-r--r--mascara/src/app/first-time/index.css33
-rw-r--r--test/integration/lib/mascara-first-time.js29
-rw-r--r--test/screens/new-ui.js4
-rw-r--r--ui/app/components/text-field/text-field.component.js5
-rw-r--r--ui/app/css/itcss/components/welcome-screen.scss97
8 files changed, 216 insertions, 233 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 214355589..90beda418 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -724,7 +724,7 @@
"message": "New Password (min 8 chars)"
},
"seedPhraseReq": {
- "message": "seed phrases are 12 words long"
+ "message": "Seed phrases are 12 words long"
},
"select": {
"message": "Select"
diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js
index 6ec05e11d..99d210ed1 100644
--- a/mascara/src/app/first-time/create-password-screen.js
+++ b/mascara/src/app/first-time/create-password-screen.js
@@ -13,8 +13,13 @@ import {
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
INITIALIZE_NOTICE_ROUTE,
} from '../../../../ui/app/routes'
+import TextField from '../../../../ui/app/components/text-field'
class CreatePasswordScreen extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
static propTypes = {
isLoading: PropTypes.bool.isRequired,
createAccount: PropTypes.func.isRequired,
@@ -27,6 +32,8 @@ class CreatePasswordScreen extends Component {
state = {
password: '',
confirmPassword: '',
+ passwordError: null,
+ confirmPasswordError: null,
}
constructor (props) {
@@ -69,82 +76,37 @@ class CreatePasswordScreen extends Component {
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
}
- renderFields () {
- const { isMascara, history } = this.props
+ handlePasswordChange (password) {
+ const { confirmPassword } = this.state
+ let confirmPasswordError = null
+ let passwordError = null
- return (
- <div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
- <div className={classnames({
- 'first-view-main': !isMascara,
- 'first-view-main__mascara': isMascara,
- })}>
- {isMascara && <div className="mascara-info first-view-phone-invisible">
- <Mascot
- animationEventEmitter={this.animationEventEmitter}
- width="225"
- height="225"
- />
- <div className="info">
- MetaMask is a secure identity vault for Ethereum.
- </div>
- <div className="info">
- It allows you to hold ether & tokens, and interact with decentralized applications.
- </div>
- </div>}
- <div className="create-password">
- <div className="create-password__title">
- Create Password
- </div>
- <input
- className="first-time-flow__input"
- type="password"
- placeholder="New Password (min 8 characters)"
- onChange={e => this.setState({password: e.target.value})}
- />
- <input
- className="first-time-flow__input create-password__confirm-input"
- type="password"
- placeholder="Confirm Password"
- onChange={e => this.setState({confirmPassword: e.target.value})}
- />
- <button
- className="first-time-flow__button"
- disabled={!this.isValid()}
- onClick={this.createAccount}
- >
- Create
- </button>
- <a
- href=""
- className="first-time-flow__link create-password__import-link"
- onClick={e => {
- e.preventDefault()
- history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
- }}
- >
- Import with seed phrase
- </a>
- { /* }
- <a
- href=""
- className="first-time-flow__link create-password__import-link"
- onClick={e => {
- e.preventDefault()
- history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
- }}
- >
- Import an account
- </a>
- { */ }
- <Breadcrumbs total={3} currentIndex={0} />
- </div>
- </div>
- </div>
- )
+ if (password && password.length < 8) {
+ passwordError = this.context.t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
+ }
+
+ this.setState({ password, passwordError, confirmPasswordError })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { password } = this.state
+ let confirmPasswordError = null
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
+ }
+
+ this.setState({ confirmPassword, confirmPasswordError })
}
render () {
const { history, isMascara } = this.props
+ const { passwordError, confirmPasswordError } = this.state
+ const { t } = this.context
return (
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
@@ -169,17 +131,30 @@ class CreatePasswordScreen extends Component {
<div className="create-password__title">
Create Password
</div>
- <input
- className="first-time-flow__input"
+ <TextField
+ id="create-password"
+ label={t('newPassword')}
type="password"
- placeholder="New Password (min 8 characters)"
- onChange={e => this.setState({password: e.target.value})}
+ className="first-time-flow__input"
+ value={this.state.password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoFocus
+ autoComplete="new-password"
+ margin="normal"
+ fullWidth
/>
- <input
- className="first-time-flow__input create-password__confirm-input"
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
type="password"
- placeholder="Confirm Password"
- onChange={e => this.setState({confirmPassword: e.target.value})}
+ className="first-time-flow__input"
+ value={this.state.confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ fullWidth
/>
<button
className="first-time-flow__button"
diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js
index 5834919de..4fda2bbc1 100644
--- a/mascara/src/app/first-time/import-seed-phrase-screen.js
+++ b/mascara/src/app/first-time/import-seed-phrase-screen.js
@@ -1,29 +1,33 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
-import classnames from 'classnames'
import {
createNewVaultAndRestore,
- hideWarning,
- displayWarning,
unMarkPasswordForgotten,
} from '../../../../ui/app/actions'
-import { DEFAULT_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
+import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
+import TextField from '../../../../ui/app/components/text-field'
class ImportSeedPhraseScreen extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
static propTypes = {
warning: PropTypes.string,
createNewVaultAndRestore: PropTypes.func.isRequired,
- hideWarning: PropTypes.func.isRequired,
- displayWarning: PropTypes.func,
leaveImportSeedScreenState: PropTypes.func,
history: PropTypes.object,
+ isLoading: PropTypes.bool,
};
state = {
seedPhrase: '',
password: '',
confirmPassword: '',
+ seedPhraseError: null,
+ passwordError: null,
+ confirmPasswordError: null,
}
parseSeedPhrase = (seedPhrase) => {
@@ -32,39 +36,47 @@ class ImportSeedPhraseScreen extends Component {
.join(' ')
}
- onChange = ({ seedPhrase, password, confirmPassword }) => {
- const {
- password: prevPassword,
- confirmPassword: prevConfirmPassword,
- } = this.state
- const { displayWarning, hideWarning } = this.props
-
- let warning = null
+ handleSeedPhraseChange (seedPhrase) {
+ let seedPhraseError = null
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
- warning = 'Seed Phrases are 12 words long'
- } else if (password && password.length < 8) {
- warning = 'Passwords require a mimimum length of 8'
- } else if ((password || prevPassword) !== (confirmPassword || prevConfirmPassword)) {
- warning = 'Confirmed password does not match'
+ seedPhraseError = this.context.t('seedPhraseReq')
}
- if (warning) {
- displayWarning(warning)
- } else {
- hideWarning()
+ this.setState({ seedPhrase, seedPhraseError })
+ }
+
+ handlePasswordChange (password) {
+ const { confirmPassword } = this.state
+ let confirmPasswordError = null
+ let passwordError = null
+
+ if (password && password.length < 8) {
+ passwordError = this.context.t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
+ }
+
+ this.setState({ password, passwordError, confirmPasswordError })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { password } = this.state
+ let confirmPasswordError = null
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
}
- seedPhrase && this.setState({ seedPhrase })
- password && this.setState({ password })
- confirmPassword && this.setState({ confirmPassword })
+ this.setState({ confirmPassword, confirmPasswordError })
}
onClick = () => {
const { password, seedPhrase } = this.state
const {
createNewVaultAndRestore,
- displayWarning,
leaveImportSeedScreenState,
history,
} = this.props
@@ -74,10 +86,23 @@ class ImportSeedPhraseScreen extends Component {
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
}
+ hasError () {
+ const { passwordError, confirmPasswordError, seedPhraseError } = this.state
+ return passwordError || confirmPasswordError || seedPhraseError
+ }
+
render () {
- const { seedPhrase, password, confirmPassword } = this.state
- const { warning, isLoading } = this.props
- const importDisabled = warning || !seedPhrase || !password || !confirmPassword || isLoading
+ const {
+ seedPhrase,
+ password,
+ confirmPassword,
+ seedPhraseError,
+ passwordError,
+ confirmPasswordError,
+ } = this.state
+ const { t } = this.context
+ const { isLoading } = this.props
+ const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
return (
<div className="first-view-main-wrapper">
@@ -103,45 +128,40 @@ class ImportSeedPhraseScreen extends Component {
<label className="import-account__input-label">Wallet Seed</label>
<textarea
className="import-account__secret-phrase"
- onChange={e => this.onChange({seedPhrase: e.target.value})}
+ onChange={e => this.handleSeedPhraseChange(e.target.value)}
value={this.state.seedPhrase}
placeholder="Separate each word with a single space"
/>
</div>
- <span
- className="error"
- >
- {this.props.warning}
+ <span className="error">
+ { seedPhraseError }
</span>
- <div className="import-account__input-wrapper">
- <label className="import-account__input-label">New Password</label>
- <input
- className="first-time-flow__input"
- type="password"
- placeholder="New Password (min 8 characters)"
- onChange={e => this.onChange({password: e.target.value})}
- />
- </div>
- <div className="import-account__input-wrapper">
- <label
- className={classnames('import-account__input-label', {
- 'import-account__input-label__disabled': password.length < 8,
- })}
- >Confirm Password</label>
- <input
- className={classnames('first-time-flow__input', {
- 'first-time-flow__input__disabled': password.length < 8,
- })}
- type="password"
- placeholder="Confirm Password"
- onChange={e => this.onChange({confirmPassword: e.target.value})}
- disabled={password.length < 8}
- />
- </div>
+ <TextField
+ id="password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoComplete="new-password"
+ margin="normal"
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ />
<button
className="first-time-flow__button"
- onClick={() => !importDisabled && this.onClick()}
- disabled={importDisabled}
+ onClick={() => !disabled && this.onClick()}
+ disabled={disabled}
>
Import
</button>
@@ -159,7 +179,5 @@ export default connect(
dispatch(unMarkPasswordForgotten())
},
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
- displayWarning: (warning) => dispatch(displayWarning(warning)),
- hideWarning: () => dispatch(hideWarning()),
})
)(ImportSeedPhraseScreen)
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
index dffc21017..25e60b84a 100644
--- a/mascara/src/app/first-time/index.css
+++ b/mascara/src/app/first-time/index.css
@@ -174,10 +174,7 @@
}
.first-time-flow__input {
- width: initial !important;
- font-size: 14px !important;
- line-height: 18px !important;
- padding: 12px !important;
+ width: 100%;
}
.tou__body {
@@ -248,7 +245,7 @@
}
.create-password__confirm-input {
- margin-top: 15px;
+ margin-top: 16px;
}
.create-password__import-link {
@@ -520,10 +517,6 @@ button.backup-phrase__confirm-seed-option:hover {
margin-top: 30px;
}
-.first-time-flow__input--error {
- border: 1px solid #FF001F !important;
-}
-
.import-account__input-error-message {
margin-top: 10px;
width: 422px;
@@ -544,7 +537,13 @@ button.backup-phrase__confirm-seed-option:hover {
}
.import-account__input {
- width: 325px !important;
+ width: 350px;
+}
+
+@media only screen and (max-width: 575px) {
+ .import-account__input {
+ width: 100%;
+ }
}
.import-account__file-input {
@@ -681,20 +680,6 @@ button.backup-phrase__confirm-seed-option:hover {
.first-time-flow__input {
width: 350px;
- font-size: 18px;
- line-height: 24px;
- padding: 15px;
- border: 1px solid #CDCDCD;
- background-color: #FFFFFF;
-}
-
-.first-time-flow__input__disabled {
- opacity: 0.5;
-}
-
-.first-time-flow__input::placeholder {
- color: #9B9B9B;
- font-weight: 200;
}
.first-time-flow__button {
diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js
index d86703277..f43a30c74 100644
--- a/test/integration/lib/mascara-first-time.js
+++ b/test/integration/lib/mascara-first-time.js
@@ -1,5 +1,4 @@
const PASSWORD = 'password123'
-const reactTriggerChange = require('react-trigger-change')
const {
timeout,
findAsync,
@@ -11,6 +10,11 @@ async function runFirstTimeUsageTest (assert, done) {
const app = await queryAsync($, '#app-content')
+ // Used to set values on TextField input component
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype, 'value'
+ ).set
+
await skipNotices(app)
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
@@ -21,12 +25,14 @@ async function runFirstTimeUsageTest (assert, done) {
assert.equal(title, 'Create Password', 'create password screen')
// enter password
- const pwBox = (await findAsync(app, '.first-time-flow__input'))[0]
- const confBox = (await findAsync(app, '.first-time-flow__input'))[1]
- pwBox.value = PASSWORD
- confBox.value = PASSWORD
- reactTriggerChange(pwBox)
- reactTriggerChange(confBox)
+ const pwBox = (await findAsync(app, '#create-password'))[0]
+ const confBox = (await findAsync(app, '#confirm-password'))[0]
+
+ nativeInputValueSetter.call(pwBox, PASSWORD)
+ pwBox.dispatchEvent(new Event('input', { bubbles: true}))
+
+ nativeInputValueSetter.call(confBox, PASSWORD)
+ confBox.dispatchEvent(new Event('input', { bubbles: true}))
// Create Password
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
@@ -77,15 +83,8 @@ async function runFirstTimeUsageTest (assert, done) {
pwBox2.focus()
await timeout(1000)
- // Used to set values on TextField input component
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
- window.HTMLInputElement.prototype, 'value'
- ).set
-
nativeInputValueSetter.call(pwBox2, PASSWORD)
-
- var ev2 = new Event('input', { bubbles: true})
- pwBox2.dispatchEvent(ev2)
+ pwBox2.dispatchEvent(new Event('input', { bubbles: true}))
const createButton2 = (await findAsync(app, 'button[type="submit"]'))[0]
createButton2.click()
diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js
index e176da529..6a8822eb3 100644
--- a/test/screens/new-ui.js
+++ b/test/screens/new-ui.js
@@ -74,8 +74,8 @@ async function captureAllScreens() {
await driver.findElement(By.css('button')).click()
await captureLanguageScreenShots('create password')
- const passwordBox = await driver.findElement(By.css('input[type=password]:nth-of-type(1)'))
- const passwordBoxConfirm = await driver.findElement(By.css('input[type=password]:nth-of-type(2)'))
+ const passwordBox = await driver.findElement(By.css('input#create-password'))
+ const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password'))
passwordBox.sendKeys('123456789')
passwordBoxConfirm.sendKeys('123456789')
await delay(500)
diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js
index 4a02f76d8..6fd3b82b4 100644
--- a/ui/app/components/text-field/text-field.component.js
+++ b/ui/app/components/text-field/text-field.component.js
@@ -8,6 +8,9 @@ const styles = {
'&$cssFocused': {
color: '#aeaeae',
},
+ '&$cssError': {
+ color: '#aeaeae',
+ },
fontWeight: '400',
color: '#aeaeae',
},
@@ -17,6 +20,7 @@ const styles = {
backgroundColor: '#f7861c',
},
},
+ cssError: {},
}
const TextField = props => {
@@ -30,6 +34,7 @@ const TextField = props => {
FormLabelClasses: {
root: classes.cssLabel,
focused: classes.cssFocused,
+ error: classes.cssError,
},
}}
InputProps={{
diff --git a/ui/app/css/itcss/components/welcome-screen.scss b/ui/app/css/itcss/components/welcome-screen.scss
index bfd174ad9..af1d67398 100644
--- a/ui/app/css/itcss/components/welcome-screen.scss
+++ b/ui/app/css/itcss/components/welcome-screen.scss
@@ -1,59 +1,60 @@
.welcome-screen {
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ font-family: Roboto;
+ font-weight: 400;
+ width: 100%;
+ flex: 1 0 auto;
+ padding: 70px 0;
+ background: $white;
+
+ @media screen and (max-width: 575px) {
+ padding: 0;
+ }
+
+ &__info {
display: flex;
flex-flow: column;
- justify-content: center;
- align-items: center;
- font-family: Roboto;
- font-weight: 400;
width: 100%;
- flex: 1 0 auto;
- padding: 70px 0;
- background: $white;
-
- @media screen and (max-width: 575px) {
- padding: 0;
- }
-
- &__info {
- display: flex;
- flex-flow: column;
- width: 100%;
- height: 100%;
- align-items: center;
-
- &__header {
- font-size: 1.65em;
- margin-bottom: 14px;
-
- @media screen and (max-width: 575px) {
- font-size: 1.5em;
- }
- }
+ height: 100%;
+ align-items: center;
+ justify-content: center;
- &__copy {
- font-size: 1em;
- width: 400px;
- max-width: 90vw;
- text-align: center;
+ &__header {
+ font-size: 1.65em;
+ margin-bottom: 14px;
- @media screen and (max-width: 575px) {
- font-size: 0.9em;
- }
- }
+ @media screen and (max-width: 575px) {
+ font-size: 1.5em;
+ }
}
- &__button {
- height: 54px;
- width: 198px;
- box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
- color: #FFFFFF;
- font-size: 20px;
- font-weight: 500;
- line-height: 26px;
+ &__copy {
+ font-size: 1em;
+ width: 400px;
+ max-width: 90vw;
text-align: center;
- text-transform: uppercase;
- margin: 35px 0 14px;
- transition: 200ms ease-in-out;
- background-color: rgba(247, 134, 28, 0.9);
+
+ @media screen and (max-width: 575px) {
+ font-size: .9em;
+ }
}
+ }
+
+ &__button {
+ height: 54px;
+ width: 198px;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
+ color: #fff;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 26px;
+ text-align: center;
+ text-transform: uppercase;
+ margin: 35px 0 14px;
+ transition: 200ms ease-in-out;
+ background-color: rgba(247, 134, 28, .9);
+ }
}