Skip to content

Commit

Permalink
[FEATURE] Ajouter une modale de confirmation des invitations sur Pix …
Browse files Browse the repository at this point in the history
…Orga (pix-14535)

 #11201
  • Loading branch information
pix-service-auto-merge authored Jan 27, 2025
2 parents 0aea832 + 33aebf7 commit c1a6ec8
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ When(`j'invite {string} à rejoindre l'organisation`, (emailAddresses) => {
.parent()
.within(() => cy.get("textarea").type(emailAddresses));
cy.get("button").contains("Inviter").click();
cy.get("button").contains("Valider").click();
});
27 changes: 26 additions & 1 deletion orga/app/components/team/invite-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,46 @@
@id="email"
type="email"
@value={{@email}}
aria-invalid={{if this.emailError "true" "false"}}
aria-describedby="email-error"
class="invite-form__email-field"
@requiredLabel={{t "common.form.mandatory-fields-title"}}
{{on "change" @onUpdateEmail}}
>
<:label>{{t "pages.team-new-item.input-label"}}</:label>
</PixTextarea>
{{#if this.emailError}}
<p id="email-error" class="invite-form__error-message">{{this.emailError}}</p>
{{/if}}
</div>

<div class="form__validation">
<PixButton @triggerAction={{@onCancel}} @variant="secondary">
{{t "common.actions.cancel"}}
</PixButton>
<PixButton @type="submit" @isLoading={{@isLoading}}>
<PixButton @triggerAction={{this.openModal}} @variant="secondary">
{{t "pages.team-new-item.invite-button"}}
</PixButton>
</div>

<PixModal
class="invite-form__modal"
@title={{t "pages.team-new.invite-form-modal.title"}}
@showModal={{this.modalOpen}}
@onCloseButtonClick={{this.closeModal}}
>
<:content>
<p>{{t "pages.team-new.invite-form-modal.warning"}}</p>
<p>{{t "pages.team-new.invite-form-modal.question"}}</p>
</:content>

<:footer>
<PixButton @variant="secondary" @isBorderVisible={{true}} @triggerAction={{this.closeModal}}>
{{t "common.actions.cancel"}}
</PixButton>
<PixButton @variant="secondary" @triggerAction={{@onSubmit}} @isLoading={{this.isLoading}}>{{t
"pages.team-new.invite-form-modal.confirm"
}}</PixButton>
</:footer>
</PixModal>
</form>
35 changes: 35 additions & 0 deletions orga/app/components/team/invite-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import isEmailValid from '../../utils/email-validator';

export default class InviteForm extends Component {
@service intl;
@tracked modalOpen = false;
@tracked emailError = null;

@action
openModal() {
const emailInput = this.args?.email?.trim();
if (!emailInput) {
this.emailError = this.intl.t('pages.team-new.errors.mandatory-email-field');
return;
}
const emails = emailInput.split(',').map((email) => email.trim());
const areEmailsValid = emails.every((email) => isEmailValid(email));

if (!areEmailsValid) {
this.emailError = this.intl.t('pages.team-new.errors.invalid-input');
return;
}
this.emailError = null;
this.modalOpen = true;
}

@action
closeModal() {
this.modalOpen = false;
}
}
1 change: 1 addition & 0 deletions orga/app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
@use 'components/progress-bar' as *;
@use 'components/register-form' as *;
@use 'components/team' as *;
@use 'components/team/invite-form' as *;
@use 'components/login-or-register' as *;
@use 'components/manage-authentication-method-modal' as *;
@use 'components/participation-filters' as *;
Expand Down
15 changes: 15 additions & 0 deletions orga/app/styles/components/team/invite-form.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@use 'pix-design-tokens/typography';

.invite-form {

&__email-field {
min-height: 70px;
}

&__error-message {
@extend %pix-body-s;

margin-top: var(--pix-spacing-2x);
color: var(--pix-error-700);
}
}
3 changes: 0 additions & 3 deletions orga/app/styles/pages/authenticated/team/new.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,4 @@
}
}

.invite-form__email-field {
min-height: 70px;
}

22 changes: 22 additions & 0 deletions orga/tests/acceptance/team-creation-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { module, test } from 'qunit';
import authenticateSession from '../helpers/authenticate-session';
import setupIntl from '../helpers/setup-intl';
import { createPrescriberByUser, createUserMembershipWithRole } from '../helpers/test-init';
import { waitForDialog } from '../helpers/wait-for';

