Fixed some issues with invites mock-data and response from api

This commit is contained in:
Erik Tiekstra
2021-09-01 13:39:01 +02:00
parent 70ab2c3ee4
commit 803422dbc5
6 changed files with 91 additions and 48 deletions

View File

@@ -2,7 +2,7 @@
<section class="employee-invite"> <section class="employee-invite">
<digi-typography> <digi-typography>
<h1>Skapa nytt personalkonto</h1> <h1>Skapa nytt personalkonto</h1>
<p>Såhär skapar du ett nytt personalkonto:</p> <p> här skapar du ett nytt personalkonto:</p>
<ol> <ol>
<li>Skicka en inbjudningslänk till personalens e-postadress.</li> <li>Skicka en inbjudningslänk till personalens e-postadress.</li>
<li>Personalen öppnar inbjudningslänken via sin e-post och skapar ett personalkonto med sitt Bank-ID.</li> <li>Personalen öppnar inbjudningslänken via sin e-post och skapar ett personalkonto med sitt Bank-ID.</li>
@@ -32,9 +32,9 @@
afLabel="E-postadress" afLabel="E-postadress"
afType="email" afType="email"
[afRequired]="true" [afRequired]="true"
[afInvalidMessage]="email.errors?.message || 'Ogiltig e-postadress'" [afInvalidMessage]="emailControl.errors?.message || 'Ogiltig e-postadress'"
[afDisableValidStyle]="true" [afDisableValidStyle]="true"
[afInvalid]="email.invalid && email.dirty" [afInvalid]="emailControl.invalid && emailControl.dirty"
></digi-ng-form-input> ></digi-ng-form-input>
<digi-button af-size="m" af-type="submit" class="employee-invite__invitation-btn" <digi-button af-size="m" af-type="submit" class="employee-invite__invitation-btn"
>Skicka inbjudningslänk</digi-button >Skicka inbjudningslänk</digi-button
@@ -42,14 +42,17 @@
</div> </div>
</div> </div>
<digi-notification-alert <digi-notification-alert
*ngIf="(latestSubmittedInvite$ | async) as latestSubmittedInvite" *ngIf="(lastInvite$ | async) as lastInvite"
af-variation="success" af-variation="success"
af-heading="Allt gick bra" af-heading="Allt gick bra"
af-heading-level="h3" af-heading-level="h3"
af-closeable="true" af-closeable="true"
(click)="onCloseAlert()" (click)="onCloseAlert()"
> >
<p>Inbjudan har skickats till {{latestSubmittedInvite.email}}</p> <p *ngIf="lastInvitedNewUsers?.length">Inbjudan har skickats till {{lastInvitedNewUsers.join(', ')}}.</p>
<p *ngIf="lastInvitedExistingUsers?.length">
Följande personal är redan registrerat: {{lastInvitedExistingUsers.join(', ')}}.
</p>
</digi-notification-alert> </digi-notification-alert>
<footer class="employee-invite__footer"> <footer class="employee-invite__footer">

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import { EmployeeService } from '@msfa-services/api/employee.service'; import { EmployeeService } from '@msfa-services/api/employee.service';
import { RequiredValidator } from '@msfa-utils/validators/required.validator'; import { RequiredValidator } from '@msfa-utils/validators/required.validator';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
@@ -10,46 +11,57 @@ import { BehaviorSubject, Observable } from 'rxjs';
styleUrls: ['./employee-invite.component.scss'], styleUrls: ['./employee-invite.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class EmployeeInviteComponent implements OnInit { export class EmployeeInviteComponent {
form: FormGroup; form: FormGroup = new FormGroup({
private latestSubmittedInvite_ = new BehaviorSubject<string>(''); email: new FormControl('', [
latestSubmittedInvite$: Observable<string> = this.latestSubmittedInvite_.asObservable(); RequiredValidator('E-postadress'),
Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'),
]),
});
private _lastInvite$ = new BehaviorSubject<EmployeeInviteResponse>(null);
lastInvite$: Observable<EmployeeInviteResponse> = this._lastInvite$.asObservable();
constructor(private employeeService: EmployeeService) {} constructor(private employeeService: EmployeeService) {}
ngOnInit(): void { get emailControl(): AbstractControl {
this.form = new FormGroup({
email: new FormControl('', [
RequiredValidator('E-postadress'),
Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'),
]),
});
}
get email() {
return this.form.get('email'); return this.form.get('email');
} }
get lastInvite(): EmployeeInviteResponse {
return this._lastInvite$.getValue();
}
get lastInvitedNewUsers(): string[] {
const invitedUsers = this.lastInvite?.invitedUsers || [];
const assignedUsers = this.lastInvite?.assignedUsers || [];
return [...invitedUsers, ...assignedUsers.map(assigned => assigned.email)];
}
get lastInvitedExistingUsers(): string[] {
const existingUsersInCurrentOrg = this.lastInvite?.existingUsersInCurrentOrg || [];
return existingUsersInCurrentOrg.map(existing => existing.email);
}
submitForm(): void { submitForm(): void {
this._lastInvite$.next(null);
if (this.form.invalid) { if (this.form.invalid) {
this.email.markAsDirty(); this.emailControl.markAsDirty();
this.email.markAsTouched(); this.emailControl.markAsTouched();
return; return;
} }
const post = this.employeeService.postEmployeeInvitation(this.form.value).subscribe({ const post = this.employeeService.postEmployeeInvitation(this.emailControl.value).subscribe({
next: () => { next: data => {
this.latestSubmittedInvite_.next(this.form.value); this._lastInvite$.next(data);
this.form.reset(); this.form.reset();
}, },
complete: () => { complete: () => {
post.unsubscribe(); post.unsubscribe();
}, },
error: (err) => console.log(err)
}); });
} }
onCloseAlert(): void { onCloseAlert(): void {
this.latestSubmittedInvite_.next(''); this._lastInvite$.next(null);
} }
} }

