[Library | Trunk]: Notifications for development

git-svn-id: https://svn.driver.research-infrastructures.eu/driver/dnet40/modules/uoa-services-library/trunk/ng-openaire-library/src/app@60572 d315682c-612b-4755-9ff5-7f18f6832af3
This commit is contained in:
k.triantafyllou 2021-03-04 11:32:57 +00:00
parent 2b77657e80
commit 391388f503
15 changed files with 690 additions and 7 deletions

View File

@ -37,7 +37,7 @@ export class PageContentFormComponent implements OnInit {
public errorMessage: string = '';
@Input() updateErrorMessage: string = '';
private subs: Subscription[] = [];
private pageHelpContent: PageHelpContent;
public pageHelpContent: PageHelpContent;
constructor(private route: ActivatedRoute, private _router: Router, private _fb: FormBuilder, private _helpContentService: HelpContentService) {
}

View File

@ -9,6 +9,7 @@ import {Role, Session, User} from "../../../login/utils/helper.class";
import {UserManagementService} from "../../../services/user-management.service";
import {Router} from "@angular/router";
import {StringUtils} from "../../../utils/string-utils.class";
import {NotificationService} from "../../../notifications/notification.service";
declare var UIkit;
@ -54,6 +55,7 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges {
constructor(private userRegistryService: UserRegistryService,
private userManagementService: UserManagementService,
private notificationService: NotificationService,
private router: Router,
private fb: FormBuilder) {
}
@ -207,6 +209,21 @@ export class RoleUsersComponent implements OnInit, OnDestroy, OnChanges {
if (!this.pending.includes(this.invited.value)) {
this.pending.push(this.invited.value);
}
if(this.notificationFn) {
this.subs.push(this.notificationService.sendNotification(this.notificationFn(this.name, this.invited.value, this.role, invitation)).subscribe(notification => {
UIkit.notification('A notification has been <b>sent</b> successfully', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
}, error => {
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
}));
}
UIkit.notification(StringUtils.capitalize(this.role) + ' invitation to ' + this.selectedUser + ' has been <b>sent</b>', {
status: 'success',
timeout: 6000,

View File

@ -10,9 +10,10 @@ 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 {NotifyFormModule} from "../../../notifications/notify-form/notify-form.module";
@NgModule({
imports: [CommonModule, AlertModalModule, ReactiveFormsModule, LoadingModule, IconsModule, InputModule, PageContentModule, SafeHtmlPipeModule],
imports: [CommonModule, AlertModalModule, ReactiveFormsModule, LoadingModule, IconsModule, InputModule, PageContentModule, SafeHtmlPipeModule, NotifyFormModule],
declarations: [RoleUsersComponent],
exports: [RoleUsersComponent]
})

View File

@ -0,0 +1,24 @@
svg:not(.outlined) circle{
fill: currentColor;
stroke: currentColor;
}
svg:not(.outlined) text {
fill: white;
stroke: white;
}
svg.outlined circle{
fill: white;
stroke: currentColor;
}
svg.outlined text {
fill: currentColor;
stroke: currentColor;
}
svg {
user-select: none;
}

View File

@ -0,0 +1,31 @@
import {Component, Input, OnInit} from "@angular/core";
@Component({
selector: 'notification-user',
template: `
<svg *ngIf="firstLetters" height="44" width="44" [ngClass]="colorClass" [class.outlined]="outline">
<circle cx="22" cy="22" r="20" stroke-width="2"></circle>
<text x="50%" y="50%" text-anchor="middle" dy=".4em" font-size="16">
{{firstLetters}}
</text>
</svg>
`,
styleUrls: ['notification-user.component.css']
})
export class NotificationUserComponent implements OnInit{
@Input()
public name: string;
@Input()
public surname: string;
@Input()
public colorClass = 'portal-color';
@Input()
public outline: boolean = false;
public firstLetters: string;
ngOnInit() {
if(this.name && this.surname) {
this.firstLetters = this.name.charAt(0) + this.surname.charAt(0);
}
}
}

View File

@ -0,0 +1,12 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {NotificationUserComponent} from "./notification-user.component";
@NgModule({
imports: [CommonModule],
declarations: [NotificationUserComponent],
exports: [NotificationUserComponent]
})
export class NotificationUserModule {
}

View File

@ -0,0 +1,56 @@
import {Notification} from "./notifications";
import {HelperFunctions} from "../utils/HelperFunctions.class";
import {Composer} from "../utils/email/composer";
export class NotificationUtils {
public static CREATE_STAKEHOLDER: Notification = new Notification('CREATE', ['monitor'], 'User ((__user__)) has created a new profile', 'stakeholder')
public static EDIT_STAKEHOLDER: Notification = new Notification('EDIT', ['monitor'], 'User ((__user__)) has updated ((__stakeholder__)) profile', 'stakeholder')
public static CREATE_INDICATOR: Notification = new Notification('CREATE', ['monitor'], 'User ((__user__)) has created a new indicator in ((__stakeholder__)) profile', 'indicator');
public static EDIT_INDICATOR: Notification = new Notification('EDIT', ['monitor'], 'User ((__user__)) has updated an indicator in ((__stakeholder__)) profile', 'indicator');
public static DELETE_INDICATOR: Notification = new Notification('DELETE', ['monitor'], 'User ((__user__)) has deleted an indicator in ((__stakeholder__)) profile', 'indicator');
public static INVITE_MONITOR_MANAGER: Notification = new Notification('INVITE_MANAGER', ['monitor'], null, 'user');
public static INVITE_MONITOR_MEMBER: Notification = new Notification('INVITE_MEMBER', ['monitor'], null, 'user');
public static createStakeholder(user: string): Notification {
let notification: Notification = HelperFunctions.copy(this.CREATE_STAKEHOLDER);
notification.message = notification.message.replace('((__user__))', user);
return notification;
}
public static editStakeholder(user: string, stakeholder: string): Notification {
let notification: Notification = HelperFunctions.copy(this.EDIT_STAKEHOLDER);
notification.message = notification.message.replace('((__user__))', user);
notification.message = notification.message.replace('((__stakeholder__))', stakeholder);
return notification;
}
public static createIndicator(user: string, stakeholder: string): Notification {
let notification: Notification = HelperFunctions.copy(this.CREATE_INDICATOR);
notification.message = notification.message.replace('((__user__))', user);
notification.message = notification.message.replace('((__stakeholder__))', stakeholder);
return notification;
}
public static editIndicator(user: string, stakeholder: string): Notification {
let notification: Notification = HelperFunctions.copy(this.EDIT_INDICATOR);
notification.message = notification.message.replace('((__user__))', user);
notification.message = notification.message.replace('((__stakeholder__))', stakeholder);
return notification;
}
public static deleteIndicator(user: string, stakeholder: string): Notification {
let notification: Notification = HelperFunctions.copy(this.DELETE_INDICATOR);
notification.message = notification.message.replace('((__user__))', user);
notification.message = notification.message.replace('((__stakeholder__))', stakeholder);
return notification;
}
public static invite(name: string,role: "manager" | "member", user, invitation: string): Notification {
let notification: Notification = HelperFunctions.copy(this.INVITE_MONITOR_MANAGER);
if(role === "member") {
notification = HelperFunctions.copy(this.INVITE_MONITOR_MEMBER);
}
notification.message = Composer.composeMessageForMonitorDashboard(name, role, user, invitation);
return notification;
}
}

View File

@ -0,0 +1,54 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {properties} from "../../../environments/environment";
import {Observable} from "rxjs";
import {Notification, NotificationUser} from "./notifications";
import {CustomOptions} from "../services/servicesUtils/customOptions.class";
import {not} from "rxjs/internal-compatibility";
import {map} from "rxjs/operators";
@Injectable({
providedIn: "root"
})
export class NotificationService {
constructor(private httpClient: HttpClient) {
}
public getAllNotifications(): Observable<Notification[]> {
return this.httpClient.get<Notification[]>(properties.notificationsAPIURL + 'all', CustomOptions.registryOptions()).pipe(map(notifications => {
notifications.forEach(notification => {
this.formatNotification(notification);
})
return notifications;
}));
}
public getNotifications(service: string): Observable<Notification[]> {
return this.httpClient.get<Notification[]>(properties.notificationsAPIURL + encodeURIComponent(service), CustomOptions.registryOptions()).pipe(map(notifications => {
notifications.forEach(notification => {
this.formatNotification(notification);
})
return notifications;
}));
}
public sendNotification(notification: Notification): Observable<Notification> {
return this.httpClient.post<Notification>(properties.notificationsAPIURL + 'save', notification, CustomOptions.registryOptions());
}
public readNotification(id: string): Observable<NotificationUser> {
return this.httpClient.put<NotificationUser>(properties.notificationsAPIURL + encodeURIComponent(id), null, CustomOptions.registryOptions());
}
private formatNotification(notification: Notification): Notification {
if (notification.title) {
notification.preview = notification.title;
} else {
notification.preview = notification.message.replace(/<[^>]*>/g, '');
notification.preview = notification.preview.replace(/(\r\n|\n|\r| +(?= ))|\s\s+/gm, " ");
}
return notification;
}
}

View File

@ -0,0 +1,115 @@
#notifications-switcher {
top: 250px !important;
position: fixed;
height: 36px;
background-color: var(--portal-main-color);
color: var(--portal-main-contrast);
border-radius: 4px 0 0 4px;
cursor: pointer;
padding: 4px;
box-shadow: -2px 2px 5px rgba(0, 0, 0, .26);
box-sizing: border-box;
right: 0;
z-index: 980;
}
#notifications-switcher #notifications-count {
position: absolute;
top: 0;
left: 0;
font-size: 10px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
height: 14px;
border-radius: 50%;
padding: 1px 5px;
}
#notifications .uk-offcanvas-bar {
background-color: white;
color: #1a1a1a;
font-size: 14px;
top: 100px;
padding: 45px 0;
border: 1px solid var(--portal-main-color);
width: 550px;
}
#notifications .uk-offcanvas-flip .uk-offcanvas-bar {
right: -550px;
}
#notifications .uk-offcanvas-bar .text-small {
font-size: 12px;
}
#notifications .uk-offcanvas-bar h5, #notifications .uk-offcanvas-bar h6 {
color: #1a1a1a;
}
#notifications button.notification-close, #notifications button.notification-close:focus {
border-radius: 4px;
background-color: #F0F0F0;
color: #76706B;
border: none;
outline: none;
}
#notifications button.notification-close:hover, #notifications button.notification-close:focus {
color: #1a1a1a;
}
#notifications .notification-list {
padding: 0 0 0 45px;
}
#notifications .notification-list:not(:last-child) {
height: 500px;
border-bottom: 1px solid #E4E4E4;
}
#notifications .notification-list ul {
overflow: auto;
padding: 10px 45px 10px 0;
height: calc(100% - 66px);
}
#notifications .notification-list ul > li:nth-child(n+2){
margin-top: 15px;
}
#notifications .notify {
padding: 20px 45px;
}
#notifications .notification {
padding: 0 45px;
}
#notifications .uk-offcanvas-bar a {
color: #2D72D6 ;
}
#notifications .uk-offcanvas-bar a:hover {
color: var(--portal-main-color);
}
#notifications .uk-offcanvas-bar .uk-button-secondary {
background-color: #4687e6;
color: #fff;
border: 1px solid transparent;
}
#notifications .uk-offcanvas-bar .uk-button-secondary:focus, #notifications .uk-offcanvas-bar .uk-button-secondary:hover {
background-color: transparent;
color: #4687e6;
border-color: #4687e6;
}
#notifications .uk-offcanvas-bar .uk-button-secondary:disabled {
background-color: transparent;
color: #bfbfbf;
border: 1px solid #ededed;
background-image: none;
box-shadow: none;
}

