69432: Functional profile security form + notifications + metadata form refactoring

This commit is contained in:
Kristof De Langhe
2020-03-11 14:32:38 +01:00
parent 1f1846c487
commit 892a985156
8 changed files with 116 additions and 31 deletions

View File

@@ -1441,12 +1441,26 @@
"profile.metadata.form.notifications.success.title": "Profile saved", "profile.metadata.form.notifications.success.title": "Profile saved",
"profile.notifications.warning.no-changes.content": "No changes were made to the Profile.",
"profile.notifications.warning.no-changes.title": "No changes",
"profile.security.form.error.matching-passwords": "The passwords do not match.",
"profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.",
"profile.security.form.label.password": "Password", "profile.security.form.label.password": "Password",
"profile.security.form.label.passwordrepeat": "Retype to confirm", "profile.security.form.label.passwordrepeat": "Retype to confirm",
"profile.security.form.notifications.success.content": "Successfully changed password.",
"profile.security.form.notifications.success.title": "Password changed",
"profile.security.form.notifications.error.title": "Error changing passwords",
"profile.security.form.notifications.error.not-same": "The provided passwords are not the same.",
"profile.title": "Update Profile", "profile.title": "Update Profile",

View File

@@ -20,7 +20,7 @@ import { EPERSON } from './models/eperson.resource-type';
@dataService(EPERSON) @dataService(EPERSON)
export class EPersonDataService extends DataService<EPerson> { export class EPersonDataService extends DataService<EPerson> {
protected linkPath: 'epersons'; protected linkPath = 'epersons';
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,

View File

@@ -1,5 +1,6 @@
<ds-form *ngIf="formModel" <ds-form *ngIf="formModel"
[formId]="'profile-page-metadata-form-id'" [formId]="'profile-page-metadata-form-id'"
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup"
[displaySubmit]="false"> [displaySubmit]="false">
</ds-form> </ds-form>

View File

@@ -29,12 +29,6 @@ export class ProfilePageMetadataFormComponent implements OnInit {
*/ */
@Input() user: EPerson; @Input() user: EPerson;
/**
* Reference to the form component
* Used for validating the form before sending update requests
*/
@ViewChild(FormComponent, { static: false }) formRef: FormComponent;
formModel: DynamicFormControlModel[] = [ formModel: DynamicFormControlModel[] = [
new DynamicInputModel({ new DynamicInputModel({
id: 'email', id: 'email',
@@ -91,7 +85,6 @@ export class ProfilePageMetadataFormComponent implements OnInit {
constructor(@Inject(GLOBAL_CONFIG) protected config: GlobalConfig, constructor(@Inject(GLOBAL_CONFIG) protected config: GlobalConfig,
protected location: Location, protected location: Location,
protected formService: FormService,
protected formBuilderService: FormBuilderService, protected formBuilderService: FormBuilderService,
protected translate: TranslateService, protected translate: TranslateService,
protected epersonService: EPersonDataService, protected epersonService: EPersonDataService,
@@ -139,37 +132,47 @@ export class ProfilePageMetadataFormComponent implements OnInit {
); );
} }
updateProfile() { updateProfile(): boolean {
if (!this.formRef.formGroup.valid) { if (!this.formGroup.valid) {
this.formService.validateAllFormFields(this.formGroup); return false;
return;
} }
const newMetadata = cloneDeep(this.user.metadata); const newMetadata = cloneDeep(this.user.metadata);
let changed = false;
this.formModel.filter((fieldModel) => fieldModel.id !== 'email').forEach((fieldModel: DynamicFormValueControlModel<string>) => { this.formModel.filter((fieldModel) => fieldModel.id !== 'email').forEach((fieldModel: DynamicFormValueControlModel<string>) => {
if (newMetadata.hasOwnProperty(fieldModel.name) && newMetadata[fieldModel.name].length > 0) { if (newMetadata.hasOwnProperty(fieldModel.name) && newMetadata[fieldModel.name].length > 0) {
if (hasValue(fieldModel.value)) { if (hasValue(fieldModel.value)) {
newMetadata[fieldModel.name][0].value = fieldModel.value; if (newMetadata[fieldModel.name][0].value !== fieldModel.value) {
newMetadata[fieldModel.name][0].value = fieldModel.value;
changed = true;
}
} else { } else {
newMetadata[fieldModel.name] = []; newMetadata[fieldModel.name] = [];
changed = true;
} }
} else if (hasValue(fieldModel.value)) { } else if (hasValue(fieldModel.value)) {
newMetadata[fieldModel.name] = [{ newMetadata[fieldModel.name] = [{
value: fieldModel.value, value: fieldModel.value,
language: null language: null
} as any]; } as any];
changed = true;
} }
}); });
this.epersonService.update(Object.assign(cloneDeep(this.user), { metadata: newMetadata })).pipe(
getSucceededRemoteData(), if (changed) {
getRemoteDataPayload() this.epersonService.update(Object.assign(cloneDeep(this.user), {metadata: newMetadata})).pipe(
).subscribe((user) => { getSucceededRemoteData(),
this.user = user; getRemoteDataPayload()
this.setFormValues(); ).subscribe((user) => {
this.notificationsService.success( this.user = user;
this.translate.instant(this.NOTIFICATION_PREFIX + 'success.title'), this.setFormValues();
this.translate.instant(this.NOTIFICATION_PREFIX + 'success.content') this.notificationsService.success(
); this.translate.instant(this.NOTIFICATION_PREFIX + 'success.title'),
}); this.translate.instant(this.NOTIFICATION_PREFIX + 'success.content')
);
});
}
return changed;
} }
} }

View File

@@ -2,5 +2,7 @@
<ds-form *ngIf="formModel" <ds-form *ngIf="formModel"
[formId]="'profile-page-security-form-id'" [formId]="'profile-page-security-form-id'"
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup"
[displaySubmit]="false"> [displaySubmit]="false">
</ds-form> </ds-form>
<div class="container-fluid text-danger" *ngIf="formGroup.hasError('notSame')">{{'profile.security.form.error.matching-passwords' | translate}}</div>

View File

@@ -1,17 +1,26 @@
import { Component, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { import {
DynamicFormControlModel, DynamicFormControlModel,
DynamicFormService, DynamicFormService, DynamicFormValueControlModel,
DynamicInputModel DynamicInputModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { ErrorResponse, RestResponse } from '../../core/cache/response.models';
import { NotificationsService } from '../../shared/notifications/notifications.service';
@Component({ @Component({
selector: 'ds-profile-page-security-form', selector: 'ds-profile-page-security-form',
templateUrl: './profile-page-security-form.component.html' templateUrl: './profile-page-security-form.component.html'
}) })
export class ProfilePageSecurityFormComponent implements OnInit { export class ProfilePageSecurityFormComponent implements OnInit {
/**
* The user to display the form for
*/
@Input() user: EPerson;
formModel: DynamicFormControlModel[] = [ formModel: DynamicFormControlModel[] = [
new DynamicInputModel({ new DynamicInputModel({
@@ -31,14 +40,18 @@ export class ProfilePageSecurityFormComponent implements OnInit {
*/ */
formGroup: FormGroup; formGroup: FormGroup;
NOTIFICATIONS_PREFIX = 'profile.security.form.notifications.';
LABEL_PREFIX = 'profile.security.form.label.'; LABEL_PREFIX = 'profile.security.form.label.';
constructor(protected formService: DynamicFormService, constructor(protected formService: DynamicFormService,
protected translate: TranslateService) { protected translate: TranslateService,
protected epersonService: EPersonDataService,
protected notificationsService: NotificationsService) {
} }
ngOnInit(): void { ngOnInit(): void {
this.formGroup = this.formService.createFormGroup(this.formModel); this.formGroup = this.formService.createFormGroup(this.formModel, { validators: this.checkPasswords });
this.updateFieldTranslations(); this.updateFieldTranslations();
this.translate.onLangChange this.translate.onLangChange
.subscribe(() => { .subscribe(() => {
@@ -53,4 +66,40 @@ export class ProfilePageSecurityFormComponent implements OnInit {
} }
); );
} }
checkPasswords(group: FormGroup) {
const pass = group.get('password').value;
const repeatPass = group.get('passwordrepeat').value;
return isEmpty(repeatPass) || pass === repeatPass ? null : { notSame: true };
}
updateSecurity() {
const pass = this.formGroup.get('password').value;
const passEntered = isNotEmpty(pass);
if (!this.formGroup.valid) {
if (passEntered) {
this.notificationsService.error(this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.not-same'));
return true;
}
return false;
}
if (passEntered) {
const operation = Object.assign({ op: 'replace', path: '/password', value: pass });
this.epersonService.immediatePatch(this.user, [operation]).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.notificationsService.success(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'success.title'),
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'success.content')
);
} else {
this.notificationsService.error(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.title'), (response as ErrorResponse).errorMessage
);
}
});
}
return passEntered;
}
} }

View File

@@ -10,7 +10,7 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header">{{'profile.card.security' | translate}}</div> <div class="card-header">{{'profile.card.security' | translate}}</div>
<div class="card-body"> <div class="card-body">
<ds-profile-page-security-form></ds-profile-page-security-form> <ds-profile-page-security-form [user]="user"></ds-profile-page-security-form>
</div> </div>
</div> </div>
<button class="btn btn-outline-primary" (click)="updateProfile()">{{'profile.form.submit' | translate}}</button> <button class="btn btn-outline-primary" (click)="updateProfile()">{{'profile.form.submit' | translate}}</button>

View File

@@ -5,6 +5,9 @@ import { select, Store } from '@ngrx/store';
import { getAuthenticatedUser } from '../core/auth/selectors'; import { getAuthenticatedUser } from '../core/auth/selectors';
import { AppState } from '../app.reducer'; import { AppState } from '../app.reducer';
import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component'; import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component';
import { ProfilePageSecurityFormComponent } from './profile-page-security-form/profile-page-security-form.component';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'ds-profile-page', selector: 'ds-profile-page',
@@ -17,12 +20,18 @@ export class ProfilePageComponent implements OnInit {
@ViewChild(ProfilePageMetadataFormComponent, { static: false }) metadataForm: ProfilePageMetadataFormComponent; @ViewChild(ProfilePageMetadataFormComponent, { static: false }) metadataForm: ProfilePageMetadataFormComponent;
@ViewChild(ProfilePageSecurityFormComponent, { static: false }) securityForm: ProfilePageSecurityFormComponent;
/** /**
* The authenticated user * The authenticated user
*/ */
user$: Observable<EPerson>; user$: Observable<EPerson>;
constructor(private store: Store<AppState>) { NOTIFICATIONS_PREFIX = 'profile.notifications.';
constructor(private store: Store<AppState>,
private notificationsService: NotificationsService,
private translate: TranslateService) {
} }
ngOnInit(): void { ngOnInit(): void {
@@ -30,6 +39,13 @@ export class ProfilePageComponent implements OnInit {
} }
updateProfile() { updateProfile() {
this.metadataForm.updateProfile(); const metadataChanged = this.metadataForm.updateProfile();
const securityChanged = this.securityForm.updateSecurity();
if (!metadataChanged && !securityChanged) {
this.notificationsService.warning(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'warning.no-changes.title'),
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'warning.no-changes.content')
);
}
} }
} }