Simplify the cloud screens (#967)

* Simplify the cloud

* lint

* Simplify registration flash message text

* Address comments

* Update title
This commit is contained in:
Paulus Schoutsen 2018-03-05 13:42:56 -08:00 committed by GitHub
parent 710139bf4b
commit bec9162198
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 226 additions and 302 deletions

View File

@ -1,6 +1,5 @@
<link rel="import" href='../../../bower_components/polymer/polymer-element.html'> <link rel="import" href='../../../bower_components/polymer/polymer-element.html'>
<link rel="import" href='../../../bower_components/paper-card/paper-card.html'> <link rel="import" href='../../../bower_components/paper-card/paper-card.html'>
<link rel="import" href='../../../bower_components/paper-button/paper-button.html'>
<link rel="import" href='../../../bower_components/paper-input/paper-input.html'> <link rel="import" href='../../../bower_components/paper-input/paper-input.html'>
<link rel="import" href="../../../src/layouts/hass-subpage.html"> <link rel="import" href="../../../src/layouts/hass-subpage.html">
@ -42,78 +41,37 @@
</style> </style>
<hass-subpage title="Forgot Password"> <hass-subpage title="Forgot Password">
<div class='content'> <div class='content'>
<template is='dom-if' if='[[!_hasToken]]'>
<paper-card> <paper-card>
<div class='card-content'> <div class='card-content'>
<h1>Forgot Password</h1> <h1>Forgot your password?</h1>
<p> <p>
Enter your email address and we will send you a link to reset your password. Enter your email address and we will send you a link to reset your password.
</p> </p>
<div class='error' hidden$='[[!_error]]'>[[_error]]</div>
<paper-input <paper-input
autofocus autofocus
id='email'
label='E-mail' label='E-mail'
value='{{email}}' value='{{email}}'
type='email' type='email'
on-keydown='_keyDown' on-keydown='_keyDown'
error-message='Invalid email'
></paper-input> ></paper-input>
<div class='error' hidden$='[[!error]]'>[[error]]</div>
</div> </div>
<div class='card-actions'> <div class='card-actions'>
<ha-progress-button <ha-progress-button
on-click='_handleEmailPasswordReset' on-click='_handleEmailPasswordReset'
progress='[[_requestInProgress]]' progress='[[_requestInProgress]]'
>Send reset email</ha-progress-button> >Send reset email</ha-progress-button>
<button
class='link'
hidden='[[_requestInProgress]]'
on-click='_handleHaveToken'
>have a token?</button>
</div> </div>
</paper-card> </paper-card>
</template>
<template is='dom-if' if='[[_hasToken]]'>
<paper-card>
<div class='card-content'>
<h1>Confirm new password</h1>
<template is='dom-if' if='[[_showEmailInputForConfirmation]]'>
<paper-input
label='E-mail'
type='email'
value='{{email}}'
on-keydown='_keyDown'
></paper-input>
</template>
<paper-input
label='Confirmation code'
value='{{_confirmationCode}}'
on-keydown='_keyDown'
type='number'
></paper-input>
<paper-input
label='New password'
value='{{_newPassword}}'
on-keydown='_keyDown'
type='password'
></paper-input>
<div class='error' hidden$='[[!error]]'>[[error]]</div>
</div>
<div class='card-actions'>
<ha-progress-button
on-click='_handleConfirmPasswordReset'
progress='[[_requestInProgress]]'
>Reset Password</ha-progress-button>
</div>
</paper-card>
</template>
</div> </div>
</hass-subpage> </hass-subpage>
</template> </template>
</dom-module> </dom-module>
<script> <script>
class HaConfigCloudForgotPassword extends class HaConfigCloudForgotPassword extends window.hassMixins.EventsMixin(Polymer.Element) {
window.hassMixins.NavigateMixin(window.hassMixins.EventsMixin(Polymer.Element)) {
static get is() { return 'ha-config-cloud-forgot-password'; } static get is() { return 'ha-config-cloud-forgot-password'; }
static get properties() { static get properties() {
@ -122,110 +80,53 @@ class HaConfigCloudForgotPassword extends
email: { email: {
type: String, type: String,
notify: true, notify: true,
}, observer: '_emailChanged'
_hasToken: {
type: Boolean,
value: false,
},
_newPassword: {
type: String,
value: '',
},
_confirmationCode: {
type: String,
value: '',
},
_showEmailInputForConfirmation: {
type: Boolean,
value: false,
}, },
_requestInProgress: { _requestInProgress: {
type: Boolean, type: Boolean,
value: false, value: false,
}, },
_error: {
type: String,
value: '',
}
}; };
} }
static get observers() { _emailChanged() {
return [ this._error = '';
'_inputChanged(email, _newPassword)', this.$.email.invalid = false;
];
}
_inputChanged() {
this.error = false;
} }
_keyDown(ev) { _keyDown(ev) {
// validate on enter // validate on enter
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
if (this._hasToken) {
this._handleConfirmPasswordReset();
} else {
this._handleEmailPasswordReset(); this._handleEmailPasswordReset();
}
ev.preventDefault(); ev.preventDefault();
} }
} }
_handleEmailPasswordReset() { _handleEmailPasswordReset() {
if (!this.email) { if (!this.email || !this.email.includes('@')) {
this.error = 'Email is required.'; this.$.email.invalid = true;
} }
if (this.error) return; if (this.$.email.invalid) return;
this._requestInProgress = true; this._requestInProgress = true;
this.hass.callApi('post', 'cloud/forgot_password', { this.hass.callApi('post', 'cloud/forgot_password', {
email: this.email, email: this.email,
}).then(() => { }).then(() => {
this._hasToken = true;
this._requestInProgress = false; this._requestInProgress = false;
}, (err) => { this.fire('cloud-done', {
this._requestInProgress = false; flashMessage: 'Check your email for instructions on how to reset your password.'
this.error = err && err.body && err.body.message ?
err.body.message : 'Unknown error';
});
}
_handleHaveToken() {
this._error = '';
this._showEmailInputForConfirmation = true;
this._hasToken = true;
}
_handleConfirmPasswordReset() {
this.error = '';
if (!this.email) {
this.error += 'Email is required. ';
}
if (!this._confirmationCode) {
this.error += 'Confirmation code is required. ';
}
if (!this._newPassword) {
this.error += 'New password is required. ';
} else if (this._newPassword.length < 6) {
this.error += 'New password should be at least 6 characters.';
}
if (this.error) return;
this._requestInProgress = true;
this.hass.callApi('post', 'cloud/confirm_forgot_password', {
email: this.email,
confirmation_code: this._confirmationCode,
new_password: this._newPassword,
}).then(() => {
// eslint-disable-next-line
alert('Password reset successful! You can now login.');
this.navigate('config/cloud/login');
}, (err) => {
this._requestInProgress = false;
this.error = err && err.body && err.body.message ?
err.body.message : 'Unknown error';
}); });
}, err => this.setProperties({
_requestInProgress: false,
_error: err && err.body && err.body.message ?
err.body.message : 'Unknown error',
}));
} }
} }

