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:
Valentin-Gabriel Mihai 2021-11-10 11:07:15 +01:00
commit 9f39fa452a
32 changed files with 886 additions and 449 deletions

View File

@ -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',

View File

@ -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];

View File

@ -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,
},
{

View File

@ -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>

View File

@ -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 {}

View File

@ -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>

View File

@ -0,0 +1,7 @@
:host {
height: calc(100vh - 61px);
&.dossier-templates {
height: calc(100vh - 111px);
}
}

View File

@ -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'),
},
];
}

View 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 {}

View File

@ -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>

View File

@ -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;
}

View File

@ -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%');
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}

View File

@ -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 {}

View File

@ -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>

View File

@ -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;

View File

@ -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 {}

View File

@ -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,
};
}
}

View File

@ -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;

View File

@ -5,7 +5,6 @@ import {
ConfirmationDialogInput,
DialogConfig,
DialogService,
ListingService,
LoadingService,
TitleColors,
} from '@iqser/common-ui';

View File

@ -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": {

View File

@ -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": {}
}
]

View File

@ -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';

View File

@ -0,0 +1,2 @@
export * from './notification-preferences';
export * from './types';

View File

@ -0,0 +1,9 @@
import { EmailNotificationScheduleType } from './types';
export interface INotificationPreferences {
emailNotificationType: EmailNotificationScheduleType;
emailNotifications: string[];
emailNotificationsEnabled: boolean;
inAppNotifications: string[];
inAppNotificationsEnabled: boolean;
}

View File

@ -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