Skip to content

Commit

Permalink
feat(admin): Add a button to send a new organization invitation
Browse files Browse the repository at this point in the history
  • Loading branch information
theotime2005 authored Jan 30, 2025
1 parent be65a3d commit 15e55f6
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 11 deletions.
31 changes: 21 additions & 10 deletions admin/app/components/organizations/invitations.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { fn } from '@ember/helper';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import dayjsFormat from 'ember-dayjs/helpers/dayjs-format';
import { t } from 'ember-intl';

export default class OrganizationInvitations extends Component {
@service accessControl;
@service intl;

get sortedInvitations() {
return this.args.invitations.sortBy('updatedAt').reverse();
Expand Down Expand Up @@ -38,16 +40,25 @@ export default class OrganizationInvitations extends Component {
<td>{{dayjsFormat invitation.updatedAt "DD/MM/YYYY [-] HH:mm"}}</td>
{{#if this.accessControl.hasAccessToOrganizationActionsScope}}
<td>
<PixButton
@size="small"
@variant="error"
class="organization-invitations-actions__button"
aria-label="Annuler l’invitation de {{invitation.email}}"
@triggerAction={{fn @onCancelOrganizationInvitation invitation}}
@iconBefore="delete"
>
Annuler l’invitation
</PixButton>
<div class="organization-invitations__actions-buttons">
<PixButton
@size="small"
aria-label={{t "common.invitations.send-new-label" invitationEmail=invitation.email}}
@triggerAction={{fn @onSendNewInvitation invitation}}
@iconBefore="refresh"
>
{{t "common.invitations.send-new"}}
</PixButton>
<PixButton
@size="small"
@variant="error"
aria-label="Annuler l’invitation de {{invitation.email}}"
@triggerAction={{fn @onCancelOrganizationInvitation invitation}}
@iconBefore="delete"
>
Annuler l’invitation
</PixButton>
</div>
</td>
{{/if}}
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default class InvitationsController extends Controller {
@service store;
@service accessControl;
@service errorResponseHandler;
@service intl;

CUSTOM_ERROR_MESSAGES = {
DEFAULT: 'Une erreur s’est produite, veuillez réessayer.',
Expand Down Expand Up @@ -47,6 +48,27 @@ export default class InvitationsController extends Controller {
this.isLoading = false;
}

@action
async sendNewInvitation(organizationInvitation) {
this.isLoading = true;
try {
await this.store.queryRecord('organization-invitation', {
email: organizationInvitation.email,
lang: organizationInvitation.lang,
role: organizationInvitation.role,
organizationId: this.model.organization.id,
});
this.pixToast.sendSuccessNotification({
message: this.intl.t('common.invitations.send-new-confirm', {
invitationEmail: organizationInvitation.email,
}),
});
} catch (error) {
this.errorResponseHandler.notify(error, this.CUSTOM_ERROR_MESSAGES);
}
this.isLoading = false;
}

_isEmailToInviteValid(email) {
if (!email) {
this.userEmailToInviteError = 'Ce champ est requis.';
Expand Down
9 changes: 8 additions & 1 deletion admin/app/styles/components/organization-invitations.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
text-align: center;
}

&-actions__button {
&__actions-buttons {
display: flex;
flex-direction: column;

button {
width: 13rem;
margin: 0.2rem;
}

svg {
margin-right: 6px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
{{/if}}
<Organizations::Invitations
@invitations={{@model.organizationInvitations}}
@onSendNewInvitation={{this.sendNewInvitation}}
@onCancelOrganizationInvitation={{this.cancelOrganizationInvitation}}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { render } from '@1024pix/ember-testing-library';
import Service from '@ember/service';
import { click } from '@ember/test-helpers';
import dayjs from 'dayjs';
import { setupIntl } from 'ember-intl/test-support';
import { setupRenderingTest } from 'ember-qunit';
import Invitations from 'pix-admin/components/organizations/invitations';
import { module, test } from 'qunit';
import sinon from 'sinon';

module('Integration | Component | organization-invitations', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks, 'fr');

module('when the admin member have access to organization scope', function (hooks) {
hooks.beforeEach(function () {
Expand Down Expand Up @@ -40,18 +42,21 @@ module('Integration | Component | organization-invitations', function (hooks) {
{
email: '[email protected]',
role: 'ADMIN',
lang: 'fr',
roleInFrench: 'Administrateur',
updatedAt: dayjs('2019-10-08T10:50:00Z').utcOffset(2),
},
{
email: '[email protected]',
role: 'MEMBER',
roleInFrench: 'Membre',
lang: 'en',
updatedAt: dayjs('2019-10-08T10:50:00Z').utcOffset(2),
},
{
email: '[email protected]',
role: null,
lang: 'nl',
roleInFrench: '-',
updatedAt: dayjs('2019-10-08T10:50:00Z').utcOffset(2),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,48 @@ module('Unit | Controller | authenticated/organizations/get/invitations', functi
assert.ok(notifyStub.calledWithExactly(anError, customErrors));
});
});

module('#sendNewInvitation', function () {
test('sends a new invitation', function (assert) {
// given
const queryRecordStub = sinon.stub();
store.queryRecord = queryRecordStub;
controller.model = { organization: { id: 1 } };
const organizationInvitation = { email: '[email protected]', lang: 'en', role: 'MEMBER' };

// when
controller.sendNewInvitation(organizationInvitation);

// then
assert.ok(
queryRecordStub.calledWith('organization-invitation', {
...organizationInvitation,
organizationId: 1,
}),
);
});

test('When an error occurs, it should send a notification error', async function (assert) {
// given
const controller = this.owner.lookup('controller:authenticated/organizations/get/invitations');
const store = this.owner.lookup('service:store');
const anError = Symbol('an error');
store.queryRecord = sinon.stub().rejects(anError);
const notifyStub = sinon.stub();
class ErrorResponseHandler extends Service {
notify = notifyStub;
}
this.owner.register('service:error-response-handler', ErrorResponseHandler);
const customErrors = Symbol('custom errors');
controller.CUSTOM_ERROR_MESSAGES = customErrors;
controller.model = { organization: { id: 1 } };
const organizationInvitation = { email: '[email protected]', lang: 'en', role: 'MEMBER' };

// when
await controller.sendNewInvitation(organizationInvitation);

// then
assert.ok(notifyStub.calledWithExactly(anError, customErrors));
});
});
});
5 changes: 5 additions & 0 deletions admin/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"mandatory": "Champ obligatoire",
"mandatory-fields": "The fields marked '<abbr title=\"required\" class=\"mandatory-mark\">'*'</abbr>' are required"
},
"invitations": {
"send-new": "Resend the invitation",
"send-new-confirm": "An email has been sent to {invitationEmail}",
"send-new-label": "Resend the invitation to {invitationEmail}"
},
"notifications": {
"close-button": {
"extra-information": "Close notification"
Expand Down
5 changes: 5 additions & 0 deletions admin/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"mandatory": "Champ obligatoire",
"mandatory-fields": "Les champs marqués de '<abbr title=\"obligatoire\" class=\"mandatory-mark\">'*'</abbr>' sont obligatoires"
},
"invitations": {
"send-new": "Renvoyer l'invitation",
"send-new-confirm": "Un email a bien été renvoyé à {invitationEmail}",
"send-new-label": "Renvoyer l'invitation à {invitationEmail}"
},
"notifications": {
"close-button": {
"extra-information": "Fermer la notification"
Expand Down

0 comments on commit 15e55f6

Please sign in to comment.