View File

@ -1,7 +1,9 @@
<link rel="import" href='../../../bower_components/polymer/polymer-element.html'> <link rel="import" href='../../../bower_components/polymer/polymer-element.html'>
<link rel="import" href='../../../bower_components/paper-card/paper-card.html'> <link rel="import" href='../../../bower_components/paper-card/paper-card.html'>
<link rel="import" href='../../../bower_components/paper-button/paper-button.html'> <link rel="import" href='../../../bower_components/paper-button/paper-button.html'>
<link rel="import" href='../../../bower_components/paper-icon-button/paper-icon-button.html'>
<link rel="import" href='../../../bower_components/paper-input/paper-input.html'> <link rel="import" href='../../../bower_components/paper-input/paper-input.html'>
<link rel="import" href='../../../bower_components/paper-ripple/paper-ripple.html'>
<link rel="import" href="../../../src/layouts/hass-subpage.html"> <link rel="import" href="../../../src/layouts/hass-subpage.html">
<link rel="import" href="../../../src/util/hass-mixins.html"> <link rel="import" href="../../../src/util/hass-mixins.html">
@ -41,6 +43,15 @@
[hidden] { [hidden] {
display: none; display: none;
} }
.flash-msg {
padding-right: 44px;
}
.flash-msg paper-icon-button {
position: absolute;
top: 8px;
right: 8px;
color: var(--secondary-text-color);
}
</style> </style>
<hass-subpage title='Cloud Login'> <hass-subpage title='Cloud Login'>
<div class='content'> <div class='content'>
@ -53,23 +64,37 @@
<p><a href='https://home-assistant.io/components/cloud/' target='_blank'>Learn more</a></p> <p><a href='https://home-assistant.io/components/cloud/' target='_blank'>Learn more</a></p>
</span> </span>
<paper-card hidden$='[[!flashMessage]]'>
<div class='card-content flash-msg'>
[[flashMessage]]
<paper-icon-button
icon='mdi:close'
on-click='_dismissFlash'
>Dismiss</paper-icon-button>
<paper-ripple id='flashRipple' noink></paper-ripple>
</div>
</paper-card>
<paper-card> <paper-card>
<div class='card-content'> <div class='card-content'>
<h1>Sign In</h1> <h1>Sign In</h1>
<div class='error' hidden$='[[!_error]]'>[[_error]]</div>
<paper-input <paper-input
label='Email' label='Email'
id='emailInput' id='email'
type='email' type='email'
value='{{email}}' value='{{email}}'
on-keydown='_keyDown' on-keydown='_keyDown'
error-message='Invalid email'
></paper-input> ></paper-input>
<paper-input <paper-input
id='password'
label='Password' label='Password'
value='{{_password}}' value='{{_password}}'
type='password' type='password'
on-keydown='_keyDown' on-keydown='_keyDown'
error-message='Passwords are at least 8 characters'
></paper-input> ></paper-input>
<div class='error' hidden$='[[!error]]'>[[error]]</div>
</div> </div>
<div class='card-actions'> <div class='card-actions'>
<ha-progress-button <ha-progress-button
@ -120,6 +145,11 @@ class HaConfigCloudLogin extends
type: Boolean, type: Boolean,
value: false, value: false,
}, },
flashMessage: {
type: String,
notify: true,
},
_error: String,
}; };
} }
@ -129,8 +159,20 @@ class HaConfigCloudLogin extends
]; ];
} }
connectedCallback() {
super.connectedCallback();
if (this.flashMessage) {
// Wait for DOM to be drawn
requestAnimationFrame(() =>
requestAnimationFrame(() =>
this.$.flashRipple.simulatedRipple()));
}
}
_inputChanged() { _inputChanged() {
this.error = false; this.$.email.invalid = false;
this.$.password.invalid = false;
this._error = false;
} }
_keyDown(ev) { _keyDown(ev) {
@ -142,13 +184,24 @@ class HaConfigCloudLogin extends
} }
_handleLogin() { _handleLogin() {
if (!this.email) { let invalid = false;
this.error = 'Email is required.';
} else if (!this._password) { if (!this.email || !this.email.includes('@')) {
this.error = 'Password is required.'; this.$.email.invalid = true;
this.$.email.focus();
invalid = true;
} }
if (this.error) return; if (this._password.length < 8) {
this.$.password.invalid = true;
if (!invalid) {
invalid = true;
this.$.password.focus();
}
}
if (invalid) return;
this._requestInProgress = true; this._requestInProgress = true;
@ -157,33 +210,49 @@ class HaConfigCloudLogin extends
password: this._password, password: this._password,
}).then((account) => { }).then((account) => {
this.fire('ha-account-refreshed', { account: account }); this.fire('ha-account-refreshed', { account: account });
this.email = ''; this.setProperties({
this._password = ''; email: '',
_password: '',
});
}, (err) => { }, (err) => {
// Do this before setProperties because changing it clears errors.
this._password = ''; this._password = '';
this._requestInProgress = false;
if (!err || !err.body || !err.body.message) { const errCode = err && err.body && err.body.code;
this.error = 'Unknown error'; if (errCode === 'PasswordChangeRequired') {
return;
} else if (err.body.code === 'UserNotConfirmed') {
alert('You need to confirm your email before logging in.');
this.navigate('/config/cloud/register#confirm');
return;
} else if (err.body.code === 'PasswordChangeRequired') {
alert('You need to change your password before logging in.'); alert('You need to change your password before logging in.');
this.navigate('/config/cloud/forgot-password'); this.navigate('/config/cloud/forgot-password');
return;
} }
this.error = err.body.message;
const props = {
_requestInProgress: false,
_error: (err && err.body && err.body.message) ? err.body.message : 'Unknown error',
};
if (errCode === 'UserNotConfirmed') {
props._error = 'You need to confirm your email before logging in.';
}
this.setProperties(props);
this.$.email.focus();
}); });
} }
_handleRegister() { _handleRegister() {
this.flashMessage = '';
this.navigate('/config/cloud/register'); this.navigate('/config/cloud/register');
} }
_handleForgotPassword() { _handleForgotPassword() {
this.flashMessage = '';
this.navigate('/config/cloud/forgot-password'); this.navigate('/config/cloud/forgot-password');
} }
_dismissFlash() {
// give some time to let the ripple finish.
setTimeout(() => { this.flashMessage = ''; }, 200);
}
} }
customElements.define(HaConfigCloudLogin.is, HaConfigCloudLogin); customElements.define(HaConfigCloudLogin.is, HaConfigCloudLogin);

