Project

General

Profile

1
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
2
import {ActivatedRoute} from '@angular/router';
3
import {Title} from '@angular/platform-browser';
4
import {AlertModal} from "../../../openaireLibrary/utils/modal/alert";
5
import {CuratorService} from "../../../openaireLibrary/connect/curators/curator.service";
6
import {UtilitiesService} from "../../../openaireLibrary/services/utilities.service";
7
import {UserManagementService} from "../../../openaireLibrary/services/user-management.service";
8
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
9
import {Subscriber} from "rxjs";
10
import {EnvProperties} from "../../../openaireLibrary/utils/properties/env-properties";
11
import {properties} from "../../../../environments/environment";
12
import {User} from "../../../openaireLibrary/login/utils/helper.class";
13
import {Affiliation, Curator} from "../../../openaireLibrary/utils/entities/CuratorInfo";
14
import {HelpContentService} from "../../../services/help-content.service";
15
import {Page} from "../../../domain/page";
16
import {CommunityService} from "../../../openaireLibrary/connect/community/community.service";
17
import {StringUtils} from "../../../openaireLibrary/utils/string-utils.class";
18

    
19
declare var UIkit;
20

    
21
@Component({
22
  selector: 'personal-info',
23
  template: `
24
    <div page-content>
25
      <div header>
26
        <users-tabs tab="personal"></users-tabs>
27
      </div>
28
      <div inner>
29
        <div class="uk-card-header">
30
          <div class="uk-flex uk-child-width-1-1 uk-child-width-1-2@m uk-grid" uk-grid>
31
            <div>
32
              <div class="uk-text-small title">
33
                Personal Info & Affiliations
34
                <span *ngIf="!loading && (curatorFb && curatorFb.dirty && !newCurator)"> (unsaved changes)</span>
35
              </div>
36
            </div>
37
            <div class="uk-text-right@m uk-text-center">
38
              <button class="uk-button uk-button-secondary outlined uk-margin-right"
39
                      (click)="reset()"
40
                      [disabled]="loading || !hasChanged">Reset
41
              </button>
42
              <button class="uk-button uk-button-secondary"
43
                      (click)="updateCurator()"
44
                      [disabled]="loading || !hasChanged || curatorFb.invalid">Save
45
              </button>
46
            </div>
47
          </div>
48
        </div>
49
        <div class="uk-card uk-card-default uk-position-relative" style="min-height: 60vh">
50
          <div [class.hidden]="loading" style="max-height: 60vh" class="uk-overflow-auto uk-padding-remove-bottom uk-padding-large">
51
            <form *ngIf="curatorFb" [formGroup]="curatorFb">
52
              <div class="uk-grid uk-margin-large-bottom" uk-grid>
53
                <div class="uk-grid uk-width-1-1 uk-flex-middle" uk-grid>
54
                  <div>
55
                    <div class="image">
56
                      <img [src]="photo"/>
57
                      <input #fileInput id="photo" type="file" class="uk-hidden" (change)="fileChangeEvent($event)"/>
58
                      <icon class="uk-text-secondary clickable" name="photo" ratio="1.5"
59
                            (click)="$event.stopPropagation();uploadPhoto(fileInput);$event.preventDefault()"></icon>
60
                      <div *ngIf="curator.photo || file" #element
61
                           uk-dropdown="mode: click; pos: bottom-left; delay-hide: 0; flip: false">
62
                        <ul class="uk-nav uk-dropdown-nav">
63
                          <li><a (click)="fileInput.click();hide(element)">Upload a new photo</a></li>
64
                          <li><a (click)="removePhoto();hide(element)">Remove this photo</a></li>
65
                        </ul>
66
                      </div>
67
                    </div>
68
                  </div>
69
                  <div class="uk-width-expand">
70
                    <h5 class="uk-text-secondary uk-text-bold">{{user.firstname + ' ' + user.lastname}}</h5>
71
                    <!--<div>
72
                      <span class="connected">Connected with: </span>
73
                      <span class="uk-text-secondary uk-text-bold">{{user.email}}</span>
74
                    </div>-->
75
                  </div>
76
                </div>
77
                <div dashboard-input class="uk-width-1-1" label="Display Name" placeholder="Write your name"
78
                     [formInput]="curatorFb.get('name')"></div>
79
                <div dashboard-input class="uk-width-1-1" label="Biography" placeholder="Write biography..."
80
                     [formInput]="curatorFb.get('bio')" type="textarea" rows="4"></div>
81
                <div class="uk-width-1-1">
82
                  <h5 class="uk-margin-large uk-text-bold">My Affiliations</h5>
83
                  <div class="uk-flex uk-flex-center">
84
                    <a (click)="editAffiliationOpen()" class="uk-flex uk-flex-middle uk-text-uppercase">
85
                      <button class="large uk-icon-button uk-button-secondary">
86
                        <icon name="add"></icon>
87
                      </button>
88
                      <button class="uk-button uk-button-link uk-margin-small-left uk-text-secondary">
89
                        Add New Affiliation
90
                      </button>
91
                    </a>
92
                  </div>
93
                  <div class="uk-margin-medium">
94
                    <div *ngFor="let affiliation of affiliations.controls; let i=index"
95
                         class="uk-card uk-card-default uk-card-body uk-text-small uk-margin-bottom">
96
                      <div class="uk-grid uk-grid-divider uk-flex-middle" uk-grid>
97
                        <div class="uk-width-expand@m uk-width-1-1 uk-grid uk-flex-middle" uk-grid>
98
                          <div class="uk-width-small">
99
                            <img [src]="affiliation.value.logo_url | urlPrefix">
100
                          </div>
101
                          <div class="uk-width-auto">
102
                            <h6>{{affiliation.value.name}}</h6>
103
                            URL: <a [href]="affiliation.value.website_url | urlPrefix" target="_blank">{{affiliation.value.website_url}}</a>
104
                          </div>
105
                        </div>
106
                        <div class="uk-width-auto@m uk-width-1-1">
107
                          <div class="uk-width-1-1 uk-flex uk-flex-center">
108
                            <div class="uk-padding-small uk-padding-remove-horizontal">
109
                              <a (click)="editAffiliationOpen(i)" class="uk-button action uk-flex uk-flex-middle">
110
                                <icon name="edit" ratio="0.7"></icon>
111
                                <span class="uk-margin-small-left">Edit Affiliation</span>
112
                              </a>
113
                              <a (click)="deleteAffiliationOpen(i)" class="uk-button action uk-flex uk-flex-middle uk-margin-small-top">
114
                                <icon name="remove" ratio="0.7"></icon>
115
                                <span class="uk-margin-small-left">Delete Affiliation</span>
116
                              </a>
117
                            </div>
118
                          </div>
119
                        </div>
120
                      </div>
121
                    </div>
122
                  </div>
123
                </div>
124
                <div class="uk-width-1-1 uk-text-small">
125
                  Your personal info will be visible in the Curators page of your Community Gateway.
126
                  Read <a (click)="privacy()">privacy policy statement</a>.
127
                </div>
128
              </div>
129
            </form>
130
          </div>
131
          <div *ngIf="loading" class="uk-position-center">
132
            <loading></loading>
133
          </div>
134
        </div>
135
      </div>
136
    </div>
137
    <modal-alert #privacyStatement (alertOutput)="privacyStatement.cancel()">
138
      <div class="uk-text-small">
139
        Your personal data and photo are processed by OpenAIRE in conformity with personal data protection legal
140
        framework.
141
        They will be stored safely in our system for as long as OpenAIRE exists. Since you press the "save" button,
142
        you give us the consent to make them public in your Community Gateway to let users know who is
143
        configuring the platform. You always have the right to exercise your rights and ask for access,
144
        rectification, erasure and restriction of your data. Please contact <a href="mailto:rcd@openaire.eu">rcd@openaire.eu</a>
145
        if you have any inquiries.
146
      </div>
147
    </modal-alert>
148
    <modal-alert #affiliationModal [okDisabled]="affiliationFb && affiliationFb.invalid" (alertOutput)="editAffiliation()">
149
      <form *ngIf="affiliationFb" [formGroup]="affiliationFb">
150
        <div class="uk-grid uk-padding uk-padding-remove-horizontal uk-child-width-1-1" uk-grid>
151
          <div dashboard-input label="Name" placeholder="Write affiliation's name" [formInput]="affiliationFb.get('name')"></div>
152
          <div dashboard-input label="Logo URL" type="logoURL" placeholder="Write your affiliation's logo URL" [formInput]="affiliationFb.get('logo_url')"></div>
153
          <div dashboard-input label="Website URL" type="URL" placeholder="Write your affiliation's website URL" [formInput]="affiliationFb.get('website_url')"></div>
154
        </div>
155
      </form>
156
    </modal-alert>
157
    <modal-alert #removeAffiliationModal (alertOutput)="removeAffiliation()">
158
    </modal-alert>
159
    <modal-alert #enableCuratorsModal (alertOutput)="enableCurators()">
160
      <div class="uk-padding uk-padding-remove-horizontal">
161
        Your personal information has been successfully saved.<br><br>
162
        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>.
163
        Do you want to <span class="uk-text-bold">enable</span> it now?
164
      </div>
165
    </modal-alert>
166
  `,
167
  styleUrls: ['personal-info.component.css']
168
})
169
export class PersonalInfoComponent implements OnInit, OnDestroy {
170
  /** Curator information */
171
  public loading = false;
172
  public user: User;
173
  public curator: Curator;
174
  public curatorFb: FormGroup;
175
  public properties: EnvProperties = properties;
176
  public curatorsPage: Page;
177
  public newCurator = false;
178
  public communityId: string;
179
  /** Photo */
180
  public photo: any = null;
181
  private photoChanged: boolean = false;
182
  public file: File = null;
183
  private maxsize: number = 200 * 1024;
184
  private deletePhoto = false;
185
  private subs: any[] = [];
186
  /** Affiliations */
187
  public affiliationFb: FormGroup;
188
  public index: number = -1;
189
  @ViewChild('fileInput') fileInput: ElementRef;
190
  @ViewChild('privacyStatement') privacyStatement: AlertModal;
191
  @ViewChild('affiliationModal') affiliationModal: AlertModal;
192
  @ViewChild('removeAffiliationModal') removeAffiliationModal: AlertModal;
193
  @ViewChild('enableCuratorsModal') enableCuratorsModal: AlertModal;
194
  
195
  constructor(private route: ActivatedRoute,
196
              private title: Title,
197
              private fb: FormBuilder,
198
              private curatorService: CuratorService,
199
              private utilitiesService: UtilitiesService,
200
              private helpContentService: HelpContentService,
201
              private communityService: CommunityService,
202
              private userManagementService: UserManagementService) {
203
  }
204
  
205
  
206
  ngOnInit() {
207
    this.subs.push(this.communityService.getCommunityAsObservable().subscribe(community => {
208
      this.communityId = community.communityId;
209
      this.subs.push(this.userManagementService.getUserInfo().subscribe(user => {
210
        this.user = user;
211
        if (this.user) {
212
          this.title.setTitle(community.communityId.toUpperCase() + " | Personal Info");
213
          this.loading = true;
214
          this.subs.push(this.curatorService.getCurator(properties).subscribe(curator => {
215
            this.initCurator(curator);
216
            this.reset();
217
            this.loading = false;
218
          }, error => {
219
            if (error.status === 404) {
220
              this.initCurator(null);
221
              this.reset();
222
            } else {
223
              console.error(error);
224
            }
225
            this.loading = false;
226
          }));
227
        }
228
      }));
229
    }));
230
  }
231
  
232
  ngOnDestroy() {
233
    this.subs.forEach(subscription => {
234
      if (subscription instanceof Subscriber) {
235
        subscription.unsubscribe();
236
      }
237
    })
238
  }
239
  
240
  hide(element: any) {
241
    UIkit.dropdown(element).hide();
242
  }
243
  
244
  initCurator(curator: Curator) {
245
    if (curator) {
246
      this.curator = curator;
247
      this.curator.email = this.user.email;
248
    } else {
249
      this.newCurator = true;
250
      this.curator = new Curator();
251
      this.curator._id = this.user.id;
252
      this.curator.email = this.user.email;
253
      this.curator.name = this.user.fullname;
254
      this.curator.affiliations = [];
255
      this.curator.bio = '';
256
      this.curator.photo = null;
257
    }
258
    this.curatorsPageStatus();
259
  }
260
  
261
  reset() {
262
    this.photoChanged = false;
263
    this.file = null;
264
    if (this.fileInput) {
265
      this.fileInput.nativeElement.value = null;
266
    }
267
    let affiliations: FormArray = this.fb.array([]);
268
    this.curator.affiliations.forEach(affiliation => {
269
      affiliations.push(this.fb.group({
270
        id: this.fb.control(affiliation.id),
271
        name: this.fb.control(affiliation.name, Validators.required),
272
        logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
273
        website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()]),
274
      }));
275
    });
