connect-admin/src/app/pages/users/personal-info/personal-info.component.ts

492 lines
21 KiB
TypeScript

import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {AlertModal} from "../../../openaireLibrary/utils/modal/alert";
import {CuratorService} from "../../../openaireLibrary/connect/curators/curator.service";
import {UtilitiesService} from "../../../openaireLibrary/services/utilities.service";
import {UserManagementService} from "../../../openaireLibrary/services/user-management.service";
import {UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms";
import {Subscriber} from "rxjs";
import {EnvProperties} from "../../../openaireLibrary/utils/properties/env-properties";
import {properties} from "../../../../environments/environment";
import {User} from "../../../openaireLibrary/login/utils/helper.class";
import {Affiliation, Curator} from "../../../openaireLibrary/utils/entities/CuratorInfo";
import {HelpContentService} from "../../../services/help-content.service";
import {Page} from "../../../domain/page";
import {CommunityService} from "../../../openaireLibrary/connect/community/community.service";
import {StringUtils} from "../../../openaireLibrary/utils/string-utils.class";
import {CommunityInfo} from "../../../openaireLibrary/connect/community/communityInfo";
import {ClearCacheService} from "../../../openaireLibrary/services/clear-cache.service";
declare var UIkit;
@Component({
selector: 'personal-info',
template: `
<div page-content>
<div header>
<div class="uk-flex uk-flex-middle uk-margin-medium-top info">
<div>
<div class="uk-text-background uk-text-bold uk-text-small">Admin Dashboard - Manage Personal Info &
Affiliations
</div>
<h1 class="uk-h6 uk-margin-remove">{{community.shortTitle}}</h1>
</div>
</div>
<users-tabs tab="personal"></users-tabs>
</div>
<div actions>
<div class="uk-container uk-section-xsmall">
<div class="uk-flex uk-flex-center uk-flex-right@m">
<button class="uk-button uk-button-secondary" [class.uk-disabled]="!hasChanged || loading"
[disabled]="!hasChanged || loading"
(click)="reset()">Reset
</button>
<button class="uk-button uk-margin-left uk-button-primary"
[disabled]="!hasChanged || loading" [class.uk-disabled]="!hasChanged || loading"
(click)="updateCurator()">Save
</button>
</div>
</div>
</div>
<div inner>
<div class="uk-section uk-section-small uk-position-relative" style="min-height: 60vh">
<div *ngIf="loading" class="uk-position-center">
<loading></loading>
</div>
<div *ngIf="!loading" class="uk-container">
<form *ngIf="curatorFb" [formGroup]="curatorFb">
<div class="uk-grid uk-margin-large-bottom" uk-grid>
<div class="uk-grid uk-width-1-1 uk-flex-middle" uk-grid>
<div>
<div class="image">
<img [src]="photo"/>
<input #fileInput id="photo" type="file" class="uk-hidden" (change)="fileChangeEvent($event)"/>
<icon class="uk-text-secondary clickable" name="photo_camera" ratio="1.5"
(click)="$event.stopPropagation();uploadPhoto(fileInput);$event.preventDefault()"></icon>
<div *ngIf="curator.photo || file" #element
uk-dropdown="mode: click; pos: bottom-left; delay-hide: 0; flip: false">
<ul class="uk-nav uk-dropdown-nav">
<li><a (click)="fileInput.click();hide(element)">Upload a new photo</a></li>
<li><a (click)="removePhoto();hide(element)">Remove this photo</a></li>
</ul>
</div>
</div>
</div>
<div class="uk-width-expand">
<div input class="uk-width-large@m uk-width-1-1" placeholder="Name"
[formInput]="curatorFb.get('name')" inputClass="border-bottom normal-font-size"></div>
</div>
</div>
<div input class="uk-width-1-1" placeholder="Biography"
[formInput]="curatorFb.get('bio')" type="textarea" rows="8"></div>
<div class="uk-width-1-1">
<h5 class="uk-margin-large uk-text-bold">My Affiliations</h5>
<div class="uk-flex uk-flex-center">
<button (click)="editAffiliationOpen()" class="uk-button uk-button-link uk-flex uk-flex-middle">
<icon [flex]="true" name="add"></icon>
<span class="uk-margin-small-left">Add New Affiliation</span>
</button>
</div>
<div class="uk-margin-medium uk-grid uk-child-width-1-2@m uk-child-width-1-1" uk-height-match="target: .uk-card-body; row: false" uk-grid>
<div *ngFor="let affiliation of affiliations.controls; let i=index">
<div class="uk-card uk-card-default uk-margin-bottom">
<div class="uk-card-body">
<div class="uk-flex uk-flex-middle uk-flex-column">
<div class="uk-flex uk-flex-center uk-height-xsmall">
<img class="uk-height-max-xsmall uk-blend-multiply" [src]="affiliation.value.logo_url | urlPrefix">
</div>
<h5>{{affiliation.value.name}}</h5>
<div class="uk-text-truncate uk-text-small">
<span class="uk-text-meta uk-margin-xsmall-right">URL:</span>
<a [href]="affiliation.value.website_url | urlPrefix" class="uk-link"
target="_blank">{{affiliation.value.website_url}}</a>
</div>
</div>
</div>
<div class="uk-card-footer uk-padding-remove-vertical">
<div class="uk-grid uk-grid-small uk-flex-nowrap uk-grid-divider uk-flex-right" uk-grid>
<div>
<div class="uk-padding-small uk-padding-remove-horizontal">
<a (click)="editAffiliationOpen(i)"
class="uk-button uk-button-link uk-flex uk-flex-middle">
<icon name="edit" [flex]="true"></icon>
<span class="uk-margin-xsmall-left">Edit</span>
</a>
</div>
</div>
<div>
<div class="uk-padding-small uk-padding-remove-horizontal">
<button class="uk-button uk-button-link uk-flex uk-flex-middle"
(click)="deleteAffiliationOpen(i)">
<icon name="delete" [flex]="true"></icon>
<span class="uk-margin-xsmall-left">Delete</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="uk-width-1-1 uk-text-small">
Your personal info will be visible in the Curators page of your Community Gateway.
Read <a (click)="privacy()">privacy policy statement</a>.
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<modal-alert #privacyStatement [overflowBody]="false" (alertOutput)="privacyStatement.cancel()">
<div class="uk-text-small">
Your personal data and photo are processed by OpenAIRE in conformity with personal data protection legal
framework.
They will be stored safely in our system for as long as OpenAIRE exists. Since you press the "save" button,
you give us the consent to make them public in your Community Gateway to let users know who is
configuring the platform. You always have the right to exercise your rights and ask for access,
rectification, erasure and restriction of your data. Please contact <a href="mailto:rcd@openaire.eu">rcd@openaire.eu</a>
if you have any inquiries.
</div>
</modal-alert>
<modal-alert #affiliationModal [okDisabled]="affiliationFb && affiliationFb.invalid" classTitle="uk-background-primary uk-light"
(alertOutput)="editAffiliation()">
<form *ngIf="affiliationFb" [formGroup]="affiliationFb">
<div class="uk-grid uk-child-width-1-1" uk-grid>
<div input placeholder="Name"
[formInput]="affiliationFb.get('name')"></div>
<div input type="logoURL" placeholder="Logo URL"
[formInput]="affiliationFb.get('logo_url')"></div>
<div input type="URL" placeholder="Website URL"
[formInput]="affiliationFb.get('website_url')"></div>
</div>
</form>
</modal-alert>
<modal-alert #removeAffiliationModal [overflowBody]="false" (alertOutput)="removeAffiliation()" classTitle="uk-background-primary uk-light">
</modal-alert>
<modal-alert #enableCuratorsModal [overflowBody]="false" (alertOutput)="enableCurators()" classTitle="uk-background-primary uk-light">
Your personal information has been successfully saved.<br><br>
This information will be visible in <span class="uk-text-bold">Curators page</span> of Research Community
Dashboard, which is <span class="uk-text-bold">disabled</span>.
Do you want to <span class="uk-text-bold">enable</span> it now?
</modal-alert>
`,
styleUrls: ['personal-info.component.less']
})
export class PersonalInfoComponent implements OnInit, OnDestroy {
/** Curator information */
public loading = false;
public user: User;
public curator: Curator;
public curatorFb: UntypedFormGroup;
public properties: EnvProperties = properties;
public curatorsPage: Page;
public newCurator = false;
public community: CommunityInfo;
/** Photo */
public photo: any = null;
private photoChanged: boolean = false;
public file: File = null;
private maxsize: number = 200 * 1024;
private deletePhoto = false;
private subs: any[] = [];
/** Affiliations */
public affiliationFb: UntypedFormGroup;
public index: number = -1;
@ViewChild('fileInput') fileInput: ElementRef;
@ViewChild('privacyStatement') privacyStatement: AlertModal;
@ViewChild('affiliationModal') affiliationModal: AlertModal;
@ViewChild('removeAffiliationModal') removeAffiliationModal: AlertModal;
@ViewChild('enableCuratorsModal') enableCuratorsModal: AlertModal;
constructor(private route: ActivatedRoute,
private title: Title,
private fb: UntypedFormBuilder,
private curatorService: CuratorService,
private utilitiesService: UtilitiesService,
private helpContentService: HelpContentService,
private communityService: CommunityService,
private userManagementService: UserManagementService,
private _clearCacheService: ClearCacheService) {
}
ngOnInit() {
this.subs.push(this.communityService.getCommunityAsObservable().subscribe(community => {
this.community = community;
this.subs.push(this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
if (this.user) {
this.title.setTitle(community.shortTitle.toUpperCase() + " | Personal Info");
this.loading = true;
this.subs.push(this.curatorService.getCurator(properties).subscribe(curator => {
this.initCurator(curator);
this.reset();
this.loading = false;
}, error => {
if (error.status === 404) {
this.initCurator(null);
this.reset();
} else {
console.error(error);
}
this.loading = false;
}));
}
}));
}));
}
ngOnDestroy() {
this.subs.forEach(subscription => {
if (subscription instanceof Subscriber) {
subscription.unsubscribe();
}
})
}
hide(element: any) {
UIkit.dropdown(element).hide();
}
initCurator(curator: Curator) {
if (curator) {
this.curator = curator;
this.curator.email = this.user.email;
} else {
this.newCurator = true;
this.curator = new Curator();
this.curator._id = this.user.id;
this.curator.email = this.user.email;
this.curator.name = this.user.fullname;
this.curator.affiliations = [];
this.curator.bio = '';
this.curator.photo = null;
}
this.curatorsPageStatus();
}
reset() {
this.photoChanged = false;
this.file = null;
if (this.fileInput) {
this.fileInput.nativeElement.value = null;
}
let affiliations: UntypedFormArray = this.fb.array([]);
this.curator.affiliations.forEach(affiliation => {
affiliations.push(this.fb.group({
id: this.fb.control(affiliation.id),
name: this.fb.control(affiliation.name, Validators.required),
logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()]),
}));
});
this.curatorFb = this.fb.group({
_id: this.fb.control(this.curator._id),
name: this.fb.control(this.curator.name, Validators.required),
bio: this.fb.control(this.curator.bio),
email: this.fb.control(this.curator.email),
photo: this.fb.control(this.curator.photo),
affiliations: affiliations
});
if (this.curator.photo) {
this.photo = this.properties.utilsService + '/download/' + this.curator.photo;
} else {
this.photo = 'assets/common-assets/curator-default.png';
}
}
get affiliations(): UntypedFormArray {
return this.curatorFb.get('affiliations') as UntypedFormArray;
}
saveCurator() {
this.curatorService.updateCurator(this.properties, this.curatorFb.value).subscribe((curator) => {
if (curator) {
this._clearCacheService.purgeBrowserCache("Curator added/ updated", this.community.communityId);
UIkit.notification('Your data has been <b>saved successfully</b>', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
this.newCurator = false;
this.deletePhoto = false;
this.initCurator(curator);
this.reset();
if (!this.curatorsEnabled) {
this.curatorsEnabledOpen();
}
this.loading = false;
}
},
error => {
this.handleUpdateError('An error has occurred. Try again later!');
this.reset();
});
}
updateCurator() {
if (this.curatorFb.valid) {
this.loading = true;
if (this.file) {
this.utilitiesService.uploadPhoto(this.properties.utilsService + '/upload/' + this.curator._id, this.file).subscribe((res) => {
if (this.curator.photo) {
this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
}
this.curatorFb.get('photo').setValue(res.filename);
this.saveCurator();
}, error => {
this.handleUpdateError('An error has occurred during photo uploading.');
}
);
} else {
if (this.deletePhoto && this.curator.photo) {
this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
this.curatorFb.get('photo').setValue(null);
}
this.saveCurator();
}
}
}
private curatorsPageStatus() {
this.helpContentService.getCommunityPagesByRoute(this.community.communityId, '/curators', this.properties.adminToolsAPIURL).subscribe((page) => {
this.curatorsPage = page;
});
}
public get curatorsEnabled(): boolean {
return !this.curatorsPage || this.curatorsPage.isEnabled;
}
fileChangeEvent(event) {
this.loading = true;
if (event.target.files && event.target.files[0]) {
this.file = event.target.files[0];
if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') {
this.handleUpdateError('You must choose a file with type: image/png or image/jpeg!');
this.file = null;
} else if (this.file.size > this.maxsize) {
this.handleUpdateError('File exceeds size\'s limit! Maximum size 200KB.');
this.file = null;
} else {
const reader = new FileReader();
reader.readAsDataURL(this.file);
reader.onload = () => {
this.photo = reader.result;
this.photoChanged = true;
this.loading = false;
};
}
} else {
this.loading = false;
}
}
removePhoto() {
this.deletePhoto = true;
this.file = null;
this.fileInput.nativeElement.value = null;
this.photoChanged = !!this.curator.photo;
this.photo = 'assets/common-assets/curator-default.png';
}
handleUpdateError(message: string) {
UIkit.notification(message, {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.loading = false;
}
privacy() {
this.privacyStatement.cancelButton = false;
this.privacyStatement.okButtonText = 'Close';
this.privacyStatement.alertTitle = 'Privacy policy statement';
this.privacyStatement.open();
}
uploadPhoto(fileInput: HTMLInputElement) {
if (!this.curator.photo && !this.file) {
fileInput.click();
}
}
get hasChanged(): boolean {
return (this.curatorFb && this.curatorFb.dirty) || this.newCurator || this.photoChanged;
}
editAffiliationOpen(index = -1) {
this.index = index;
let affiliation: Affiliation = new Affiliation();
if (index === -1) {
this.affiliationModal.alertTitle = 'Add Affiliation';
this.affiliationModal.okButtonText = 'Add';
} else {
this.affiliationModal.alertTitle = 'Edit Affiliation';
this.affiliationModal.okButtonText = 'Update';
affiliation = this.affiliations.at(index).value;
}
this.affiliationFb = this.fb.group({
id: this.fb.control(affiliation.id),
name: this.fb.control(affiliation.name, Validators.required),
logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()])
});
this.affiliationModal.okButtonLeft = false;
this.affiliationModal.cancelButtonText = 'Cancel';
this.affiliationModal.open();
}
deleteAffiliationOpen(index: number) {
this.index = index;
let affiliation: Affiliation = this.affiliations.at(index).value;
this.removeAffiliationModal.alertTitle = 'Delete Affiliation';
this.removeAffiliationModal.message = 'Do you want to remove <b>' +
affiliation.name + '</b> from your Affiliations?';
this.removeAffiliationModal.okButtonText = 'Yes';
this.removeAffiliationModal.cancelButtonText = 'No';
this.removeAffiliationModal.open();
}
editAffiliation() {
if (this.index === -1) {
this.affiliations.push(this.affiliationFb);
} else {
this.affiliations.at(this.index).setValue(this.affiliationFb.value);
}
this.curatorFb.markAsDirty();
}
removeAffiliation() {
this.affiliations.removeAt(this.index);
this.curatorFb.markAsDirty();
}
private curatorsEnabledOpen() {
this.enableCuratorsModal.okButtonLeft = false;
this.enableCuratorsModal.alertTitle = 'Enable Curators Page';
this.enableCuratorsModal.okButtonText = 'Yes';
this.enableCuratorsModal.cancelButtonText = 'No';
this.enableCuratorsModal.open();
}
enableCurators() {
this.helpContentService.togglePages(this.community.communityId, [this.curatorsPage._id], true, this.properties.adminToolsAPIURL).subscribe(() => {
this.curatorsPage.isEnabled = true;
this._clearCacheService.purgeBrowserCache("Curators page enabled", this.community.communityId);
UIkit.notification('Curators Page has been <b>enabled successfully</b>', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
}, error => {
this.handleUpdateError('An error has occurred. Try again later!');
});
}
}