Pull request #142: RED-1265
Merge in RED/ui from RED-1265 to master * commit '813b887cfba4cc6ae1f36b87929a8c02cef9fbb6': refactor user profile add user profile screen
This commit is contained in:
commit
95710964dc
@ -9,6 +9,7 @@ import { 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 = [
|
||||
{
|
||||
@ -37,6 +38,20 @@ const routes = [
|
||||
routeGuards: [AuthGuard, RedRoleGuard]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ui/my-profile',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: UserProfileScreenComponent
|
||||
}
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ui/admin',
|
||||
component: BaseScreenComponent,
|
||||
|
||||
@ -29,12 +29,13 @@ import { DownloadsListScreenComponent } from './components/downloads-list-screen
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { SharedModule } from './modules/shared/shared.module';
|
||||
import { FileUploadDownloadModule } from './modules/upload-download/file-upload-download.module';
|
||||
import { UserProfileScreenComponent } from './components/user-profile/user-profile-screen.component';
|
||||
|
||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
const screens = [BaseScreenComponent, PdfViewerScreenComponent, HtmlDebugScreenComponent, DownloadsListScreenComponent];
|
||||
const screens = [BaseScreenComponent, PdfViewerScreenComponent, HtmlDebugScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
|
||||
|
||||
const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastComponent, NotificationsComponent, ...screens];
|
||||
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
<div class="red-top-bar">
|
||||
<div class="top-bar-row">
|
||||
<div class="menu-placeholder" *ngIf="!permissionsService.isUser()"></div>
|
||||
<div class="menu visible-lt-lg" *ngIf="permissionsService.isUser()">
|
||||
<button [matMenuTriggerFor]="menuNav" mat-flat-button>
|
||||
<mat-icon svgIcon="red:menu"></mat-icon>
|
||||
<div class='red-top-bar'>
|
||||
<div class='top-bar-row'>
|
||||
<div class='menu-placeholder' *ngIf='!permissionsService.isUser()'></div>
|
||||
<div class='menu visible-lt-lg' *ngIf='permissionsService.isUser()'>
|
||||
<button [matMenuTriggerFor]='menuNav' mat-flat-button>
|
||||
<mat-icon svgIcon='red:menu'></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuNav="matMenu">
|
||||
<button mat-menu-item routerLink="/ui/projects" translate="top-bar.navigation-items.projects"></button>
|
||||
<button *ngIf="appStateService.activeProject" [routerLink]="'/ui/projects/' + appStateService.activeProjectId" mat-menu-item>
|
||||
<mat-menu #menuNav='matMenu'>
|
||||
<button mat-menu-item routerLink='/ui/projects' translate='top-bar.navigation-items.projects'></button>
|
||||
<button *ngIf='appStateService.activeProject'
|
||||
[routerLink]="'/ui/projects/' + appStateService.activeProjectId" mat-menu-item>
|
||||
{{ appStateService.activeProject.project.projectName }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="appStateService.activeFile"
|
||||
*ngIf='appStateService.activeFile'
|
||||
[routerLink]="'/ui/projects/' + appStateService.activeProjectId + '/file/' + appStateService.activeFile.fileId"
|
||||
mat-menu-item
|
||||
>
|
||||
@ -19,84 +20,97 @@
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="menu flex-2 visible-lg breadcrumbs-container" *ngIf="permissionsService.isUser()">
|
||||
<div class='menu flex-2 visible-lg breadcrumbs-container' *ngIf='permissionsService.isUser()'>
|
||||
<a
|
||||
class="breadcrumb"
|
||||
routerLink="/ui/projects"
|
||||
translate="top-bar.navigation-items.projects"
|
||||
routerLinkActive="active"
|
||||
*ngIf="projectsView"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
class='breadcrumb'
|
||||
routerLink='/ui/projects'
|
||||
translate='top-bar.navigation-items.projects'
|
||||
routerLinkActive='active'
|
||||
*ngIf='projectsView'
|
||||
[routerLinkActiveOptions]='{ exact: true }'
|
||||
></a>
|
||||
<a
|
||||
class="breadcrumb back-to-projects"
|
||||
routerLink="/ui/projects"
|
||||
routerLinkActive="active"
|
||||
*ngIf="settingsView"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
class='breadcrumb back-to-projects'
|
||||
routerLink='/ui/projects'
|
||||
routerLinkActive='active'
|
||||
*ngIf='settingsView'
|
||||
[routerLinkActiveOptions]='{ exact: true }'
|
||||
>
|
||||
<mat-icon svgIcon="red:expand"></mat-icon>
|
||||
<mat-icon svgIcon='red:expand'></mat-icon>
|
||||
{{ 'top-bar.navigation-items.back-to-projects' | translate }}
|
||||
</a>
|
||||
<ng-container *ngIf="projectsView">
|
||||
<mat-icon class="primary" *ngIf="!appStateService.activeProject" svgIcon="red:arrow-down"></mat-icon>
|
||||
<mat-icon *ngIf="appStateService.activeProject" svgIcon="red:arrow-right"></mat-icon>
|
||||
<ng-container *ngIf='projectsView'>
|
||||
<mat-icon class='primary' *ngIf='!appStateService.activeProject' svgIcon='red:arrow-down'></mat-icon>
|
||||
<mat-icon *ngIf='appStateService.activeProject' svgIcon='red:arrow-right'></mat-icon>
|
||||
<a
|
||||
*ngIf="appStateService.activeProject"
|
||||
class="breadcrumb"
|
||||
*ngIf='appStateService.activeProject'
|
||||
class='breadcrumb'
|
||||
[routerLink]="'/ui/projects/' + appStateService.activeProjectId"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
routerLinkActive='active'
|
||||
[routerLinkActiveOptions]='{ exact: true }'
|
||||
>
|
||||
{{ appStateService.activeProject.project.projectName }}
|
||||
</a>
|
||||
<mat-icon svgIcon="red:arrow-right" *ngIf="appStateService.activeFile"></mat-icon>
|
||||
<mat-icon svgIcon='red:arrow-right' *ngIf='appStateService.activeFile'></mat-icon>
|
||||
<a
|
||||
*ngIf="appStateService.activeFile"
|
||||
class="breadcrumb"
|
||||
*ngIf='appStateService.activeFile'
|
||||
class='breadcrumb'
|
||||
[routerLink]="'/ui/projects/' + appStateService.activeProjectId + '/file/' + appStateService.activeFile.fileId"
|
||||
routerLinkActive="active"
|
||||
routerLinkActive='active'
|
||||
>
|
||||
{{ appStateService.activeFile.filename }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="center flex-1">
|
||||
<redaction-hidden-action (action)="userPreferenceService.toggleDevFeatures()">
|
||||
<div class='center flex-1'>
|
||||
<redaction-hidden-action (action)='userPreferenceService.toggleDevFeatures()'>
|
||||
<redaction-logo></redaction-logo>
|
||||
</redaction-hidden-action>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
<span class="dev-mode" *ngIf="userPreferenceService.areDevFeaturesEnabled" translate="dev-mode"></span>
|
||||
<div class='app-name'>{{ titleService.getTitle() }}</div>
|
||||
<span class='dev-mode' *ngIf='userPreferenceService.areDevFeaturesEnabled' translate='dev-mode'></span>
|
||||
</div>
|
||||
<div class="menu right flex-2">
|
||||
<redaction-notifications class="mr-8" *ngIf="userPreferenceService.areDevFeaturesEnabled"></redaction-notifications>
|
||||
<redaction-user-button [user]="user" [matMenuTriggerFor]="userMenu" [showDot]="showPendingDownloadsDot"></redaction-user-button>
|
||||
<mat-menu #userMenu="matMenu">
|
||||
<div class='menu right flex-2'>
|
||||
<redaction-notifications class='mr-8'
|
||||
*ngIf='userPreferenceService.areDevFeaturesEnabled'></redaction-notifications>
|
||||
<redaction-user-button [user]='user' [matMenuTriggerFor]='userMenu'
|
||||
[showDot]='showPendingDownloadsDot'></redaction-user-button>
|
||||
<mat-menu #userMenu='matMenu'>
|
||||
<button
|
||||
*ngIf="permissionsService.isManager()"
|
||||
(click)="appStateService.reset()"
|
||||
*ngIf='permissionsService.isUser()'
|
||||
[routerLink]="'/ui/my-profile'"
|
||||
mat-menu-item
|
||||
translate='top-bar.navigation-items.my-account.children.my-profile'
|
||||
></button>
|
||||
<button
|
||||
*ngIf='permissionsService.isManager()'
|
||||
(click)='appStateService.reset()'
|
||||
[routerLink]="'/ui/admin'"
|
||||
mat-menu-item
|
||||
translate="top-bar.navigation-items.my-account.children.admin"
|
||||
translate='top-bar.navigation-items.my-account.children.admin'
|
||||
></button>
|
||||
<button
|
||||
*ngIf="permissionsService.isUser()"
|
||||
*ngIf='permissionsService.isUser()'
|
||||
[routerLink]="'/ui/downloads'"
|
||||
mat-menu-item
|
||||
translate="top-bar.navigation-items.my-account.children.downloads"
|
||||
translate='top-bar.navigation-items.my-account.children.downloads'
|
||||
></button>
|
||||
<button [matMenuTriggerFor]="language" mat-menu-item translate="top-bar.navigation-items.my-account.children.language.label"></button>
|
||||
<mat-menu #language="matMenu">
|
||||
<button (click)="changeLanguage('en')" mat-menu-item translate="top-bar.navigation-items.my-account.children.language.english"></button>
|
||||
<button (click)="changeLanguage('de')" mat-menu-item translate="top-bar.navigation-items.my-account.children.language.german"></button>
|
||||
<button [matMenuTriggerFor]='language' mat-menu-item
|
||||
translate='top-bar.navigation-items.my-account.children.language.label'></button>
|
||||
<mat-menu #language='matMenu'>
|
||||
<button
|
||||
*ngFor='let lang of languages'
|
||||
(click)='changeLanguage(lang)'
|
||||
mat-menu-item
|
||||
translate
|
||||
>top-bar.navigation-items.my-account.children.language.{{lang}}</button>
|
||||
</mat-menu>
|
||||
<button (click)="logout()" mat-menu-item>
|
||||
<mat-icon svgIcon="red:logout"></mat-icon>
|
||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||
<button (click)='logout()' mat-menu-item>
|
||||
<mat-icon svgIcon='red:logout'></mat-icon>
|
||||
<span translate='top-bar.navigation-items.my-account.children.logout'> </span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class='divider'></div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@ -9,6 +9,7 @@ import { AppConfigService } from '../../modules/app-config/app-config.service';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { FileDownloadService } from '../../modules/upload-download/services/file-download.service';
|
||||
import { StatusOverlayService } from '../../modules/upload-download/services/status-overlay.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-base-screen',
|
||||
@ -32,7 +33,8 @@ export class BaseScreenComponent {
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
private readonly _router: Router,
|
||||
private readonly _languageService: LanguageService,
|
||||
private readonly _userService: UserService
|
||||
private readonly _userService: UserService,
|
||||
private readonly _translateService: TranslateService
|
||||
) {
|
||||
_router.events.subscribe(() => {
|
||||
this._projectsView = this._router.url.indexOf('/ui/projects') === 0;
|
||||
@ -51,6 +53,10 @@ export class BaseScreenComponent {
|
||||
return this.fileDownloadService.hasPendingDownloads;
|
||||
}
|
||||
|
||||
get languages(): string[] {
|
||||
return this._translateService.langs;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._userService.logout();
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
<section class="red-content-inner">
|
||||
<div class="left-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 [formGroup]="formGroup" (submit)="save()">
|
||||
<div class="dialog-content">
|
||||
<div class="dialog-content-left">
|
||||
<div class="red-input-group required">
|
||||
<label [translate]="'user-profile.form.email'"></label>
|
||||
<input formControlName="email" name="email" type="email" />
|
||||
</div>
|
||||
|
||||
<div class="red-input-group">
|
||||
<label [translate]="'user-profile.form.first-name'"></label>
|
||||
<input formControlName="firstName" name="firstName" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="red-input-group">
|
||||
<label [translate]="'user-profile.form.last-name'"></label>
|
||||
<input formControlName="lastName" name="lastName" type="text" />
|
||||
</div>
|
||||
<div class="red-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">
|
||||
{{ 'top-bar.navigation-items.my-account.children.language.' + 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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
@ -0,0 +1,18 @@
|
||||
@import '../../../assets/styles/red-mixins';
|
||||
|
||||
.left-container {
|
||||
background-color: $grey-2;
|
||||
justify-content: center;
|
||||
@include scroll-bar;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: calc(100% + 50px);
|
||||
z-index: 1;
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
import { 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 { User, UserControllerService } from '@redaction/red-ui-http';
|
||||
|
||||
interface ProfileModel {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-profile-screen',
|
||||
templateUrl: './user-profile-screen.component.html',
|
||||
styleUrls: ['./user-profile-screen.component.scss']
|
||||
})
|
||||
export class UserProfileScreenComponent implements OnInit {
|
||||
public viewReady = false;
|
||||
public formGroup: FormGroup;
|
||||
private _initialValue: ProfileModel;
|
||||
private _user: User;
|
||||
|
||||
constructor(
|
||||
public readonly permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _userControllerService: UserControllerService,
|
||||
private readonly _languageService: LanguageService,
|
||||
private readonly _translateService: TranslateService
|
||||
) {
|
||||
this.formGroup = this._formBuilder.group({
|
||||
email: [undefined, [Validators.required, Validators.email]],
|
||||
firstName: [undefined],
|
||||
lastName: [undefined],
|
||||
language: [undefined]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _loadData(): void {
|
||||
try {
|
||||
this._user = this._userService.getUserById(this._userService.userId);
|
||||
this._initialValue = {
|
||||
email: this._user.email,
|
||||
firstName: this._user.firstName,
|
||||
lastName: this._user.lastName,
|
||||
language: this._languageService.currentLanguage
|
||||
};
|
||||
this.formGroup.patchValue(this._initialValue, { emitEvent: false });
|
||||
} catch (e) {
|
||||
} finally {
|
||||
this.viewReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
public get languageChanged(): boolean {
|
||||
return this._initialValue['language'] !== this.formGroup.get('language').value;
|
||||
}
|
||||
|
||||
public get profileChanged(): boolean {
|
||||
const keys = Object.keys(this.formGroup.getRawValue());
|
||||
keys.splice(keys.indexOf('language'), 1);
|
||||
|
||||
for (const key of keys) {
|
||||
if (this._initialValue[key] !== this.formGroup.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public get languages(): string[] {
|
||||
return this._translateService.langs;
|
||||
}
|
||||
|
||||
public async save(): Promise<void> {
|
||||
this.viewReady = false;
|
||||
|
||||
if (this.languageChanged) {
|
||||
this._languageService.changeLanguage(this.formGroup.get('language').value);
|
||||
}
|
||||
|
||||
if (this.profileChanged) {
|
||||
const value = this.formGroup.value as ProfileModel;
|
||||
delete value.language;
|
||||
|
||||
await this._userControllerService
|
||||
.updateProfile(
|
||||
{
|
||||
...value,
|
||||
roles: this._user.roles
|
||||
},
|
||||
this._user.userId
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
this._initialValue = this.formGroup.value;
|
||||
this.viewReady = true;
|
||||
}
|
||||
}
|
||||
@ -8,34 +8,6 @@
|
||||
justify-content: center;
|
||||
@include scroll-bar;
|
||||
overflow: auto;
|
||||
|
||||
.dialog {
|
||||
border-radius: 8px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 70px;
|
||||
background-color: $white;
|
||||
max-width: 650px;
|
||||
height: fit-content;
|
||||
box-shadow: 0 1px 5px 0 rgba(40, 50, 65, 0.19);
|
||||
position: unset;
|
||||
|
||||
.heading-l {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
display: flex;
|
||||
|
||||
.dialog-content-left {
|
||||
min-width: 300px;
|
||||
margin-right: 64px;
|
||||
}
|
||||
|
||||
.link-action {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
|
||||
@ -63,10 +63,11 @@
|
||||
"children": {
|
||||
"admin": "Settings",
|
||||
"downloads": "My Downloads",
|
||||
"my-profile": "My Profile",
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"english": "English",
|
||||
"german": "German"
|
||||
"en": "English",
|
||||
"de": "German"
|
||||
},
|
||||
"logout": "Logout"
|
||||
}
|
||||
@ -791,6 +792,17 @@
|
||||
"save": "Save Document Info",
|
||||
"save-approval": "Save and Send for Approval"
|
||||
},
|
||||
"user-profile": {
|
||||
"title": "My profile",
|
||||
"form": {
|
||||
"email": "Email",
|
||||
"first-name": "First name",
|
||||
"last-name": "Last name"
|
||||
},
|
||||
"actions": {
|
||||
"save": "Save profile"
|
||||
}
|
||||
},
|
||||
"user-listing": {
|
||||
"table-header": {
|
||||
"title": "{{length}} users"
|
||||
|
||||
@ -120,6 +120,34 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.dialog {
|
||||
border-radius: 8px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 70px;
|
||||
background-color: $white;
|
||||
max-width: 650px;
|
||||
height: fit-content;
|
||||
box-shadow: 0 1px 5px 0 rgba(40, 50, 65, 0.19);
|
||||
position: unset;
|
||||
|
||||
.heading-l {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
display: flex;
|
||||
|
||||
.dialog-content-left {
|
||||
min-width: 300px;
|
||||
margin-right: 64px;
|
||||
}
|
||||
|
||||
.link-action {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
redaction-initials-avatar .username:not(.always-visible) {
|
||||
display: none;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user