welcome screen

This commit is contained in:
2020-04-26 16:38:06 +02:00
committed by smuddy
parent 3b6bebcbac
commit 5d47c0c611
29 changed files with 538 additions and 16 deletions

View File

@@ -11,6 +11,7 @@ service cloud.firestore {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin' return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'
} }
allow create: if request.resource.id == request.auth.uid;
allow read: if isUser(resource) || isAdmin(); allow read: if isUser(resource) || isAdmin();
allow write: if isUser(resource) || isAdmin(); allow write: if isUser(resource) || isAdmin();
} }

View File

@@ -1,6 +1,7 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {PreloadAllModules, RouterModule, Routes} from '@angular/router'; import {PreloadAllModules, RouterModule, Routes} from '@angular/router';
import {AngularFireAuthGuard, redirectUnauthorizedTo} from '@angular/fire/auth-guard'; import {AngularFireAuthGuard, redirectUnauthorizedTo} from '@angular/fire/auth-guard';
import {RoleGuard} from './widget-modules/guards/role.guard';
const routes: Routes = [ const routes: Routes = [
{ {
@@ -11,24 +12,37 @@ const routes: Routes = [
{ {
path: 'songs', path: 'songs',
loadChildren: () => import('./modules/songs/songs.module').then(m => m.SongsModule), loadChildren: () => import('./modules/songs/songs.module').then(m => m.SongsModule),
canActivate: [AngularFireAuthGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: {authGuardPipe: () => redirectUnauthorizedTo(['user', 'login'])} data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['user']
}
}, },
{ {
path: 'shows', path: 'shows',
loadChildren: () => import('./modules/shows/shows.module').then(m => m.ShowsModule), loadChildren: () => import('./modules/shows/shows.module').then(m => m.ShowsModule),
canActivate: [AngularFireAuthGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: {authGuardPipe: () => redirectUnauthorizedTo(['user', 'login'])} data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['leader']
}
}, },
{ {
path: 'presentation', path: 'presentation',
loadChildren: () => import('./modules/presentation/presentation.module').then(m => m.PresentationModule), loadChildren: () => import('./modules/presentation/presentation.module').then(m => m.PresentationModule),
canActivate: [AngularFireAuthGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: {authGuardPipe: () => redirectUnauthorizedTo(['user', 'login'])} data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['presenter']
}
}, },
{ {
path: 'user', path: 'user',
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule) loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule)
},
{
path: 'brand',
loadChildren: () => import('./modules/brand/brand.module').then(m => m.BrandModule),
} }
]; ];

View File

@@ -0,0 +1,4 @@
<div class="brand">
<app-logo></app-logo>
<div class="copyright">© 2020 - Benjamin Ifland</div>
</div>

View File

@@ -0,0 +1,12 @@
.copyright {
color: #fff;
opacity: 0.7;
font-size: 20px;
transform: translate(173px, -40px);
}
.brand {
@media screen and (max-width: 860px) {
transform: scale(0.5) translateY(-50%);
}
}

View File

@@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {BrandComponent} from './brand.component';
describe('BrandComponent', () => {
let component: BrandComponent;
let fixture: ComponentFixture<BrandComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [BrandComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BrandComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-brand',
templateUrl: './brand.component.html',
styleUrls: ['./brand.component.less']
})
export class BrandComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,30 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {BrandComponent} from './brand.component';
import {RouterModule, Routes} from '@angular/router';
import {LogoModule} from '../../widget-modules/components/logo/logo.module';
import {NewUserComponent} from './new-user/new-user.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: BrandComponent
},
{
path: 'new-user',
component: NewUserComponent
},
];
@NgModule({
declarations: [BrandComponent, NewUserComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
LogoModule
]
})
export class BrandModule {
}

View File

@@ -0,0 +1,8 @@
<div class="frame">
<app-brand class="brand"></app-brand>
<div *ngIf="user$|async as user" class="text">
<div class="welcome">WILLKOMMEN</div>
<div class="name">{{user.name}}</div>
<div class="roles">Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den Administrator!</div>
</div>
</div>

View File

