Merge pull request #1336 from 4Science/CST-4634-Many-notifications-when-trying-to-create-an-eperson-with-already-existing-email

Added custom validator and mail check during eperson registration
This commit is contained in:
Tim Donohue
2021-10-15 12:22:14 -05:00
committed by GitHub
5 changed files with 361 additions and 92 deletions

View File

@@ -28,6 +28,9 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
describe('EPersonFormComponent', () => { describe('EPersonFormComponent', () => {
let component: EPersonFormComponent; let component: EPersonFormComponent;
@@ -99,12 +102,78 @@ describe('EPersonFormComponent', () => {
} }
}); });
return createSuccessfulRemoteDataObject$(ePerson); return createSuccessfulRemoteDataObject$(ePerson);
},
getEPersonByEmail(email): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(null);
} }
}; };
builderService = getMockFormBuilderService(); builderService = Object.assign(getMockFormBuilderService(),{
createFormGroup(formModel, options = null) {
const controls = {};
formModel.forEach( model => {
model.parent = parent;
const controlModel = model;
const controlState = { value: controlModel.value, disabled: controlModel.disabled };
const controlOptions = this.createAbstractControlOptions(controlModel.validators, controlModel.asyncValidators, controlModel.updateOn);
controls[model.id] = new FormControl(controlState, controlOptions);
});
return new FormGroup(controls, options);
},
createAbstractControlOptions(validatorsConfig = null, asyncValidatorsConfig = null, updateOn = null) {
return {
validators: validatorsConfig !== null ? this.getValidators(validatorsConfig) : null,
};
},
getValidators(validatorsConfig) {
return this.getValidatorFns(validatorsConfig);
},
getValidatorFns(validatorsConfig, validatorsToken = this._NG_VALIDATORS) {
let validatorFns = [];
if (this.isObject(validatorsConfig)) {
validatorFns = Object.keys(validatorsConfig).map(validatorConfigKey => {
const validatorConfigValue = validatorsConfig[validatorConfigKey];
if (this.isValidatorDescriptor(validatorConfigValue)) {
const descriptor = validatorConfigValue;
return this.getValidatorFn(descriptor.name, descriptor.args, validatorsToken);
}
return this.getValidatorFn(validatorConfigKey, validatorConfigValue, validatorsToken);
});
}
return validatorFns;
},
getValidatorFn(validatorName, validatorArgs = null, validatorsToken = this._NG_VALIDATORS) {
let validatorFn;
if (Validators.hasOwnProperty(validatorName)) { // Built-in Angular Validators
validatorFn = Validators[validatorName];
} else { // Custom Validators
if (this._DYNAMIC_VALIDATORS && this._DYNAMIC_VALIDATORS.has(validatorName)) {
validatorFn = this._DYNAMIC_VALIDATORS.get(validatorName);
} else if (validatorsToken) {
validatorFn = validatorsToken.find(validator => validator.name === validatorName);
}
}
if (validatorFn === undefined) { // throw when no validator could be resolved
throw new Error(`validator '${validatorName}' is not provided via NG_VALIDATORS, NG_ASYNC_VALIDATORS or DYNAMIC_FORM_VALIDATORS`);
}
if (validatorArgs !== null) {
return validatorFn(validatorArgs);
}
return validatorFn;
},
isValidatorDescriptor(value) {
if (this.isObject(value)) {
return value.hasOwnProperty('name') && value.hasOwnProperty('args');
}
return false;
},
isObject(value) {
return typeof value === 'object' && value !== null;
}
});
authService = new AuthServiceStub(); authService = new AuthServiceStub();
authorizationService = jasmine.createSpyObj('authorizationService', { authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true) isAuthorized: observableOf(true),
}); });
groupsDataService = jasmine.createSpyObj('groupsDataService', { groupsDataService = jasmine.createSpyObj('groupsDataService', {
findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -146,6 +215,131 @@ describe('EPersonFormComponent', () => {
expect(component).toBeDefined(); expect(component).toBeDefined();
}); });
describe('check form validation', () => {
let firstName;
let lastName;
let email;
let canLogIn;
let requireCertificate;
let expected;
beforeEach(() => {
firstName = 'testName';
lastName = 'testLastName';
email = 'testEmail@test.com';
canLogIn = false;
requireCertificate = false;
expected = Object.assign(new EPerson(), {
metadata: {
'eperson.firstname': [
{
value: firstName
}
],
'eperson.lastname': [
{
value: lastName
},
],
},
email: email,
canLogIn: canLogIn,
requireCertificate: requireCertificate,
});
spyOn(component.submitForm, 'emit');
component.canLogIn.value = canLogIn;
component.requireCertificate.value = requireCertificate;
fixture.detectChanges();
component.initialisePage();
fixture.detectChanges();
});
describe('firstName, lastName and email should be required', () => {
it('form should be invalid because the firstName is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.firstName.valid).toBeFalse();
expect(component.formGroup.controls.firstName.errors.required).toBeTrue();
});
}));
it('form should be invalid because the lastName is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.lastName.valid).toBeFalse();
expect(component.formGroup.controls.lastName.errors.required).toBeTrue();
});
}));
it('form should be invalid because the email is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.required).toBeTrue();
});
}));
});
describe('after inserting information firstName,lastName and email not required', () => {
beforeEach(() => {
component.formGroup.controls.firstName.setValue('test');
component.formGroup.controls.lastName.setValue('test');
component.formGroup.controls.email.setValue('test@test.com');
fixture.detectChanges();
});
it('firstName should be valid because the firstName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.firstName.valid).toBeTrue();
expect(component.formGroup.controls.firstName.errors).toBeNull();
});
}));
it('lastName should be valid because the lastName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.lastName.valid).toBeTrue();
expect(component.formGroup.controls.lastName.errors).toBeNull();
});
}));
it('email should be valid because the email is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeTrue();
expect(component.formGroup.controls.email.errors).toBeNull();
});
}));
});
describe('after inserting email wrong should show pattern validation error', () => {
beforeEach(() => {
component.formGroup.controls.email.setValue('test@test');
fixture.detectChanges();
});
it('email should not be valid because the email pattern', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.pattern).toBeTruthy();
});
}));
});
describe('after already utilized email', () => {
beforeEach(() => {
const ePersonServiceWithEperson = Object.assign(ePersonDataServiceStub,{
getEPersonByEmail(): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(EPersonMock);
}
});
component.formGroup.controls.email.setValue('test@test.com');
component.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(ePersonServiceWithEperson));
fixture.detectChanges();
});
it('email should not be valid because email is already taken', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
});
}));
});
});
describe('when submitting the form', () => { describe('when submitting the form', () => {
let firstName; let firstName;
let lastName; let lastName;

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import {
DynamicCheckboxModel, DynamicCheckboxModel,
@@ -8,7 +8,7 @@ import {
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators'; import { debounceTime, switchMap, take } from 'rxjs/operators';
import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -33,10 +33,11 @@ import { RequestService } from '../../../core/data/request.service';
import { NoContent } from '../../../core/shared/NoContent.model'; import { NoContent } from '../../../core/shared/NoContent.model';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
@Component({ @Component({
selector: 'ds-eperson-form', selector: 'ds-eperson-form',
templateUrl: './eperson-form.component.html' templateUrl: './eperson-form.component.html',
}) })
/** /**
* A form used for creating and editing EPeople * A form used for creating and editing EPeople
@@ -161,7 +162,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/ */
isImpersonated = false; isImpersonated = false;
constructor(public epersonService: EPersonDataService, /**
* Subscription to email field value change
*/
emailValueChangeSubscribe: Subscription;
constructor(protected changeDetectorRef: ChangeDetectorRef,
public epersonService: EPersonDataService,
public groupsDataService: GroupDataService, public groupsDataService: GroupDataService,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private translateService: TranslateService, private translateService: TranslateService,
@@ -187,6 +194,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
* This method will initialise the page * This method will initialise the page
*/ */
initialisePage() { initialisePage() {
observableCombineLatest( observableCombineLatest(
this.translateService.get(`${this.messagePrefix}.firstName`), this.translateService.get(`${this.messagePrefix}.firstName`),
this.translateService.get(`${this.messagePrefix}.lastName`), this.translateService.get(`${this.messagePrefix}.lastName`),
@@ -219,9 +227,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
name: 'email', name: 'email',
validators: { validators: {
required: null, required: null,
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$' pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$',
}, },
required: true, required: true,
errorMessages: {
emailTaken: 'error.validation.emailTaken',
pattern: 'error.validation.NotValidEmail'
},
hint: emailHint hint: emailHint
}); });
this.canLogIn = new DynamicCheckboxModel( this.canLogIn = new DynamicCheckboxModel(
@@ -260,11 +272,18 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
canLogIn: eperson != null ? eperson.canLogIn : true, canLogIn: eperson != null ? eperson.canLogIn : true,
requireCertificate: eperson != null ? eperson.requireCertificate : false requireCertificate: eperson != null ? eperson.requireCertificate : false
}); });
if (eperson === null && !!this.formGroup.controls.email) {
this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService));
this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => {
this.changeDetectorRef.detectChanges();
});
}
})); }));
const activeEPerson$ = this.epersonService.getActiveEPerson(); const activeEPerson$ = this.epersonService.getActiveEPerson();
this.groups = activeEPerson$.pipe( this.groups = activeEPerson$.pipe(
switchMap((eperson) => { switchMap((eperson) => {
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
currentPage: 1, currentPage: 1,
@@ -280,7 +299,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
); );
this.canImpersonate$ = activeEPerson$.pipe( this.canImpersonate$ = activeEPerson$.pipe(
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) switchMap((eperson) => {
if (hasValue(eperson)) {
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
} else {
return observableOf(false);
}
})
); );
this.canDelete$ = activeEPerson$.pipe( this.canDelete$ = activeEPerson$.pipe(
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
@@ -343,10 +368,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
getFirstCompletedRemoteData() getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<EPerson>) => { ).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', {name: ePersonToCreate.name})); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name }));
this.submitForm.emit(ePersonToCreate); this.submitForm.emit(ePersonToCreate);
} else { } else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', {name: ePersonToCreate.name})); this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: ePersonToCreate.name }));
this.cancelForm.emit(); this.cancelForm.emit();
} }
}); });
@@ -382,10 +407,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
const response = this.epersonService.updateEPerson(editedEperson); const response = this.epersonService.updateEPerson(editedEperson);
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<EPerson>) => { response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', {name: editedEperson.name})); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name }));
this.submitForm.emit(editedEperson); this.submitForm.emit(editedEperson);
} else { } else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', {name: editedEperson.name})); this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: editedEperson.name }));
this.cancelForm.emit(); this.cancelForm.emit();
} }
}); });
@@ -395,6 +420,87 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
} }
} }
/**
* Event triggered when the user changes page
* @param event
*/
onPageChange(event) {
this.updateGroups({
currentPage: event,
elementsPerPage: this.config.pageSize
});
}
/**
* Start impersonating the EPerson
*/
impersonate() {
this.authService.impersonate(this.epersonInitial.id);
this.isImpersonated = true;
}
/**
* Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing.
* It'll either show a success or error message depending on whether the delete was successful or not.
*/
delete() {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = eperson;
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) {
if (hasValue(eperson.id)) {
this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name }));
this.submitForm.emit();
} else {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
}
this.cancelForm.emit();
});
}
}
});
});
}
/**
* Stop impersonating the EPerson
*/
stopImpersonating() {
this.authService.stopImpersonatingAndRefresh();
this.isImpersonated = false;
}
/**
* Cancel the current edit when component is destroyed & unsub all subscriptions
*/
ngOnDestroy(): void {
this.onCancel();
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
this.paginationService.clearPagination(this.config.id);
if (hasValue(this.emailValueChangeSubscribe)) {
this.emailValueChangeSubscribe.unsubscribe();
}
}
/**
* This method will ensure that the page gets reset and that the cache is cleared
*/
reset() {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
this.requestService.removeByHrefSubstring(eperson.self);
});
this.initialisePage();
}
/** /**
* Checks for the given ePerson if there is already an ePerson in the system with that email * Checks for the given ePerson if there is already an ePerson in the system with that email
* and shows notification if this is the case * and shows notification if this is the case
@@ -417,17 +523,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
})); }));
} }
/**
* Event triggered when the user changes page
* @param event
*/
onPageChange(event) {
this.updateGroups({
currentPage: event,
elementsPerPage: this.config.pageSize
});
}
/** /**
* Update the list of groups by fetching it from the rest api or cache * Update the list of groups by fetching it from the rest api or cache
*/ */
@@ -436,71 +531,4 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options); this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options);
})); }));
} }
/**
* Start impersonating the EPerson
*/
impersonate() {
this.authService.impersonate(this.epersonInitial.id);
this.isImpersonated = true;
}
/**
* Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing.
* It'll either show a success or error message depending on whether the delete was successful or not.
*/
delete() {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = eperson;
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) {
if (hasValue(eperson.id)) {
this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name }));
this.submitForm.emit();
} else {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
}
this.cancelForm.emit();
});
}}
});
});
}
/**
* Stop impersonating the EPerson
*/
stopImpersonating() {
this.authService.stopImpersonatingAndRefresh();
this.isImpersonated = false;
}
/**
* Cancel the current edit when component is destroyed & unsub all subscriptions
*/
ngOnDestroy(): void {
this.onCancel();
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
this.paginationService.clearPagination(this.config.id);
}
/**
* This method will ensure that the page gets reset and that the cache is cleared
*/
reset() {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
this.requestService.removeByHrefSubstring(eperson.self);
});
this.initialisePage();
}
} }