View File

@ -1,6 +1,5 @@
<link rel="import" href='../../../bower_components/polymer/polymer-element.html'> <link rel="import" href='../../../bower_components/polymer/polymer-element.html'>
<link rel="import" href='../../../bower_components/paper-card/paper-card.html'> <link rel="import" href='../../../bower_components/paper-card/paper-card.html'>
<link rel="import" href='../../../bower_components/paper-button/paper-button.html'>
<link rel="import" href='../../../bower_components/paper-input/paper-input.html'> <link rel="import" href='../../../bower_components/paper-input/paper-input.html'>
<link rel="import" href="../../../src/layouts/hass-subpage.html"> <link rel="import" href="../../../src/layouts/hass-subpage.html">
@ -57,7 +56,6 @@
</p> </p>
</span> </span>
<template is='dom-if' if='[[!_hasConfirmationCode]]'>
<paper-card> <paper-card>
<div class='card-content'> <div class='card-content'>
<div class='header'> <div class='header'>
@ -66,16 +64,20 @@
</div> </div>
<paper-input <paper-input
autofocus autofocus
id='email'
label='Email address' label='Email address'
type='email' type='email'
value='{{email}}' value='{{email}}'
on-keydown='_keyDown' on-keydown='_keyDown'
error-message='Invalid email'
></paper-input> ></paper-input>
<paper-input <paper-input
id='password'
label='Password' label='Password'
value='{{_password}}' value='{{_password}}'
type='password' type='password'
on-keydown='_keyDown' on-keydown='_keyDown'
error-message='Your password needs to be at least 8 characters'
></paper-input> ></paper-input>
</div> </div>
<div class='card-actions'> <div class='card-actions'>
@ -86,48 +88,10 @@
<button <button
class='link' class='link'
hidden='[[_requestInProgress]]' hidden='[[_requestInProgress]]'
on-click='_handleShowVerifyAccount'
>have confirmation code?</button>
</div>
</paper-card>
</template>
<template is='dom-if' if='[[_hasConfirmationCode]]'>
<paper-card>
<div class='card-content'>
<div class='header'>
<h1>Verify email</h1>
<div class='error' hidden$='[[!_error]]'>[[_error]]</div>
</div>
<p>
Check your email address, we've emailed you a verification code to activate your account.
</p>
<template is='dom-if' if='[[_showEmailInputForConfirmation]]'>
<paper-input
label='Email address'
type='email'
value='{{email}}'
on-keydown='_keyDown'
></paper-input>
</template>
<paper-input
label='Confirmation code'
value='{{_confirmationCode}}'
on-keydown='_keyDown'
type='number'
></paper-input>
</div>
<div class='card-actions'>
<ha-progress-button
on-click='_handleVerifyEmail'
progress='[[_requestInProgress]]'
>Verify Email</ha-progress-button>
<paper-button
on-click='_handleResendVerifyEmail' on-click='_handleResendVerifyEmail'
>Resend Verify Email</paper-button> >Resend confirmation email</button>
</div> </div>
</paper-card> </paper-card>
</template>
</ha-config-section> </ha-config-section>
</div> </div>
</hass-subpage> </hass-subpage>
@ -135,8 +99,7 @@
</dom-module> </dom-module>
<script> <script>
class HaConfigCloudRegister extends class HaConfigCloudRegister extends window.hassMixins.EventsMixin(Polymer.Element) {
window.hassMixins.NavigateMixin(window.hassMixins.EventsMixin(Polymer.Element)) {
static get is() { return 'ha-config-cloud-register'; } static get is() { return 'ha-config-cloud-register'; }
static get properties() { static get properties() {
@ -156,14 +119,10 @@ class HaConfigCloudRegister extends
type: String, type: String,
value: '', value: '',
}, },
_showEmailInputForConfirmation: { _error: {
type: Boolean, type: String,
value: false, value: '',
}, },
_hasConfirmationCode: {
type: Boolean,
value: () => document.location.hash === '#confirm'
}
}; };
} }
@ -174,93 +133,75 @@ class HaConfigCloudRegister extends
} }
_inputChanged() { _inputChanged() {
this._error = false; this._error = '';
this.$.email.invalid = false;
this.$.password.invalid = false;
} }
_keyDown(ev) { _keyDown(ev) {
// validate on enter // validate on enter
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
if (this._hasConfirmationCode) {
this._handleVerifyEmail();
} else {
this._handleRegister(); this._handleRegister();
}
ev.preventDefault(); ev.preventDefault();
} }
} }
_handleRegister() { _handleRegister() {
if (!this.email) { let invalid = false;
this._error = 'Email is required.';
} else if (!this._password) { if (!this.email || !this.email.includes('@')) {
this._error = 'Password is required.'; this.$.email.invalid = true;
this.$.email.focus();
invalid = true;
} }
if (this._error) return; if (this._password.length < 8) {
this.$.password.invalid = true;
if (!invalid) {
invalid = true;
this.$.password.focus();
}
}
if (invalid) return;
this._requestInProgress = true; this._requestInProgress = true;
this.hass.callApi('post', 'cloud/register', { this.hass.callApi('post', 'cloud/register', {
email: this.email, email: this.email,
password: this._password, password: this._password,
}).then(() => { }).then(() => this._verificationEmailSent(), (err) => {
this._requestInProgress = false; // Do this before setProperties because changing it clears errors.
this._hasConfirmationCode = true;
}, (err) => {
this._password = ''; this._password = '';
this._requestInProgress = false;
this._error = err && err.body && err.body.message ? this.setProperties({
err.body.message : 'Unknown error'; _requestInProgress: false,
_error: err && err.body && err.body.message ? err.body.message : 'Unknown error'
}); });
}
_handleShowVerifyAccount() {
this._error = '';
this._showEmailInputForConfirmation = true;
this._hasConfirmationCode = true;
}
_handleVerifyEmail() {
if (!this.email) {
this._error = 'Email is required.';
} else if (!this._confirmationCode) {
this._error = 'Confirmation code is required.';
}
if (this._error) return;
this._requestInProgress = true;
this.hass.callApi('post', 'cloud/confirm_register', {
email: this.email,
confirmation_code: this._confirmationCode,
}).then(() => {
// eslint-disable-next-line
alert('Confirmation successful. You can now login.');
this.navigate('config/cloud/login');
}, (err) => {
this._confirmationCode = '';
this._error = err && err.body && err.body.message ?
err.body.message : 'Unknown error';
this._requestInProgress = false;
}); });
} }
_handleResendVerifyEmail() { _handleResendVerifyEmail() {
if (!this.email) { if (!this.email) {
this._error = 'Email is required.'; this.$.email.invalid = true;
return;
} }
if (this._error) return;
this.hass.callApi('post', 'cloud/resend_confirm', { this.hass.callApi('post', 'cloud/resend_confirm', {
email: this.email, email: this.email,
}).then(() => { }).then(() => this._verificationEmailSent(), err => this.setProperties({
// eslint-disable-next-line _error: err && err.body && err.body.message ? err.body.message : 'Unknown error'
alert('Email resend.'); }));
}, (err) => { }
this._error = err && err.body && err.body.message ?
err.body.message : 'Unknown error'; _verificationEmailSent() {
this.setProperties({
_requestInProgress: false,
_password: '',
});
this.fire('cloud-done', {
flashMessage: 'Account created! Check your email for instructions on how to activate your account.'
}); });
} }
} }

View File

@ -32,6 +32,7 @@
hass='[[hass]]' hass='[[hass]]'
is-wide='[[isWide]]' is-wide='[[isWide]]'
email='{{_loginEmail}}' email='{{_loginEmail}}'
flash-message='{{_flashMessage}}'
></ha-config-cloud-login> ></ha-config-cloud-login>
</template> </template>
@ -80,6 +81,10 @@
account: { account: {
type: Object, type: Object,
}, },
_flashMessage: {
type: String,
value: '',
},
route: Object, route: Object,
@ -95,6 +100,14 @@
]; ];
} }
ready() {
super.ready();
this.addEventListener('cloud-done', (ev) => {
this._flashMessage = ev.detail.flashMessage;
this.navigate('/config/cloud/login');
});
}
_checkRoute(route) { _checkRoute(route) {
if (!route || route.path.substr(0, 6) !== '/cloud') return; if (!route || route.path.substr(0, 6) !== '/cloud') return;