From 02337a391cd3f8119fe9fbb04d351553be932571 Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:21:52 +0200 Subject: [PATCH 01/10] feat(api): accept for /api/password-reset-demands a simpler payload as well as the previous (now deprecated) ember-data-centric value --- .../password/password.controller.js | 2 +- .../application/password/password.route.js | 4 + .../password/password.route.test.js | 99 +++++++++++++------ .../password/password.controller.test.js | 7 +- .../password/password.controller.test.js | 6 +- 5 files changed, 77 insertions(+), 41 deletions(-) diff --git a/api/src/identity-access-management/application/password/password.controller.js b/api/src/identity-access-management/application/password/password.controller.js index 2d588f3bc4a..de7b11c3798 100644 --- a/api/src/identity-access-management/application/password/password.controller.js +++ b/api/src/identity-access-management/application/password/password.controller.js @@ -9,7 +9,7 @@ const checkResetDemand = async function (request, h, dependencies = { userSerial return dependencies.userSerializer.serialize(user); }; const createResetPasswordDemand = async function (request, h, dependencies = { resetPasswordSerializer }) { - const { email } = request.payload.data.attributes; + const email = request.payload.email; const locale = extractLocaleFromRequest(request); const resetPasswordDemand = await usecases.createResetPasswordDemand({ diff --git a/api/src/identity-access-management/application/password/password.route.js b/api/src/identity-access-management/application/password/password.route.js index c25664d94cd..031f98e6401 100644 --- a/api/src/identity-access-management/application/password/password.route.js +++ b/api/src/identity-access-management/application/password/password.route.js @@ -11,6 +11,10 @@ export const passwordRoutes = [ handler: (request, h) => passwordController.createResetPasswordDemand(request, h), validate: { payload: Joi.object({ + email: Joi.when('data.attributes.email', { + then: Joi.string().email().default(Joi.ref('data.attributes.email')), + otherwise: Joi.string().email().required(), + }), data: { attributes: { email: Joi.string().email().required(), diff --git a/api/tests/identity-access-management/acceptance/application/password/password.route.test.js b/api/tests/identity-access-management/acceptance/application/password/password.route.test.js index 700b61b7a94..438d7bbcf03 100644 --- a/api/tests/identity-access-management/acceptance/application/password/password.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/password/password.route.test.js @@ -13,44 +13,85 @@ describe('Acceptance | Identity Access Management | Application | Route | passwo describe('POST /api/password-reset-demands', function () { let options; - beforeEach(async function () { - options = { - method: 'POST', - url: '/api/password-reset-demands', - payload: { - data: { - attributes: { email }, - }, - }, - }; + context('with simple payload', function () { + beforeEach(async function () { + options = { + method: 'POST', + url: '/api/password-reset-demands', + payload: { email }, + }; - config.mailing.enabled = false; + config.mailing.enabled = false; - const userId = databaseBuilder.factory.buildUser({ email }).id; - databaseBuilder.factory.buildAuthenticationMethod.withPixAsIdentityProviderAndHashedPassword({ userId }); - await databaseBuilder.commit(); - }); + const userId = databaseBuilder.factory.buildUser({ email }).id; + databaseBuilder.factory.buildAuthenticationMethod.withPixAsIdentityProviderAndHashedPassword({ userId }); + await databaseBuilder.commit(); + }); + + context('when given email doesn’t exist', function () { + it('replies with 404', async function () { + // given + options.payload.email = 'unknown@example.net'; - context('when email provided is unknown', function () { - it('replies with 404', async function () { - // given - options.payload.data.attributes.email = 'unknown@example.net'; + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(404); + }); + }); - // when - const response = await server.inject(options); + context('when given email exists', function () { + it('replies with 201', async function () { + // when + const response = await server.inject(options); - // then - expect(response.statusCode).to.equal(404); + // then + expect(response.statusCode).to.equal(201); + }); }); }); - context('when existing email is provided', function () { - it('replies with 201', async function () { - // when - const response = await server.inject(options); + context('with deprecated ember-data-centric payload', function () { + beforeEach(async function () { + options = { + method: 'POST', + url: '/api/password-reset-demands', + payload: { + data: { + attributes: { email }, + }, + }, + }; + + config.mailing.enabled = false; + + const userId = databaseBuilder.factory.buildUser({ email }).id; + databaseBuilder.factory.buildAuthenticationMethod.withPixAsIdentityProviderAndHashedPassword({ userId }); + await databaseBuilder.commit(); + }); + + context('when given email doesn’t exist', function () { + it('replies with 404', async function () { + // given + options.payload.data.attributes.email = 'unknown@example.net'; + + // when + const response = await server.inject(options); + + // then + expect(response.statusCode).to.equal(404); + }); + }); + + context('when given email exists', function () { + it('replies with 201', async function () { + // when + const response = await server.inject(options); - // then - expect(response.statusCode).to.equal(201); + // then + expect(response.statusCode).to.equal(201); + }); }); }); }); diff --git a/api/tests/identity-access-management/integration/application/password/password.controller.test.js b/api/tests/identity-access-management/integration/application/password/password.controller.test.js index 0ec0058d9cd..f79a3931ae2 100644 --- a/api/tests/identity-access-management/integration/application/password/password.controller.test.js +++ b/api/tests/identity-access-management/integration/application/password/password.controller.test.js @@ -20,12 +20,7 @@ describe('Integration | Identity Access Management | Application | Controller | const headers = { 'accept-language': 'fr', }; - const payload = { - data: { - type: 'password-reset-demands', - attributes: { email }, - }, - }; + const payload = { email }; it('returns a 201 HTTP status code with a response', async function () { // given diff --git a/api/tests/identity-access-management/unit/application/password/password.controller.test.js b/api/tests/identity-access-management/unit/application/password/password.controller.test.js index 5f0b637ece2..211b4b15e39 100644 --- a/api/tests/identity-access-management/unit/application/password/password.controller.test.js +++ b/api/tests/identity-access-management/unit/application/password/password.controller.test.js @@ -41,11 +41,7 @@ describe('Unit | Identity Access Management | Application | Controller | passwor headers: { 'accept-language': locale, }, - payload: { - data: { - attributes: { email }, - }, - }, + payload: { email }, }; const resetPasswordDemand = { id: 1, From dfd870d37f2fcd5089a573d0894cb30f72fac8b5 Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:07:02 +0200 Subject: [PATCH 02/10] feat(mon-pix): add new component authentication/password-reset-demand --- .../password-reset-demand-form.gjs | 119 +++++++++++++ .../authentication/password-reset-demand.scss | 41 +++++ mon-pix/app/styles/app.scss | 1 + .../password-reset-demand-form-test.gjs | 159 ++++++++++++++++++ mon-pix/translations/en.json | 12 ++ mon-pix/translations/es.json | 12 ++ mon-pix/translations/fr.json | 12 ++ mon-pix/translations/nl.json | 12 ++ 8 files changed, 368 insertions(+) create mode 100644 mon-pix/app/components/authentication/password-reset-demand-form.gjs create mode 100644 mon-pix/app/components/authentication/password-reset-demand.scss create mode 100644 mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs diff --git a/mon-pix/app/components/authentication/password-reset-demand-form.gjs b/mon-pix/app/components/authentication/password-reset-demand-form.gjs new file mode 100644 index 00000000000..cb1c02e3535 --- /dev/null +++ b/mon-pix/app/components/authentication/password-reset-demand-form.gjs @@ -0,0 +1,119 @@ +import PixButton from '@1024pix/pix-ui/components/pix-button'; +import PixButtonLink from '@1024pix/pix-ui/components/pix-button-link'; +import PixInput from '@1024pix/pix-ui/components/pix-input'; +import PixMessage from '@1024pix/pix-ui/components/pix-message'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { t } from 'ember-intl'; +import ENV from 'mon-pix/config/environment'; + +import isEmailValid from '../../utils/email-validator.js'; + +export default class PasswordResetDemandForm extends Component { + @service intl; + + @tracked isLoading = false; + @tracked errorMessage; + @tracked emailInputvalidationStatus; + @tracked emailInputvalidationErrorMessage; + + email; + + @action + handleEmailChange(event) { + this.email = event.target.value; + this.emailInputvalidationStatus = isEmailValid(this.email) ? 'success' : 'error'; + this.emailInputvalidationErrorMessage = this.intl.t( + 'components.authentication.password-reset-demand-form.invalid-email', + ); + } + + @action + async handlePasswordResetDemand(event) { + if (event) event.preventDefault(); + + this.errorMessage = null; + + const email = this.email.trim(); + if (!email || this.emailInputvalidationStatus === 'error') { + return; + } + + try { + this.isLoading = true; + const response = await window.fetch(`${ENV.APP.API_HOST}/api/password-reset-demands`, { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + method: 'POST', + body: JSON.stringify({ email }), + }); + if (response.status == 404) { + this.errorMessage = this.intl.t('pages.password-reset-demand.error.message'); + } else if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + } catch (error) { + this.errorMessage = this.intl.t('common.api-error-messages.internal-server-error'); + } finally { + this.isLoading = false; + } + } + + +} diff --git a/mon-pix/app/components/authentication/password-reset-demand.scss b/mon-pix/app/components/authentication/password-reset-demand.scss new file mode 100644 index 00000000000..581c7d5de1a --- /dev/null +++ b/mon-pix/app/components/authentication/password-reset-demand.scss @@ -0,0 +1,41 @@ +.authentication-password-reset-demand-form { + input { + padding: var(--pix-spacing-3x); + } + + &__rule { + @extend %pix-body-xs; + + color: var(--pix-neutral-500); + } + + &__error { + margin-top: var(--pix-spacing-4x); + } + + &__input-block { + margin-top: var(--pix-spacing-4x); + + .pix-input { + width: 100%; + } + } + + &__button { + width: 100%; + margin-top: var(--pix-spacing-4x); + } + + &__help { + @extend %pix-body-s; + + margin-top: var(--pix-spacing-4x); + color: var(--pix-neutral-70); + text-align: center; + } + + &__help-contact-us-link { + display: inline; + padding: 0; + } +} \ No newline at end of file diff --git a/mon-pix/app/styles/app.scss b/mon-pix/app/styles/app.scss index f7fa0e40523..5cb403cc76a 100644 --- a/mon-pix/app/styles/app.scss +++ b/mon-pix/app/styles/app.scss @@ -138,6 +138,7 @@ of an adaptative/mobile-first approach — refactoring is welcome here */ @import 'authentication-layout'; @import 'authentication/oidc-provider-selector'; @import 'authentication/other-authentication-providers'; +@import 'authentication/password-reset-demand'; @import 'authentication/signin-form'; @import 'authentication/signup-form'; @import 'authentication/sso-selection-form'; diff --git a/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs b/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs new file mode 100644 index 00000000000..8545e4a8802 --- /dev/null +++ b/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs @@ -0,0 +1,159 @@ +import { fillByLabel, render } from '@1024pix/ember-testing-library'; +import { click } from '@ember/test-helpers'; +import { t } from 'ember-intl/test-support'; +import PasswordResetDemandForm from 'mon-pix/components/authentication/password-reset-demand-form'; +import { module, test } from 'qunit'; +import sinon from 'sinon'; + +import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering'; + +module('Integration | Component | Authentication | password-reset-demand-form', function (hooks) { + setupIntlRenderingTest(hooks); + + test('it displays a contact us link', async function (assert) { + // given + const screen = await render(); + + // then + const link = await screen.queryByRole('link', { + name: t('components.authentication.password-reset-demand-form.contact-us-link.link-text'), + }); + assert.dom(link).exists(); + assert.strictEqual(link.getAttribute('href'), 'https://pix.fr/support'); + }); + + module('email input validation', function () { + module('when the email input is valid', function () { + test('it doesn’t display any error message', async function (assert) { + // given + const validEmail = 'someone@example.net'; + const screen = await render(); + + // when + await fillByLabel(t('pages.password-reset-demand.fields.email.label'), validEmail); + + // then + assert.dom(screen.queryByRole('alert')).doesNotExist(); + }); + }); + + module('when the email input is invalid', function () { + test('it displays an "invalid email" error message', async function (assert) { + // given + const invalidEmail = 'invalid email'; + const screen = await render(); + + // when + await fillByLabel(t('pages.password-reset-demand.fields.email.label'), invalidEmail); + + // then + assert + .dom(screen.queryByText(t('components.authentication.password-reset-demand-form.invalid-email'))) + .exists(); + }); + }); + }); + + module('password-reset-demand sending', function (hooks) { + hooks.beforeEach(function () { + sinon.stub(window, 'fetch'); + }); + + hooks.afterEach(function () { + sinon.restore(); + }); + + module('when the password-reset-demand is successful', function () { + test('it doesn’t display any error message', async function (assert) { + // given + window.fetch.resolves( + fetchMock({ + status: 201, + }), + ); + + const email = 'someone@example.net'; + const screen = await render(); + + // when + await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await click( + screen.getByRole('button', { + name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), + }), + ); + + // then + assert.dom(screen.queryByRole('alert')).doesNotExist(); + }); + }); + + module('when there is no corresponding user account', function () { + test('it displays an "account not found" error message', async function (assert) { + // given + window.fetch.resolves( + fetchMock({ + status: 404, + body: { + errors: [{ title: 'Not Found' }], + }, + }), + ); + + const email = 'someone@example.net'; + const screen = await render(); + + // when + await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await click( + screen.getByRole('button', { + name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), + }), + ); + + // then + // The following doesn’t work because of a PixUi span inside the role element + //assert.dom(screen.queryByRole('alert', { name: t('pages.password-reset-demand.error.message') })).exists(); + assert.dom(screen.queryByText(t('pages.password-reset-demand.error.message'))).exists(); + }); + }); + + module('when there is an unknown error', function () { + test('it displays an "unknown error" error message', async function (assert) { + // given + window.fetch.resolves( + fetchMock({ + status: 500, + }), + ); + + const email = 'someone@example.net'; + const screen = await render(); + + // when + await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await click( + screen.getByRole('button', { + name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), + }), + ); + + // then + // The following doesn’t work because of a PixUi span inside the role element + //assert + // .dom(screen.queryByRole('alert', { name: t('common.api-error-messages.internal-server-error') })) + // .exists(); + assert.dom(screen.queryByText(t('common.api-error-messages.internal-server-error'))).exists(); + }); + }); + }); +}); + +function fetchMock({ body, status }) { + return new window.Response(JSON.stringify(body), { + status, + headers: { + 'Content-type': 'application/json', + }, + }); +} diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index f9a941a8bf8..c4ccc6b0835 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -176,6 +176,18 @@ }, "legend": "Information required for sign up." } + }, + "password-reset-demand-form": { + "invalid-email": "Your email address is invalid.", + "rule": "All fields are required.", + "no-email-question": "No email address?", + "contact-us-link": { + "link-text": "Contact us", + "link-url": "https://pix.org/en/support" + }, + "actions": { + "receive-reset-button": "Receive a reset link" + } } }, "invited": { diff --git a/mon-pix/translations/es.json b/mon-pix/translations/es.json index 7c93d770446..3884152693f 100644 --- a/mon-pix/translations/es.json +++ b/mon-pix/translations/es.json @@ -169,6 +169,18 @@ }, "legend": "Información necesaria para la inscripción." } + }, + "password-reset-demand-form": { + "invalid-email": "Your email address is invalid.", + "rule": "All fields are required.", + "no-email-question": "No email address?", + "contact-us-link": { + "link-text": "Contact us", + "link-url": "https://pix.org/en/support" + }, + "actions": { + "receive-reset-button": "Receive a reset link" + } } }, "invited": { diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index efea1b0a460..d3af40e83fe 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -176,6 +176,18 @@ }, "legend": "Information nécessaire pour l'inscription." } + }, + "password-reset-demand-form": { + "invalid-email": "Votre adresse e-mail n’est pas valide.", + "rule": "Tous les champs sont obligatoires.", + "no-email-question": "Pas d’adresse e-mail renseignée ?", + "contact-us-link": { + "link-text": "Contactez-nous", + "link-url": "https://pix.fr/support" + }, + "actions": { + "receive-reset-button": "Recevoir un lien de réinitialisation" + } } }, "invited": { diff --git a/mon-pix/translations/nl.json b/mon-pix/translations/nl.json index 67abeebc12f..03385089ddd 100644 --- a/mon-pix/translations/nl.json +++ b/mon-pix/translations/nl.json @@ -169,6 +169,18 @@ }, "legend": "Vereiste informatie voor registratie." } + }, + "password-reset-demand-form": { + "invalid-email": "Your email address is invalid.", + "rule": "All fields are required.", + "no-email-question": "No email address?", + "contact-us-link": { + "link-text": "Contact us", + "link-url": "https://pix.org/nl-be/support" + }, + "actions": { + "receive-reset-button": "Receive a reset link" + } } }, "invited": { From f98a9a1914e410f07b3cd0659e2d54270edaa5f6 Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:58:16 +0200 Subject: [PATCH 03/10] feat(mon-pix): add isNewAuthenticationDesignEnabled on password-reset-demand.hbs --- .../app/controllers/password-reset-demand.js | 10 +++ .../app/templates/password-reset-demand.hbs | 10 ++- .../password-reset-demand-form-test.js | 81 ++++++++++--------- 3 files changed, 62 insertions(+), 39 deletions(-) create mode 100644 mon-pix/app/controllers/password-reset-demand.js diff --git a/mon-pix/app/controllers/password-reset-demand.js b/mon-pix/app/controllers/password-reset-demand.js new file mode 100644 index 00000000000..c5f4dc93c1c --- /dev/null +++ b/mon-pix/app/controllers/password-reset-demand.js @@ -0,0 +1,10 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; + +export default class LoginController extends Controller { + @service featureToggles; + + get isNewAuthenticationDesignEnabled() { + return this.featureToggles.featureToggles.isNewAuthenticationDesignEnabled; + } +} diff --git a/mon-pix/app/templates/password-reset-demand.hbs b/mon-pix/app/templates/password-reset-demand.hbs index 7ec2c3248f1..62822c6d429 100644 --- a/mon-pix/app/templates/password-reset-demand.hbs +++ b/mon-pix/app/templates/password-reset-demand.hbs @@ -1,4 +1,8 @@ {{page-title (t "pages.password-reset-demand.page-title")}} -
- -
\ No newline at end of file + +{{#if this.isNewAuthenticationDesignEnabled}} +{{else}} +
+ +
+{{/if}} \ No newline at end of file diff --git a/mon-pix/tests/acceptance/password-reset-demand-form-test.js b/mon-pix/tests/acceptance/password-reset-demand-form-test.js index 4a5e5712b43..da47e1609d7 100644 --- a/mon-pix/tests/acceptance/password-reset-demand-form-test.js +++ b/mon-pix/tests/acceptance/password-reset-demand-form-test.js @@ -13,50 +13,59 @@ module('Acceptance | Password reset demand form', function (hooks) { setupMirage(hooks); setupIntl(hooks); - test('can visit /mot-passe-oublie', async function (assert) { - // when - await visit('/mot-de-passe-oublie'); + module('when "New authentication design" feature toggle is disabled', function (hooks) { + hooks.beforeEach(function () { + server.create('feature-toggle', { + id: 0, + isNewAuthenticationDesignEnabled: false, + }); + }); - // then - assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); - }); + test('can visit /mot-passe-oublie', async function (assert) { + // when + await visit('/mot-de-passe-oublie'); - test('should stay on mot de passe oublié page, and show success message, when email sent correspond to an existing user', async function (assert) { - // given - this.server.create('user', { - id: 1, - firstName: 'Brandone', - lastName: 'Martins', - email: 'brandone.martins@pix.com', - password: '1024pix!', + // then + assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); }); - await visit('/mot-de-passe-oublie'); - await fillIn('#email', 'brandone.martins@pix.com'); - // when - await clickByLabel(t('pages.password-reset-demand.actions.reset')); + test('should stay on mot de passe oublié page, and show success message, when email sent correspond to an existing user', async function (assert) { + // given + this.server.create('user', { + id: 1, + firstName: 'Brandone', + lastName: 'Martins', + email: 'brandone.martins@pix.com', + password: '1024pix!', + }); + await visit('/mot-de-passe-oublie'); + await fillIn('#email', 'brandone.martins@pix.com'); - assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); - assert.dom('.password-reset-demand-form__body').exists(); - }); + // when + await clickByLabel(t('pages.password-reset-demand.actions.reset')); - test('should stay in mot-passe-oublie page when sent email do not correspond to any existing user', async function (assert) { - // given - this.server.create('user', { - id: 1, - firstName: 'Brandone', - lastName: 'Martins', - email: 'brandone.martins@pix.com', - password: '1024pix!', + assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); + assert.dom('.password-reset-demand-form__body').exists(); }); - const screen = await visit('/mot-de-passe-oublie'); - await fillIn('#email', 'unexisting@user.com'); - // when - await clickByLabel(t('pages.password-reset-demand.actions.reset')); + test('should stay in mot-passe-oublie page when sent email do not correspond to any existing user', async function (assert) { + // given + this.server.create('user', { + id: 1, + firstName: 'Brandone', + lastName: 'Martins', + email: 'brandone.martins@pix.com', + password: '1024pix!', + }); + const screen = await visit('/mot-de-passe-oublie'); + await fillIn('#email', 'unexisting@user.com'); - // then - assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); - assert.dom(screen.getByText(t('pages.password-reset-demand.error.message'))).exists(); + // when + await clickByLabel(t('pages.password-reset-demand.actions.reset')); + + // then + assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); + assert.dom(screen.getByText(t('pages.password-reset-demand.error.message'))).exists(); + }); }); }); From e1f7a109dd491b2154886d502fd4071dfa7653a5 Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:07:17 +0200 Subject: [PATCH 04/10] feat(mon-pix): use new component authentication/password-reset-demand --- .../app/templates/password-reset-demand.hbs | 15 ++++++- .../password-reset-demand-form-test.js | 42 ++++++++++++++++++- mon-pix/translations/en.json | 3 +- mon-pix/translations/es.json | 3 +- mon-pix/translations/fr.json | 3 +- mon-pix/translations/nl.json | 3 +- 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/mon-pix/app/templates/password-reset-demand.hbs b/mon-pix/app/templates/password-reset-demand.hbs index 62822c6d429..cb609c44000 100644 --- a/mon-pix/app/templates/password-reset-demand.hbs +++ b/mon-pix/app/templates/password-reset-demand.hbs @@ -1,6 +1,19 @@ -{{page-title (t "pages.password-reset-demand.page-title")}} +{{page-title (t "pages.password-reset-demand.title")}} {{#if this.isNewAuthenticationDesignEnabled}} + + <:header> + + {{t "common.actions.login"}} + + + + <:content> +

{{t "pages.password-reset-demand.title"}}

+ + +
+ {{else}}
diff --git a/mon-pix/tests/acceptance/password-reset-demand-form-test.js b/mon-pix/tests/acceptance/password-reset-demand-form-test.js index da47e1609d7..dc8f864b4af 100644 --- a/mon-pix/tests/acceptance/password-reset-demand-form-test.js +++ b/mon-pix/tests/acceptance/password-reset-demand-form-test.js @@ -29,7 +29,7 @@ module('Acceptance | Password reset demand form', function (hooks) { assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); }); - test('should stay on mot de passe oublié page, and show success message, when email sent correspond to an existing user', async function (assert) { + test('stays on mot de passe oublié page, and shows success message when email sent correspond to an existing user', async function (assert) { // given this.server.create('user', { id: 1, @@ -48,7 +48,7 @@ module('Acceptance | Password reset demand form', function (hooks) { assert.dom('.password-reset-demand-form__body').exists(); }); - test('should stay in mot-passe-oublie page when sent email do not correspond to any existing user', async function (assert) { + test('stays in mot-passe-oublie page when sent email do not correspond to any existing user', async function (assert) { // given this.server.create('user', { id: 1, @@ -68,4 +68,42 @@ module('Acceptance | Password reset demand form', function (hooks) { assert.dom(screen.getByText(t('pages.password-reset-demand.error.message'))).exists(); }); }); + + module('when "New authentication design" feature toggle is enabled', function (hooks) { + hooks.beforeEach(function () { + server.create('feature-toggle', { + id: 0, + isNewAuthenticationDesignEnabled: true, + }); + }); + + test('can visit /mot-passe-oublie', async function (assert) { + // when + await visit('/mot-de-passe-oublie'); + + // then + assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); + }); + + test('stays on "mot de passe oublié" page, and shows success message when email sent correspond to an existing user', async function (assert) { + // given + this.server.create('user', { + id: 1, + firstName: 'Brandone', + lastName: 'Martins', + email: 'brandone.martins@pix.com', + password: '1024pix!', + }); + const screen = await visit('/mot-de-passe-oublie'); + await fillIn( + screen.getByRole('textbox', { name: t('pages.password-reset-demand.fields.email.label') }), + 'brandone.martins@pix.com', + ); + + // when + await clickByLabel(t('components.authentication.password-reset-demand-form.actions.receive-reset-button')); + + assert.strictEqual(currentURL(), '/mot-de-passe-oublie'); + }); + }); }); diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index c4ccc6b0835..564ebffe4f5 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -47,7 +47,8 @@ "show-more": "Show more", "sign-out": "Sign out", "stay": "Stay", - "validate": "Validate" + "validate": "Validate", + "login": "Log in to Pix" }, "api-error-messages": { "bad-request-error": "The data entered was not in the correct format.", diff --git a/mon-pix/translations/es.json b/mon-pix/translations/es.json index 3884152693f..c8bb63397b3 100644 --- a/mon-pix/translations/es.json +++ b/mon-pix/translations/es.json @@ -47,7 +47,8 @@ "sign-out": "Desconectarse", "stay": "Quedarse", "validate": "Validar", - "refresh-page": "Actualizar la página" + "refresh-page": "Actualizar la página", + "login": "Log in to Pix" }, "api-error-messages": { "bad-request-error": "Los datos que has enviado no están en el formato correcto.", diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index d3af40e83fe..021ee46d30a 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -47,7 +47,8 @@ "show-more": "Afficher plus", "sign-out": "Se déconnecter", "stay": "Rester", - "validate": "Valider" + "validate": "Valider", + "login": "Se connecter sur Pix" }, "api-error-messages": { "bad-request-error": "Les données que vous avez soumises ne sont pas au bon format.", diff --git a/mon-pix/translations/nl.json b/mon-pix/translations/nl.json index 03385089ddd..19d091f6b21 100644 --- a/mon-pix/translations/nl.json +++ b/mon-pix/translations/nl.json @@ -47,7 +47,8 @@ "sign-out": "Log uit.", "stay": "Blijf", "validate": "Valideer", - "refresh-page": "Pagina verversen" + "refresh-page": "Pagina verversen", + "login": "Log in to Pix" }, "api-error-messages": { "bad-request-error": "De gegevens die u hebt opgegeven, hebben niet het juiste formaat.", From aa462292e73aa684de6259baef35e7acf03475aa Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:47:43 +0200 Subject: [PATCH 05/10] feat(mon-pix): change page title according to design --- .../integration/components/password-reset-demand-form-test.js | 2 +- mon-pix/translations/en.json | 2 +- mon-pix/translations/fr.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mon-pix/tests/integration/components/password-reset-demand-form-test.js b/mon-pix/tests/integration/components/password-reset-demand-form-test.js index c5549c6ee58..cd9b5e5ef3f 100644 --- a/mon-pix/tests/integration/components/password-reset-demand-form-test.js +++ b/mon-pix/tests/integration/components/password-reset-demand-form-test.js @@ -28,7 +28,7 @@ module('Integration | Component | password reset demand form', function (hooks) // then assert.dom(screen.getByRole('img', { name: "Page d'accueil de Pix.org" })).exists(); assert.dom(screen.getByRole('link', { name: "Page d'accueil de Pix.org" })).hasProperty('href', 'https://pix.org/'); - assert.dom(screen.getByRole('heading', { name: 'Mot de passe oublié ?' })).exists(); + assert.dom(screen.getByRole('heading', { name: t('pages.password-reset-demand.title') })).exists(); assert.dom(screen.getByText("Entrez votre adresse e-mail ci-dessous, et c'est repartix")).exists(); assert.dom(screen.getByRole('textbox', { name: '* Adresse e-mail' })).exists(); assert.dom(screen.getByRole('button', { name: 'Réinitialiser mon mot de passe' })).exists(); diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index 564ebffe4f5..4e13bbaa31d 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -1587,7 +1587,7 @@ "instructions": "An email explaining how to reset your password\n has been sent to the email address {email}.", "subtitle": "Password reset request" }, - "title": "Forgot your password?" + "title": "Forgotten password" }, "profile": { "accessibility": { diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index 021ee46d30a..90c71af6fc8 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -1587,7 +1587,7 @@ "instructions": "Un e-mail contenant la démarche à suivre afin de réinitialiser votre mot de passe\n vous a été envoyé à l’adresse e-mail {email}.", "subtitle": "Demande de réinitialisation de mot de passe" }, - "title": "Mot de passe oublié ?" + "title": "Mot de passe oublié" }, "profile": { "accessibility": { From 9fd52e38994887b7a6faf3491b3fbaa23514d790 Mon Sep 17 00:00:00 2001 From: LEGO Technix <109212476+lego-technix@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:39:38 +0200 Subject: [PATCH 06/10] refactor(mon-pix): mark FormTextfield component as deprecated --- mon-pix/app/components/form-textfield.hbs | 2 ++ mon-pix/app/components/form-textfield.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mon-pix/app/components/form-textfield.hbs b/mon-pix/app/components/form-textfield.hbs index 299dbd29f90..fa8f2ef20bd 100644 --- a/mon-pix/app/components/form-textfield.hbs +++ b/mon-pix/app/components/form-textfield.hbs @@ -1,3 +1,5 @@ +{{! DEPRECATED COMPONENT: DON’T USE IT! USE COMPONENTS OF THE DESIGN SYSTEM INSTEAD!! }} + {{! template-lint-disable require-input-label no-unknown-arguments-for-builtin-components }}
diff --git a/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs b/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs index 8545e4a8802..594fff07611 100644 --- a/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs +++ b/mon-pix/tests/integration/components/authentication/password-reset-demand-form-test.gjs @@ -30,7 +30,7 @@ module('Integration | Component | Authentication | password-reset-demand-form', const screen = await render(); // when - await fillByLabel(t('pages.password-reset-demand.fields.email.label'), validEmail); + await fillByLabel(t('components.authentication.password-reset-demand-form.fields.email.label'), validEmail); // then assert.dom(screen.queryByRole('alert')).doesNotExist(); @@ -44,11 +44,15 @@ module('Integration | Component | Authentication | password-reset-demand-form', const screen = await render(); // when - await fillByLabel(t('pages.password-reset-demand.fields.email.label'), invalidEmail); + await fillByLabel(t('components.authentication.password-reset-demand-form.fields.email.label'), invalidEmail); // then assert - .dom(screen.queryByText(t('components.authentication.password-reset-demand-form.invalid-email'))) + .dom( + screen.queryByText( + t('components.authentication.password-reset-demand-form.fields.email.error-message-invalid'), + ), + ) .exists(); }); }); @@ -76,7 +80,7 @@ module('Integration | Component | Authentication | password-reset-demand-form', const screen = await render(); // when - await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await fillByLabel(t('components.authentication.password-reset-demand-form.fields.email.label'), email); await click( screen.getByRole('button', { name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), @@ -104,7 +108,7 @@ module('Integration | Component | Authentication | password-reset-demand-form', const screen = await render(); // when - await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await fillByLabel(t('components.authentication.password-reset-demand-form.fields.email.label'), email); await click( screen.getByRole('button', { name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), @@ -114,7 +118,7 @@ module('Integration | Component | Authentication | password-reset-demand-form', // then // The following doesn’t work because of a PixUi span inside the role element //assert.dom(screen.queryByRole('alert', { name: t('pages.password-reset-demand.error.message') })).exists(); - assert.dom(screen.queryByText(t('pages.password-reset-demand.error.message'))).exists(); + assert.dom(screen.queryByText(t('components.authentication.password-reset-demand-form.404-message'))).exists(); }); }); @@ -131,7 +135,7 @@ module('Integration | Component | Authentication | password-reset-demand-form', const screen = await render(); // when - await fillByLabel(t('pages.password-reset-demand.fields.email.label'), email); + await fillByLabel(t('components.authentication.password-reset-demand-form.fields.email.label'), email); await click( screen.getByRole('button', { name: t('components.authentication.password-reset-demand-form.actions.receive-reset-button'), diff --git a/mon-pix/translations/en.json b/mon-pix/translations/en.json index 4daa11b4e9d..0ac3c40a373 100644 --- a/mon-pix/translations/en.json +++ b/mon-pix/translations/en.json @@ -152,6 +152,7 @@ "signup-heading": "Other ways to sign up" }, "password-reset-demand-form": { + "404-message": "The email address entered does not match any Pix account", "actions": { "receive-reset-button": "Receive a reset link" }, @@ -159,7 +160,13 @@ "link-text": "Contact us", "link-url": "https://pix.org/en/support" }, - "invalid-email": "Your email address is invalid.", + "fields": { + "email": { + "error-message-invalid": "Your email address is invalid.", + "label": "Email address", + "placeholder": "ex: john.doe@email.com" + } + }, "no-email-question": "No email address?", "rule": "All fields are required." }, diff --git a/mon-pix/translations/es.json b/mon-pix/translations/es.json index c8bb63397b3..7b8f72964c1 100644 --- a/mon-pix/translations/es.json +++ b/mon-pix/translations/es.json @@ -172,13 +172,20 @@ } }, "password-reset-demand-form": { - "invalid-email": "Your email address is invalid.", + "404-message": "Esta dirección de correo electrónico no corresponde a ninguna cuenta", "rule": "All fields are required.", "no-email-question": "No email address?", "contact-us-link": { "link-text": "Contact us", "link-url": "https://pix.org/en/support" }, + "fields": { + "email": { + "invalid-email": "Your email address is invalid.", + "label": "Email address", + "placeholder": "ex: john.doe@email.com" + } + }, "actions": { "receive-reset-button": "Receive a reset link" } diff --git a/mon-pix/translations/fr.json b/mon-pix/translations/fr.json index 7b26e4cdabb..e7c816612dc 100644 --- a/mon-pix/translations/fr.json +++ b/mon-pix/translations/fr.json @@ -152,6 +152,7 @@ "signup-heading": "Autres moyens d’inscription" }, "password-reset-demand-form": { + "404-message": "Cette adresse e-mail ne correspond à aucun compte", "actions": { "receive-reset-button": "Recevoir un lien de réinitialisation" }, @@ -159,7 +160,13 @@ "link-text": "Contactez-nous", "link-url": "https://pix.fr/support" }, - "invalid-email": "Votre adresse e-mail n’est pas valide.", + "fields": { + "email": { + "error-message-invalid": "Votre adresse e-mail n’est pas valide.", + "label": "Adresse e-mail", + "placeholder": "ex: jean.dupont@email.com" + } + }, "no-email-question": "Pas d’adresse e-mail renseignée ?", "rule": "Tous les champs sont obligatoires." }, diff --git a/mon-pix/translations/nl.json b/mon-pix/translations/nl.json index 19d091f6b21..fc2c7a52d18 100644 --- a/mon-pix/translations/nl.json +++ b/mon-pix/translations/nl.json @@ -172,13 +172,20 @@ } }, "password-reset-demand-form": { - "invalid-email": "Your email address is invalid.", + "404-message": "Dit e-mailadres komt niet overeen met een account", "rule": "All fields are required.", "no-email-question": "No email address?", "contact-us-link": { "link-text": "Contact us", "link-url": "https://pix.org/nl-be/support" }, + "fields": { + "email": { + "error-message-invalid": "Your email address is invalid.", + "label": "Email address", + "placeholder": "ex: john.doe@email.com" + } + }, "actions": { "receive-reset-button": "Receive a reset link" }