267 lines
10 KiB
TypeScript
267 lines
10 KiB
TypeScript
import {Component, Input, OnInit, Output} from '@angular/core';
|
|
import {NbDialogRef} from '@nebular/theme';
|
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
|
import {TranslateService} from '@ngx-translate/core';
|
|
import {AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
|
import {ProfilePasswordFormData} from '@shared/modules/profile-settings/password-input-from/util/profile-password-form-data.model';
|
|
import {Patterns} from '@shared/modules/profile-settings/patterns';
|
|
import {LanguageOptions} from '@shared/modules/export-report-dialog/export-report-dialog.component';
|
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
|
import {User} from '@shared/models/user.model';
|
|
import {UserService} from '@shared/services/user-service/user.service';
|
|
import {BehaviorSubject, Observable, of} from 'rxjs';
|
|
import deepEqual from 'deep-equal';
|
|
import {UpdateUser, UpdateUserSettings} from '@shared/stores/session-state/session-state.actions';
|
|
import {mapTo, tap} from 'rxjs/operators';
|
|
import {Store} from '@ngxs/store';
|
|
import {SessionState} from '@shared/stores/session-state/session-state';
|
|
|
|
@Component({
|
|
selector: 'app-profile-settings',
|
|
templateUrl: './profile-settings.component.html',
|
|
styleUrls: ['./profile-settings.component.scss']
|
|
})
|
|
@UntilDestroy()
|
|
export class ProfileSettingsComponent implements OnInit {
|
|
/**
|
|
* @param data contains all relevant information the dialog needs
|
|
* @param data.title The translation key for the dialog title
|
|
* @param data.key The translation key for the shown message
|
|
* @param data.data The data that may be used in the message translation key
|
|
*/
|
|
@Input() data: any;
|
|
user: BehaviorSubject<User> = new BehaviorSubject<User>(null);
|
|
|
|
@Output() userServiceError: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
|
|
// HTML only
|
|
readonly fa = FA;
|
|
readonly USER_IMG = 'assets/images/demo/anon-user-icon.png';
|
|
|
|
// User form
|
|
userFormGroup: UntypedFormGroup;
|
|
userNameControl: AbstractControl;
|
|
userFirstNameControl: AbstractControl;
|
|
userLastNameControl: AbstractControl;
|
|
userEmailControl: AbstractControl;
|
|
oldUserData: UserProfileFormData;
|
|
|
|
// Password form
|
|
passwordFormGroup: UntypedFormGroup;
|
|
userPasswordInputControl: AbstractControl;
|
|
private readonly initialPasswordInput: ProfilePasswordFormData = {oldPassword: '', newPassword: '', confirmNewPassword: ''};
|
|
passwordStrong: boolean;
|
|
oldPasswordData: ProfilePasswordFormData;
|
|
|
|
|
|
// Language change
|
|
profileLanguageControl = new UntypedFormControl(LanguageOptions.ENGLISH);
|
|
profileLanguages = LanguageOptions;
|
|
|
|
constructor(protected dialogRef: NbDialogRef<any>,
|
|
private fb: UntypedFormBuilder,
|
|
private store: Store,
|
|
private userService: UserService,
|
|
private translateService: TranslateService) {
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.profileLanguageControl.setValue(this.translateService.currentLang);
|
|
this.setupUserFormGroup();
|
|
// Load user profile
|
|
this.userService.loadUserProfile().pipe(
|
|
untilDestroyed(this)
|
|
).subscribe({
|
|
next: (user: User) => {
|
|
this.user.next(user);
|
|
this.userNameControl.setValue(user.username);
|
|
this.userFirstNameControl.setValue(user.firstName);
|
|
this.userLastNameControl.setValue(user.lastName);
|
|
this.userEmailControl.setValue(user.email ? user.email : null);
|
|
},
|
|
error: err => {
|
|
console.error(err);
|
|
}
|
|
});
|
|
this.oldUserData = Object.assign({}, this.userFormGroup.getRawValue());
|
|
// Setup password
|
|
this.setupPasswordFormGroup();
|
|
this.oldPasswordData = Object.assign({}, this.passwordFormGroup.getRawValue());
|
|
}
|
|
|
|
setupUserFormGroup(): void {
|
|
this.userFormGroup = this.fb.group({
|
|
username: [{value: '', disabled: true}, [Validators.required, Validators.pattern(Patterns.NO_WHITESPACES)]],
|
|
firstName: [{value: '', disabled: true}, [Validators.required, Validators.pattern(Patterns.NO_WHITESPACES)]],
|
|
lastName: [{value: '', disabled: true}, [Validators.required, Validators.pattern(Patterns.NO_WHITESPACES)]],
|
|
eMail: [{value: '', disabled: true}, [Validators.required, Validators.email, Validators.pattern(Patterns.NO_WHITESPACES)]],
|
|
avatarUploader: null
|
|
});
|
|
// Get form controls
|
|
this.userNameControl = this.userFormGroup.get('username');
|
|
this.userFirstNameControl = this.userFormGroup.get('firstName');
|
|
this.userLastNameControl = this.userFormGroup.get('lastName');
|
|
this.userEmailControl = this.userFormGroup.get('eMail');
|
|
}
|
|
|
|
setupPasswordFormGroup(): void {
|
|
this.passwordFormGroup = this.fb.group({
|
|
passwordInput: this.initialPasswordInput
|
|
});
|
|
// get password-form control
|
|
this.userPasswordInputControl = this.passwordFormGroup.get('passwordInput');
|
|
}
|
|
|
|
changePasswordInKeycloak(): void {
|
|
this.userService.redirectToChangePasswordAction().then(r => {
|
|
// tslint:disable-next-line:no-console
|
|
console.info('Redirecting to Keycloak for password change...');
|
|
});
|
|
}
|
|
|
|
onClickLanguage(language: string): void {
|
|
this.translateService.use(language);
|
|
// ToDo: Update userAccount language property
|
|
const user = this.store.selectSnapshot(SessionState.userAccount);
|
|
this.store.dispatch(new UpdateUserSettings({...user, interfaceLang: language}));
|
|
}
|
|
|
|
onClickConfirm(): void {
|
|
// ToDo: use handleUserUpdate() here
|
|
const userFormData: UserProfileFormData = this.userFormGroup.getRawValue();
|
|
// ToDo: use handlePasswordChange() here
|
|
const passwordFormData: ProfilePasswordFormData = this.passwordFormGroup.getRawValue();
|
|
// tslint:disable-next-line:no-console
|
|
console.info('User', userFormData);
|
|
// tslint:disable-next-line:no-console
|
|
console.info('Password', passwordFormData);
|
|
// ToDo: Fix?
|
|
if (this.allowChangeProfileData() && this.allowChangePassword()) {
|
|
const formAndUser: [UserProfileFormData, User] = this.extractFormAndUser();
|
|
this.handleUserUpdate(formAndUser['1']).subscribe({
|
|
complete: () => {
|
|
this.handlePasswordChange();
|
|
}
|
|
});
|
|
}
|
|
// Changes occur only on the profile data without the password
|
|
else if (this.allowChangeProfileData()) {
|
|
const formAndUser: [UserProfileFormData, User] = this.extractFormAndUser();
|
|
this.handleUserUpdate(formAndUser['1']).subscribe({
|
|
complete: () => this.dialogRef.close({confirm: true})
|
|
});
|
|
}
|
|
// Changes occur only on the password
|
|
else if (this.allowChangePassword()) {
|
|
this.handlePasswordChange();
|
|
}
|
|
// this.dialogRef.close({confirm: true});
|
|
}
|
|
|
|
private handleUserUpdate(user: User): Observable<void> {
|
|
return this.userService.changeUserProperties(user)
|
|
.pipe(
|
|
tap({
|
|
next: resultingUser => {
|
|
this.store.dispatch(new UpdateUserSettings(resultingUser));
|
|
this.store.dispatch(new UpdateUser(resultingUser, true));
|
|
},
|
|
error: error => {
|
|
console.error(error);
|
|
}
|
|
}),
|
|
mapTo(void 0),
|
|
untilDestroyed(this)
|
|
);
|
|
}
|
|
|
|
private handlePasswordChange(): void {
|
|
const formData = this.passwordFormGroup.getRawValue();
|
|
if (formData && formData.passwordInput) {
|
|
const oldPassword = formData.passwordInput.oldPassword;
|
|
const newPassword = formData.passwordInput.newPassword;
|
|
// ToDo: Fix connection to keycloak
|
|
/*this.userService.getCurrentAuthenticatedUser().pipe(
|
|
switchMap((currentUser: CognitoUser) => {
|
|
return this.userService.changePassword(currentUser, oldPassword, newPassword).pipe(
|
|
catchError((err) => {
|
|
if (err && 'message' in err) {
|
|
if (err.code === 'LimitExceededException') {
|
|
this.notificationService.showPopup('userProfile.password.limitExceeded', PopupType.FAILURE);
|
|
} else {
|
|
this.notificationService.showPopup('userProfile.password.invalidPassword', PopupType.FAILURE);
|
|
}
|
|
}
|
|
this.userServiceError.next(true);
|
|
return throwError(err);
|
|
}),
|
|
tap(() => {
|
|
this.notificationService.showPopup('userProfile.password.changePasswordSuccess', PopupType.SUCCESS);
|
|
this.userDialogRef.close('confirm');
|
|
})
|
|
);
|
|
}),
|
|
).subscribe({
|
|
error: err => console.error(err)
|
|
});*/
|
|
}
|
|
}
|
|
|
|
onClickCancel(): void {
|
|
this.dialogRef.close();
|
|
}
|
|
|
|
extractFormAndUser(): [UserProfileFormData, User] {
|
|
const user: User = Object.assign(new User(), this.store.selectSnapshot(SessionState.userAccount));
|
|
const formData: UserProfileFormData = this.userFormGroup.getRawValue();
|
|
user.username = formData.username;
|
|
user.firstName = formData.firstName;
|
|
user.lastName = formData.lastName;
|
|
user.email = formData.email;
|
|
|
|
return [formData, user];
|
|
}
|
|
|
|
/**
|
|
* Checks for valid changes on both the change-profile-data and the change-password forms.
|
|
* @return true if we have one of the followings:
|
|
* 1. Valid changes on both forms
|
|
* 2. Valid changes on change-password form
|
|
* 3. Valid changes on change-profile-data form with empty old, new and confirm passwords
|
|
*/
|
|
allowConfirm(): boolean {
|
|
const userFormValid = this.allowChangeProfileData();
|
|
|
|
const passwordFormData: ProfilePasswordFormData = this.passwordFormGroup.getRawValue().passwordInput;
|
|
const passwordFormValid = this.allowChangePassword();
|
|
const passwordFormEmpty = (deepEqual(passwordFormData.oldPassword, '')
|
|
&& deepEqual(passwordFormData.newPassword, '')
|
|
&& deepEqual(passwordFormData.confirmNewPassword, ''));
|
|
|
|
return (userFormValid && passwordFormValid) || (passwordFormValid) || (userFormValid && passwordFormEmpty);
|
|
}
|
|
|
|
/**
|
|
* @return true if the change-profile-data form is valid and there are actual changes.
|
|
*/
|
|
allowChangeProfileData(): boolean {
|
|
const formData: UserProfileFormData = this.userFormGroup.getRawValue();
|
|
return this.userFormGroup.dirty && this.userFormGroup.valid && !deepEqual(formData, this.oldUserData);
|
|
}
|
|
|
|
/**
|
|
* @return true if the change-password form is valid and the password is strong.
|
|
*/
|
|
allowChangePassword(): boolean {
|
|
return this.passwordStrong && this.passwordFormGroup.valid && this.userFormGroup.dirty;
|
|
}
|
|
}
|
|
|
|
export interface UserProfileFormData {
|
|
username: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
avatarUploader: FileList;
|
|
}
|