276
    this.curatorFb = this.fb.group({
277
      _id: this.fb.control(this.curator._id),
278
      name: this.fb.control(this.curator.name, Validators.required),
279
      bio: this.fb.control(this.curator.bio),
280
      email: this.fb.control(this.curator.email),
281
      photo: this.fb.control(this.curator.photo),
282
      affiliations: affiliations
283
    });
284
    if (this.curator.photo) {
285
      this.photo = this.properties.utilsService + '/download/' + this.curator.photo;
286
    } else {
287
      this.photo = 'assets/common-assets/curator-default.png';
288
    }
289
  }
290
  
291
  get affiliations(): FormArray {
292
    return this.curatorFb.get('affiliations') as FormArray;
293
  }
294
  
295
  saveCurator() {
296
    this.curatorService.updateCurator(this.properties, this.curatorFb.value).subscribe((curator) => {
297
        if (curator) {
298
          UIkit.notification('Your data has been <b>saved successfully</b>', {
299
            status: 'success',
300
            timeout: 6000,
301
            pos: 'bottom-right'
302
          });
303
          this.newCurator = false;
304
          this.deletePhoto = false;
305
          this.initCurator(curator);
306
          this.reset();
307
          if(!this.curatorsEnabled) {
308
            this.curatorsEnabledOpen();
309
          }
310
          this.loading = false;
311
        }
312
      },
313
      error => {
314
        this.handleUpdateError('An error has occurred. Try again later!');
315
        this.reset();
316
      });
317
  }