module('Acceptance | Team Creation', function (hooks) {
setupApplicationTest(hooks);
Expand Down Expand Up @@ -55,6 +56,7 @@ module('Acceptance | Team Creation', function (hooks) {
let inputLabel;
let cancelButton;
let inviteButton;
let confirmButton;

hooks.beforeEach(async function () {
user = createUserMembershipWithRole('ADMIN');
Expand All @@ -67,6 +69,7 @@ module('Acceptance | Team Creation', function (hooks) {
inputLabel = `${t('pages.team-new-item.input-label')} *`;
inviteButton = t('pages.team-new-item.invite-button');
cancelButton = t('common.actions.cancel');
confirmButton = t('pages.team-new.invite-form-modal.confirm');
});

test('it should be accessible', async function (assert) {
Expand Down Expand Up @@ -95,6 +98,12 @@ module('Acceptance | Team Creation', function (hooks) {
await clickByName(inviteButton);

// then
await waitForDialog();

//when
await clickByName(confirmButton);

//then
const organizationInvitation = server.db.organizationInvitations[server.db.organizationInvitations.length - 1];

assert.strictEqual(organizationInvitation.email, email);
Expand All @@ -118,6 +127,9 @@ module('Acceptance | Team Creation', function (hooks) {
// when
await clickByName(inviteButton);

await waitForDialog();
await clickByName(confirmButton);

// then
assert.ok(screen.getByText(t('pages.team-new.success.multiple-invitations')));
});
Expand Down Expand Up @@ -182,6 +194,8 @@ module('Acceptance | Team Creation', function (hooks) {

// when
await clickByName(inviteButton);
await waitForDialog();
await clickByName(confirmButton);

// then

Expand Down Expand Up @@ -211,6 +225,8 @@ module('Acceptance | Team Creation', function (hooks) {

// when
await clickByName(inviteButton);
await waitForDialog();
await clickByName(confirmButton);

// then

Expand Down Expand Up @@ -240,6 +256,8 @@ module('Acceptance | Team Creation', function (hooks) {

// when
await clickByName(inviteButton);
await waitForDialog();
await clickByName(confirmButton);

// then

Expand Down Expand Up @@ -269,6 +287,8 @@ module('Acceptance | Team Creation', function (hooks) {

// when
await clickByName(inviteButton);
await waitForDialog();
await clickByName(confirmButton);

// then

Expand Down Expand Up @@ -302,6 +322,8 @@ module('Acceptance | Team Creation', function (hooks) {

// When
await clickByName(inviteButton);
await waitForDialog();
await clickByName(confirmButton);

// Then
const expectedErrorMessage = t('pages.team-new.errors.sending-email-to-invalid-email-address', {
Expand Down
83 changes: 80 additions & 3 deletions orga/tests/integration/components/team/invite-form-test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { fillByLabel, render } from '@1024pix/ember-testing-library';
import { click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { t } from 'ember-intl/test-support';
import { module, test } from 'qunit';
import sinon from 'sinon';

import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering';
import { waitForDialog } from '../../../helpers/wait-for';

module('Integration | Component | Team::InviteForm', function (hooks) {
setupIntlRenderingTest(hooks);
Expand All @@ -17,16 +19,16 @@ module('Integration | Component | Team::InviteForm', function (hooks) {

test('it should contain email input and validation button', async function (assert) {
// when
await render(
const screen = await render(
hbs`<Team::InviteForm @onSubmit={{this.inviteSpy}} @onCancel={{this.cancelSpy}} @onUpdateEmail={{this.updateEmail}} />`,
);

// then
assert.dom('#email').exists();
assert.dom('#email').isRequired();
assert.dom('button[type="submit"]').exists();
assert.ok(screen.getByText(t('pages.team-new-item.invite-button')));
assert.dom(screen.queryByRole('dialog')).doesNotExist();
});

test('it should bind organizationInvitation properties with email form input', async function (assert) {
// given
this.set('email', '[email protected]');
Expand All @@ -46,4 +48,79 @@ module('Integration | Component | Team::InviteForm', function (hooks) {
// then
assert.ok(this.updateEmail.called);
});
test('it should open confirmation modal when invite button is clicked', async function (assert) {
// given
this.set('email', '[email protected]');
const screen = await render(
hbs`<Team::InviteForm
@email={{this.email}}
@onSubmit={{this.inviteSpy}}
@onCancel={{this.cancelSpy}}
@onUpdateEmail={{this.updateEmail}}
/>`,
);

// when
const inputLabel = `${t('pages.team-new-item.input-label')} *`;
await fillByLabel(inputLabel, '[email protected]');
const inviteButton = await screen.findByRole('button', {
name: t('pages.team-new-item.invite-button'),
});

await click(inviteButton);
await waitForDialog();

// then
assert.dom(screen.getByRole('dialog')).isVisible();
});
test('it should display error message when no email is in input and invite button is clicked', async function (assert) {
// given
this.set('email', '');
const errorMessage = t('pages.team-new.errors.mandatory-email-field');
const screen = await render(
hbs`<Team::InviteForm
@email={{this.email}}
@onSubmit={{this.inviteSpy}}
@onCancel={{this.cancelSpy}}
@onUpdateEmail={{this.updateEmail}}
/>`,
);

// when
const inputLabel = `${t('pages.team-new-item.input-label')} *`;
await fillByLabel(inputLabel, '');
const inviteButton = await screen.findByRole('button', {
name: t('pages.team-new-item.invite-button'),
});

await click(inviteButton);

// then
assert.dom(await screen.findByText(errorMessage)).exists();
assert.dom(screen.queryByRole('dialog')).doesNotExist();
});
test('it should display error message when email format is not correct', async function (assert) {
// given
this.set('email', '[email protected];alex-mail.incorrect');
const errorMessage = t('pages.team-new.errors.invalid-input');
const screen = await render(
hbs`<Team::InviteForm
@email={{this.email}}
@onSubmit={{this.inviteSpy}}
@onCancel={{this.cancelSpy}}
@onUpdateEmail={{this.updateEmail}}
/>`,
);

// when
const inviteButton = await screen.findByRole('button', {
name: t('pages.team-new-item.invite-button'),
});

await click(inviteButton);

// then
assert.dom(await screen.findByText(errorMessage)).exists();
assert.dom(screen.queryByRole('dialog')).doesNotExist();
});
});
8 changes: 8 additions & 0 deletions orga/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,8 @@
"email-requirement": "Enter here the email address of the member you want to invite.",
"errors": {
"default": "The service is temporarily unavailable. Please try again later.",
"invalid-input": "The data entered was not in the correct format",
"mandatory-email-field": "This field is mandatory",
"sending-email-to-invalid-email-address": "Sending email to the address {email} failed. The sending server replied: \"{errorMessage}\".",
"status": {
"400": "The email address format is invalid.",
Expand All @@ -1677,6 +1679,12 @@
"500": "Something went wrong. Please try again."
}
},
"invite-form-modal": {
"confirm": "Confirm",
"question": "Do you want to continue? ",
"title": "Confirm the addition of new team members",
"warning": "Members you add will have access to participant results and campaign analytics."
},
"invited-members": "By clicking on the link provided in the invitation, the invited members will be able to create an account or log in with an existing Pix account.",
"several-email-requirement": "Invite several members by separating the email addresses with commas.",
"success": {
Expand Down
8 changes: 8 additions & 0 deletions orga/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@
"email-requirement": "Saisissez ici l'adresse e-mail du membre que vous souhaitez inviter.",
"errors": {
"default": "Le service est momentanément indisponible. Veuillez réessayer ultérieurement.",
"invalid-input": "Les données que vous avez soumises ne sont pas au bon format",
"mandatory-email-field": "Ce champ est obligatoire",
"sending-email-to-invalid-email-address": "L'envoi d'e-mail pour l'adresse {email} a échoué. Le serveur d'envoi a répondu : \"{errorMessage}\"",
"status": {
"400": "Le format de l'adresse e-mail est incorrect.",
Expand All @@ -1683,6 +1685,12 @@
"500": "Quelque chose s'est mal passé. Veuillez réessayer."
}
},
"invite-form-modal": {
"confirm": "Valider",
"question": "Voulez-vous continuer ? ",
"title": "Confirmer l’ajout de nouveaux membres dans l'équipe",
"warning": "Les membres que vous ajoutez auront accès aux résultats des participants et à l’analyse des campagnes."
},
"invited-members": "À la réception de l'e-mail, les invités pourront choisir de se créer un compte Pix ou de se connecter avec un compte Pix existant.",
"several-email-requirement": "Vous pouvez inviter plusieurs membres en séparant les adresses e-mails par des virgules.",
"success": {
Expand Down
8 changes: 8 additions & 0 deletions orga/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,8 @@
"email-requirement": "Voer hier het e-mailadres in van het lid dat je wilt uitnodigen.",
"errors": {
"default": "De service is tijdelijk niet beschikbaar. Probeer het later nog eens.",
"invalid-input": "The data entered was not in the correct format",
"mandatory-email-field": "This field is mandatory",
"sending-email-to-invalid-email-address": "Het verzenden van e-mail naar adres {email} is mislukt. De verzendende server antwoordde: \"{errorMessage}\".",
"status": {
"400": "De indeling van het e-mailadres is onjuist.",
Expand All @@ -1682,6 +1684,12 @@
}
},
"invited-members": "Als gasten de e-mail ontvangen, kunnen ze kiezen of ze een Pix-account willen aanmaken of willen inloggen met een bestaand Pix-account.",
"invite-form-modal": {
"confirm": "Confirm",
"question": "Do you want to continue? ",
"title": "Confirm the addition of new team members",
"warning": "Members you add will have access to participant results and campaign analytics."
},
"several-email-requirement": "Je kunt meerdere leden uitnodigen door e-mailadressen te scheiden met komma's.",
"success": {
"invitation": "Er is een uitnodiging verstuurd naar het e-mailadres {email}.",
Expand Down

0 comments on commit c1a6ec8

Please sign in to comment.