Pull request #304: VM/NotificationsPreferences
Merge in RED/ui from VM/NotificationsPreferences to master * commit '070df8a0ab5ced2e9c2e525f2f2e856ce045bcef': updated EmailNotificationScheduleTypes to match with the backend values removed 'red-page-layout.scss' solved comments solved pr comments updated request added model removed changes pushed by mistake updates for integration with backend WIP on notification preferences service added notifications preferences form WIP on Notifications Preferences moved user profile screen into account settings WIP on adding Notifications Preferences
This commit is contained in:
commit
9f39fa452a
@ -7,7 +7,6 @@ import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -21,18 +20,9 @@ const routes = [
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'main/my-profile',
|
||||
path: 'main/account',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: UserProfileScreenComponent,
|
||||
},
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
},
|
||||
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||
},
|
||||
{
|
||||
path: 'main/admin',
|
||||
|
||||
@ -20,7 +20,6 @@ import { DownloadsListScreenComponent } from '@components/downloads-list-screen/
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
||||
import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { BASE_HREF } from './tokens';
|
||||
import { MONACO_PATH, MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
@ -51,7 +50,7 @@ function cleanupBaseUrl(baseUrl: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
|
||||
const screens = [BaseScreenComponent, DownloadsListScreenComponent];
|
||||
|
||||
const components = [AppComponent, AuthErrorComponent, NotificationsComponent, SpotlightSearchComponent, ...screens];
|
||||
|
||||
|
||||
@ -32,9 +32,10 @@ export class BaseScreenComponent {
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly userMenuItems: readonly MenuItem[] = [
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.my-profile'),
|
||||
routerLink: '/main/my-profile',
|
||||
show: true,
|
||||
name: _('top-bar.navigation-items.my-account.children.account'),
|
||||
routerLink: '/main/account',
|
||||
show: this.currentUser.isUser,
|
||||
action: this.appStateService.reset,
|
||||
showDot: () => false,
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
<section class="content-inner">
|
||||
<div class="content-container full-height">
|
||||
<div class="overlay-shadow"></div>
|
||||
<div class="dialog">
|
||||
<div class="dialog-header">
|
||||
<div class="heading-l" translate="user-profile.title"></div>
|
||||
</div>
|
||||
<form (submit)="save()" [formGroup]="formGroup">
|
||||
<div class="dialog-content">
|
||||
<div class="dialog-content-left">
|
||||
<div class="iqser-input-group required">
|
||||
<label translate="user-profile.form.email"></label>
|
||||
<input formControlName="email" name="email" type="email" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group">
|
||||
<label translate="user-profile.form.first-name"></label>
|
||||
<input formControlName="firstName" name="firstName" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group">
|
||||
<label translate="user-profile.form.last-name"></label>
|
||||
<input formControlName="lastName" name="lastName" type="text" />
|
||||
</div>
|
||||
<div class="iqser-input-group">
|
||||
<label translate="top-bar.navigation-items.my-account.children.language.label"></label>
|
||||
<mat-select formControlName="language">
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ translations[language] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button
|
||||
[disabled]="formGroup.invalid || !(profileChanged || languageChanged)"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
>
|
||||
{{ 'user-profile.actions.save' | translate }}
|
||||
</button>
|
||||
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile.actions.change-password' | translate }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -0,0 +1,36 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '../../state/app-state.guard';
|
||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||
|
||||
const routes = [
|
||||
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' },
|
||||
{
|
||||
path: 'user-profile',
|
||||
component: BaseAccountScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
requiredRoles: ['RED_USER'],
|
||||
},
|
||||
loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule),
|
||||
},
|
||||
{
|
||||
path: 'notifications',
|
||||
component: BaseAccountScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
},
|
||||
loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AccountRoutingModule {}
|
||||
@ -0,0 +1,7 @@
|
||||
<iqser-side-nav [title]="'account-settings' | translate">
|
||||
<ng-container *ngFor="let item of items">
|
||||
<div [routerLinkActiveOptions]="{ exact: false }" [routerLink]="'../' + item.screen" class="item" routerLinkActive="active">
|
||||
{{ item.label | translate }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</iqser-side-nav>
|
||||
@ -0,0 +1,7 @@
|
||||
:host {
|
||||
height: calc(100vh - 61px);
|
||||
|
||||
&.dossier-templates {
|
||||
height: calc(100vh - 111px);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
interface NavItem {
|
||||
readonly label: string;
|
||||
readonly screen: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-account-side-nav',
|
||||
templateUrl: './account-side-nav.component.html',
|
||||
styleUrls: ['./account-side-nav.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccountSideNavComponent {
|
||||
readonly items: NavItem[] = [
|
||||
{
|
||||
screen: 'user-profile',
|
||||
label: _('user-profile'),
|
||||
},
|
||||
{
|
||||
screen: 'notifications',
|
||||
label: _('notifications'),
|
||||
},
|
||||
];
|
||||
}
|
||||
14
apps/red-ui/src/app/modules/account/account.module.ts
Normal file
14
apps/red-ui/src/app/modules/account/account.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { AccountRoutingModule } from './account-routing.module';
|
||||
import { AccountSideNavComponent } from './account-side-nav/account-side-nav.component';
|
||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||
import { NotificationPreferencesService } from './services/notification-preferences.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AccountSideNavComponent, BaseAccountScreenComponent],
|
||||
imports: [CommonModule, SharedModule, AccountRoutingModule],
|
||||
providers: [NotificationPreferencesService],
|
||||
})
|
||||
export class AccountModule {}
|
||||
@ -0,0 +1,20 @@
|
||||
<section class="settings">
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<redaction-account-side-nav></redaction-account-side-nav>
|
||||
|
||||
<div>
|
||||
<div class="content-inner">
|
||||
<div class="content-container full-height">
|
||||
<div class="overlay-shadow"></div>
|
||||
<div class="dialog">
|
||||
<div class="dialog-header">
|
||||
<div class="heading-l" [translate]="translations[path]"></div>
|
||||
</div>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,11 +1,16 @@
|
||||
@use 'variables';
|
||||
@use 'common-mixins';
|
||||
@use 'apps/red-ui/src/assets/styles/variables';
|
||||
@use 'libs/common-ui/src/assets/styles/common-mixins';
|
||||
|
||||
.content-container {
|
||||
background-color: variables.$grey-2;
|
||||
justify-content: center;
|
||||
@include common-mixins.scroll-bar;
|
||||
overflow: auto;
|
||||
|
||||
.dialog {
|
||||
width: var(--width);
|
||||
min-height: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.full-height {
|
||||
@ -17,11 +22,3 @@
|
||||
height: calc(100% + 50px);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: white;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { notificationsTranslations } from '../translations/notifications-translations';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-base-account-screen',
|
||||
templateUrl: './base-account-screen-component.html',
|
||||
styleUrls: ['./base-account-screen-component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BaseAccountScreenComponent implements OnInit {
|
||||
readonly translations = notificationsTranslations;
|
||||
path: string;
|
||||
|
||||
constructor(private readonly _router: Router, private readonly _hostRef: ViewContainerRef) {
|
||||
this.path = this._router.url.split('/').pop();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._setDialogWidth();
|
||||
}
|
||||
|
||||
private _setDialogWidth() {
|
||||
const element = this._hostRef.element.nativeElement as HTMLElement;
|
||||
element.style.setProperty('--width', this.path === 'user-profile' ? 'unset' : '100%');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
export const NotificationCategories = {
|
||||
inAppNotifications: 'inAppNotifications',
|
||||
emailNotifications: 'emailNotifications',
|
||||
} as const;
|
||||
|
||||
export const NotificationCategoriesValues = Object.values(NotificationCategories);
|
||||
|
||||
export const OwnDossiersNotificationsTypes = {
|
||||
dossierStatusChanges: 'dossierStatusChanges',
|
||||
requestToJoinTheDossier: 'requestToJoinTheDossier',
|
||||
documentStatusChanges: 'documentStatusChanges',
|
||||
documentIsSentForApproval: 'documentIsSentForApproval',
|
||||
} as const;
|
||||
|
||||
export const OwnDossiersNotificationsTypesValues = Object.values(OwnDossiersNotificationsTypes);
|
||||
|
||||
export const ReviewerOnDossiersNotificationsTypes = {
|
||||
whenIAmAssignedOnADocument: 'whenIAmAssignedOnADocument',
|
||||
whenIAmUnassignedFromADocument: 'whenIAmUnassignedFromADocument',
|
||||
whenADocumentIsApproved: 'whenADocumentIsApproved',
|
||||
} as const;
|
||||
|
||||
export const ReviewerOnDossiersNotificationsTypesValues = Object.values(ReviewerOnDossiersNotificationsTypes);
|
||||
|
||||
export const ApproverOnDossiersNotificationsTypes = {
|
||||
whenADocumentIsSentForApproval: 'whenADocumentIsSentForApproval',
|
||||
whenADocumentIsAssignedToAReviewer: 'whenADocumentIsAssignedToAReviewer',
|
||||
whenAReviewerIsUnassignedFromADocument: 'whenAReviewerIsUnassignedFromADocument',
|
||||
} as const;
|
||||
|
||||
export const ApproverOnDossiersNotificationsTypesValues = Object.values(ApproverOnDossiersNotificationsTypes);
|
||||
|
||||
export const NotificationGroupsKeys = ['own', 'reviewer', 'approver'] as const;
|
||||
export const NotificationGroupsValues = [
|
||||
OwnDossiersNotificationsTypesValues,
|
||||
ReviewerOnDossiersNotificationsTypesValues,
|
||||
ApproverOnDossiersNotificationsTypesValues,
|
||||
] as const;
|
||||
@ -0,0 +1,43 @@
|
||||
<form (submit)="save()" [formGroup]="formGroup">
|
||||
<div class="dialog-content">
|
||||
<div *ngFor="let category of notificationCategories">
|
||||
<div class="iqser-input-group header w-full">
|
||||
<mat-slide-toggle color="primary" formControlName="{{ category }}Enabled">{{
|
||||
translations[category] | translate
|
||||
}}</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="options-content" *ngIf="isCategoryActive(category)">
|
||||
<div class="radio-container" *ngIf="category === 'emailNotifications'">
|
||||
<div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">
|
||||
<iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">
|
||||
</iqser-round-checkbox>
|
||||
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="statement" translate="notifications-screen.options-title"></div>
|
||||
|
||||
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
|
||||
<div class="group-title" [translate]="translations[key]"></div>
|
||||
<div class="iqser-input-group">
|
||||
<mat-checkbox
|
||||
*ngFor="let preference of notificationGroupsValues[i]"
|
||||
color="primary"
|
||||
[checked]="isPreferenceChecked(category, preference)"
|
||||
(change)="addRemovePreference($event.checked, category, preference)"
|
||||
>
|
||||
{{ translations[preference] | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
|
||||
{{ 'user-profile-screen.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,51 @@
|
||||
@use 'variables';
|
||||
@use 'libs/common-ui/src/assets/styles/common-mixins';
|
||||
|
||||
.dialog-content {
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
grid-column: span 2;
|
||||
padding: 10px 10px;
|
||||
margin-bottom: -1px;
|
||||
border-top: 1px solid variables.$separator;
|
||||
border-bottom: 1px solid variables.$separator;
|
||||
}
|
||||
|
||||
.options-content {
|
||||
padding: 10px 48px;
|
||||
|
||||
.statement {
|
||||
opacity: 0.7;
|
||||
color: variables.$grey-1;
|
||||
font-weight: 500;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.radio-container {
|
||||
display: flex;
|
||||
padding: 10px 0 10px;
|
||||
.radio-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 30px;
|
||||
iqser-round-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
padding: 10px 0;
|
||||
|
||||
.group-title {
|
||||
color: variables.$grey-1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.iqser-input-group {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { notificationsTranslations } from '../../../translations/notifications-translations';
|
||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants';
|
||||
import { EmailNotificationScheduleTypesValues } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-notifications-screen',
|
||||
templateUrl: './notifications-screen.component.html',
|
||||
styleUrls: ['./notifications-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NotificationsScreenComponent implements OnInit {
|
||||
readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues;
|
||||
readonly notificationCategories = NotificationCategoriesValues;
|
||||
readonly notificationGroupsKeys = NotificationGroupsKeys;
|
||||
readonly notificationGroupsValues = NotificationGroupsValues;
|
||||
readonly translations = notificationsTranslations;
|
||||
|
||||
formGroup: FormGroup;
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _notificationPreferencesService: NotificationPreferencesService,
|
||||
) {
|
||||
this.formGroup = this._formBuilder.group({
|
||||
inAppNotificationsEnabled: [undefined],
|
||||
emailNotificationsEnabled: [undefined],
|
||||
emailNotificationType: [undefined],
|
||||
emailNotifications: [undefined],
|
||||
inAppNotifications: [undefined],
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._initializeForm();
|
||||
}
|
||||
|
||||
isCategoryActive(category: string) {
|
||||
return this.formGroup.get(`${category}Enabled`).value;
|
||||
}
|
||||
|
||||
setEmailNotificationType(type: string) {
|
||||
this.formGroup.get('emailNotificationType').setValue(type);
|
||||
}
|
||||
|
||||
getEmailNotificationType() {
|
||||
return this.formGroup.get('emailNotificationType').value;
|
||||
}
|
||||
|
||||
isPreferenceChecked(category: string, preference: string) {
|
||||
return this.formGroup.get(category).value.includes(preference);
|
||||
}
|
||||
|
||||
addRemovePreference(checked: boolean, category: string, preference: string) {
|
||||
const preferences = this.formGroup.get(category).value;
|
||||
if (checked) {
|
||||
preferences.push(preference);
|
||||
} else {
|
||||
const indexOfPreference = preferences.indexOf(preference);
|
||||
preferences.splice(indexOfPreference, 1);
|
||||
}
|
||||
this.formGroup.get(category).setValue(preferences);
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await this._notificationPreferencesService.update(this.formGroup.value).toPromise();
|
||||
} catch (e) {
|
||||
this._toaster.error(_('notifications-screen.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private async _initializeForm() {
|
||||
this._loadingService.start();
|
||||
|
||||
const notificationPreferences = await this._notificationPreferencesService.get().toPromise();
|
||||
this.formGroup.patchValue(notificationPreferences);
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
|
||||
|
||||
const routes = [{ path: '', component: NotificationsScreenComponent }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationsScreenComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
@ -0,0 +1,37 @@
|
||||
<form (submit)="save()" [formGroup]="formGroup">
|
||||
<div class="dialog-content">
|
||||
<div class="dialog-content-left">
|
||||
<div class="iqser-input-group required">
|
||||
<label translate="user-profile-screen.form.email"></label>
|
||||
<input formControlName="email" name="email" type="email" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group">
|
||||
<label translate="user-profile-screen.form.first-name"></label>
|
||||
<input formControlName="firstName" name="firstName" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group">
|
||||
<label translate="user-profile-screen.form.last-name"></label>
|
||||
<input formControlName="lastName" name="lastName" type="text" />
|
||||
</div>
|
||||
<div class="iqser-input-group">
|
||||
<label translate="top-bar.navigation-items.my-account.children.language.label"></label>
|
||||
<mat-select formControlName="language">
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ translations[language] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
<div class="iqser-input-group">
|
||||
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="formGroup.invalid || !(profileChanged || languageChanged)" color="primary" mat-flat-button type="submit">
|
||||
{{ 'user-profile-screen.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,3 @@
|
||||
a {
|
||||
color: black;
|
||||
}
|
||||
@ -1,19 +1,20 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { LanguageService } from '@i18n/language.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { languagesTranslations } from '../../translations/languages-translations';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LoadingService } from '@iqser/common-ui';
|
||||
import { IProfile } from '@red/domain';
|
||||
import { languagesTranslations } from '../../../translations/languages-translations';
|
||||
import { PermissionsService } from '../../../../../services/permissions.service';
|
||||
import { UserService } from '../../../../../services/user.service';
|
||||
import { ConfigService } from '../../../../../services/config.service';
|
||||
import { LanguageService } from '../../../../../i18n/language.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-profile-screen',
|
||||
templateUrl: './user-profile-screen.component.html',
|
||||
styleUrls: ['./user-profile-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UserProfileScreenComponent implements OnInit {
|
||||
formGroup: FormGroup;
|
||||
@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
|
||||
|
||||
const routes = [{ path: '', component: UserProfileScreenComponent }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [UserProfileScreenComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule],
|
||||
})
|
||||
export class UserProfileModule {}
|
||||
@ -0,0 +1,31 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { EmailNotificationScheduleTypes, INotificationPreferences } from '@red/domain';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationPreferencesService extends GenericService<INotificationPreferences> {
|
||||
constructor(protected readonly _injector: Injector, private readonly _userService: UserService) {
|
||||
super(_injector, 'notification-preferences');
|
||||
}
|
||||
|
||||
get(): Observable<INotificationPreferences> {
|
||||
return super.get<INotificationPreferences>().pipe(catchError(() => of(this._defaultPreferences)));
|
||||
}
|
||||
|
||||
update(notificationPreferences: INotificationPreferences): Observable<void> {
|
||||
return super._post(notificationPreferences);
|
||||
}
|
||||
|
||||
private get _defaultPreferences(): INotificationPreferences {
|
||||
return {
|
||||
emailNotificationType: EmailNotificationScheduleTypes.INSTANT,
|
||||
emailNotifications: [],
|
||||
emailNotificationsEnabled: false,
|
||||
inAppNotifications: [],
|
||||
inAppNotificationsEnabled: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
export const notificationsTranslations: { [key: string]: string } = {
|
||||
daily: _('notifications-screen.schedule.instant'),
|
||||
daily_summary: _('notifications-screen.schedule.daily'),
|
||||
weekly_summary: _('notifications-screen.schedule.weekly'),
|
||||
inAppNotifications: _('notifications-screen.category.in-app-notifications'),
|
||||
emailNotifications: _('notifications-screen.category.email-notifications'),
|
||||
documentIsSentForApproval: _('notifications-screen.options.document-is-sent-for-approval'),
|
||||
documentStatusChanges: _('notifications-screen.options.document-status-changes'),
|
||||
dossierStatusChanges: _('notifications-screen.options.dossier-status-changes'),
|
||||
requestToJoinTheDossier: _('notifications-screen.options.request-to-join-the-dossier'),
|
||||
whenADocumentIsApproved: _('notifications-screen.options.when-a-document-is-approved'),
|
||||
whenADocumentIsAssignedToAReviewer: _('notifications-screen.options.when-a-document-is-assigned-to-a-reviewer'),
|
||||
whenADocumentIsSentForApproval: _('notifications-screen.options.when-a-document-is-sent-for-approval'),
|
||||
whenAReviewerIsUnassignedFromADocument: _('notifications-screen.options.when-a-reviewer-is-unassigned-from-a-document'),
|
||||
whenIAmAssignedOnADocument: _('notifications-screen.options.when-i-am-assigned-on-a-document'),
|
||||
whenIAmUnassignedFromADocument: _('notifications-screen.options.when-i-am-unassigned-from-a-document'),
|
||||
approver: _('notifications-screen.groups.approver'),
|
||||
own: _('notifications-screen.groups.own'),
|
||||
reviewer: _('notifications-screen.groups.reviewer'),
|
||||
notifications: _('notifications-screen.title'),
|
||||
'user-profile': _('user-profile-screen.title'),
|
||||
} as const;
|
||||
@ -5,7 +5,6 @@ import {
|
||||
ConfirmationDialogInput,
|
||||
DialogConfig,
|
||||
DialogService,
|
||||
ListingService,
|
||||
LoadingService,
|
||||
TitleColors,
|
||||
} from '@iqser/common-ui';
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"account-settings": "Account Settings",
|
||||
"actions": {
|
||||
"all": "All",
|
||||
"none": "None"
|
||||
@ -1298,9 +1299,39 @@
|
||||
"user-promoted-to-approver": "<b>{user}</b> promoted to approver in dossier: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b>!",
|
||||
"user-removed-as-dossier-member": "<b>{user}</b> removed as a member of: <b><a href=\"{dossierHref}\" target=\"_blank\">{dossierName}</a></b> !"
|
||||
},
|
||||
"notifications": {
|
||||
"mark-as": "Mark as {type, select, read{read} unread{unread} other{}}",
|
||||
"no-data": "You have no notifications."
|
||||
"notifications": "Notifications",
|
||||
"notifications-screen": {
|
||||
"category": {
|
||||
"email-notifications": "Email Notifications",
|
||||
"in-app-notifications": "In-App Notifications"
|
||||
},
|
||||
"error": {
|
||||
"generic": "Something went wrong... Preferences update failed!"
|
||||
},
|
||||
"groups": {
|
||||
"approver": "Dossiers you are approver on",
|
||||
"own": "Dossiers you own",
|
||||
"reviewer": "Dossiers you are reviewer on"
|
||||
},
|
||||
"options-title": "Choose on which category you want to be notified",
|
||||
"options": {
|
||||
"document-is-sent-for-approval": "Document is sent for approval",
|
||||
"document-status-changes": "Document status changes",
|
||||
"dossier-status-changes": "Dossier status changes",
|
||||
"request-to-join-the-dossier": "Request to join the dossier",
|
||||
"when-a-document-is-approved": "When a document is approved",
|
||||
"when-a-document-is-assigned-to-a-reviewer": "When a document is assigned to a reviewer",
|
||||
"when-a-document-is-sent-for-approval": "When a document is sent for approval",
|
||||
"when-a-reviewer-is-unassigned-from-a-document": "When a reviewer is unassigned from a document",
|
||||
"when-i-am-assigned-on-a-document": "When I am assigned on a document",
|
||||
"when-i-am-unassigned-from-a-document": "When I am unassigned from a document"
|
||||
},
|
||||
"schedule": {
|
||||
"daily": "Daily Summary",
|
||||
"instant": "Instant",
|
||||
"weekly": "Weekly Summary"
|
||||
},
|
||||
"title": "Notifications Preferences"
|
||||
},
|
||||
"overwrite-files-dialog": {
|
||||
"options": {
|
||||
@ -1477,6 +1508,7 @@
|
||||
"dossiers": "Active Dossiers",
|
||||
"my-account": {
|
||||
"children": {
|
||||
"account": "Account",
|
||||
"admin": "Settings",
|
||||
"downloads": "My Downloads",
|
||||
"language": {
|
||||
@ -1485,7 +1517,6 @@
|
||||
"label": "Language"
|
||||
},
|
||||
"logout": "Logout",
|
||||
"my-profile": "My Profile",
|
||||
"trash": "Trash"
|
||||
}
|
||||
}
|
||||
@ -1557,17 +1588,18 @@
|
||||
}
|
||||
},
|
||||
"user-management": "User Management",
|
||||
"user-profile": {
|
||||
"user-profile": "My Profile",
|
||||
"user-profile-screen": {
|
||||
"actions": {
|
||||
"change-password": "Change Password",
|
||||
"save": "Save profile"
|
||||
"save": "Save Changes"
|
||||
},
|
||||
"form": {
|
||||
"email": "Email",
|
||||
"first-name": "First name",
|
||||
"last-name": "Last name"
|
||||
},
|
||||
"title": "My profile"
|
||||
"title": "Edit Profile"
|
||||
},
|
||||
"user-stats": {
|
||||
"chart": {
|
||||
|
||||
@ -1,15 +1,9 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../.eslintrc.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"files": ["*.ts"],
|
||||
"extends": [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
@ -20,9 +14,7 @@
|
||||
"plugin:@angular-eslint/recommended--extra"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"libs/red-domain/tsconfig.*?.json"
|
||||
]
|
||||
"project": ["libs/red-domain/tsconfig.*?.json"]
|
||||
},
|
||||
"rules": {
|
||||
"@angular-eslint/directive-selector": [
|
||||
@ -55,33 +47,20 @@
|
||||
"error",
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": [
|
||||
"private"
|
||||
],
|
||||
"format": [
|
||||
"camelCase"
|
||||
],
|
||||
"modifiers": ["private"],
|
||||
"format": ["camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
},
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": [
|
||||
"protected"
|
||||
],
|
||||
"format": [
|
||||
"camelCase"
|
||||
],
|
||||
"modifiers": ["protected"],
|
||||
"format": ["camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
},
|
||||
{
|
||||
"selector": "memberLike",
|
||||
"modifiers": [
|
||||
"private"
|
||||
],
|
||||
"format": [
|
||||
"UPPER_CASE",
|
||||
"camelCase"
|
||||
],
|
||||
"modifiers": ["private"],
|
||||
"format": ["UPPER_CASE", "camelCase"],
|
||||
"leadingUnderscore": "require"
|
||||
}
|
||||
],
|
||||
@ -94,22 +73,12 @@
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off"
|
||||
},
|
||||
"plugins": [
|
||||
"@angular-eslint/eslint-plugin",
|
||||
"@typescript-eslint"
|
||||
]
|
||||
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@nrwl/nx/angular-template",
|
||||
"plugin:@angular-eslint/template/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
|
||||
@ -6,6 +6,7 @@ export * from './lib/users';
|
||||
export * from './lib/pages';
|
||||
export * from './lib/audit';
|
||||
export * from './lib/notifications';
|
||||
export * from './lib/notifications-preferences';
|
||||
export * from './lib/dossier-templates';
|
||||
export * from './lib/dictionaries';
|
||||
export * from './lib/redaction-log';
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './notification-preferences';
|
||||
export * from './types';
|
||||
@ -0,0 +1,9 @@
|
||||
import { EmailNotificationScheduleType } from './types';
|
||||
|
||||
export interface INotificationPreferences {
|
||||
emailNotificationType: EmailNotificationScheduleType;
|
||||
emailNotifications: string[];
|
||||
emailNotificationsEnabled: boolean;
|
||||
inAppNotifications: string[];
|
||||
inAppNotificationsEnabled: boolean;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export const EmailNotificationScheduleTypes = {
|
||||
INSTANT: 'DAILY',
|
||||
DAILY: 'DAILY_SUMMARY',
|
||||
WEEKLY: 'WEEKLY_SUMMARY',
|
||||
} as const;
|
||||
|
||||
export type EmailNotificationScheduleType = keyof typeof EmailNotificationScheduleTypes;
|
||||
export const EmailNotificationScheduleTypesValues = Object.values(EmailNotificationScheduleTypes);
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user