argos/dmp-frontend/src/app/ui/user-profile/user-profile.component.ts

395 lines
14 KiB
TypeScript

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { RoleOrganizationType } from '@app/core/common/enum/role-organization-type';
import { CultureInfo } from '@app/core/model/culture-info';
import { Reference } from '@app/core/model/reference/reference';
import { User, UserCredential } from '@app/core/model/user/user';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { CultureService } from '@app/core/services/culture/culture-service';
import { LanguageService } from '@app/core/services/language/language.service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { PopupNotificationDialogComponent } from "@app/library/notification/popup/popup-notification.component";
import { BaseComponent } from '@common/base/base.component';
import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment-timezone';
import { Observable, of } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { AddAccountDialogComponent } from './add-account/add-account-dialog.component';
import { UserProfileEditorModel } from './user-profile-editor.model';
import { nameof } from 'ts-simple-nameof';
import { Guid } from '@common/types/guid';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss'],
})
export class UserProfileComponent extends BaseComponent implements OnInit, OnDestroy {
userProfileEditorModel: UserProfileEditorModel;
user: Observable<User>;
//TODO: refactor
userCredentials: Observable<UserCredential[]>;
firstEmail: String;
userLanguage: String;
currentUserId: string;
cultures: Observable<CultureInfo[]>;
timezones: Observable<any[]>;
editMode = false;
languages = [];
roleOrganizationEnum = RoleOrganizationType;
errorMessages = [];
nestedCount = [];
nestedIndex = 0;
organisationsAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = {
filterFn: this.filterOrganisations.bind(this),
initialItems: (excludedItems: any[]) => this.filterOrganisations('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))),
displayFn: (item) => item['name'],
titleFn: (item) => item['name'],
subtitleFn: (item) => item['tag'] ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item['tag'] : (item['key'] ? this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.SOURCE:') + item['key'] : this.language.instant('TYPES.EXTERNAL-DATASET-TYPE.NO-SOURCE'))
};
formGroup: UntypedFormGroup;
constructor(
private userService: UserService,
private route: ActivatedRoute,
private router: Router,
private authService: AuthService,
private language: TranslateService,
private cultureService: CultureService,
private authentication: AuthService,
private languageService: LanguageService,
private configurationService: ConfigurationService,
private uiNotificationService: UiNotificationService,
private dialog: MatDialog,
public enumUtils: EnumUtils,
private httpClient: HttpClient,
private matomoService: MatomoService
) {
super();
this.languages = this.languageService.getAvailableLanguagesCodes();
}
public getProviderIcons(userCredential: UserCredential, culture:string): string[] {
if (userCredential.data.externalProviderNames === undefined || userCredential.data.externalProviderNames?.length === 0) {
return [this.configurationService.authProviders.defaultAuthProvider.providerClass];
}
const providerNames: string[] = [];
for (let providerName of userCredential.data.externalProviderNames) {
const providerImage = this.configurationService.authProviders.findOrGetDefault(providerName.toString(), culture).providerClass;
if (providerImage !== null) {
providerNames.push(providerImage);
}
}
return providerNames;
}
public getProviderIcon(providerName:string, culture:string): string {
return this.configurationService.authProviders.find(providerName, culture).providerClass;
}
ngOnInit() {
this.matomoService.trackPageView('User Profile');
this.route.params
.pipe(takeUntil(this._destroyed))
.subscribe((params: Params) => {
this.currentUserId = this.authService.userId()?.toString();
this.user = this.userService.getSingle(
Guid.parse(this.currentUserId),
[
nameof<User>(x => x.id),
nameof<User>(x => x.name),
nameof<User>(x => x.additionalInfo.language),
nameof<User>(x => x.additionalInfo.timezone),
nameof<User>(x => x.additionalInfo.culture),
nameof<User>(x => x.additionalInfo.organization),
nameof<User>(x => x.additionalInfo.roleOrganization),
`${nameof<User>(x => x.credentials)}.${nameof<UserCredential>(x => x.data.email)}`,
`${nameof<User>(x => x.credentials)}.${nameof<UserCredential>(x => x.data.externalProviderNames)}`,
]
)
.pipe(map(result => {
//tested ui with fake data
// const fakecredentials: UserCredential = {
// "id": Guid.createEmpty(),
// "externalId": "123",
// "user": null,
// "createdAt": new Date(),
// "data": {
// "email": "dmpadmin@dmp.com",
// "externalProviderNames": ["Google", "Facebook"]
// }
// };
// result.credentials.push(fakecredentials);
// result.credentials[0].data.externalProviderNames = ['Google'];
this.userLanguage = result.additionalInfo.language;
this.firstEmail = result.credentials[0].data.email;
this.userCredentials = of(result.credentials);
this.userProfileEditorModel = new UserProfileEditorModel().fromModel(result);
this.formGroup = this.userProfileEditorModel.buildForm(this.languageService.getAvailableLanguagesCodes());
//this.formGroup.get('language').valueChanges.pipe(takeUntil(this._destroyed)).subscribe(x => { if (x) this.translate.use(x.value) })
this.formGroup.get('timezone').valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(x => { if (x) { this.timezones = this._filterTimezone(x); } });
this.formGroup.get('culture').valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(x => { if (x) { this.cultures = this._filterCulture(x); } });
// this.initializeDisabledFormGroup();
this.unlock();
return result;
}));
//TODO: refactor
// this.userService.getEmails(userId).pipe(takeUntil(this._destroyed))
// .subscribe(result => {
// this.user.subscribe(x => {
// const mainEmail = result.filter(el => el.email === x.email)
// const otherEmails = result.filter(el => el.email !== x.email)
// this.userCredentials = [...mainEmail, ...otherEmails];
// }
// )
// });
});
}
ngOnDestroy(): void {
}
public hasProvider(userCredential: UserCredential, provider: string): boolean {
const result = userCredential.data.externalProviderNames?.find(x => x == provider);
return result != null;
}
private filterOrganisations(value: string): Observable<Reference[]> {
//TODO: refactor
// return this.externalSourcesService.searchDMPOrganizations(value);
return null;
}
private _filterTimezone(value: string): Observable<any[]> {
if (value && typeof value === 'string') {
const filterValue = value.toLowerCase();
return of(moment.tz.names().filter(option => option.toLowerCase().includes(filterValue)));
} else {
return of(moment.tz.names());
}
}
private _filterCulture(value: string): Observable<any[]> {
if (value && typeof value === 'string') {
const filterValue = value.toLowerCase();
return of(this.cultureService.getCultureValues().filter(option => option.displayName.toLowerCase().includes(filterValue)));
} else {
return of(this.cultureService.getCultureValues());
}
}
displayFn(culture?: CultureInfo): string | undefined {
if (culture == null
|| culture.displayName == null
|| culture.nativeName == null)
return undefined;
return culture.displayName + '-' + culture.nativeName;
}
save() {
//TODO: refactor
// if (!this.formGroup.valid) {
// this.printErrors(this.formGroup);
// this.showValidationErrorsDialog();
// this.nestedCount = [];
// this.nestedIndex = 0;
// this.errorMessages = [];
// return;
// }
// this.userService.updateUserSettings(this.formGroup.value)
// .pipe(takeUntil(this._destroyed))
// .subscribe(
// x => {
// this.editMode = false;
// this.languageService.changeLanguage(this.formGroup.value.language.value);
// this.authService.refresh()
// .pipe(takeUntil(this._destroyed))
// .subscribe(result => {
// this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
// this.router.navigate(['/profile']);
// });
// // .subscribe(result => window.location.reload());
// },
// error => {
// console.log(error);
// });
}
public unlock() {
this.editMode = true;
this.formGroup.enable();
}
public initializeDisabledFormGroup() {
this.formGroup.disable();
}
public lock() {
if (!this.formGroup.valid) { return; }
//TODO: refactor
// this.userService.updateUserSettings(this.formGroup.value)
// .pipe(takeUntil(this._destroyed))
// .subscribe(
// x => {
// this.editMode = false;
// this.languageService.changeLanguage(this.formGroup.value.language.value);
// this.formGroup.disable();
// this.authService.refresh()
// .pipe(takeUntil(this._destroyed))
// .subscribe(result => this.router.navigate(['/profile']));
// // .subscribe(result => window.location.reload());
// },
// error => {
// console.log(error);
// });
}
private showValidationErrorsDialog(projectOnly?: boolean) {
const dialogRef = this.dialog.open(FormValidationErrorsDialogComponent, {
disableClose: true,
autoFocus: false,
restoreFocus: false,
data: {
errorMessages: this.errorMessages,
projectOnly: projectOnly
},
});
}
public applyFallbackAvatar(ev: Event) {
(ev.target as HTMLImageElement).src = 'assets/images/profile-placeholder.png';
}
public removeAccount(userCredential: any) {
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.MESSAGE'),
confirmButton: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.CONFIRM'),
cancelButton: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.CANCEL')
},
maxWidth: '35em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
//TODO: refactor
console.log('removed!');
// const unlinkAccountModel: UnlinkAccountRequestModel = {
// userId: this.currentUserId,
// email: userCredential.email,
// provider: userCredential.provider
// };
// this.unlinkAccountEmailConfirmation.sendConfirmationEmail(unlinkAccountModel).pipe(takeUntil(this._destroyed)).subscribe(
// result => {
// this.dialog.open(PopupNotificationDialogComponent, {
// data: {
// title: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT.TITLE'),
// message: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT.MESSAGE', { 'accountToBeUnlinked': userCredential.email })
// }, maxWidth: '35em'
// });
// },
// error => { this.onCallbackError(error); }
// );
}
});
}
onCallbackError(errorResponse: HttpErrorResponse) {
this.uiNotificationService.snackBarNotification(errorResponse.message, SnackBarNotificationLevel.Warning);
}
public addAccount() {
const dialogRef = this.dialog.open(AddAccountDialogComponent, {
restoreFocus: false,
autoFocus: false,
width: '653px',
maxHeight: '90vh',
data: {
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
//TODO refactor
// this.mergeEmailConfirmation.sendConfirmationEmail(result).pipe(takeUntil(this._destroyed))
// .subscribe(res => {
// this.dialog.open(PopupNotificationDialogComponent, {
// data: {
// title: this.language.instant('USER-PROFILE.MERGING-EMAILS-DIALOG.TITLE'),
// message: this.language.instant('USER-PROFILE.MERGING-EMAILS-DIALOG.MESSAGE')
// }, maxWidth: '30em'
// });
// }, err => { });
}
});
}
printErrors(rootform: UntypedFormGroup) {
if (!rootform.valid) {
Object.keys(rootform.controls).forEach(key => {
const errors = rootform.get(key).errors;
if (errors !== null) {
let numbering: string = '';
for (let j = 0; j < this.nestedCount.length; j++) {
numbering += this.nestedCount[j];
if (j < this.nestedIndex) {
numbering += '.';
} else {
break;
}
}
Object.keys(errors).forEach(keyError => {
if (typeof errors[keyError] === 'boolean') {
this.errorMessages.push(numbering + ' ' + key + ' is ' + keyError);
} else {
this.errorMessages.push(numbering + ' ' + key + ': ' + keyError + ': ' + JSON.stringify(errors[keyError]));
}
});
} else {
if (rootform.get(key) instanceof UntypedFormGroup) {
this.printErrors(<UntypedFormGroup>rootform.get(key));
} else if (rootform.get(key) instanceof UntypedFormArray) {
this.nestedIndex++;
this.nestedCount[this.nestedIndex] = 0;
for (let childForm of (<UntypedFormArray>rootform.get(key)).controls) {
this.nestedCount[this.nestedIndex]++;
this.printErrors(<any>childForm);
}
this.nestedCount[this.nestedIndex] = 0;
this.nestedIndex--;
}
}
});
}
}
}