318
  
319
  
320
  updateCurator() {
321
    if (this.curatorFb.valid) {
322
      this.loading = true;
323
      if (this.file) {
324
        this.utilitiesService.uploadPhoto(this.properties.utilsService + '/upload/' + this.curator._id, this.file).subscribe((res) => {
325
            if (this.curator.photo) {
326
              this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
327
            }
328
            this.curatorFb.get('photo').setValue(res.filename);
329
            this.saveCurator();
330
          }, error => {
331
            this.handleUpdateError('An error has occurred during photo uploading.');
332
          }
333
        );
334
      } else {
335
        if (this.deletePhoto && this.curator.photo) {
336
          this.utilitiesService.deletePhoto(this.properties.utilsService + '/delete/' + this.curator.photo).subscribe();
337
          this.curatorFb.get('photo').setValue(null);
338
        }
339
        this.saveCurator();
340
      }
341
    }
342
  }
343
  
344
  private curatorsPageStatus() {
345
    this.helpContentService.getCommunityPagesByRoute(this.communityId, '/curators', this.properties.adminToolsAPIURL).subscribe((page) => {
346
      this.curatorsPage = page;
347
    });
348
  }
349
  
350
  public get curatorsEnabled(): boolean {
351
    return !this.curatorsPage || this.curatorsPage.isEnabled;
352
  }