@@ -0,0 +1,118 @@
@animation-duration: 20s;
.frame {
width: 512px;
position: relative;
}
.brand {
position: absolute;
left: 0;
animation: @animation-duration brand ease-in-out forwards;
opacity: 0;
}
.text {
position: absolute;
left: 0;
transform: translateX(50%);
color: #fff;
margin-top: 60px;
.welcome {
opacity: 0;
font-size: 80px;
animation: @animation-duration welcome ease-in-out forwards;
}
.name {
opacity: 0;
font-size: 50px;
animation: @animation-duration name ease-in-out forwards;
}
.roles {
opacity: 0;
font-size: 20px;
margin-top: 40px;
animation: @animation-duration roles ease-in-out forwards;
}
}
@keyframes brand {
10% {
opacity: 0;
transform: translateX(0);
}
15% {
opacity: 1;
transform: translateX(0);
}
20% {
opacity: 1;
transform: translateX(0);
}
25% {
opacity: 1;
transform: translateX(-50%);
}
100% {
opacity: 1;
transform: translateX(-50%);
}
}
@keyframes welcome {
30% {
opacity: 0;
transform: scale(1.4);
filter: blur(50px);
}
40% {
opacity: 1;
transform: translateX(0);
filter: blur(0px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes name {
50% {
opacity: 0;
transform: scale(1.4);
filter: blur(50px);
}
60% {
opacity: 1;
transform: translateX(0);
filter: blur(0px);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes roles {
70% {
opacity: 0;
transform: scale(1.4);
filter: blur(50px);
}
80% {
opacity: 1;
transform: translateX(0);
filter: blur(0px);
}
100% {
opacity: 1;
transform: scale(1);
}
}

View File

@@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NewUserComponent} from './new-user.component';
describe('NewUserComponent', () => {
let component: NewUserComponent;
let fixture: ComponentFixture<NewUserComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NewUserComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NewUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {UserService} from '../../../services/user/user.service';
import {Observable} from 'rxjs';
import {User} from '../../../services/user/user';
@Component({
selector: 'app-new-user',
templateUrl: './new-user.component.html',
styleUrls: ['./new-user.component.less']
})
export class NewUserComponent {
public user$: Observable<User>
constructor(private userService: UserService) {
this.user$ = userService.user$
}
}

View File

@@ -1,5 +1,8 @@
<app-card *ngIf="user$|async as user" heading="Hallo {{user.name}}"> <app-card *ngIf="user$|async as user" heading="Hallo {{user.name}}">
<p>{{user.role|role}}</p> <p>
<span *ngIf="getUserRoles(user.role).length===0" class="warn">Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den Administrator!</span>
<span>{{transdormUserRoles(user.role)}}</span>
</p>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>bevorzugte Anzeige der Akkorde</mat-label> <mat-label>bevorzugte Anzeige der Akkorde</mat-label>

View File

@@ -1,3 +1,7 @@
.mat-form-field { .warn {
width: 100%; color: #621700;
}
p {
margin-bottom: 20px;
} }

View File

@@ -25,4 +25,7 @@ export class InfoComponent implements OnInit {
await this.userService.update$(uid, {chordMode: value}); await this.userService.update$(uid, {chordMode: value});
} }
public getUserRoles = (roles: string): string[] => roles?.split(';') ?? [];
public transdormUserRoles = (roles: string): string => this.getUserRoles(roles).join(', ');
} }

View File

@@ -1,4 +1,4 @@
<app-card> <!--<app-card>
<div [formGroup]="form" class="form"> <div [formGroup]="form" class="form">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>E-Mail Addresse</mat-label> <mat-label>E-Mail Addresse</mat-label>
@@ -18,4 +18,27 @@
</div> </div>
</app-card> </app-card>-->
<div class="frame">
<app-logo></app-logo>
<div class="login">
<div [formGroup]="form" class="form">
<mat-form-field appearance="outline">
<mat-label>E-Mail Addresse</mat-label>
<input (keyup.enter)="onLogin()" formControlName="user" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Passwort</mat-label>
<input (keyup.enter)="onLogin()" formControlName="pass" matInput type="password">
</mat-form-field>
<p *ngIf="errorMessage" class="error">{{errorMessage|authMessage}}</p>
<button (click)="onLogin()" class="btn-login" color="primary" mat-stroked-button>Anmelden</button>
<button class="btn-password" mat-stroked-button routerLink="/user/password">Passwort zurücksetzen</button>
<button class="btn-user" mat-stroked-button routerLink="/user/new">neuen Benutzer anlegen</button>
</div>
</div>
</div>

View File

@@ -6,3 +6,47 @@ p.error {
margin: 8px 10px; margin: 8px 10px;
color: darkred; color: darkred;
} }
.login {
padding: 20px;
width: 400px;
margin: 100px 0;
background: #fffc;
border-radius: 8px;
font-size: 18px;
@media screen and (max-width: 860px) {
margin: 20px;
box-sizing: border-box;
width: 90vw;
}
}
button {
font-size: 18px;
}
.btn-login {
margin-bottom: 40px;
}
.btn-password {
margin-bottom: 20px;
color: #888;
}
.btn-user {
color: #888;
}
.frame {
display: flex;
position: relative;
flex-wrap: wrap;
}
app-logo {
transform: scale(0.84);
@media screen and (max-width: 860px) {
display: none;
}
}

View File

@@ -20,6 +20,7 @@ import {RoleModule} from '../../services/user/role.module';
import {UserComponent} from './info/users/user/user.component'; import {UserComponent} from './info/users/user/user.component';
import {NewComponent} from './new/new.component'; import {NewComponent} from './new/new.component';
import {ButtonModule} from '../../widget-modules/components/button/button.module'; import {ButtonModule} from '../../widget-modules/components/button/button.module';
import {LogoModule} from '../../widget-modules/components/logo/logo.module';
@NgModule({ @NgModule({
@@ -37,6 +38,7 @@ import {ButtonModule} from '../../widget-modules/components/button/button.module
FormsModule, FormsModule,
RoleModule, RoleModule,
ButtonModule, ButtonModule,
LogoModule,
] ]

View File

@@ -58,7 +58,7 @@ export class UserService {
await this.db.doc('users/' + userId).set({name, chordMode: 'onlyFirst'}); await this.db.doc('users/' + userId).set({name, chordMode: 'onlyFirst'});
const dUser = await this.readUser(aUser.user.uid); const dUser = await this.readUser(aUser.user.uid);
this._user$.next(dUser); this._user$.next(dUser);
await this.router.navigateByUrl('/user/info'); await this.router.navigateByUrl('/brand/new-user');
} }
private readUser$ = (uid) => this.db.doc$<User>('users/' + uid); private readUser$ = (uid) => this.db.doc$<User>('users/' + uid);

View File

@@ -1,12 +1,12 @@
<nav [class.hidden]="(windowScroll$|async)>60" class="head"> <nav [class.hidden]="(windowScroll$|async)>60" class="head">
<div class="links"> <div class="links">
<app-brand></app-brand> <app-brand routerLink="/brand"></app-brand>
<app-link *appRole="['user','presenter', 'leader']" [icon]="faSongs" link="/songs" text="Lieder"></app-link> <app-link *appRole="['user','presenter', 'leader']" [icon]="faSongs" link="/songs" text="Lieder"></app-link>
<app-link *appRole="['leader']" [icon]="faShows" link="/shows" text="Veranstaltungen"></app-link> <app-link *appRole="['leader']" [icon]="faShows" link="/shows" text="Veranstaltungen"></app-link>
<app-link *appRole="['presenter']" [icon]="faPresentation" link="/presentation" text="Präsentation"></app-link> <app-link *appRole="['presenter']" [icon]="faPresentation" link="/presentation" text="Präsentation"></app-link>
<app-link [icon]="faUser" link="/user" text="Benutzer"></app-link> <app-link [icon]="faUser" link="/user" text="Benutzer"></app-link>
</div> </div>
<div class="actions"> <div *appRole="['user','presenter', 'leader']" class="actions">
<app-filter></app-filter> <app-filter></app-filter>
</div> </div>
</nav> </nav>

View File

@@ -37,4 +37,5 @@ nav {
.links { .links {
display: flex; display: flex;
height: 50px;
} }

View File

@@ -1,6 +1,6 @@
<button mat-button> <button mat-button>
<span *ngIf="icon"><fa-icon [icon]="icon"></fa-icon><span class="content">&nbsp;</span></span> <span *ngIf="icon"><fa-icon [icon]="icon"></fa-icon><span class="content">&nbsp;</span></span>
<span class="content"> <span class="button-content">
<ng-content></ng-content> <ng-content></ng-content>
</span> </span>
</button> </button>

View File

@@ -5,7 +5,7 @@ button {
} }
} }
.content { .button-content {
@media screen and (max-width: 860px) { @media screen and (max-width: 860px) {
display: none ; display: none ;
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,5 @@
svg {
width: 512px;
height: 512px;
margin: 40px;
}

View File

@@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {LogoComponent} from './logo.component';
describe('LogoComponent', () => {
let component: LogoComponent;
let fixture: ComponentFixture<LogoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LogoComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LogoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.less']
})
export class LogoComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,16 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LogoComponent} from './logo.component';
@NgModule({
declarations: [LogoComponent],
exports: [
LogoComponent
],
imports: [
CommonModule
]
})
export class LogoModule {
}

View File

@@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {RoleGuard} from './role.guard';
describe('RoleGuard', () => {
let guard: RoleGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(RoleGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});

View File

@@ -0,0 +1,45 @@
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Observable} from 'rxjs';
import {UserService} from '../../services/user/user.service';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class RoleGuard implements CanActivate {
constructor(private userService: UserService, private router: Router) {
}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> {
const requiredRoles = next.data.requiredRoles;
if (!requiredRoles) throw new Error('requiredRoles is not defined!');
return this.userService.user$.pipe(
map(user => {
const roles = user.role?.split(';') ?? [];
if (roles.indexOf('admin') !== -1) return true;
const allowed = roles.some(s => requiredRoles.indexOf(s) !== -1);
return allowed || this.router.createUrlTree(this.defaultRoute(roles));
})
)
}
private defaultRoute(roles: string[]): string[] {
if (!roles || roles.length === 0) return ['brand', 'new-user'];
switch (roles[0]) {
case 'user':
return ['songs'];
case 'presenter':
return ['presentation'];
case 'leader':
return ['shows'];
}
return ['brand', 'new-user'];
}
}