View File

@ -0,0 +1,123 @@
import {Component, Input, OnDestroy, OnInit, ViewEncapsulation} from "@angular/core";
import {Notification} from "../notifications";
import {NotificationService} from "../notification.service";
import {Subscription} from "rxjs";
import {User} from "../../login/utils/helper.class";
import {Dates} from "../../utils/string-utils.class";
import {Option} from "../../sharedComponents/input/input.component";
@Component({
selector: 'notification-sidebar',
template: `
<div id="notifications-switcher" uk-toggle href="#notifications">
<icon name="mail"></icon>
<span [class.uk-hidden]="unreadCount === 0" id="notifications-count">
{{unreadCount}}
</span>
</div>
<div id="notifications" uk-offcanvas="flip: true; bg-close: false;">
<div class="uk-offcanvas-bar">
<button class="uk-offcanvas-close notification-close" type="button">
<icon name="close"></icon>
</button>
<ng-template [ngIf]="!notification">
<div class="notification-list uk-position-relative">
<h5 class="uk-text-bold">Notifications</h5>
<h6 *ngIf="notifications.length == 0" class="uk-position-center uk-margin-remove">No notifications</h6>
<ul *ngIf="notifications.length > 0" class="uk-list">
<li *ngFor="let notification of notifications; let i=index" class="clickable" (click)="select(notification)">
<div class="uk-grid uk-grid-small" uk-grid>
<notification-user [name]="user.firstname" [surname]="user.lastname" [outline]="true"
colorClass="uk-text-secondary"></notification-user>
<div class="uk-width-expand">
<div class="uk-width-1-1 uk-flex uk-flex-middle">
<div class="uk-width-expand multi-line-ellipsis lines-2">
<p class="uk-margin-remove">
{{notification.preview}}
</p>
</div>
<div class="uk-margin-left uk-flex uk-flex-center uk-text-secondary">
<icon *ngIf="!notification.read" name="bullet" ratio="0.6"></icon>
</div>
</div>
<span class="uk-text-secondary text-small">{{getDate(notification.date)}}</span>
</div>
</div>
</li>
</ul>
</div>
<div *ngIf="availableGroups.length > 0" [availableGroups]="availableGroups" [service]="service" notify-form class="notify"></div>
</ng-template>
<div *ngIf="notification" class="notification">
<div class="uk-flex uk-flex-middle uk-margin-medium-bottom">
<span class="uk-text-secondary clickable" (click)="back($event)">
<icon ratio="1.5" name="arrow_left"></icon>
</span>
<h5 *ngIf="notification.title" class="uk-text-bold uk-margin-left">{{notification.title}}</h5>
</div>
<div class="uk-flex uk-flex-middle uk-margin-medium-bottom">
<notification-user [name]="notification.name" [surname]="notification.surname" colorClass="uk-text-secondary" [outline]="true"></notification-user>
<div class="uk-margin-left">
{{notification.name + ' ' + notification.surname}}<br>
<span style="opacity: 0.8;" class="text-small uk-margin-small-top">
{{notification.date | date:'medium'}} ({{getDate(notification.date)}})
</span>
</div>
</div>
<div [innerHTML]="notification.message | safeHtml">
</div>
</div>
</div>
</div>
`,
styleUrls: ['notification-sidebar.component.css'],
encapsulation: ViewEncapsulation.None
})
export class NotificationsSidebarComponent implements OnInit, OnDestroy {
@Input()
public user: User;
public notifications: Notification[] = [];
@Input()
public availableGroups: Option[] = [];
@Input()
public service: string;
public notification: Notification;
private subscriptions: any[] = [];
constructor(private notificationService: NotificationService) {
}
ngOnInit() {
this.subscriptions.push(this.notificationService.getNotifications(this.service).subscribe(notifications => {
this.notifications = notifications;
}));
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => {
if (subscription instanceof Subscription) {
subscription.unsubscribe();
}
})
}
get unreadCount(): number {
return this.notifications.filter(notification => !notification.read).length;
}
getDate(date: Date): string {
return Dates.timeSince(date);
}
select(notification: Notification) {
this.notificationService.readNotification(notification._id).subscribe(user => {
notification.read = true;
this.notification = notification;
});
}
back(event) {
event.stopPropagation();
this.notification = null;
}
}

