From 892a985156815572dd803ba993cb019bef2f697e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 11 Mar 2020 14:32:38 +0100 Subject: [PATCH] 69432: Functional profile security form + notifications + metadata form refactoring --- resources/i18n/en.json5 | 14 +++++ src/app/core/eperson/eperson-data.service.ts | 2 +- .../profile-page-metadata-form.component.html | 1 + .../profile-page-metadata-form.component.ts | 49 ++++++++-------- .../profile-page-security-form.component.html | 2 + .../profile-page-security-form.component.ts | 57 +++++++++++++++++-- .../profile-page/profile-page.component.html | 2 +- .../profile-page/profile-page.component.ts | 20 ++++++- 8 files changed, 116 insertions(+), 31 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index a9cee1bd6f..63e2b161fe 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -1441,12 +1441,26 @@ "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.label.password": "Password", "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", diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index ef2e76c7c6..6a46b20792 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -20,7 +20,7 @@ import { EPERSON } from './models/eperson.resource-type'; @dataService(EPERSON) export class EPersonDataService extends DataService { - protected linkPath: 'epersons'; + protected linkPath = 'epersons'; constructor( protected requestService: RequestService, diff --git a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.html b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.html index 8f68b82b7b..c1c1cff0f3 100644 --- a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.html +++ b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.html @@ -1,5 +1,6 @@ diff --git a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts index cf59f2d331..dbd49b802a 100644 --- a/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts +++ b/src/app/profile-page/profile-page-metadata-form/profile-page-metadata-form.component.ts @@ -29,12 +29,6 @@ export class ProfilePageMetadataFormComponent implements OnInit { */ @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[] = [ new DynamicInputModel({ id: 'email', @@ -91,7 +85,6 @@ export class ProfilePageMetadataFormComponent implements OnInit { constructor(@Inject(GLOBAL_CONFIG) protected config: GlobalConfig, protected location: Location, - protected formService: FormService, protected formBuilderService: FormBuilderService, protected translate: TranslateService, protected epersonService: EPersonDataService, @@ -139,37 +132,47 @@ export class ProfilePageMetadataFormComponent implements OnInit { ); } - updateProfile() { - if (!this.formRef.formGroup.valid) { - this.formService.validateAllFormFields(this.formGroup); - return; + updateProfile(): boolean { + if (!this.formGroup.valid) { + return false; } const newMetadata = cloneDeep(this.user.metadata); + let changed = false; this.formModel.filter((fieldModel) => fieldModel.id !== 'email').forEach((fieldModel: DynamicFormValueControlModel) => { if (newMetadata.hasOwnProperty(fieldModel.name) && newMetadata[fieldModel.name].length > 0) { 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 { newMetadata[fieldModel.name] = []; + changed = true; } } else if (hasValue(fieldModel.value)) { newMetadata[fieldModel.name] = [{ value: fieldModel.value, language: null } as any]; + changed = true; } }); - this.epersonService.update(Object.assign(cloneDeep(this.user), { metadata: newMetadata })).pipe( - getSucceededRemoteData(), - getRemoteDataPayload() - ).subscribe((user) => { - this.user = user; - this.setFormValues(); - this.notificationsService.success( - this.translate.instant(this.NOTIFICATION_PREFIX + 'success.title'), - this.translate.instant(this.NOTIFICATION_PREFIX + 'success.content') - ); - }); + + if (changed) { + this.epersonService.update(Object.assign(cloneDeep(this.user), {metadata: newMetadata})).pipe( + getSucceededRemoteData(), + getRemoteDataPayload() + ).subscribe((user) => { + this.user = user; + this.setFormValues(); + this.notificationsService.success( + this.translate.instant(this.NOTIFICATION_PREFIX + 'success.title'), + this.translate.instant(this.NOTIFICATION_PREFIX + 'success.content') + ); + }); + } + + return changed; } } diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html index 59c6f5c248..81519e5a42 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html @@ -2,5 +2,7 @@ +
{{'profile.security.form.error.matching-passwords' | translate}}
diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts index dd5ac478e2..d5a8d358b8 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts @@ -1,17 +1,26 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { DynamicFormControlModel, - DynamicFormService, + DynamicFormService, DynamicFormValueControlModel, DynamicInputModel } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; 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({ selector: 'ds-profile-page-security-form', templateUrl: './profile-page-security-form.component.html' }) export class ProfilePageSecurityFormComponent implements OnInit { + /** + * The user to display the form for + */ + @Input() user: EPerson; formModel: DynamicFormControlModel[] = [ new DynamicInputModel({ @@ -31,14 +40,18 @@ export class ProfilePageSecurityFormComponent implements OnInit { */ formGroup: FormGroup; + NOTIFICATIONS_PREFIX = 'profile.security.form.notifications.'; + LABEL_PREFIX = 'profile.security.form.label.'; constructor(protected formService: DynamicFormService, - protected translate: TranslateService) { + protected translate: TranslateService, + protected epersonService: EPersonDataService, + protected notificationsService: NotificationsService) { } ngOnInit(): void { - this.formGroup = this.formService.createFormGroup(this.formModel); + this.formGroup = this.formService.createFormGroup(this.formModel, { validators: this.checkPasswords }); this.updateFieldTranslations(); this.translate.onLangChange .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; + } } diff --git a/src/app/profile-page/profile-page.component.html b/src/app/profile-page/profile-page.component.html index 3b0b2d712b..1d1112f52e 100644 --- a/src/app/profile-page/profile-page.component.html +++ b/src/app/profile-page/profile-page.component.html @@ -10,7 +10,7 @@
{{'profile.card.security' | translate}}
- +
diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index eb182aab4e..1b0baddbee 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -5,6 +5,9 @@ import { select, Store } from '@ngrx/store'; import { getAuthenticatedUser } from '../core/auth/selectors'; import { AppState } from '../app.reducer'; 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({ selector: 'ds-profile-page', @@ -17,12 +20,18 @@ export class ProfilePageComponent implements OnInit { @ViewChild(ProfilePageMetadataFormComponent, { static: false }) metadataForm: ProfilePageMetadataFormComponent; + @ViewChild(ProfilePageSecurityFormComponent, { static: false }) securityForm: ProfilePageSecurityFormComponent; + /** * The authenticated user */ user$: Observable; - constructor(private store: Store) { + NOTIFICATIONS_PREFIX = 'profile.notifications.'; + + constructor(private store: Store, + private notificationsService: NotificationsService, + private translate: TranslateService) { } ngOnInit(): void { @@ -30,6 +39,13 @@ export class ProfilePageComponent implements OnInit { } 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') + ); + } } }