353
  
354
  fileChangeEvent(event) {
355
    this.loading = true;
356
    if (event.target.files && event.target.files[0]) {
357
      this.file = event.target.files[0];
358
      if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') {
359
        this.handleUpdateError('You must choose a file with type: image/png or image/jpeg!');
360
        this.file = null;
361
      } else if (this.file.size > this.maxsize) {
362
        this.handleUpdateError('File exceeds size\'s limit! Maximum size 200KB.');
363
        this.file = null;
364
      } else {
365
        const reader = new FileReader();
366
        reader.readAsDataURL(this.file);
367
        reader.onload = () => {
368
          this.photo = reader.result;
369
          this.photoChanged = true;
370
          this.loading = false;
371
        };
372
      }
373
    } else {
374
      this.loading = false;
375
    }
376
  }
377
  
378
  removePhoto() {
379
    this.deletePhoto = true;
380
    this.file = null;
381
    this.fileInput.nativeElement.value = null;
382
    this.photoChanged = !!this.curator.photo;
383
    this.photo = 'assets/common-assets/curator-default.png';
384
  }
385
  
386
  handleUpdateError(message: string) {
387
    UIkit.notification(message, {
388
      status: 'danger',
389
      timeout: 6000,
390
      pos: 'bottom-right'
391
    });
392
    this.loading = false;
393
  }