View File

@ -0,0 +1,20 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {NotificationsSidebarComponent} from "./notifications-sidebar.component";
import {IconsModule} from "../../utils/icons/icons.module";
import {IconsService} from "../../utils/icons/icons.service";
import {arrow_left, bullet, close, mail} from "../../utils/icons/icons";
import {NotificationUserModule} from "../notification-user/notification-user.module";
import {NotifyFormModule} from "../notify-form/notify-form.module";
import {SafeHtmlPipeModule} from "../../utils/pipes/safeHTMLPipe.module";
@NgModule({
imports: [CommonModule, IconsModule, NotificationUserModule, NotifyFormModule, SafeHtmlPipeModule],
declarations: [NotificationsSidebarComponent],
exports: [NotificationsSidebarComponent]
})
export class NotificationsSidebarModule {
constructor(private iconsService: IconsService) {
this.iconsService.registerIcons([mail, close, bullet, arrow_left]);
}
}

View File

@ -0,0 +1,31 @@
export class Notification {
actionType: string;
services: string[];
user: string;
name: string;
surname: string;
title?: string;
preview: string;
message: string;
stakeholder: string;
stakeholderType: string;
entity: string;
entityType: string;
date: Date;
groups: string[];
read: boolean = false;
_id: string;
constructor(actionType: string, services: string[], message: string, entityType: string) {
this.actionType = actionType;
this.services = services;
this.message = message;
this.entityType = entityType;
}
}
export interface NotificationUser {
_id: string,
read: string[]
}

