[Library | Trunk]: Create subscriber invite and subscribers components.

git-svn-id: https://svn.driver.research-infrastructures.eu/driver/dnet40/modules/uoa-services-library/trunk/ng-openaire-library/src/app@60274 d315682c-612b-4755-9ff5-7f18f6832af3
This commit is contained in:
k.triantafyllou 2021-01-21 15:15:53 +00:00
parent 83493a1abc
commit 7d147b89e7
15 changed files with 699 additions and 144 deletions

View File

@ -1,211 +1,252 @@
import { Injectable } from '@angular/core';
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import { CommunityInfo } from './communityInfo';
import {CommunityInfo} from './communityInfo';
import {EnvProperties} from '../../utils/properties/env-properties';
import {map} from "rxjs/operators";
import {BehaviorSubject, from, Subscriber} from "rxjs";
import {properties} from "../../../../environments/environment";
import {HelperFunctions} from "../../utils/HelperFunctions.class";
@Injectable({ providedIn: 'root' })
@Injectable({providedIn: 'root'})
export class CommunityService {
public community: BehaviorSubject<CommunityInfo> = null;
private promise: Promise<boolean> = null;
private sub;
constructor(private http: HttpClient) {
this.community = new BehaviorSubject(null);
}
sub;
ngOnDestroy() {
this.clearSubscriptions();
}
clearSubscriptions(){
clearSubscriptions() {
if (this.sub instanceof Subscriber) {
this.sub.unsubscribe();
}
}
// TODO Remove these functions
getCommunityByService(properties: EnvProperties, url: string) {
this.promise = new Promise<any>(resolve => {
this.sub = this.getCommunity(properties, url).subscribe(res => {
this.community.next(res);
resolve();
},
error => {
this.community.error(error);
resolve();
})
this.community.next(res);
resolve();
},
error => {
this.community.error(error);
resolve();
})
});
}
async getCommunityByStateAsync(properties: EnvProperties, url: string) {
if(!this.promise) {
async getCommunityByStateAsync(properties: EnvProperties, url: string) {
if (!this.promise) {
this.getCommunityByService(properties, url);
}
await this.promise;
this.clearSubscriptions();
return this.community.getValue();
}
public getCommunityAsObservable() {
return this.community.asObservable();
}
setCommunity(community: CommunityInfo) {
this.community.next(community);
}
private formalize(element: any) {
return HelperFunctions.copy(element);
}
getCommunityByState(properties: EnvProperties, url: string) {
return from(this.getCommunityByStateAsync(properties, url));
}
getCommunity(properties: EnvProperties, url: string) {
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
.pipe(map(res => this.parseCommunity(res)));
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
.pipe(map(res => this.parseCommunity(res)));
}
// TODO remove NEW from function names
getCommunityNew(communityId: string) {
if(!this.community.value || this.community.value.communityId !== communityId) {
this.promise = new Promise<any>((resolve, reject) => {
this.sub = this.http.get<CommunityInfo>(properties.communityAPI + communityId)
.pipe(map(community => this.formalize(this.parseCommunity(community)))).subscribe(community => {
this.community.next(community);
resolve();
},
error => {
this.community.next(null);
reject();
})
});
}
return from(this.getCommunityAsync());
}
async getCommunityAsync() {
await this.promise;
this.clearSubscriptions();
return this.community.getValue();
}
updateCommunity(url: string, community: any) {
//const headers = new Headers({'Content-Type': 'application/json'});
//const options = new RequestOptions({headers: headers});
const options = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
const body = JSON.stringify(community);
return this.http.post(url, body, options);
/*.map(res => res.json())*/
//const headers = new Headers({'Content-Type': 'application/json'});
//const options = new RequestOptions({headers: headers});
const options = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
const body = JSON.stringify(community);
return this.http.post(url, body, options);
/*.map(res => res.json())*/
}
async isCommunityManagerByStateAsync(properties: EnvProperties, url: string, manager: string) {
if(!this.promise) {
if (!this.promise) {
this.getCommunityByService(properties, url);
}
await this.promise;
let community: CommunityInfo = this.community.getValue();
return (community.managers.indexOf(manager) !== -1);
}
isCommunityManagerByState(properties: EnvProperties, url: string, manager: string) {
return from(this.isCommunityManagerByStateAsync(properties, url, manager));
}
/**
* @deprecated
*/
isCommunityManager(properties: EnvProperties, url: string, manager: string) {
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => community.managers.indexOf(manager) !== -1));
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => community.managers.indexOf(manager) !== -1));
}
async isTypeByStateAsync(properties: EnvProperties, url: string, type: string) {
if(!this.promise) {
if (!this.promise) {
this.getCommunityByService(properties, url);
}
await this.promise;
let community: CommunityInfo = this.community.getValue();
return (community && community.type && community.type === type);
}
isRITypeByState(properties: EnvProperties, url: string) {
return from(this.isTypeByStateAsync(properties, url, "ri"));
}
isCommunityTypeByState(properties: EnvProperties, url: string) {
return from(this.isTypeByStateAsync(properties, url, "community"));
}
/**
* @deprecated
*/
isRIType(properties: EnvProperties, url: string) {
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => (community && community.type && community.type === 'ri')));
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => (community && community.type && community.type === 'ri')));
}
/**
* @deprecated
*/
isCommunityType(properties: EnvProperties, url: string) {
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => (community && community.type && community.type === 'community')));
return this.http.get((properties.useCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
//.map(res => <any> res.json())
.pipe(map(res => this.parseCommunity(res)))
.pipe(map(community => (community && community.type && community.type === 'community')));
}
/**
* @deprecated
*/
* @deprecated
*/
isSubscribedToCommunity(pid: string, email: string, url: string) {
return this.http.get(url + '/'+ properties.adminToolsPortalType +'/' + pid + '/subscribers')
//.map(res => ((<any>res === '') ? {} : <any> res.json()))
.pipe(map(res => {
if (res['subscribers'] && res['subscribers'] != null) {
for (let i = 0; i < res['subscribers'].length; i++ ) {
if (res['subscribers'][i] != null && res['subscribers'][i].email === email) {
return true;
}
}
}
return false;
}));
}
private parseCommunity(data: any): CommunityInfo {
const resData = Array.isArray(data) ? data[0] : data;
const community: CommunityInfo = new CommunityInfo();
community['title'] = resData.name;
community['shortTitle'] = resData.shortName;
community['communityId'] = resData.id;
community['queryId'] = resData.queryId;
community['logoUrl'] = resData.logoUrl;
community['description'] = resData.description;
community['date'] = resData.creationDate;
community['zenodoCommunity'] = resData.zenodoCommunity;
community['status'] = 'all';
if (resData.hasOwnProperty('status')) {
community['status'] = resData.status;
const status = ['all', 'hidden', 'manager'];
if (status.indexOf(community['status']) === -1) {
community['status'] = 'hidden';
return this.http.get(url + '/' + properties.adminToolsPortalType + '/' + pid + '/subscribers')
//.map(res => ((<any>res === '') ? {} : <any> res.json()))
.pipe(map(res => {
if (res['subscribers'] && res['subscribers'] != null) {
for (let i = 0; i < res['subscribers'].length; i++) {
if (res['subscribers'][i] != null && res['subscribers'][i].email === email) {
return true;
}
}
}
}
if (resData.type != null) {
community['type'] = resData.type;
}
if (resData.managers != null) {
if (community['managers'] === undefined) {
community['managers'] = new Array<string>();
}
const managers = resData.managers;
const length = Array.isArray(managers) ? managers.length : 1;
for (let i = 0; i < length; i++) {
const manager = Array.isArray(managers) ? managers[i] : managers;
community.managers[i] = manager;
}
}
if (resData.subjects != null) {
if (community['subjects'] === undefined) {
community['subjects'] = new Array<string>();
}
const subjects = resData.subjects;
const length = Array.isArray(subjects) ? subjects.length : 1;
for (let i = 0; i < length; i++) {
const subject = Array.isArray(subjects) ? subjects[i] : subjects;
community.subjects[i] = subject;
}
}
return community;
return false;
}));
}
private parseCommunity(data: any): CommunityInfo {
const resData = Array.isArray(data) ? data[0] : data;
const community: CommunityInfo = new CommunityInfo();
community['title'] = resData.name;
community['shortTitle'] = resData.shortName;
community['communityId'] = resData.id;
community['queryId'] = resData.queryId;
community['logoUrl'] = resData.logoUrl;
community['description'] = resData.description;
community['date'] = resData.creationDate;
community['zenodoCommunity'] = resData.zenodoCommunity;
community['status'] = 'all';
if (resData.hasOwnProperty('status')) {
community['status'] = resData.status;
const status = ['all', 'hidden', 'manager'];
if (status.indexOf(community['status']) === -1) {
community['status'] = 'hidden';
}
}
if (resData.type != null) {
community['type'] = resData.type;
}
if (resData.managers != null) {
if (community['managers'] === undefined) {
community['managers'] = new Array<string>();
}
const managers = resData.managers;
const length = Array.isArray(managers) ? managers.length : 1;
for (let i = 0; i < length; i++) {
const manager = Array.isArray(managers) ? managers[i] : managers;
community.managers[i] = manager;
}
}
if (resData.subjects != null) {
if (community['subjects'] === undefined) {
community['subjects'] = new Array<string>();
}
const subjects = resData.subjects;
const length = Array.isArray(subjects) ? subjects.length : 1;
for (let i = 0; i < length; i++) {
const subject = Array.isArray(subjects) ? subjects[i] : subjects;
community.subjects[i] = subject;
}
}
return community;
}
}

View File

@ -12,14 +12,16 @@
</li>
</ul>
</div>
<div [class.uk-invisible]="loadActive || loadPending" class="uk-width-1-5@l uk-width-1-3@m uk-width-1-1 uk-flex uk-flex-right@m uk-flex-center">
<div [class.uk-invisible]="loadActive || loadPending"
class="uk-width-1-5@l uk-width-1-3@m uk-width-1-1 uk-flex uk-flex-right@m uk-flex-center">
<a *ngIf="exists" class="uk-text-uppercase uk-flex uk-flex-middle" (click)="openInviteModal()">
<button class="uk-icon-button large uk-button-secondary">
<icon name="person_add"></icon>
</button>
<button class="uk-button uk-button-link uk-margin-small-left uk-text-secondary">Invite {{role}}</button>
</a>
<a *ngIf="!exists && isPortalAdmin" class="uk-text-uppercase uk-flex uk-flex-middle" (click)="openCreateRoleModal()">
<a *ngIf="!exists && isPortalAdmin" class="uk-text-uppercase uk-flex uk-flex-middle"
(click)="openCreateRoleModal()">
<button class="uk-icon-button large uk-button-secondary">
<icon name="person_add"></icon>
</button>
@ -32,7 +34,7 @@
<div *ngIf="loadActive || loadPending" class="uk-margin-large-top">
<loading></loading>
</div>
<div *ngIf="!loadActive && !loadPending" class="uk-margin-medium-top">
<div *ngIf="!loadActive && !loadPending">
<div *ngIf="(showActive && active.length == 0) || (!showActive && pending.length == 0)"
class="uk-card uk-card-default uk-padding-large uk-text-center uk-margin-bottom uk-text-bold">
<div *ngIf="showActive">No {{role}}s for {{name}}</div>

View File

@ -8,8 +8,6 @@ import {properties} from "../../../../../environments/environment";
import {Session, User} from "../../../login/utils/helper.class";
import {UserManagementService} from "../../../services/user-management.service";
import {Router} from "@angular/router";
import {LoginErrorCodes} from "../../../login/utils/guardHelper.class";
import {Composer} from "../../../utils/email/composer";
import {StringUtils} from "../../../utils/string-utils.class";
declare var UIkit;

View File

@ -2,7 +2,6 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RoleUsersComponent} from './role-users.component';
import {ReactiveFormsModule} from '@angular/forms';
import {EmailService} from "../../../utils/email/email.service";
import {AlertModalModule} from "../../../utils/modal/alertModal.module";
import {LoadingModule} from "../../../utils/loading/loading.module";
import {IconsService} from "../../../utils/icons/icons.service";
@ -15,8 +14,7 @@ import {SafeHtmlPipeModule} from "../../../utils/pipes/safeHTMLPipe.module";
@NgModule({
imports: [CommonModule, AlertModalModule, ReactiveFormsModule, LoadingModule, IconsModule, InputModule, PageContentModule, SafeHtmlPipeModule],
declarations: [RoleUsersComponent],
exports: [RoleUsersComponent],
providers: [EmailService]
exports: [RoleUsersComponent]
})
export class RoleUsersModule {
constructor(private iconsService: IconsService) {

View File

@ -0,0 +1,90 @@
<div page-content>
<div header>
<ng-content></ng-content>
<div [class.uk-invisible]="loading" class="uk-flex uk-flex-right@m uk-flex-center uk-flex-wrap uk-flex-middle uk-grid" uk-grid>
<div search-input [control]="filterForm.controls.keyword" [showSearch]="false" placeholder="Search"
[bordered]="true" colorClass="uk-text-secondary" toggleTitle="locate subscribers"></div>
<div>
<a *ngIf="exists" class="uk-text-uppercase uk-flex uk-flex-middle" [class.uk-disabled]="!subscriberInvite || subscriberInvite.loading" (click)="openInviteModal()">
<button class="uk-icon-button large uk-button-secondary">
<icon name="person_add"></icon>
</button>
<button class="uk-button uk-button-link uk-margin-small-left uk-text-secondary">Invite Subscribers</button>
</a>
<a *ngIf="!exists && isPortalAdmin" class="uk-text-uppercase uk-flex uk-flex-middle" (click)="openCreateRoleModal()">
<button class="uk-icon-button large uk-button-secondary">
<icon name="person_add"></icon>
</button>
<button class="uk-button uk-button-link uk-margin-small-left uk-text-secondary">Create Group</button>
</a>
</div>
</div>
</div>
<div inner>
<div *ngIf="loading" class="uk-margin-large-top">
<loading></loading>
</div>
<div *ngIf="!loading">
<div *ngIf="showSubscribers.length == 0"
class="uk-card uk-card-default uk-padding-large uk-text-center uk-margin-bottom uk-text-bold">
<div>No subscribers for {{name}}</div>
</div>
<div *ngIf="showSubscribers.length > 0">
<no-load-paging *ngIf="showSubscribers.length > pageSize" [type]="'subscribers'"
(pageChange)="updatePage($event)"
[page]="page" [pageSize]="pageSize"
[totalResults]="showSubscribers.length">
</no-load-paging>
<div class="uk-margin-medium-top uk-margin-medium-bottom">
<div class="uk-card uk-card-default uk-card-body uk-text-small uk-margin-bottom"
*ngFor="let item of showSubscribers.slice((page - 1)*pageSize, page*pageSize)">
<div class="uk-grid uk-grid-divider uk-flex uk-flex-middle" uk-grid>
<div class="uk-width-3-4@l uk-width-1-2@m">
<div class="uk-padding-small uk-padding-remove-horizontal">
<span class="uk-text-muted">Email: </span>
<span class="uk-text-bold">{{item.email}}</span>
</div>
</div>
<div class="uk-width-expand">
<div class="uk-padding-small uk-padding-remove-horizontal uk-flex uk-flex-center">
<a (click)="openDeleteModal(item)" class="uk-button action uk-flex uk-flex-middle">
<icon name="remove_circle_outline" ratio="0.7" [flex]="true"></icon>
<span class="uk-margin-small-left">Remove subscriber</span>
</a>
</div>
</div>
</div>
</div>
</div>
<no-load-paging *ngIf="showSubscribers.length > pageSize" [type]="'subscribers'"
(pageChange)="updatePage($event)"
[page]="page" [pageSize]="pageSize"
[totalResults]="showSubscribers.length">
</no-load-paging>
</div>
</div>
</div>
</div>
<modal-alert *ngIf="user" #inviteModal (alertOutput)="subscriberInvite.invite()" [okDisabled]="!subscriberInvite.valid" [large]="true">
<div class="uk-padding uk-padding-remove-horizontal">
<subscriber-invite #subscriberInvite [user]="user"></subscriber-invite>
</div>
</modal-alert>
<modal-alert #deleteModal (alertOutput)="deleteSubscriber()">
<div *ngIf="selectedUser" class="uk-padding-small uk-padding-remove-horizontal">
Are you sure you want to remove <span class="uk-text-bold">{{selectedUser}}</span> from subscribers?
</div>
</modal-alert>
<modal-alert #createRoleModal (alertOutput)="createGroup()" [okDisabled]="roleFb && roleFb.invalid">
<div *ngIf="roleFb" class="uk-padding uk-padding-remove-horizontal">
<div class="uk-grid" uk-grid [formGroup]="roleFb">
<div dashboard-input [formInput]="roleFb.get('name')"
label="Name"
placeholder="Write a name..." class="uk-width-1-1"></div>
<div dashboard-input [formInput]="roleFb.get('description')"
label="Description"
type="textarea"
placeholder="Write a description..." class="uk-width-1-1"></div>
</div>
</div>
</modal-alert>

View File

@ -0,0 +1,184 @@
import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild} from '@angular/core';
import {Subscription} from 'rxjs/Rx';
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import {AlertModal} from "../../../utils/modal/alert";
import {UserRegistryService} from "../../../services/user-registry.service";
import {EnvProperties} from "../../../utils/properties/env-properties";
import {properties} from "../../../../../environments/environment";
import {Session, User} from "../../../login/utils/helper.class";
import {UserManagementService} from "../../../services/user-management.service";
import {Router} from "@angular/router";
import {Composer} from "../../../utils/email/composer";
import {SubscriberInviteComponent} from "../../../sharedComponents/subscriber-invite/subscriber-invite.component";
declare var UIkit;
@Component({
selector: 'subscribers',
templateUrl: 'subscribers.component.html'
})
export class SubscribersComponent implements OnInit, OnDestroy, OnChanges {
@Input()
public id: string;
@Input()
public type: string;
@Input()
public name: string;
@Input()
public link: string;
@Input()
public message: string = null;
public user: User = null;
public subscribers: any[];
public showSubscribers: any[];
public subs: any[] = [];
public loading: boolean = true;
public selectedUser: string = null;
public properties: EnvProperties = properties;
public exists: boolean = true;
public roleFb: FormGroup;
/* Paging */
page: number = 1;
pageSize: number = 10;
/* Search */
filterForm: FormGroup;
@ViewChild('inviteModal') inviteModal: AlertModal;
@ViewChild('deleteModal') deleteModal: AlertModal;
@ViewChild('createRoleModal') createRoleModal: AlertModal;
@ViewChild('subscriberInvite') subscriberInvite: SubscriberInviteComponent;
constructor(private userRegistryService: UserRegistryService,
private userManagementService: UserManagementService,
private router: Router,
private fb: FormBuilder) {
}
ngOnInit() {
this.filterForm = this.fb.group({
keyword: this.fb.control('')
});
this.subs.push(this.filterForm.get('keyword').valueChanges.subscribe(value => {
this.filterBySearch(value);
}));
this.updateList();
this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.role) {
this.updateList();
}
}
ngOnDestroy() {
this.subs.forEach(sub => {
if (sub instanceof Subscription) {
sub.unsubscribe();
}
});
}
updateList() {
this.loading = true;
this.subs.push(this.userRegistryService.getActiveEmail(this.type, this.id, 'member').subscribe(users => {
this.subscribers = users;
this.filterBySearch(this.filterForm.value.keyword);
this.loading = false;
this.exists = true;
}, error => {
this.subscribers = [];
this.showSubscribers = [];
if (error.status === 404) {
this.exists = false;
}
this.loading = false;
}));
}
openDeleteModal(item: any) {
this.selectedUser = item.email;
this.deleteModal.alertTitle = 'Delete subscriber';
this.deleteModal.open();
}
openInviteModal() {
if(this.subscriberInvite && !this.subscriberInvite.loading) {
this.inviteModal.alertTitle = 'Invite users to subscribe';
this.inviteModal.okButtonLeft = false;
this.inviteModal.okButtonText = 'Send';
this.subscriberInvite.reset();
this.inviteModal.open();
}
}
openCreateRoleModal() {
this.createRoleModal.alertTitle = 'Create group';
this.createRoleModal.okButtonLeft = false;
this.createRoleModal.okButtonText = 'create';
this.roleFb = this.fb.group({
name: this.fb.control(Session.mapType(this.type) + '.' + this.id, Validators.required),
description: this.fb.control('', Validators.required)
});
setTimeout(() => {
this.roleFb.get('name').disable();
}, 0);
this.createRoleModal.open();
}
deleteSubscriber() {
this.loading = true;
this.userRegistryService.remove(this.type, this.id, this.selectedUser, 'member').subscribe(() => {
this.subscribers = this.subscribers.filter(user => user.email != this.selectedUser);
this.filterBySearch(this.filterForm.value.keyword);
this.userManagementService.updateUserInfo();
UIkit.notification(this.selectedUser + ' <b>is no longer</b> subscribed to ' + this.name + ' Dashboard', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
this.loading = false;
}, error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.loading = false;
});
}
createGroup() {
this.loading = true;
this.roleFb.get('name').enable();
this.userRegistryService.createRole(this.type, this.id, this.roleFb.value).subscribe(() => {
UIkit.notification('Group has been <b> successfully created</b>', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
this.updateList();
}, error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.loading = false;
});
}
public get isPortalAdmin() {
return Session.isPortalAdministrator(this.user) || Session.isCurator(this.type, this.user);
}
public updatePage($event) {
this.page = $event.value;
}
private filterBySearch(value: any) {
this.showSubscribers = this.subscribers.filter(subscriber => !value || subscriber.email.includes(value));
}
}