View File

@@ -0,0 +1,25 @@
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { getFirstSucceededRemoteData, } from '../../../../core/shared/operators';
export class ValidateEmailNotTaken {
/**
* This method will create the validator with the ePersonDataService requested from component
* @param ePersonDataService the service with DI in the component that this validator is being utilized.
*/
static createValidator(ePersonDataService: EPersonDataService) {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return ePersonDataService.getEPersonByEmail(control.value)
.pipe(
getFirstSucceededRemoteData(),
map(res => {
return !!res.payload ? { emailTaken: true } : null;
})
);
};
}
}

View File

@@ -7,7 +7,11 @@ import { EffectsModule } from '@ngrx/effects';
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core'; import {
DYNAMIC_ERROR_MESSAGES_MATCHER,
DYNAMIC_MATCHER_PROVIDERS,
DynamicErrorMessagesMatcher
} from '@ng-dynamic-forms/core';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
@@ -52,6 +56,7 @@ import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
import { UUIDService } from './core/shared/uuid.service'; import { UUIDService } from './core/shared/uuid.service';
import { CookieService } from './core/services/cookie.service'; import { CookieService } from './core/services/cookie.service';
import { AbstractControl } from '@angular/forms';
export function getBase() { export function getBase() {
return environment.ui.nameSpace; return environment.ui.nameSpace;
@@ -61,6 +66,14 @@ export function getMetaReducers(): MetaReducer<AppState>[] {
return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers; return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers;
} }
/**
* Condition for displaying error messages on email form field
*/
export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher =
(control: AbstractControl, model: any, hasFocus: boolean) => {
return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus);
};
const IMPORTS = [ const IMPORTS = [
CommonModule, CommonModule,
SharedModule, SharedModule,
@@ -146,6 +159,10 @@ const PROVIDERS = [
multi: true, multi: true,
deps: [ CookieService, UUIDService ] deps: [ CookieService, UUIDService ]
}, },
{
provide: DYNAMIC_ERROR_MESSAGES_MATCHER,
useValue: ValidateEmailErrorStateMatcher
},
...DYNAMIC_MATCHER_PROVIDERS, ...DYNAMIC_MATCHER_PROVIDERS,
]; ];

View File

@@ -1290,6 +1290,11 @@
"error.validation.filerequired": "The file upload is mandatory", "error.validation.filerequired": "The file upload is mandatory",
"error.validation.required": "This field is required",
"error.validation.NotValidEmail": "This E-mail is not a valid email",
"error.validation.emailTaken": "This E-mail is already taken",
"file-section.error.header": "Error obtaining files for this item", "file-section.error.header": "Error obtaining files for this item",