View File

@ -0,0 +1,181 @@
import {ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
import {User} from "../../login/utils/helper.class";
import {UserManagementService} from "../../services/user-management.service";
import {of, Subscription} from "rxjs";
import {NotificationService} from "../notification.service";
import {Notification} from "../notifications";
import {InputComponent, Option} from "../../sharedComponents/input/input.component";
import {properties} from "../../../../environments/environment";
declare var UIkit;
@Component({
selector: '[notify-form]',
template: `
<form *ngIf="user && form" [formGroup]="form">
<ng-template [ngIf]="form.get('notify')">
<mat-checkbox formControlName="notify" class="uk-text-small">{{label}}</mat-checkbox>
<div [class.uk-hidden]="!form.get('notify').value" class="uk-grid uk-grid-small uk-margin-top" uk-grid>
<div style="margin-left: -5px;">
<notification-user [name]="user.firstname" [surname]="user.lastname"></notification-user>
</div>
<div dashboard-input [formInput]="form.get('message')"
rows="3" placeholder="Send a notification"
type="textarea" class="uk-width-expand"></div>
</div>
</ng-template>
<ng-template [ngIf]="form.get('groups') && availableGroups">
<div class="uk-grid uk-grid-small" uk-grid>
<span style="opacity: 0.5;" class="uk-text-bold uk-margin-small-top">Send to: </span>
<div [class.uk-hidden]="focused" class="uk-width-expand uk-margin-small" (click)="focus($event)">
<span *ngIf="groups.length === 0" class="placeholder">Add a recipient</span>
<span *ngIf="groups.length > 0" [attr.uk-tooltip]="(groups.length > 2)?groups.join(', '):null">
{{groups.slice(0, 2).join(', ')}}
<span *ngIf="groups.length > 2" style="opacity: 0.5; margin-left: 4px">+ {{groups.length - 2}} more</span>
</span>
</div>
<div #recipients dashboard-input type="chips" [options]="availableGroups" [class.uk-hidden]="!focused"
panelClass="uk-text-small" [showOptionsOnEmpty]="false"
inputClass="input-borderless" class="uk-width-expand" (focusEmitter)="onFocus($event)" [panelWidth]="400"
[smallChip]="true" [gridSmall]="true" [formInput]="form.get('groups')">
</div>
</div>
<div class="uk-grid uk-grid-small uk-margin-top" uk-grid>
<div>
<notification-user [name]="user.firstname" [surname]="user.lastname"></notification-user>
</div>
<div dashboard-input [formInput]="form.get('message')"
[rows]="4" placeholder="Send a notification"
type="textarea" class="uk-width-expand">
<div options class="uk-margin-top uk-width-1-1 uk-flex uk-flex-right">
<button *ngIf="!sending && message" (click)="sendNotification()"
class="uk-button uk-button-small uk-button-secondary">Send</button>
<button *ngIf="sending || !message" (click)="sendNotification()"
class="uk-button uk-button-small uk-button-secondary" disabled>Send</button>
</div>
</div>
</div>
</ng-template>
</form>
`
})
export class NotifyFormComponent implements OnInit, OnDestroy {
@Input()
public label: string = 'Notify Managers';
public form: FormGroup;
@Input()
public availableGroups: Option[] = null;
@Input() service: string;
public user: User;
public focused: boolean = false;
public groups: string[] = [];
@ViewChild('recipients') recipients: InputComponent;
private notification: Notification;
private subscriptions: any[] = [];
public sending: boolean = false;
constructor(private fb: FormBuilder,
private cdr: ChangeDetectorRef,
private userManagementService: UserManagementService,
private notificationService: NotificationService) {
}
ngOnInit() {
this.reset();
this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
this.user = user;
}));
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => {
if (subscription instanceof Subscription) {
subscription.unsubscribe();
}
})
}
reset(message: string = null) {
if (!this.availableGroups) {
this.form = this.fb.group({
notify: this.fb.control(properties.environment === 'development'),
message: this.fb.control(message)
});
this.subscriptions.push(this.form.get('notify').valueChanges.subscribe(value => {
if (value) {
this.form.get('message').markAsUntouched();
}
}));
} else {
this.form = this.fb.group({
groups: this.fb.array([]),
message: this.fb.control(message)
});
this.groups = [];
this.subscriptions.push(this.form.get('groups').valueChanges.subscribe(value => {
this.groups = [];
value.forEach(group => {
this.groups.push(this.availableGroups.find(available => available.value === group).label);
});
this.cdr.detectChanges();
}));
}
}
sendNotification(notification: Notification = null) {
if (this.message) {
if(notification === null) {
notification = new Notification('CUSTOM', [this.service], null, null);
notification.groups = this.groupsAsFromArray.value;
this.sending = true;
}
this.notification = notification;
this.notification.message = this.form.value.message;
// TODO remove
this.notification.name = this.user.firstname;
this.notification.surname = this.user.lastname;
this.subscriptions.push(this.notificationService.sendNotification(this.notification).subscribe(notification => {
this.sending = false;
UIkit.notification('A notification has been <b>sent</b> successfully', {
status: 'success',
timeout: 6000,
pos: 'bottom-right'
});
this.reset();
}, error => {
this.sending = false;
UIkit.notification('An error has occurred. Please try again later', {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.reset();
}));
}
}
get groupsAsFromArray(): FormArray {
return this.form.get('groups')?(<FormArray>this.form.get('groups')):null;
}
get message(): string {
if ((this.form.get('notify') && !this.form.get('notify').value) || (this.groupsAsFromArray && this.groupsAsFromArray.length === 0)) {
return null;
}
return this.form.get('message').value;
}
onFocus(event: boolean) {
this.focused = event;
}
focus(event) {
this.focused = true;
event.stopPropagation();
this.cdr.detectChanges();
setTimeout(() => {
this.recipients.searchInput.nativeElement.focus();
}, 0);
}
}

View File

@ -0,0 +1,16 @@
import {NgModule} from "@angular/core";
import {NotifyFormComponent} from "./notify-form.component";
import {CommonModule} from "@angular/common";
import {MatCheckboxModule} from "@angular/material/checkbox";
import {ReactiveFormsModule} from "@angular/forms";
import {InputModule} from "../../sharedComponents/input/input.module";
import {NotificationUserModule} from "../notification-user/notification-user.module";
@NgModule({
imports: [CommonModule, MatCheckboxModule, ReactiveFormsModule, InputModule, NotificationUserModule],
declarations: [NotifyFormComponent],
exports: [NotifyFormComponent]
})
export class NotifyFormModule {
}

View File

@ -5,10 +5,12 @@ import {AlertModalModule} from "../utils/modal/alertModal.module";
import {ReactiveFormsModule} from "@angular/forms";
import {LoadingModule} from "../utils/loading/loading.module";
import {InputModule} from "../sharedComponents/input/input.module";
import {EmailService} from "../utils/email/email.service";
@NgModule({
imports: [CommonModule, AlertModalModule, ReactiveFormsModule, LoadingModule, InputModule],
declarations: [RoleVerificationComponent],
exports: [RoleVerificationComponent]
exports: [RoleVerificationComponent],
providers: [EmailService]
})
export class RoleVerificationModule {}