View File

@ -0,0 +1,26 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {SubscribersComponent} from './subscribers.component';
import {ReactiveFormsModule} from '@angular/forms';
import {AlertModalModule} from "../../../utils/modal/alertModal.module";
import {LoadingModule} from "../../../utils/loading/loading.module";
import {IconsService} from "../../../utils/icons/icons.service";
import {person_add, remove_circle_outline} from "../../../utils/icons/icons";
import {IconsModule} from "../../../utils/icons/icons.module";
import {InputModule} from "../../../sharedComponents/input/input.module";
import {PageContentModule} from "../../sharedComponents/page-content/page-content.module";
import {SafeHtmlPipeModule} from "../../../utils/pipes/safeHTMLPipe.module";
import {NoLoadPaging} from "../../../searchPages/searchUtils/no-load-paging.module";
import {SearchInputModule} from "../../../sharedComponents/search-input/search-input.module";
import {SubscriberInviteModule} from "../../../sharedComponents/subscriber-invite/subscriber-invite.module";
@NgModule({
imports: [CommonModule, AlertModalModule, ReactiveFormsModule, LoadingModule, IconsModule, InputModule, PageContentModule, SafeHtmlPipeModule, NoLoadPaging, SearchInputModule, SubscriberInviteModule],
declarations: [SubscribersComponent],
exports: [SubscribersComponent]
})
export class SubscribersModule {
constructor(private iconsService: IconsService) {
this.iconsService.registerIcons([remove_circle_outline, person_add]);
}
}