394
  
395
  privacy() {
396
    this.privacyStatement.cancelButton = false;
397
    this.privacyStatement.okButtonText = 'Close';
398
    this.privacyStatement.alertTitle = 'Privacy policy statement';
399
    this.privacyStatement.open();
400
  }
401
  
402
  uploadPhoto(fileInput: HTMLInputElement) {
403
    if (!this.curator.photo && !this.file) {
404
      fileInput.click();
405
    }
406
  }
407
  
408
  get hasChanged(): boolean {
409
    return (this.curatorFb && this.curatorFb.dirty) || this.newCurator || this.photoChanged;
410
  }
411
  
412
  editAffiliationOpen(index = -1) {
413
    this.index = index;
414
    let affiliation: Affiliation = new Affiliation();
415
    if(index === -1) {
416
      this.affiliationModal.alertTitle = 'Add Affiliation';
417
      this.affiliationModal.okButtonText = 'Add';
418
    } else {
419
      this.affiliationModal.alertTitle = 'Edit Affiliation';
420
      this.affiliationModal.okButtonText = 'Update';
421
      affiliation = this.affiliations.at(index).value;
422
    }
423
    this.affiliationFb = this.fb.group({
424
      id: this.fb.control(affiliation.id),
425
      name: this.fb.control(affiliation.name, Validators.required),
426
      logo_url: this.fb.control(affiliation.logo_url, [Validators.required, StringUtils.urlValidator()]),
427
      website_url: this.fb.control(affiliation.website_url, [Validators.required, StringUtils.urlValidator()])
428
    });
429
    this.affiliationModal.okButtonLeft = false;
430
    this.affiliationModal.cancelButtonText = 'Cancel';
431
    this.affiliationModal.open();
432
  }
433
  
434
  deleteAffiliationOpen(index: number) {
435
    this.index = index;
436
    let affiliation: Affiliation = this.affiliations.at(index).value;
437
    this.removeAffiliationModal.alertTitle = 'Delete Affiliation';
438
    this.removeAffiliationModal.message = 'Do you want to remove <b>' +
439
      affiliation.name +  '</b> from your Affiliations?';
440
    this.removeAffiliationModal.okButtonText = 'Yes';
441
    this.removeAffiliationModal.cancelButtonText = 'No';
442
    this.removeAffiliationModal.open();
443
  }
444
  
445
  editAffiliation() {
446
    if(this.index === -1) {
447
      this.affiliations.push(this.affiliationFb);
448
    } else {
449
      this.affiliations.at(this.index).setValue(this.affiliationFb.value);
450
    }
451
    this.curatorFb.markAsDirty();
452
  }
453
  
454
  removeAffiliation() {
455
    this.affiliations.removeAt(this.index);
456
    this.curatorFb.markAsDirty();
457
  }
458
  
459
  private curatorsEnabledOpen() {
460
    this.enableCuratorsModal.okButtonLeft = false;
461
    this.enableCuratorsModal.alertTitle = 'Enable Curators Page';
462
    this.enableCuratorsModal.okButtonText = 'Yes';
463
    this.enableCuratorsModal.cancelButtonText = 'No';
464
    this.enableCuratorsModal.open();
465
  }
466
  
467
  enableCurators() {
468
    this.helpContentService.togglePages(this.communityId, [this.curatorsPage._id], true, this.properties.adminToolsAPIURL).subscribe(() => {
469
      this.curatorsPage.isEnabled = true;
470
      UIkit.notification('Curators Page has been <b>enabled successfully</b>', {
471
        status: 'success',
472
        timeout: 6000,
473
        pos: 'bottom-right'
474
      });
475
    },error => {
476
      this.handleUpdateError('An error has occurred. Try again later!');
477
    });
478
  }
479
}
(2-2/3)