Merge pull request #195 from hashicorp/feature-change-password-ui

feat: 🎸 add change password route and component
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2020-09-17 12:08:01 -04:00
commit bbb4426533
15 files changed
+272 -1

No files matched your search

@@ -0,0 +1,50 @@
<Rose::Form
@onSubmit={{fn this.submit @submit}}
@cancel={{fn this.cancel @cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
as |form|
>
<form.input
@name="currentPassword"
@type="password"
@value={{this.currentPassword}}
@label={{t "form.label.current_password"}}
@error={{@model.errors.name}}
readonly={{false}}
autocomplete="current-password"
as |field|>
{{#if @model.errors.name}}
<field.errors as |errors|>
{{#each @model.errors.name as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
<form.input
@name="newPassword"
@type="password"
@value={{this.newPassword}}
@label={{t "form.label.new_password"}}
@error={{@model.errors.name}}
readonly={{false}}
autocomplete="new-password"
as |field|>
{{#if @model.errors.name}}
<field.errors as |errors|>
{{#each @model.errors.name as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
<form.actions
@disabled={{this.cannotSave}}
@submitText={{t "actions.save"}}
@cancelText={{t "actions.cancel"}} />
</Rose::Form>
@@ -0,0 +1,73 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { computed } from '@ember/object';
import { action } from '@ember/object';
import { next } from '@ember/runloop';
export default class FormAccountPasswordChangePasswordIndexComponent extends Component {
// =properties
/**
* @type {string}
*/
@tracked currentPassword;
/**
* @type {string}
*/
@tracked newPassword;
/**
* @type {boolean}
*/
@computed('currentPassword', 'newPassword')
get canSave() {
return this.currentPassword && this.newPassword;
}
/**
* @type {boolean}
*/
@computed('canSave')
get cannotSave() {
return !this.canSave;
}
// =methods
/**
* Unsets the password fields.
*/
resetPasswords() {
this.currentPassword = null;
this.newPassword = null;
}
// =actions
/**
* Call passed submit function with passwords.
* Unset passwords before callack.
* @param {function} fn
*/
@action
submit(fn) {
const currentPassword = this.currentPassword;
const newPassword = this.newPassword;
next(() => this.resetPasswords());
fn(currentPassword, newPassword);
}
/**
* Call passed cancel function.
* Unset passwords before callback.
* @param {function} fn
*/
@action
cancel(fn) {
this.resetPasswords();
fn();
}
}
@@ -18,7 +18,7 @@ export default class FormAccountPasswordSetPasswordIndexComponent extends Compon
*/
@computed('password')
get cannotSave() {
return !(this.password?.length > 0);
return !this.password;
}
// =methods
+4
View File
@@ -91,4 +91,8 @@ Router.map(function () {
});
});
});
this.route('account', function() {
this.route('change-password');
});
});
+19
View File
@@ -0,0 +1,19 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class AccountRoute extends Route {
// =services
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
}
@@ -0,0 +1,60 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
/**
* The change password flow is associated only with password-type auth methods.
*/
export default class AccountChangePasswordRoute extends Route {
// =services
@service session;
@service intl;
@service notify;
// =methods
/**
* Loads the account associated with the auth token.
* @return {?AccountModel}
*/
model() {
const account_id = this.session?.data?.authenticated?.account_id;
return this.store.findRecord('account', account_id);
}
// =actions
/**
* Changes the password on the account and then redirects to the index route.
* @param {AccountModel} account
* @param {string} currentPassword
* @param {string} newPassword
*/
@action
async changePassword(account, currentPassword, newPassword) {
try {
await account.changePassword(currentPassword, newPassword);
// Transition to index after success. We ignore errors in the transition,
// since the index route is responsible for additional
// redirects depending on the authentication context. These additional
// redirects may abort this initial transition, resulting in a
// TransitionAborted error, which we do not want to show to the user.
await this.replaceWith('index').catch(() => {});
this.notify.success(this.intl.t('notify.save-success'));
} catch (error) {
// TODO: replace with translated strings
this.notify.error(error.message, { closeAfter: null });
}
}
/**
* On cancel, redirects to index route for further processing.
*/
@action
cancel() {
this.replaceWith('index');
}
}
+1
View File
@@ -0,0 +1 @@
{{outlet}}
@@ -0,0 +1,5 @@
<Form::Account::Password::ChangePassword
@model={{@model}}
@submit={{route-action "changePassword" @model}}
@cancel={{route-action "cancel" @model}}
/>
+3
View File
@@ -14,6 +14,9 @@
@iconOnly={{true}}
@icon="user-square-fill" as |dropdown|
>
<dropdown.link @route="account.change-password">
{{t 'actions.change-password'}}
</dropdown.link>
<dropdown.button
{{on "click" (route-action "invalidateSession")}}
>
+4
View File
@@ -70,6 +70,10 @@ export default function() {
if (method === 'set-password') {
updatedAttrs.attributes.password = attrs.password;
}
// Set new password
if (method === 'change-password') {
updatedAttrs.attributes.password = attrs.new_password;
}
return account.update(updatedAttrs);
});
+1
View File
@@ -37,6 +37,7 @@ export function authHandler({ scopes, authMethods }, request) {
scope: scopeAttrs,
id: 'token123',
token: 'thetokenstring',
account_id: '1',
user_id: 'user123',
auth_method_id: 'authmethod123',
created_time: '',
@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module(
'Integration | Component | form/account/password/change-password',
function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
this.submit = () => {};
this.cancel = () => {};
this.model = { type: 'password' };
await render(hbs`
<Form::Account::Password::ChangePassword
@model={{this.model}}
@submit={{this.submit}}
@cancel={{this.cancel}}
/>`);
assert.ok(find('form'));
});
}
);
+11
View File
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | account', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:account');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | account/change-password', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:account/change-password');
assert.ok(route);
});
});
+3
View File
@@ -30,6 +30,7 @@ actions:
add-hosts: Add Hosts
add-account: Create Account
set-password: Set Password
change-password: Change Password
auth:
titles:
no-scopes: No Scopes Available
@@ -154,6 +155,8 @@ form:
protocol: Protocol
actions: Actions
default_port: Default Port
current_password: Current Password
new_password: New Password
errors:
titles:
unauthenticated-error: You are not signed in