View File

@ -9,6 +9,10 @@
transform: translateY(-50%);
}
.uk-grid-small .left {
left: 20px;
}
.left + .input-box {
padding-left: 41px;
}
@ -23,3 +27,8 @@
.right + .input-box {
padding-right: 41px;
}
.input-message {
font-family: "Roboto", sans-serif;
font-size: 14px;
}

View File

@ -17,9 +17,9 @@ export interface Option {
template: `
<div *ngIf="label && type != 'checkbox'" class="uk-text-bold uk-form-label uk-margin-small-bottom">{{label + (required ? ' *' : '')}}</div>
<div *ngIf="hint" class="uk-margin-bottom uk-text-small uk-form-hint">{{hint}}</div>
<div class="uk-grid uk-flex uk-flex-middle" uk-grid>
<div class="uk-grid uk-flex uk-flex-middle" [class.uk-grid-small]="gridSmall" uk-grid>
<ng-content></ng-content>
<div [class.uk-hidden]="hideControl" class="uk-width-expand@m uk-position-relative" [class.uk-flex-first]="!extraLeft">
<div [class.uk-hidden]="hideControl" class="uk-width-expand uk-position-relative" [class.uk-flex-first]="!extraLeft">
<ng-template [ngIf]="icon && formControl.enabled">
<span class="uk-text-muted" [ngClass]="iconLeft?('left'):'right'">
<icon [name]="icon"></icon>
@ -52,8 +52,9 @@ export interface Option {
</mat-form-field>
</div>
</ng-template>
<span *ngIf="formControl.invalid && formControl.errors.error" class="uk-text-small uk-text-danger">{{formControl.errors.error}}</span>
<span *ngIf="warning" class="uk-text-small uk-text-warning">{{warning}}</span>
<span *ngIf="formControl.invalid && formControl.errors.error" class="uk-text-danger input-message">{{formControl.errors.error}}</span>
<span *ngIf="warning" class="uk-text-warning input-message">{{warning}}</span>
<span *ngIf="note" class="input-message">{{note}}</span>
</div>
</div>
<mat-checkbox *ngIf="type === 'checkbox'" [formControl]="formControl">{{label}}</mat-checkbox>
@ -70,10 +71,12 @@ export class InputComponent implements OnInit, OnDestroy, OnChanges {
@Input('placeholder') placeholder = '';
@ViewChild('select') select: MatSelect;
@Input() extraLeft: boolean = true;
@Input() gridSmall: boolean = false;
@Input() hideControl: boolean = false;
@Input() icon: string = null;
@Input() iconLeft: boolean = false;
@Input() warning: string = null;
@Input() note: string = null;
public required: boolean = false;
private initValue: any;
private subscriptions: any[] = [];

View File

@ -40,14 +40,14 @@ import {MatAutocompleteTrigger} from '@angular/material/autocomplete';
<span [ngClass]="colorClass" class="icon-bg">
<icon class="uk-position-center" name="search"></icon>
</span>
<span class="uk-text-uppercase overlay">search</span>
<span class="uk-text-uppercase overlay">{{toggleTitle}}</span>
</button>
<button [disabled]="loading" class="search uk-flex uk-flex-middle uk-hidden@m"
(mousedown)="$event.preventDefault()" (click)="search()">
<span [ngClass]="colorClass" class="icon-bg">
<icon class="uk-position-center" name="search"></icon>
</span>
<span class="uk-text-uppercase overlay">search</span>
<span class="uk-text-uppercase overlay">{{toggleTitle}}</span>
</button>
</div>`
})
@ -68,6 +68,8 @@ export class SearchInputComponent {
colorClass: string = 'portal-color';
@Input()
bordered: boolean = false;
@Input()
toggleTitle: string = 'search';
@ViewChild('input') input: ElementRef;
@ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
@Output()

View File

@ -0,0 +1,4 @@
.field-label {
width: 100px;
text-align: right;
}

View File

@ -0,0 +1,156 @@
import {Component, Input, OnDestroy, OnInit} from "@angular/core";
import {AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators} from "@angular/forms";
import {Subscriber} from "rxjs";
import {StringUtils} from "../../utils/string-utils.class";
import {Email} from "../../utils/email/email";
import {Body} from "../../utils/email/body";
import {CommunityService} from "../../connect/community/community.service";
import {Composer} from "../../utils/email/composer";
import {User} from "../../login/utils/helper.class";
import {EmailService} from "../../utils/email/email.service";
import {properties} from "../../../../environments/environment";
declare var UIkit;
@Component({
selector: 'subscriber-invite',
template: `
<div class="uk-grid uk-child-width-1-1" uk-grid [formGroup]="inviteForm">
<div dashboard-input [formInput]="inviteForm.get('name')" [gridSmall]="true">
<div class="uk-text-bold field-label">
From *:
</div>
</div>
<div dashboard-input [formInput]="inviteForm.get('recipients')" [gridSmall]="true" note="Separate multiple emails with a comma">
<div class="uk-text-bold field-label uk-margin-bottom">
To *:
</div>
</div>
<div class="uk-grid uk-grid-small" uk-grid>
<div class="uk-text-bold field-label">
Message *:
</div>
<div class="uk-width-expand">
<ckeditor class="form-control" formControlName="message" id="message"
debounce="400"
[config]="{ extraAllowedContent: '* [uk-*](*) ; span', disallowedContent: 'script; *[on*]', removeButtons: 'Save,NewPage,DocProps,Preview,Print',
extraPlugins: 'divarea'}"></ckeditor>
<div class="uk-margin-top">
{{body.signature}}
<span *ngIf="body.fromName == ''" class="uk-text-muted">
<i>{{body.fromMessage}}...</i>
</span>
<span *ngIf="body.fromName !=''">
{{body.fromMessage}}
<b>{{body.fromName}}</b>
</span><br>
<a href="https://www.openaire.eu">www.openaire.eu</a>
</div>
</div>
</div>
</div>
`,
styleUrls: ['subscriber-invite.component.css']
})
export class SubscriberInviteComponent implements OnInit, OnDestroy {
@Input()
public user: User;
public inviteForm: FormGroup;
public email: Email;
public body: Body;
public loading: boolean = false;
private subscriptions: any[] = [];
constructor(private fb: FormBuilder,
private emailService: EmailService,
private communityService: CommunityService) {
}
ngOnInit() {
this.reset();
}
ngOnDestroy() {
this.unsubscribe();
}
unsubscribe() {
this.subscriptions.forEach(subscription => {
if(subscription instanceof Subscriber) {
subscription.unsubscribe();
}
});
}
reset() {
this.unsubscribe();
this.inviteForm = this.fb.group({
name: this.fb.control('', Validators.required),
recipients: this.fb.control('', [Validators.required, this.emailsValidator]),
message: this.fb.control('', Validators.required)
});
this.subscriptions.push(this.communityService.getCommunityAsObservable().subscribe(community => {
this.inviteForm.get('name').enable();
this.inviteForm.get('name').setValue(this.user.fullname);
this.inviteForm.get('name').disable();
this.body = Composer.initializeInvitationsBody(community.communityId, community.title, this.user.fullname);
this.email = Composer.initializeInvitationsEmail(community.title);
this.inviteForm.get('message').setValue(this.body.paragraphs);
}));
}
emailsValidator(control: AbstractControl): ValidationErrors | null {
if (control.value === '' || !control.value || StringUtils.validateEmails(control.value)) {
return null;
}
return { emails: true };
}
invite() {
this.loading = true;
this.parseRecipients();
this.body.paragraphs = this.inviteForm.value.message;
this.email.body = Composer.formatEmailBodyForInvitation(this.body);
this.subscriptions.push(this.emailService.sendEmail(properties, this.email).subscribe(res => {
if(res['success']) {
UIkit.notification('Invitation to subscribe has been <b>sent</b>', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
} else {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
}
this.loading = false;
},error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.loading = false;
}));
}
public parseRecipients() {
// remove spaces
let emails = this.inviteForm.get('recipients').value.replace(/\s/g, '');
// remove commas
emails = emails.split(",");
// remove empty fields
for (let i = 0; i < emails.length; i++) {
if (!(emails[i] == "")) {
this.email.recipients.push(emails[i]);
}
}
}
get valid() {
return this.inviteForm && this.inviteForm.valid;
}
}

View File

@ -0,0 +1,17 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {SubscriberInviteComponent} from "./subscriber-invite.component";
import {InputModule} from "../input/input.module";
import {CKEditorModule} from "ng2-ckeditor";
import {ReactiveFormsModule} from "@angular/forms";
import {EmailService} from "../../utils/email/email.service";
@NgModule({
imports: [CommonModule, InputModule, CKEditorModule, ReactiveFormsModule],
declarations: [SubscriberInviteComponent],
exports: [SubscriberInviteComponent],
providers: [EmailService]
})
export class SubscriberInviteModule {
}

View File

@ -242,4 +242,23 @@ export class Composer {
email.recipient = recipient;
return email;
}
public static composeEmailForCommunityDashboard(name: string, recipient: string, role: "manager" | "member") {
let email: Email = new Email();
email.subject = 'OpenAIRE Monitor Dashboard | ' + name;
email.body = '<p>Dear ((__user__)),</p>' +
'<p>You have been invited to be a ' + role +' of the OpenAIRE Monitor Dashboard for the ' + name + '.</p>' +
'<p>Click <a href="((__link__))">this URL</a> and use the verification code below to accept the invitation.</p>' +
'<p>The verification code is <b>((__code__))</b>.</p>' +
(role === "manager"?
'<p>As a manager of the OpenAIRE Monitor Dashboard, you will have access to the administration part of the dashboard, ' +
'where you will be able to customize and manage the profile of the ' + name + '.</p>':
'<p>As a member of the OpenAIRE Monitor Dashboard, you will have access to the restricted access areas of the profile for the ' + name + '.') +
'<p>Please contact us at <a href="mailto:' + properties.helpdeskEmail+'">' + properties.helpdeskEmail +
'</a> if you have any questions or concerns.</p>' +
'<p>Kind Regards<br>The OpenAIRE Team</p>' +
'<p><a href="' + properties.domain + '">OpenAIRE Monitor</a></p>'
email.recipient = recipient;
return email;
}
}

View File

@ -1,5 +1,5 @@
import {UrlSegment} from '@angular/router';
import {ValidatorFn, Validators} from "@angular/forms";
import {AbstractControl, ValidatorFn, Validators} from "@angular/forms";
export class Dates {
public static yearMin = 1800;
@ -214,6 +214,12 @@ export class StringUtils {
return decodeURIComponent(params);
}
public static validateEmails(emails: string): boolean {
return (emails.split(',')
.map(email => Validators.email(<AbstractControl>{ value: email.trim() }))
.find(_ => _ !== null) === undefined);
}
public static b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);