View File

@@ -1,4 +1,18 @@
export interface EmployeeInviteMockApiResponse { interface ExistingUser {
id: number, ciamUserId: string;
createdAt: number firstName: string;
lastName: string;
email: string;
status: string;
roles: string[];
tjansteKoder: string[];
allaUtforandeVerksamheter: boolean;
utforandeVerksamhetIds: number[];
adressIds: number[];
}
export interface EmployeeInviteResponse {
assignedUsers: ExistingUser[];
invitedUsers: string[];
existingUsersInCurrentOrg: ExistingUser[];
} }

View File

@@ -42,12 +42,16 @@ export interface EmployeeRequestData {
adressIds: number[]; adressIds: number[];
} }
export interface EmployeeInviteRequestData {
emails: string[];
}
export function mapEmployeeToRequestData(data: Employee): EmployeeRequestData { export function mapEmployeeToRequestData(data: Employee): EmployeeRequestData {
const { email, roles, tjanstCodes, allaUtforandeVerksamheter, utforandeVerksamhet, utforandeVerksamhetIds, utforandeAdressIds } = data; const {
email,
roles,
tjanstCodes,
allaUtforandeVerksamheter,
utforandeVerksamhet,
utforandeVerksamhetIds,
utforandeAdressIds,
} = data;
return { return {
email, email,
@@ -56,7 +60,7 @@ export function mapEmployeeToRequestData(data: Employee): EmployeeRequestData {
utforandeVerksamhetIds, utforandeVerksamhetIds,
adressIds: utforandeAdressIds, adressIds: utforandeAdressIds,
allaUtforandeVerksamheter: allaUtforandeVerksamheter, allaUtforandeVerksamheter: allaUtforandeVerksamheter,
utforandeVerksamhet: utforandeVerksamhet utforandeVerksamhet: utforandeVerksamhet,
}; };
} }

View File

@@ -6,6 +6,7 @@ import { RoleEnum } from '@msfa-enums/role.enum';
import { SortOrder } from '@msfa-enums/sort-order.enum'; import { SortOrder } from '@msfa-enums/sort-order.enum';
import { environment } from '@msfa-environment'; import { environment } from '@msfa-environment';
import { DeleteEmployeeMockApiResponse } from '@msfa-models/api/delete-employee.response.model'; import { DeleteEmployeeMockApiResponse } from '@msfa-models/api/delete-employee.response.model';
import { EmployeeInviteResponse } from '@msfa-models/api/employee-invite.response.model';
import { import {
EmployeeCompactResponse, EmployeeCompactResponse,
EmployeeResponse, EmployeeResponse,
@@ -14,7 +15,6 @@ import {
import { Params } from '@msfa-models/api/params.model'; import { Params } from '@msfa-models/api/params.model';
import { import {
Employee, Employee,
EmployeeInviteRequestData,
EmployeesData, EmployeesData,
mapEmployeeToRequestData, mapEmployeeToRequestData,
mapResponseToEmployee, mapResponseToEmployee,
@@ -25,7 +25,7 @@ import { mapRoleResponseToRoleObject, Role } from '@msfa-models/role.model';
import { Sort } from '@msfa-models/sort.model'; import { Sort } from '@msfa-models/sort.model';
import { ErrorService } from '@msfa-services/error.service'; import { ErrorService } from '@msfa-services/error.service';
import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';
import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators';
import { TjanstService } from './tjanst.service'; import { TjanstService } from './tjanst.service';
@Injectable({ @Injectable({
@@ -177,14 +177,15 @@ export class EmployeeService extends UnsubscribeDirective {
); );
} }
public postEmployeeInvitation(email: string): Observable<string[]> { public postEmployeeInvitation(email: string): Observable<EmployeeInviteResponse> {
return this.httpClient return this.httpClient
.patch<{ data: EmployeeInviteRequestData }>(`${this._apiBaseUrl}/invite`, { .patch<{ data: EmployeeInviteResponse }>(`${this._apiBaseUrl}/invite`, {
emails: Object.values(email), emails: [email],
}) })
.pipe( .pipe(
take(1), take(1),
map(res => res.data.emails), tap(res => console.log(res)),
map(res => res.data),
catchError(error => throwError({ message: error as string, type: ErrorType.API })) catchError(error => throwError({ message: error as string, type: ErrorType.API }))
); );
} }

View File

@@ -11,18 +11,17 @@ server.use(
'/api/*': '/$1', '/api/*': '/$1',
'*sort=utforandeVerksamhet*': '$1sort=utforandeVerksamhet[0]$2', '*sort=utforandeVerksamhet*': '$1sort=utforandeVerksamhet[0]$2',
'*sort=tjanst*': '$1sort=tjanst[0]$2', '*sort=tjanst*': '$1sort=tjanst[0]$2',
'/users/invite*': '/invites$1',
'/users/:id': '/employees?ciamUserId=:id', '/users/:id': '/employees?ciamUserId=:id',
'/users*': '/employees$1', '/users*': '/employees$1',
'/employees*search=*': '/employees$1fullName_like=$2', '/employees*search=*': '/employees$1fullName_like=$2',
'/employees*onlyEmployeesWithoutAuthorization=*': '/employees$1roles.length_lte=1', '/employees*onlyEmployeesWithoutAuthorization=*': '/employees$1roles.length_lte=1',
'/employees/invite': '/invites',
'/employees*': '/employees$1', '/employees*': '/employees$1',
'/services*': '/tjanster$1', '/services*': '/tjanster$1',
'/participants': '/participants?_embed=employees', '/participants': '/participants?_embed=employees',
'/participant/:id': '/participants/:id?_embed=employees', '/participant/:id': '/participants/:id?_embed=employees',
'/auth/userinfo': '/currentUser', '/auth/userinfo': '/currentUser',
'/auth/organizations': '/currentUser', '/auth/organizations': '/currentUser',
'/avrop/handledare/assign*': '/avrop$1',
'/avrop/tjanster*': '/avropTjanster$1', '/avrop/tjanster*': '/avropTjanster$1',
'/avrop/handledare*': '/handledare$1', '/avrop/handledare*': '/handledare$1',
'/avrop/utforandeverksamheter*': '/utforandeVerksamheter$1', '/avrop/utforandeverksamheter*': '/utforandeVerksamheter$1',
@@ -33,7 +32,6 @@ server.use(
'/deltagare?*': '/avrop?$1', '/deltagare?*': '/avrop?$1',
'/deltagare/:sokandeId/avrop': '/avrop?sokandeId=:sokandeId', '/deltagare/:sokandeId/avrop': '/avrop?sokandeId=:sokandeId',
'/deltagare/:sokandeId/*': '/deltagare/:sokandeId', '/deltagare/:sokandeId/*': '/deltagare/:sokandeId',
'/employees/invite': '/invites',
'*page=*': '$1_page=$2', '*page=*': '$1_page=$2',
'*limit=*': '$1_limit=$2', '*limit=*': '$1_limit=$2',
'*sort=*': '$1_sort=$2', '*sort=*': '$1_sort=$2',
@@ -55,8 +53,19 @@ router.render = (req, res) => {
} }
if (method === 'PATCH') { if (method === 'PATCH') {
// Returning status 204 as this is the same as in the API, BUT we're not actually updating the mock-api if (pathname === '/handledare/assign') {
return res.status(204).jsonp(); // Returning status 204 as this is the same as in the API, BUT we're not actually updating the mock-api
return res.status(204).jsonp();
}
if (pathname === '/invites' && req.body?.emails) {
return res.status(200).jsonp({
data: {
invitedUsers: req.body.emails,
assignedUsers: [],
existingUsersInCurrentOrg: [],
},
});
}
} }
// Return custom error when status is 404. // Return custom error when status is 404.