From 0173aa0768c64629013afe891dedb4578d0c57a8 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 17 Sep 2020 10:24:22 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20change=20password?= =?UTF-8?q?=20route=20and=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../password/change-password/index.hbs | 50 +++++++++++++ .../account/password/change-password/index.js | 73 +++++++++++++++++++ .../account/password/set-password/index.js | 2 +- ui/core/app/router.js | 4 + ui/core/app/routes/account.js | 19 +++++ ui/core/app/routes/account/change-password.js | 60 +++++++++++++++ ui/core/app/templates/account.hbs | 1 + .../app/templates/account/change-password.hbs | 5 ++ ui/core/app/templates/application.hbs | 3 + ui/core/mirage/config.js | 4 + ui/core/mirage/route-handlers/auth.js | 1 + .../account/password/change-password-test.js | 26 +++++++ ui/core/tests/unit/routes/account-test.js | 11 +++ .../routes/account/change-password-test.js | 11 +++ ui/core/translations/en-us.yaml | 3 + 15 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 ui/core/app/components/form/account/password/change-password/index.hbs create mode 100644 ui/core/app/components/form/account/password/change-password/index.js create mode 100644 ui/core/app/routes/account.js create mode 100644 ui/core/app/routes/account/change-password.js create mode 100644 ui/core/app/templates/account.hbs create mode 100644 ui/core/app/templates/account/change-password.hbs create mode 100644 ui/core/tests/integration/components/form/account/password/change-password-test.js create mode 100644 ui/core/tests/unit/routes/account-test.js create mode 100644 ui/core/tests/unit/routes/account/change-password-test.js diff --git a/ui/core/app/components/form/account/password/change-password/index.hbs b/ui/core/app/components/form/account/password/change-password/index.hbs new file mode 100644 index 000000000..a0d85390d --- /dev/null +++ b/ui/core/app/components/form/account/password/change-password/index.hbs @@ -0,0 +1,50 @@ + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + + diff --git a/ui/core/app/components/form/account/password/change-password/index.js b/ui/core/app/components/form/account/password/change-password/index.js new file mode 100644 index 000000000..0375feaea --- /dev/null +++ b/ui/core/app/components/form/account/password/change-password/index.js @@ -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(); + } + +} diff --git a/ui/core/app/components/form/account/password/set-password/index.js b/ui/core/app/components/form/account/password/set-password/index.js index 124f783b9..5752ea980 100644 --- a/ui/core/app/components/form/account/password/set-password/index.js +++ b/ui/core/app/components/form/account/password/set-password/index.js @@ -18,7 +18,7 @@ export default class FormAccountPasswordSetPasswordIndexComponent extends Compon */ @computed('password') get cannotSave() { - return !(this.password?.length > 0); + return !this.password; } // =methods diff --git a/ui/core/app/router.js b/ui/core/app/router.js index dc30cf017..204ced768 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -91,4 +91,8 @@ Router.map(function () { }); }); }); + + this.route('account', function() { + this.route('change-password'); + }); }); diff --git a/ui/core/app/routes/account.js b/ui/core/app/routes/account.js new file mode 100644 index 000000000..34e9a9e11 --- /dev/null +++ b/ui/core/app/routes/account.js @@ -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'); + } + +} diff --git a/ui/core/app/routes/account/change-password.js b/ui/core/app/routes/account/change-password.js new file mode 100644 index 000000000..858230e9c --- /dev/null +++ b/ui/core/app/routes/account/change-password.js @@ -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'); + } + +} diff --git a/ui/core/app/templates/account.hbs b/ui/core/app/templates/account.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/ui/core/app/templates/account.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/core/app/templates/account/change-password.hbs b/ui/core/app/templates/account/change-password.hbs new file mode 100644 index 000000000..ae761d18a --- /dev/null +++ b/ui/core/app/templates/account/change-password.hbs @@ -0,0 +1,5 @@ + diff --git a/ui/core/app/templates/application.hbs b/ui/core/app/templates/application.hbs index 2422a624e..2f295e9b4 100644 --- a/ui/core/app/templates/application.hbs +++ b/ui/core/app/templates/application.hbs @@ -14,6 +14,9 @@ @iconOnly={{true}} @icon="user-square-fill" as |dropdown| > + + {{t 'actions.change-password'}} + diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 7f2ea5ba0..e7044298f 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -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); }); diff --git a/ui/core/mirage/route-handlers/auth.js b/ui/core/mirage/route-handlers/auth.js index a0e409646..96813a7d5 100644 --- a/ui/core/mirage/route-handlers/auth.js +++ b/ui/core/mirage/route-handlers/auth.js @@ -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: '', diff --git a/ui/core/tests/integration/components/form/account/password/change-password-test.js b/ui/core/tests/integration/components/form/account/password/change-password-test.js new file mode 100644 index 000000000..54a6f61dd --- /dev/null +++ b/ui/core/tests/integration/components/form/account/password/change-password-test.js @@ -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` + `); + + assert.ok(find('form')); + }); + } +); diff --git a/ui/core/tests/unit/routes/account-test.js b/ui/core/tests/unit/routes/account-test.js new file mode 100644 index 000000000..ab640d01d --- /dev/null +++ b/ui/core/tests/unit/routes/account-test.js @@ -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); + }); +}); diff --git a/ui/core/tests/unit/routes/account/change-password-test.js b/ui/core/tests/unit/routes/account/change-password-test.js new file mode 100644 index 000000000..ee6856d35 --- /dev/null +++ b/ui/core/tests/unit/routes/account/change-password-test.js @@ -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); + }); +}); diff --git a/ui/core/translations/en-us.yaml b/ui/core/translations/en-us.yaml index 301ac40a2..e458c2b50 100644 --- a/ui/core/translations/en-us.yaml +++ b/ui/core/translations/en-us.yaml @@ -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