import legacy frontend & fix dependency issues
This commit is contained in:
@@ -1,53 +1,85 @@
|
||||
import {
|
||||
ApplicationConfig,
|
||||
isDevMode,
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
ApplicationConfig,
|
||||
LOCALE_ID,
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
PreloadAllModules,
|
||||
provideRouter,
|
||||
withComponentInputBinding,
|
||||
withPreloading,
|
||||
PreloadAllModules,
|
||||
provideRouter,
|
||||
withComponentInputBinding,
|
||||
withPreloading,
|
||||
} from '@angular/router';
|
||||
|
||||
import {
|
||||
AutoRefreshTokenService,
|
||||
createInterceptorCondition,
|
||||
INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||
IncludeBearerTokenCondition,
|
||||
provideKeycloak,
|
||||
UserActivityService,
|
||||
withAutoRefreshToken,
|
||||
AutoRefreshTokenService,
|
||||
createInterceptorCondition,
|
||||
INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||
IncludeBearerTokenCondition,
|
||||
provideKeycloak,
|
||||
UserActivityService,
|
||||
withAutoRefreshToken,
|
||||
} from 'keycloak-angular';
|
||||
|
||||
import {
|
||||
DateFnsAdapter,
|
||||
MAT_DATE_FNS_FORMATS,
|
||||
} from '@angular/material-date-fns-adapter';
|
||||
import {
|
||||
DateAdapter,
|
||||
MAT_DATE_FORMATS,
|
||||
MAT_DATE_LOCALE,
|
||||
} from '@angular/material/core';
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { de } from 'date-fns/locale';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideRouter(routes, withPreloading(PreloadAllModules), withComponentInputBinding()),
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideKeycloak({
|
||||
config: {
|
||||
url: isDevMode() ? 'http://localhost:8080/' : 'https://keycloak.toni714.de:8443/',
|
||||
realm: 'taekwondo',
|
||||
clientId: 'angular-frontend',
|
||||
},
|
||||
initOptions: {
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
|
||||
},
|
||||
features: [withAutoRefreshToken({ sessionTimeout: 600000 })],
|
||||
providers: [AutoRefreshTokenService, UserActivityService],
|
||||
}),
|
||||
{
|
||||
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||
useValue: [
|
||||
createInterceptorCondition<IncludeBearerTokenCondition>({
|
||||
urlPattern: isDevMode()? /^http:\/\/localhost:3000.*$/ : /^https:\/\/tkd-api\.toni714\.de.*$/,
|
||||
providers: [
|
||||
provideRouter(
|
||||
routes,
|
||||
withPreloading(PreloadAllModules),
|
||||
withComponentInputBinding(),
|
||||
),
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideKeycloak({
|
||||
config: {
|
||||
url: true
|
||||
? 'http://localhost:8080/'
|
||||
: 'https://keycloak.toni714.de:8443/',
|
||||
realm: 'taekwondo',
|
||||
clientId: 'angular-frontend',
|
||||
},
|
||||
initOptions: {
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri:
|
||||
window.location.origin + '/silent-check-sso.html',
|
||||
},
|
||||
features: [withAutoRefreshToken({ sessionTimeout: 600000 })],
|
||||
providers: [AutoRefreshTokenService, UserActivityService],
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
{
|
||||
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||
useValue: [
|
||||
createInterceptorCondition<IncludeBearerTokenCondition>({
|
||||
urlPattern: true
|
||||
? /^http:\/\/localhost:(3000|8080)\/.*$/
|
||||
: /^https:\/\/tkd-api\.toni714\.de\/.*$/,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
||||
useValue: { appearance: 'outline', subscriptSizing: 'dynamic' },
|
||||
},
|
||||
{ provide: LOCALE_ID, useValue: 'de' },
|
||||
{
|
||||
provide: DateAdapter,
|
||||
useClass: DateFnsAdapter,
|
||||
deps: [MAT_DATE_LOCALE],
|
||||
},
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
|
||||
{ provide: MAT_DATE_LOCALE, useValue: de },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,55 +1,74 @@
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
Routes,
|
||||
UrlTree,
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
Routes,
|
||||
UrlTree,
|
||||
} from '@angular/router';
|
||||
import { AuthGuardData, createAuthGuard } from 'keycloak-angular';
|
||||
import { Login } from './components/login/login';
|
||||
import { MissingPage } from './components/missing-page/missing-page';
|
||||
import { Claim } from './generated-api/api';
|
||||
|
||||
export const roles = {
|
||||
UserAdmin: 'useradmin',
|
||||
MemberAdmin: 'memberadmin',
|
||||
UserAdmin: 'useradmin',
|
||||
MemberAdmin: 'memberadmin',
|
||||
};
|
||||
|
||||
function requireRoles(roles: string[]=[], any: boolean = false): CanActivateFn {
|
||||
const isAllowed = async (
|
||||
_: ActivatedRouteSnapshot,
|
||||
__: RouterStateSnapshot,
|
||||
authData: AuthGuardData
|
||||
): Promise<boolean | UrlTree> => {
|
||||
const { authenticated, grantedRoles } = authData;
|
||||
const allRoles = Object.values(grantedRoles).flat();
|
||||
function requireRoles(
|
||||
roles: string[] = [],
|
||||
any: boolean = false,
|
||||
): CanActivateFn {
|
||||
const isAllowed = async (
|
||||
_: ActivatedRouteSnapshot,
|
||||
__: RouterStateSnapshot,
|
||||
authData: AuthGuardData,
|
||||
): Promise<boolean | UrlTree> => {
|
||||
const { authenticated, grantedRoles } = authData;
|
||||
const allRoles = Object.values(grantedRoles).flat();
|
||||
|
||||
const hasRequiredRoles = any
|
||||
? roles.some((role) => allRoles.includes(role))
|
||||
: roles.every((role) => allRoles.includes(role));
|
||||
const hasRequiredRoles = any
|
||||
? roles.some(role => allRoles.includes(role))
|
||||
: roles.every(role => allRoles.includes(role));
|
||||
|
||||
if (authenticated && hasRequiredRoles) {
|
||||
return true;
|
||||
}
|
||||
const router = inject(Router);
|
||||
return router.parseUrl('/login');
|
||||
};
|
||||
if (authenticated && hasRequiredRoles) {
|
||||
return true;
|
||||
}
|
||||
const router = inject(Router);
|
||||
return router.parseUrl('/login');
|
||||
};
|
||||
|
||||
return createAuthGuard<CanActivateFn>(isAllowed);
|
||||
return createAuthGuard<CanActivateFn>(isAllowed);
|
||||
}
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: Login },
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('./components/home/home').then((mod) => mod.Home),
|
||||
canActivate: [requireRoles()],
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
loadComponent: () => import('./components/home/home').then((mod) => mod.Home),
|
||||
canActivate: [requireRoles()],
|
||||
},
|
||||
{ path: '**', component: MissingPage },
|
||||
{ path: 'login', component: Login },
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () =>
|
||||
import('./components/home/home').then(mod => mod.Home),
|
||||
canActivate: [requireRoles()],
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
loadComponent: () =>
|
||||
import('./components/home/home').then(mod => mod.Home),
|
||||
canActivate: [requireRoles()],
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
loadComponent: () =>
|
||||
import('./components/test/test').then(mod => mod.Test),
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
loadComponent: () =>
|
||||
import('./components/view-members/view-members').then(
|
||||
mod => mod.ViewMembers,
|
||||
),
|
||||
canActivate: [requireRoles([Claim.Memberadmin])],
|
||||
},
|
||||
{ path: '**', component: MissingPage },
|
||||
];
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<p>Name: {{ member.vorname }} {{ member.nachname }}</p>
|
||||
<p>Passnummer: {{ member.passnummer }}</p>
|
||||
<p>Geburtsdatum: {{ member.geburtsdatum??undefined | datePipe }}</p>
|
||||
<mat-form-field>
|
||||
<mat-label>Kündigun zum</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="kuendigungzum" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="submit">Speichern</button>
|
||||
</form>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
form > *{
|
||||
margin-top: 20px;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DeleteMemberDialogComponent } from './delete-member-dialog.component.js';
|
||||
|
||||
describe('DeleteMemberDialogComponent', () => {
|
||||
let component: DeleteMemberDialogComponent;
|
||||
let fixture: ComponentFixture<DeleteMemberDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DeleteMemberDialogComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DeleteMemberDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-member-dialog',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule,
|
||||
MatDatepickerModule,
|
||||
DatePipePipe,
|
||||
],
|
||||
templateUrl: './delete-member-dialog.component.html',
|
||||
styleUrl: './delete-member-dialog.component.scss',
|
||||
})
|
||||
export class DeleteMemberDialogComponent {
|
||||
form: FormGroup;
|
||||
member: api.Member;
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||
) {
|
||||
this.member = data.member;
|
||||
this.form = new FormGroup({
|
||||
kuendigungzum: new FormControl('', [Validators.required]),
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await api.scheduleDeletion(
|
||||
this.member.id!,
|
||||
this.form.controls['kuendigungzum'].value,
|
||||
);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput placeholder="Vorname" formControlName="vorname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput placeholder="Nachname" formControlName="nachname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geschlecht</mat-label>
|
||||
<input matInput placeholder="Geschlecht" formControlName="geschlecht" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Passnummer</mat-label>
|
||||
<input matInput placeholder="Passnummer" formControlName="passnummer" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsdatum</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="geburtsdatum" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsort</mat-label>
|
||||
<input matInput placeholder="Geburtsort" formControlName="geburtsort" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Unterrichtsvertrag?</mat-label>
|
||||
<mat-select matInput formControlName="uvertrag">
|
||||
<mat-option [value]="true">Ja</mat-option>
|
||||
<mat-option [value]="false">Nein</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Vereinsmitglied?</mat-label>
|
||||
<mat-select matInput formControlName="verein">
|
||||
<mat-option [value]="true">Ja</mat-option>
|
||||
<mat-option [value]="false">Nein</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="submit">Speichern</button>
|
||||
</form>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form > *{
|
||||
margin-top: 20px;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditMemberDialogComponent } from './edit-member-dialog.component.js';
|
||||
|
||||
describe('EditMemberDialogComponent', () => {
|
||||
let component: EditMemberDialogComponent;
|
||||
let fixture: ComponentFixture<EditMemberDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EditMemberDialogComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EditMemberDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { MemberDtoHelper } from '../../util/member.js';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-member-dialog',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule,
|
||||
MatDatepickerModule,
|
||||
],
|
||||
templateUrl: './edit-member-dialog.component.html',
|
||||
styleUrl: './edit-member-dialog.component.scss',
|
||||
})
|
||||
export class EditMemberDialogComponent {
|
||||
form: FormGroup;
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
||||
private memberDtoHelper: MemberDtoHelper,
|
||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||
) {
|
||||
this.form = this.memberDtoHelper.newMemberForm(data.member);
|
||||
}
|
||||
|
||||
submit() {
|
||||
const member: api.Member = this.memberDtoHelper.memberFromForm(
|
||||
this.form,
|
||||
);
|
||||
api.updateMember(member);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -1 +1,6 @@
|
||||
<p>missing-page works!</p>
|
||||
<p>Diese Seite existiert nicht!</p>
|
||||
@if(this.auth.loggedIn()){
|
||||
<a mat-stroked-button [routerLink]="['/home']">Zur Starteite</a>
|
||||
}@else {
|
||||
<a mat-stroked-button [routerLink]="['/']">Zur Login Seite</a>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Authentication } from '../../services/authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'app-missing-page',
|
||||
imports: [],
|
||||
templateUrl: './missing-page.html',
|
||||
styleUrl: './missing-page.scss',
|
||||
selector: 'app-missing-page',
|
||||
imports: [MatButtonModule, RouterModule],
|
||||
templateUrl: './missing-page.html',
|
||||
styleUrl: './missing-page.scss',
|
||||
})
|
||||
export class MissingPage {
|
||||
|
||||
protected readonly auth = inject(Authentication);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<h1>Neues Mitglied anlegen</h1>
|
||||
<form [formGroup]="memberForm" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput formControlName="vorname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput formControlName="nachname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geschlecht</mat-label>
|
||||
<input matInput formControlName="geschlecht" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsdatum</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="geburtsdatum" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsort</mat-label>
|
||||
<input matInput formControlName="geburtsort" />
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="uvertrag"
|
||||
>Unterrichtsvertrag Abgegeben?</mat-checkbox
|
||||
>
|
||||
<mat-checkbox formControlName="uvertrag">Verein Beigetreten?</mat-checkbox>
|
||||
<mat-form-field>
|
||||
<mat-label>Passnummer (falls vorhanden)</mat-label>
|
||||
<input matInput formControlName="passnummer" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Kontakt</mat-label>
|
||||
<input matInput formControlName="kontakt" />
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" type="submit">Hinzufügen</button>
|
||||
</form>
|
||||
@@ -0,0 +1,9 @@
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NewMemberComponent } from './new-member.component.js';
|
||||
|
||||
describe('NewMemberComponent', () => {
|
||||
let component: NewMemberComponent;
|
||||
let fixture: ComponentFixture<NewMemberComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NewMemberComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NewMemberComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import * as api from '../../generated-api/api.js';
|
||||
import { MemberDtoHelper } from '../../util/member.js';
|
||||
|
||||
@Component({
|
||||
selector: 'app-new-member',
|
||||
standalone: true,
|
||||
providers: [],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatDatepickerModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
],
|
||||
templateUrl: './new-member.component.html',
|
||||
styleUrl: './new-member.component.scss',
|
||||
})
|
||||
export class NewMemberComponent {
|
||||
private readonly memberDtoHelper = inject(MemberDtoHelper);
|
||||
|
||||
constructor() {}
|
||||
memberForm = this.memberDtoHelper.newMemberForm();
|
||||
private dialogRef: MatDialogRef<NewMemberComponent> = inject(
|
||||
MatDialogRef<NewMemberComponent>,
|
||||
);
|
||||
|
||||
onSubmit() {
|
||||
const newMember: api.PickMemberExcludeKeyofMemberIdOrLoeschenam =
|
||||
this.memberDtoHelper.newMemberFromForm(this.memberForm);
|
||||
|
||||
api.createMember(newMember)
|
||||
.then(() => {
|
||||
console.log('Successfully added Member');
|
||||
this.dialogRef.close();
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error adding member', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1 +1,17 @@
|
||||
<p>test works!</p>
|
||||
<h1>Testeite für interne Berechtigung</h1>
|
||||
<p>API: {{apiStatus()}}</p>
|
||||
<p>Auth: {{authTest()}}</p>
|
||||
Claim:
|
||||
<ul>
|
||||
@let claims = this.auth.claims();
|
||||
@if(claims) {
|
||||
<pre>
|
||||
{{claims | json}}
|
||||
</pre>
|
||||
}
|
||||
</ul>
|
||||
<details name="UserInfo:">
|
||||
<pre>
|
||||
{{this.auth.userInfo() | json}}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { JsonPipe } from '@angular/common';
|
||||
import { Component, effect, inject, signal } from '@angular/core';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { Authentication } from '../../services/authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test',
|
||||
imports: [],
|
||||
imports: [JsonPipe],
|
||||
templateUrl: './test.html',
|
||||
styleUrl: './test.scss',
|
||||
})
|
||||
export class Test {
|
||||
protected auth = inject(Authentication);
|
||||
|
||||
protected apiStatus = signal<string>("Waiting");
|
||||
protected authTest = signal<string>("Waiting");
|
||||
|
||||
public constructor(){
|
||||
effect(()=>{
|
||||
const loggedIn=this.auth.loggedIn();
|
||||
if(!loggedIn){
|
||||
this.apiStatus.set("N/A - not logged in");
|
||||
this.authTest.set("N/A - not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
this.apiStatus.set("Waiting for API");
|
||||
this.authTest.set("Waiting for API");
|
||||
api.getOk().then((status) => {
|
||||
this.apiStatus.set("Ok");
|
||||
}).catch((e) => {
|
||||
this.apiStatus.set("Error");
|
||||
});
|
||||
api.getAuth().then((response) => {
|
||||
this.authTest.set("Ok");
|
||||
}).catch((e) => {
|
||||
this.authTest.set(e);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<h1>Mitglieder & Sportlerübersicht</h1>
|
||||
<button mat-raised-button color="accent" (click)="newMember()">
|
||||
Neues Mitglied
|
||||
</button>
|
||||
<div class="filter-container mat-elevation-z8">
|
||||
<div class="filter-header">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
(keyup)="applyFilter($event)"
|
||||
[value]="filter$ | async"
|
||||
placeholder="Filter"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="dataSource$ | async as dataSource">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="edit">
|
||||
<th mat-header-cell *matHeaderCellDef>Bearb.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="editMember(element.id)"
|
||||
aria-label="Edit Member"
|
||||
>
|
||||
<mat-icon fontIcon="edit"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef>Entf.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="deleteMember(element.id)"
|
||||
aria-label="Delete Member"
|
||||
>
|
||||
<mat-icon fontIcon="delete"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="vorname">
|
||||
<th mat-header-cell *matHeaderCellDef>Vorname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.vorname | stylizeNames"></span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="nachname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nachname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.nachname | stylizeNames"></span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geschlecht">
|
||||
<th mat-header-cell *matHeaderCellDef>Geschlecht</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geschlecht }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="passnummer">
|
||||
<th mat-header-cell *matHeaderCellDef>Passnummer</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.passnummer | nobreakHyphen }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geburtsdatum">
|
||||
<th mat-header-cell *matHeaderCellDef>Geburtsdatum</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.geburtsdatum | datePipe }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geburtsort">
|
||||
<th mat-header-cell *matHeaderCellDef>Geburtsort</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="uvertrag">
|
||||
<th mat-header-cell *matHeaderCellDef>Unterrichtsvertrag</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon
|
||||
*ngIf="element.uvertrag"
|
||||
class="positive"
|
||||
aria-label="Unterrichtsvertrag Check"
|
||||
fontIcon="check"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="!element.uvertrag"
|
||||
class="negative"
|
||||
aria-label="Unterrichtsvertrag Cross"
|
||||
fontIcon="close"
|
||||
></mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="verein">
|
||||
<th mat-header-cell *matHeaderCellDef>Vereinsmitgliedschaft</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon
|
||||
*ngIf="element.verein"
|
||||
class="positive"
|
||||
aria-label="Verein Check"
|
||||
fontIcon="check"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="!element.verein"
|
||||
class="negative"
|
||||
aria-label="Verein Cross"
|
||||
fontIcon="close"
|
||||
></mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="kontakt">
|
||||
<th mat-header-cell *matHeaderCellDef>Kontakt</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.kontakt }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="kuendigungzum">
|
||||
<th mat-header-cell *matHeaderCellDef>Kündigung zum</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ (element.kuendigungzum | datePipe) ?? "-" }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ViewMembers } from './view-members';
|
||||
|
||||
describe('ViewMembers', () => {
|
||||
let component: ViewMembers;
|
||||
let fixture: ComponentFixture<ViewMembers>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ViewMembers]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ViewMembers);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { BehaviorSubject, firstValueFrom, from, Observable } from 'rxjs';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
||||
import { NobreakHyphenPipe } from '../../pipes/nobreak-hyphen.pipe.js';
|
||||
import { StylizeNamesPipe } from '../../pipes/stylize-names.pipe.js';
|
||||
import { DeleteMemberDialogComponent } from '../delete-member-dialog/delete-member-dialog.component.js';
|
||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||
import { NewMemberComponent } from '../new-member/new-member.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-view-members',
|
||||
imports: [
|
||||
MatTableModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
NgIf,
|
||||
StylizeNamesPipe,
|
||||
NobreakHyphenPipe,
|
||||
RouterModule,
|
||||
MatButtonModule,
|
||||
AsyncPipe,
|
||||
DatePipePipe,
|
||||
],
|
||||
templateUrl: './view-members.html',
|
||||
styleUrl: './view-members.scss',
|
||||
})
|
||||
export class ViewMembers {
|
||||
async deleteMember(memberId: any) {
|
||||
const dataSource = await firstValueFrom(this.dataSource$);
|
||||
|
||||
const dialogRef = this.dialog.open(DeleteMemberDialogComponent, {
|
||||
data: {
|
||||
member: dataSource.data.find(member => member.id === memberId),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
newMember() {
|
||||
const dialogRef = this.dialog.open(NewMemberComponent, {});
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
async editMember(memberId: any) {
|
||||
const dataSource = await firstValueFrom(this.dataSource$);
|
||||
|
||||
const dialogRef = this.dialog.open(EditMemberDialogComponent, {
|
||||
data: {
|
||||
member: dataSource.data.find(member => member.id === memberId),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
dataSource$: Observable<MatTableDataSource<api.Member>> =
|
||||
new BehaviorSubject(new MatTableDataSource([] as api.Member[]));
|
||||
filter$: BehaviorSubject<string> = new BehaviorSubject('');
|
||||
displayedColumns: string[] = [
|
||||
'edit',
|
||||
'delete',
|
||||
'vorname',
|
||||
'nachname',
|
||||
'geschlecht',
|
||||
'passnummer',
|
||||
'geburtsdatum',
|
||||
'geburtsort',
|
||||
'uvertrag',
|
||||
'verein',
|
||||
'kontakt',
|
||||
'kuendigungzum',
|
||||
];
|
||||
|
||||
applyFilter(event: KeyboardEvent) {
|
||||
let filterValue = (event.target as HTMLInputElement).value ?? '';
|
||||
filterValue = filterValue.trim();
|
||||
filterValue = filterValue.toLowerCase();
|
||||
this.filter$.next(filterValue);
|
||||
}
|
||||
constructor(private dialog: MatDialog) {
|
||||
this.dataSource$ = this.updateData();
|
||||
}
|
||||
|
||||
updateData() {
|
||||
return from(
|
||||
api.getMembers().then(members => {
|
||||
let dataSource = new MatTableDataSource<api.Member>(members);
|
||||
this.filter$.subscribe(filter => {
|
||||
dataSource.filter = filter;
|
||||
});
|
||||
return dataSource;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DatePipePipe } from './date-pipe.pipe';
|
||||
|
||||
describe('DatePipePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new DatePipePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import * as tz from '@date-fns/tz';
|
||||
import { format, Locale } from 'date-fns';
|
||||
|
||||
@Pipe({
|
||||
name: 'datePipe',
|
||||
standalone: true
|
||||
})
|
||||
export class DatePipePipe implements PipeTransform {
|
||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale){}
|
||||
|
||||
transform(value?: string): string {
|
||||
if(!value) return "-";
|
||||
const date=new tz.TZDate(value!, "UTC");
|
||||
const timeZone=Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const zonedDate=date.withTimeZone(timeZone);
|
||||
return format(zonedDate, 'P', { locale: this.locale });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NobreakHyphenPipe } from './nobreak-hyphen.pipe.js';
|
||||
|
||||
describe('NobreakHyphenPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new NobreakHyphenPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'nobreakHyphen',
|
||||
standalone: true,
|
||||
})
|
||||
export class NobreakHyphenPipe implements PipeTransform {
|
||||
transform(value: string): unknown {
|
||||
return value?.replaceAll('-', '‑'); // second '-' is a ‑ "no-break hyphen", first is a regular hyphen
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { StylizeNamesPipe } from './stylize-names.pipe.js';
|
||||
|
||||
describe('StylizeNamesPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new StylizeNamesPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'stylizeNames',
|
||||
standalone: true,
|
||||
})
|
||||
export class StylizeNamesPipe implements PipeTransform {
|
||||
transform(input?: string): string|undefined {
|
||||
if (!input) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
let formatted = '';
|
||||
formatted=this.capitalizeFirstLetters(input);
|
||||
formatted=this.boldUnderscored(formatted);
|
||||
return formatted;
|
||||
}
|
||||
|
||||
boldUnderscored(input: string): string {
|
||||
const segments=input.split('_');
|
||||
for(let i=0;i<Math.floor(segments.length/2);i++){
|
||||
if(segments.length>=2*i+1){
|
||||
segments[2*i+1]='<b>'+segments[2*i+1]+'</b>';
|
||||
}
|
||||
}
|
||||
return segments.join('');
|
||||
}
|
||||
|
||||
capitalizeFirstLetters(input: string): string {
|
||||
const words=input.split(' ');
|
||||
for(let i=0;i<words.length;i++){
|
||||
if(words[i].charAt(0)=='_'){
|
||||
words[i]=words[i].charAt(0)+words[i].charAt(1).toUpperCase()+words[i].slice(2);
|
||||
}else{
|
||||
words[i]=words[i].charAt(0).toUpperCase()+words[i].slice(1);
|
||||
}
|
||||
}
|
||||
return words.join(' ');
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,127 @@
|
||||
import { computed, effect, inject, Injectable, signal } from '@angular/core';
|
||||
import {
|
||||
KEYCLOAK_EVENT_SIGNAL,
|
||||
KeycloakEventType,
|
||||
ReadyArgs,
|
||||
typeEventArgs,
|
||||
KEYCLOAK_EVENT_SIGNAL,
|
||||
KeycloakEventType,
|
||||
ReadyArgs,
|
||||
typeEventArgs,
|
||||
} from 'keycloak-angular';
|
||||
import Keycloak, { KeycloakProfile } from 'keycloak-js';
|
||||
import { defaults as apiDefaults, Claim } from '../generated-api/api';
|
||||
|
||||
export enum AuthenticationState {
|
||||
Authenticated,
|
||||
Unauthenticated,
|
||||
Unknown,
|
||||
Authenticated,
|
||||
Unauthenticated,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class Authentication {
|
||||
private readonly keycloak = inject(Keycloak);
|
||||
private readonly keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
|
||||
private readonly _authenticationState = signal<AuthenticationState>(AuthenticationState.Unknown);
|
||||
private readonly _userInfo = signal<KeycloakProfile | null>(null);
|
||||
public readonly authenticationState = this._authenticationState.asReadonly();
|
||||
public readonly loggedIn = computed(()=>{
|
||||
return this.authenticationState() === AuthenticationState.Authenticated;
|
||||
});
|
||||
public readonly userInfo = this._userInfo.asReadonly();
|
||||
|
||||
public constructor() {
|
||||
effect(() => {
|
||||
const event = this.keycloakSignal();
|
||||
switch (event?.type) {
|
||||
case KeycloakEventType.Ready:
|
||||
this._authenticationState.set(
|
||||
typeEventArgs<ReadyArgs>(event.args)
|
||||
? AuthenticationState.Authenticated
|
||||
: AuthenticationState.Unauthenticated
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthSuccess:
|
||||
this._authenticationState.set(AuthenticationState.Authenticated);
|
||||
break;
|
||||
case KeycloakEventType.AuthLogout:
|
||||
this._authenticationState.set(AuthenticationState.Unauthenticated);
|
||||
break;
|
||||
case KeycloakEventType.AuthError:
|
||||
this._authenticationState.set(AuthenticationState.Unauthenticated);
|
||||
break;
|
||||
case KeycloakEventType.AuthRefreshError:
|
||||
this._authenticationState.set(AuthenticationState.Unauthenticated);
|
||||
break;
|
||||
case KeycloakEventType.AuthRefreshSuccess:
|
||||
this._authenticationState.set(AuthenticationState.Authenticated);
|
||||
break;
|
||||
case KeycloakEventType.TokenExpired:
|
||||
this._authenticationState.set(AuthenticationState.Unauthenticated);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
private readonly keycloak = inject(Keycloak);
|
||||
private readonly keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
|
||||
private readonly _authenticationState = signal<AuthenticationState>(
|
||||
AuthenticationState.Unknown,
|
||||
);
|
||||
private readonly _userInfo = signal<KeycloakProfile | null>(null);
|
||||
private readonly _claims = signal<Record<Claim, boolean> | null>(null);
|
||||
public readonly authenticationState =
|
||||
this._authenticationState.asReadonly();
|
||||
public readonly loggedIn = computed(() => {
|
||||
return this.authenticationState() === AuthenticationState.Authenticated;
|
||||
});
|
||||
public readonly userInfo = this._userInfo.asReadonly();
|
||||
public readonly claims = this._claims.asReadonly();
|
||||
|
||||
effect(async () => {
|
||||
const authenticationState = this.authenticationState();
|
||||
if (authenticationState !== AuthenticationState.Authenticated) {
|
||||
this._userInfo.set(null);
|
||||
}
|
||||
const profile = await this.keycloak.loadUserProfile();
|
||||
this._userInfo.set(profile);
|
||||
});
|
||||
}
|
||||
public constructor() {
|
||||
effect(() => {
|
||||
const event = this.keycloakSignal();
|
||||
|
||||
public async login(location?: string): Promise<void> {
|
||||
if(this._authenticationState() === AuthenticationState.Authenticated){
|
||||
return;
|
||||
apiDefaults.headers['Authorization'] = this.keycloak.token
|
||||
? `Bearer ${this.keycloak.token}`
|
||||
: undefined;
|
||||
|
||||
switch (event?.type) {
|
||||
case KeycloakEventType.Ready:
|
||||
this._authenticationState.set(
|
||||
typeEventArgs<ReadyArgs>(event.args)
|
||||
? AuthenticationState.Authenticated
|
||||
: AuthenticationState.Unauthenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthSuccess:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Authenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthLogout:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Unauthenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthError:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Unauthenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthRefreshError:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Unauthenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.AuthRefreshSuccess:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Authenticated,
|
||||
);
|
||||
break;
|
||||
case KeycloakEventType.TokenExpired:
|
||||
this._authenticationState.set(
|
||||
AuthenticationState.Unauthenticated,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
effect(async () => {
|
||||
const authenticationState = this.authenticationState();
|
||||
if (authenticationState !== AuthenticationState.Authenticated) {
|
||||
this._userInfo.set(null);
|
||||
}
|
||||
const profile = await this.keycloak.loadUserProfile();
|
||||
|
||||
const claims: Partial<Record<Claim, boolean>> = {};
|
||||
for (const claim of Object.values(Claim)) {
|
||||
if (this.keycloak.hasRealmRole(claim)) {
|
||||
claims[claim] = true;
|
||||
} else {
|
||||
claims[claim] = false;
|
||||
}
|
||||
}
|
||||
|
||||
this._userInfo.set(profile);
|
||||
this._claims.set(claims as Record<Claim, boolean>);
|
||||
});
|
||||
}
|
||||
|
||||
var redirectUri = location
|
||||
? `${window.location.origin}/${location}`
|
||||
: `${window.location.origin}/login`;
|
||||
return await this.keycloak.login({
|
||||
redirectUri: redirectUri,
|
||||
locale: 'de-DE',
|
||||
});
|
||||
}
|
||||
public async login(location?: string): Promise<void> {
|
||||
if (this._authenticationState() === AuthenticationState.Authenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
public async logout(): Promise<void> {
|
||||
return await this.keycloak.logout({ redirectUri: window.location.origin + '/login' });
|
||||
}
|
||||
var redirectUri = location
|
||||
? `${window.location.origin}/${location}`
|
||||
: `${window.location.origin}/login`;
|
||||
return await this.keycloak.login({
|
||||
redirectUri: redirectUri,
|
||||
locale: 'de-DE',
|
||||
});
|
||||
}
|
||||
|
||||
public async logout(): Promise<void> {
|
||||
return await this.keycloak.logout({
|
||||
redirectUri: window.location.origin + '/login',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import { Locale, parse } from 'date-fns';
|
||||
import {
|
||||
Member,
|
||||
PickMemberExcludeKeyofMemberIdOrLoeschenam,
|
||||
} from '../generated-api/api';
|
||||
|
||||
interface MemberForm {
|
||||
id: FormControl<string | null | undefined>;
|
||||
vorname: FormControl<string | null | undefined>;
|
||||
nachname: FormControl<string | null | undefined>;
|
||||
geschlecht: FormControl<string | null | undefined>;
|
||||
geburtsdatum: FormControl<Date | null>;
|
||||
geburtsort: FormControl<string | null | undefined>;
|
||||
passnummer: FormControl<string | null | undefined>;
|
||||
uvertrag: FormControl<boolean | null | undefined>;
|
||||
verein: FormControl<boolean | null | undefined>;
|
||||
kontakt: FormControl<string | null | undefined>;
|
||||
graduierung: FormControl<string | null | undefined>;
|
||||
kuendigungzum: FormControl<string | null | undefined>;
|
||||
letztePruefung: FormControl<string | null | undefined>;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MemberDtoHelper {
|
||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale) {}
|
||||
public newMemberForm(member?: Member): FormGroup<MemberForm> {
|
||||
const geburtsdatum = member?.geburtsdatum;
|
||||
let enDate: Date | null = null;
|
||||
if (geburtsdatum) {
|
||||
enDate = parse(geburtsdatum, 'dd.MM.yyyy', new Date(), {
|
||||
//TODO get dd.MM.yyyy from MAT_DATE_LOCALE or LOCALE_ID
|
||||
locale: this.locale,
|
||||
});
|
||||
}
|
||||
|
||||
return new FormGroup({
|
||||
id: new FormControl(member?.id),
|
||||
vorname: new FormControl(member?.vorname),
|
||||
nachname: new FormControl(member?.nachname),
|
||||
geschlecht: new FormControl(member?.geschlecht),
|
||||
geburtsdatum: new FormControl(enDate),
|
||||
geburtsort: new FormControl(member?.geburtsort),
|
||||
passnummer: new FormControl(member?.passnummer),
|
||||
uvertrag: new FormControl(member?.uvertrag),
|
||||
verein: new FormControl(member?.verein),
|
||||
kontakt: new FormControl(member?.kontakt),
|
||||
graduierung: new FormControl(member?.graduierung),
|
||||
kuendigungzum: new FormControl(member?.kuendigungzum),
|
||||
letztePruefung: new FormControl(member?.letztePruefung),
|
||||
});
|
||||
}
|
||||
|
||||
memberFromForm(form: FormGroup<MemberForm>): Member {
|
||||
return {
|
||||
id: form.controls.id.value ?? null,
|
||||
vorname: form.controls.vorname.value ?? null,
|
||||
nachname: form.controls.nachname.value ?? null,
|
||||
geschlecht: form.controls.geschlecht.value ?? null,
|
||||
geburtsdatum: form.controls.geburtsdatum.value
|
||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
||||
: null,
|
||||
geburtsort: form.controls.geburtsort.value ?? null,
|
||||
passnummer: form.controls.passnummer.value ?? null,
|
||||
uvertrag: form.controls.uvertrag.value ?? null,
|
||||
verein: form.controls.verein.value ?? null,
|
||||
kontakt: form.controls.kontakt.value ?? null,
|
||||
graduierung: form.controls.graduierung?.value ?? null,
|
||||
kuendigungzum: form.controls.kuendigungzum.value ?? null, //TODO not for new member
|
||||
letztePruefung: form.controls.letztePruefung?.value ?? null, //TODO not for new member
|
||||
loeschenam: null, //TODO? not client settable
|
||||
};
|
||||
}
|
||||
|
||||
newMemberFromForm(
|
||||
form: FormGroup<MemberForm>,
|
||||
): PickMemberExcludeKeyofMemberIdOrLoeschenam {
|
||||
return {
|
||||
vorname: form.controls.vorname.value ?? null,
|
||||
nachname: form.controls.nachname.value ?? null,
|
||||
geschlecht: form.controls.geschlecht.value ?? null,
|
||||
geburtsdatum: form.controls.geburtsdatum.value
|
||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
||||
: null,
|
||||
geburtsort: form.controls.geburtsort.value ?? null,
|
||||
passnummer: form.controls.passnummer.value ?? null,
|
||||
uvertrag: form.controls.uvertrag.value ?? null,
|
||||
verein: form.controls.verein.value ?? null,
|
||||
kontakt: form.controls.kontakt.value ?? null,
|
||||
graduierung: form.controls.graduierung?.value ?? null,
|
||||
kuendigungzum: null, //TODO not for new member
|
||||
letztePruefung: null, //TODO not for new member
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user