diff --git a/src/app/app.component.html b/src/app/app.component.html index b5c063dbe0a69ace77722e1b727b927589e45ff6..49832c88f4eb5d81419b4f0108ad6349b99bd2c0 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,12 +1,5 @@ <div fxLayout="column" fxFill> - <mat-toolbar color="primary"> - <mat-toolbar-row> - <span class="app-toolbar-title"> - {{ title }} - </span> - <!-- Insert navigation module here --> - </mat-toolbar-row> - </mat-toolbar> + <app-header></app-header> <div fxFlex class="app-component"> <router-outlet></router-outlet> </div> diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e3397a5b8ddc827fc41f5f72ff0c50fe01bb879c..cdb2b87b8f12804181c2ad3fe95b801f42ef7726 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -77,6 +77,7 @@ import { import { ContentTextParticipantComponent } from './components/fragments/content-text-participant/content-text-participant.component'; import { ContentTextCreatorComponent } from './components/fragments/content-text-creator/content-text-creator.component'; import { AuthenticationInterceptor } from './interceptors/authentication.interceptor'; +import { HeaderComponent } from './components/fragments/header/header.component'; @NgModule({ declarations: [ @@ -108,7 +109,8 @@ import { AuthenticationInterceptor } from './interceptors/authentication.interce ContentCarouselPageComponent, ContentTextParticipantComponent, StatisticsComponent, - ContentTextCreatorComponent + ContentTextCreatorComponent, + HeaderComponent ], entryComponents: [ RegisterComponent, diff --git a/src/app/components/fragments/header/header.component.html b/src/app/components/fragments/header/header.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d4ef907110c1ba91af3daa1b23869b4b4e019cd1 --- /dev/null +++ b/src/app/components/fragments/header/header.component.html @@ -0,0 +1,16 @@ +<mat-toolbar color="primary"> + <mat-toolbar-row> + <span>ARSnova</span> + <span class="fill-remaining-space"></span> + <mat-menu #appMenu="matMenu" [overlapTrigger]="false"> + <button mat-menu-item (click)="logout()"> + <mat-icon>exit_to_app</mat-icon> + <span>Logout</span> + </button> + </mat-menu> + <button *ngIf="user" mat-button [matMenuTriggerFor]="appMenu"> + <mat-icon>account_box</mat-icon> + <span>{{user.loginId}}</span> + </button> + </mat-toolbar-row> +</mat-toolbar> diff --git a/src/app/components/fragments/header/header.component.scss b/src/app/components/fragments/header/header.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/fragments/header/header.component.spec.ts b/src/app/components/fragments/header/header.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d0479d7d071a5e6d19a74d195ff34b2edeb5616 --- /dev/null +++ b/src/app/components/fragments/header/header.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeaderComponent } from './header.component'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture<HeaderComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HeaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/fragments/header/header.component.ts b/src/app/components/fragments/header/header.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..557177d444db5557e0e48f82b83b9085eee2c894 --- /dev/null +++ b/src/app/components/fragments/header/header.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; +import { AuthenticationService } from '../../../services/http/authentication.service'; +import { NotificationService } from '../../../services/util/notification.service'; +import { Router } from '@angular/router'; +import { User } from '../../../models/user'; + +@Component({ + selector: 'app-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderComponent implements OnInit { + user: User; + + constructor(private authenticationService: AuthenticationService, + private notification: NotificationService, + public router: Router) { + } + + ngOnInit() { + // Subscribe to user data + this.authenticationService.watchUser.subscribe(newUser => this.user = newUser); + } + + logout() { + this.authenticationService.logout(); + this.notification.show(`Logged out`); + this.router.navigate(['/']); + } +} diff --git a/src/app/services/http/authentication.service.ts b/src/app/services/http/authentication.service.ts index 3fce9041ec14810bb82fcd9b3e33c020eb502131..d0ed68091baf40ef7b3a196a0ba1fd970bccd710 100644 --- a/src/app/services/http/authentication.service.ts +++ b/src/app/services/http/authentication.service.ts @@ -6,11 +6,12 @@ import { UserRole } from '../../models/user-roles.enum'; import { DataStoreService } from '../util/data-store.service'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { ClientAuthentication } from '../../models/client-authentication'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Injectable() export class AuthenticationService { private readonly STORAGE_KEY: string = 'USER'; - private user: User; + private user = new BehaviorSubject<User>(undefined); private apiUrl = { base: 'https://arsnova-staging.mni.thm.de/api', v2: 'https://arsnova-staging.mni.thm.de/api/v2', @@ -30,7 +31,7 @@ export class AuthenticationService { private http: HttpClient) { if (dataStoreService.has(this.STORAGE_KEY)) { // Load user data from local data store if available - this.user = JSON.parse(dataStoreService.get(this.STORAGE_KEY)); + this.user.next(JSON.parse(dataStoreService.get(this.STORAGE_KEY))); } } @@ -74,28 +75,28 @@ export class AuthenticationService { logout() { // Destroy the persisted user data this.dataStoreService.remove(this.STORAGE_KEY); - this.user = undefined; + this.user.next(undefined); } getUser(): User { - return this.user; + return this.user.getValue(); } private setUser(user: User): void { - this.user = user; - this.dataStoreService.set(this.STORAGE_KEY, JSON.stringify(this.user)); + this.user.next(user); + this.dataStoreService.set(this.STORAGE_KEY, JSON.stringify(user)); } isLoggedIn(): boolean { - return this.user !== undefined; + return this.user.getValue() !== undefined; } getRole(): UserRole { - return this.isLoggedIn() ? this.user.role : undefined; + return this.isLoggedIn() ? this.user.getValue().role : undefined; } getToken(): string { - return this.user.token; + return this.isLoggedIn() ? this.user.getValue().token : undefined; } private checkLogin(clientAuthentication: Observable<ClientAuthentication>, userRole: UserRole): Observable<boolean> { @@ -115,4 +116,7 @@ export class AuthenticationService { }); } + get watchUser() { + return this.user.asObservable(); + } } diff --git a/src/theme/_theme.scss b/src/theme/_theme.scss index 2c565013a906dbc9b982164f71e6c26074e0919e..b960620909c641d4e2abb914cdb484ef92b07148 100644 --- a/src/theme/_theme.scss +++ b/src/theme/_theme.scss @@ -6,4 +6,5 @@ html, body { height: 100%; } +@import '_util.scss'; @import '_form.scss'; diff --git a/src/theme/_util.scss b/src/theme/_util.scss new file mode 100644 index 0000000000000000000000000000000000000000..b7da323b68967dd24667e48616df31db78844166 --- /dev/null +++ b/src/theme/_util.scss @@ -0,0 +1,5 @@ +.fill-remaining-space { + /* This fills the remaining space, by using flexbox. + Every toolbar row uses a flexbox row layout. */ + flex: 1 1 auto; +}