feat: Added redirect after login for users. Closes #17

This commit is contained in:
2025-05-24 11:26:42 +02:00
parent cf2fa0b607
commit 86347e254b
18 changed files with 189 additions and 22 deletions

View File

@@ -55,7 +55,7 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit {
} }
openUserCard(id?: string) { openUserCard(id?: string) {
this.dialog.open(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => { this.dialog.open<UserEditComponent, UserEditComponent.InputData, UserEditComponent.ReturnData>(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => {
if (r) this.ngOnInit() if (r) this.ngOnInit()
}) })
} }

View File

@@ -11,12 +11,17 @@ import { catchError, throwError } from 'rxjs';
import { Moment } from 'moment'; import { Moment } from 'moment';
import * as moment from 'moment'; import * as moment from 'moment';
export namespace UserEditComponent {
export type InputData = {type: "new" | "edit", id?: string, groups: Group[]}
export type ReturnData = true | undefined
}
@Component({ @Component({
selector: 'app-user-edit', selector: 'app-user-edit',
templateUrl: './user-edit.component.html', templateUrl: './user-edit.component.html',
styleUrls: ['./user-edit.component.scss'] styleUrls: ['./user-edit.component.scss']
}) })
export class UserEditComponent { export class UserEditComponent {
lockout = false; lockout = false;
locked = false; locked = false;
loading = false; loading = false;
@@ -33,7 +38,7 @@ export class UserEditComponent {
regDate?: Moment; regDate?: Moment;
constructor ( constructor (
public dialogRef: MatDialogRef<UserEditComponent>, public dialogRef: MatDialogRef<UserEditComponent>,
@Inject(MAT_DIALOG_DATA) public data: ({type: "edit", id: string} | {type: "new"}) & {groups: Group[]}, @Inject(MAT_DIALOG_DATA) public data: UserEditComponent.InputData,
readonly ls: LocalStorageService, readonly ls: LocalStorageService,
readonly acu: AdminCommService, readonly acu: AdminCommService,
private dialog: MatDialog, private dialog: MatDialog,
@@ -42,7 +47,7 @@ export class UserEditComponent {
this.groups = data.groups this.groups = data.groups
if (data.type == "edit") { if (data.type == "edit") {
this.id = data.id this.id = data.id
this.acu.accs.getUser(data.id).subscribe((r) => { this.acu.accs.getUser(data.id!).subscribe((r) => {
this.regDate = moment(r.regDate) this.regDate = moment(r.regDate)
var flags: Array<number> = [] var flags: Array<number> = []
if (r.admin) { if (r.admin) {

View File

@@ -4,6 +4,6 @@ import { LocalStorageService } from './services/local-storage.service';
export const adminGuard: CanActivateChildFn = (childRoute, state) => { export const adminGuard: CanActivateChildFn = (childRoute, state) => {
const router = inject(Router) const router = inject(Router)
if (inject(LocalStorageService).admin == false) return router.parseUrl('/') if (inject(LocalStorageService).admin == undefined) return router.parseUrl('/')
return true return true
}; };

View File

@@ -0,0 +1,14 @@
<h1 mat-dialog-title>Dodatkowe ustawienia</h1>
<mat-dialog-content>
<mat-action-list>
@for (link of LINKS; track link) {
<button mat-list-item (click)="open(link.component)">
<mat-icon matListItemIcon *ngIf="link.icon">{{link.icon}}</mat-icon>
<div matListItemTitle>{{link.title}}</div>
</button>
}
</mat-action-list>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-dialog-close mat-button>Zamknij</button>
</mat-dialog-actions>

View File

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

View File

@@ -0,0 +1,29 @@
import { ComponentType } from '@angular/cdk/portal';
import { Component } from '@angular/core';
import { Link } from 'src/app/types/link';
import { RedirectComponent } from './redirect/redirect.component';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'app-extra',
templateUrl: './extra.component.html',
styleUrl: './extra.component.scss'
})
export class ExtraComponent {
constructor (private dialog: MatDialog) {}
private readonly _LINKS: (Omit<Link, "href"> & {component: ComponentType<any>})[] = [
{ title: "Domyślna strona po logowaniu", component: RedirectComponent, enabled: true, icon: "home" }
]
public get LINKS() {
return this._LINKS.filter((v) => {
return v.enabled
});
}
open(component: ComponentType<any>) {
this.dialog.open(component)
}
}

View File

@@ -0,0 +1,13 @@
<h1>Domyślna strona po logowaniu</h1>
<mat-dialog-content>
<p>Wpisz link względem /ipwa/ w poniższym polu.</p>
<p>Przykład: /app/menu</p>
<mat-form-field>
<input matInput type="text" [(ngModel)]="redirect">
<mat-label>Link</mat-label>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-dialog-close mat-button>Anuluj</button>
<button (click)="save()" mat-flat-button>Zapisz</button>
</mat-dialog-actions>

View File

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

View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { AuthClient } from 'src/app/services/auth.client';
@Component({
selector: 'app-redirect',
templateUrl: './redirect.component.html',
styleUrl: './redirect.component.scss'
})
export class RedirectComponent {
protected redirect = ""
constructor (public dialogRef: MatDialogRef<RedirectComponent>, private ac: AuthClient) {
this.redirect = ac.redirect
}
protected save() {
this.ac.redirect = this.redirect
this.dialogRef.close()
}
}

View File

@@ -30,6 +30,10 @@
<div matListItemTitle>Panel administracyjny</div> <div matListItemTitle>Panel administracyjny</div>
<div matListItemLine>Poprzednio Tryb edycji</div> <div matListItemLine>Poprzednio Tryb edycji</div>
</button> </button>
<button mat-list-item (click)="openExtra()">
<mat-icon matListItemIcon>settings_applications</mat-icon>
<div matListItemTitle>Dodatkowe ustawienia</div>
</button>
<button mat-list-item (click)="openAbout()"> <button mat-list-item (click)="openAbout()">
<mat-icon matListItemIcon>info</mat-icon> <mat-icon matListItemIcon>info</mat-icon>
<div matListItemTitle>O programie</div> <div matListItemTitle>O programie</div>

View File

@@ -10,6 +10,7 @@ import { LocalStorageService } from 'src/app/services/local-storage.service';
import { KeyComponent } from './key/key.component'; import { KeyComponent } from './key/key.component';
import { CleanComponent } from './clean/clean.component'; import { CleanComponent } from './clean/clean.component';
import { AboutComponent } from './about/about.component'; import { AboutComponent } from './about/about.component';
import { ExtraComponent } from './extra/extra.component';
@Component({ @Component({
selector: 'app-personal', selector: 'app-personal',
@@ -63,6 +64,10 @@ export class PersonalComponent {
this.ac.check() this.ac.check()
} }
protected openExtra() {
this.dialog.open(ExtraComponent)
}
protected openAbout() { protected openAbout() {
this.dialog.open(AboutComponent) this.dialog.open(AboutComponent)
} }

View File

@@ -79,6 +79,8 @@ import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summa
import { HourDisplayComponent } from './admin-view/grades/attendence-summary/hour-display/hour-display.component'; import { HourDisplayComponent } from './admin-view/grades/attendence-summary/hour-display/hour-display.component';
import { AboutComponent } from './app-view/personal/about/about.component'; import { AboutComponent } from './app-view/personal/about/about.component';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { ExtraComponent } from './app-view/personal/extra/extra.component';
import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -122,6 +124,8 @@ import { environment } from 'src/environments/environment';
AttendenceSummaryComponent, AttendenceSummaryComponent,
HourDisplayComponent, HourDisplayComponent,
AboutComponent, AboutComponent,
ExtraComponent,
RedirectComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@@ -22,7 +22,7 @@ export class LoginComponent implements OnInit {
ngOnInit() { ngOnInit() {
if (this.ls.loggedIn) { if (this.ls.loggedIn) {
this.router.navigateByUrl('app') this.router.navigateByUrl(this.ac.redirect || 'app')
} }
} }
@@ -33,7 +33,7 @@ export class LoginComponent implements OnInit {
return throwError(() => new Error(err.message)) return throwError(() => new Error(err.message))
})).subscribe((data) => { })).subscribe((data) => {
this.ls.loggedIn = true this.ls.loggedIn = true
this.router.navigateByUrl('app') this.router.navigateByUrl(this.ac.redirect || 'app')
if (data.admin) { if (data.admin) {
this.ls.admin = data.admin this.ls.admin = data.admin
} }

View File

@@ -1,7 +1,7 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { catchError, EMPTY, throwError } from 'rxjs'; import { catchError, EMPTY, tap, throwError } from 'rxjs';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { LocalStorageService } from './local-storage.service'; import { LocalStorageService } from './local-storage.service';
import { Status } from '../types/status'; import { Status } from '../types/status';
@@ -12,11 +12,22 @@ import { Status } from '../types/status';
export class AuthClient { export class AuthClient {
constructor(private http: HttpClient, private router: Router, private ls: LocalStorageService) { } constructor(private http: HttpClient, private router: Router, private ls: LocalStorageService) { }
private _redirect: string = "";
public get redirect(): string {
return this._redirect;
}
public set redirect(value: string) {
this._redirect = value;
this.putRedirect(value).subscribe()
}
public login(uname: string, pass: string) { public login(uname: string, pass: string) {
return this.http.post<any>(environment.apiEndpoint + '/auth/login', { return this.http.post<Status & {admin: number, redirect: string}>(environment.apiEndpoint + '/auth/login', {
username: uname, username: uname,
password: pass password: pass
}, {withCredentials: true}) }, {withCredentials: true}).pipe(tap((v) => {
if (v.redirect) this._redirect = v.redirect
}))
} }
public logout() { public logout() {
@@ -24,15 +35,26 @@ export class AuthClient {
} }
public check() { public check() {
this.http.get(environment.apiEndpoint + '/auth/check', {withCredentials: true}).pipe(catchError((err) => { this.http.get<{
admin?: number,
room?: string,
features: number,
menu: {
defaultItems: {
sn: string[];
kol: string[];
}
},
vapid: string
}>(environment.apiEndpoint + '/auth/check', {withCredentials: true}).pipe(catchError((err) => {
if (err.status == 401 && this.ls.loggedIn) { if (err.status == 401 && this.ls.loggedIn) {
this.ls.logOut() this.ls.logOut()
this.router.navigateByUrl("/login") this.router.navigateByUrl("/login")
return EMPTY return EMPTY
} }
return throwError(() => new Error(err.message)) return throwError(() => new Error(err.message))
})).subscribe((data: any)=>{ })).subscribe((data)=>{
if (data.admin) { this.ls.admin = data.admin } else { this.ls.admin = false } this.ls.admin = data.admin
if (this.ls.capFlag != data.features) { if (this.ls.capFlag != data.features) {
this.ls.capFlag = data.features this.ls.capFlag = data.features
document.location.reload() document.location.reload()
@@ -48,4 +70,8 @@ export class AuthClient {
public chpass(oldpass:string,newpass:string) { public chpass(oldpass:string,newpass:string) {
return this.http.post(environment.apiEndpoint + '/auth/chpass', {"oldPass": oldpass, "newPass": newpass}, {withCredentials: true, responseType: "text"}) return this.http.post(environment.apiEndpoint + '/auth/chpass', {"oldPass": oldpass, "newPass": newpass}, {withCredentials: true, responseType: "text"})
} }
private putRedirect(redirect: string) {
return this.http.put<Status>(environment.apiEndpoint + '/auth/redirect', {redirect: redirect}, {withCredentials: true})
}
} }

View File

@@ -13,8 +13,8 @@ export class LocalStorageService {
} }
logOut() { logOut() {
this.loggedIn = false this.loggedIn = undefined
this.admin = false this.admin = undefined
} }
public hasRoom() { public hasRoom() {
@@ -26,11 +26,11 @@ export class LocalStorageService {
} }
get room() { get room() {
return localStorage.getItem('room')! return localStorage.getItem('room') ?? undefined
} }
set room(value: string) { set room(value: string | undefined) {
if (value == "") { if (!value) {
localStorage.removeItem('room') localStorage.removeItem('room')
} else { } else {
localStorage.setItem('room', value) localStorage.setItem('room', value)
@@ -49,10 +49,10 @@ export class LocalStorageService {
if (localStorage.getItem("loggedIn")) { if (localStorage.getItem("loggedIn")) {
return true return true
} }
return false return
} }
set loggedIn(is: boolean) { set loggedIn(is: true | undefined) {
if (is) { if (is) {
localStorage.setItem("loggedIn", "true") localStorage.setItem("loggedIn", "true")
} else { } else {
@@ -60,7 +60,7 @@ export class LocalStorageService {
} }
} }
set admin(newInt: number | false) { set admin(newInt: number | undefined) {
if (newInt) { if (newInt) {
localStorage.setItem("admin", newInt.toString()) localStorage.setItem("admin", newInt.toString())
} else { } else {
@@ -70,7 +70,7 @@ export class LocalStorageService {
get admin() { get admin() {
var lsa = localStorage.getItem("admin") var lsa = localStorage.getItem("admin")
return lsa ? Number.parseInt(lsa) : false return lsa ? Number.parseInt(lsa) : undefined
} }
set amgreg(toggle: boolean) { set amgreg(toggle: boolean) {

View File

@@ -11,4 +11,5 @@ export default interface User {
surname?: string; surname?: string;
groups: string[]; groups: string[];
regDate: Moment; regDate: Moment;
defaultPage?: string;
} }