fix: NCWF-2 bugs and desing fixes (#7)
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de> Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
|
||||
import { Component, DestroyRef, HostListener, OnInit, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router } from '@angular/router';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
@@ -8,6 +8,10 @@ import { CurrentUser } from '../../models/auth.models';
|
||||
import { LoginDialogComponent } from '../login-dialog/login-dialog.component';
|
||||
import { RegisterDialogComponent } from '../register-dialog/register-dialog.component';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { ChallengeEventService } from '../../services/challenge-event.service';
|
||||
import { ChallengeService } from '../../services/challenge.service';
|
||||
import { ChallengeWebSocketService } from '../../services/challenge-websocket.service';
|
||||
import { Challenge } from '../../models/challenge.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toolbar',
|
||||
@@ -21,35 +25,104 @@ export class ToolbarComponent implements OnInit {
|
||||
private readonly authService = inject(AuthService);
|
||||
private readonly authDialogService = inject(AuthDialogService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
private readonly challengeEventService = inject(ChallengeEventService);
|
||||
private readonly challengeService = inject(ChallengeService);
|
||||
private readonly challengeWs = inject(ChallengeWebSocketService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
private pollHandle: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
currentUser: CurrentUser | null = null;
|
||||
showLoginDialog = false;
|
||||
showRegisterDialog = false;
|
||||
isDarkMode = false;
|
||||
profileOpen = false;
|
||||
notifOpen = false;
|
||||
incomingChallenges: Challenge[] = [];
|
||||
acceptingId: string | null = null;
|
||||
decliningId: string | null = null;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.destroyRef.onDestroy(() => this.stopPolling());
|
||||
|
||||
this.authService.currentUser$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((user) => {
|
||||
.subscribe(user => {
|
||||
this.currentUser = user;
|
||||
if (user) {
|
||||
this.challengeWs.connect();
|
||||
this.startPolling();
|
||||
} else {
|
||||
this.challengeWs.disconnect();
|
||||
this.stopPolling();
|
||||
this.challengeEventService.clear();
|
||||
}
|
||||
});
|
||||
|
||||
this.authDialogService.dialogState$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((state) => {
|
||||
.subscribe(state => {
|
||||
this.showLoginDialog = state === 'login';
|
||||
this.showRegisterDialog = state === 'register';
|
||||
});
|
||||
|
||||
this.themeService.darkMode$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((isDarkMode) => {
|
||||
this.isDarkMode = isDarkMode;
|
||||
});
|
||||
.subscribe(isDark => { this.isDarkMode = isDark; });
|
||||
|
||||
this.challengeEventService.getIncomingChallenges$()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(challenges => { this.incomingChallenges = challenges; });
|
||||
}
|
||||
|
||||
private startPolling(): void {
|
||||
this.fetchIncoming();
|
||||
this.pollHandle = setInterval(() => this.fetchIncoming(), 5000);
|
||||
}
|
||||
|
||||
private stopPolling(): void {
|
||||
if (this.pollHandle !== null) {
|
||||
clearInterval(this.pollHandle);
|
||||
this.pollHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private fetchIncoming(): void {
|
||||
this.challengeService.listChallenges().subscribe({
|
||||
next: response => {
|
||||
const incoming = response.in ?? response.incoming ?? [];
|
||||
this.challengeEventService.setIncomingChallenges(incoming);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
onDocumentClick(event: MouseEvent): void {
|
||||
if (!(event.target as HTMLElement).closest('[data-dropdown]')) {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggleProfile(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
const wasOpen = this.profileOpen;
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
this.profileOpen = !wasOpen;
|
||||
}
|
||||
|
||||
toggleNotif(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
const wasOpen = this.notifOpen;
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
this.notifOpen = !wasOpen;
|
||||
}
|
||||
|
||||
openLoginDialog(): void {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
this.authDialogService.openLogin();
|
||||
}
|
||||
|
||||
@@ -58,6 +131,8 @@ export class ToolbarComponent implements OnInit {
|
||||
}
|
||||
|
||||
openRegisterDialog(): void {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
this.authDialogService.openRegister();
|
||||
}
|
||||
|
||||
@@ -66,15 +141,36 @@ export class ToolbarComponent implements OnInit {
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
this.authService.logout();
|
||||
}
|
||||
|
||||
toggleTheme(): void {
|
||||
toggleTheme(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.themeService.toggleTheme();
|
||||
}
|
||||
|
||||
goToHome(): void {
|
||||
void this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
goToProfile(): void {
|
||||
this.router.navigate(['/profile']);
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
void this.router.navigate(['/profile']);
|
||||
}
|
||||
|
||||
goToGames(): void {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
void this.router.navigate(['/games']);
|
||||
}
|
||||
|
||||
goToChallenges(): void {
|
||||
this.profileOpen = false;
|
||||
this.notifOpen = false;
|
||||
void this.router.navigate(['/challenges']);
|
||||
}
|
||||
|
||||
onLoginSuccess(): void {
|
||||
@@ -84,4 +180,48 @@ export class ToolbarComponent implements OnInit {
|
||||
onRegisterSuccess(): void {
|
||||
this.closeRegisterDialog();
|
||||
}
|
||||
|
||||
getInitial(): string {
|
||||
return this.currentUser?.username?.charAt(0).toUpperCase() ?? '?';
|
||||
}
|
||||
|
||||
getTimeControlDisplay(challenge: Challenge): string {
|
||||
const { limit, increment } = challenge.timeControl;
|
||||
if (!limit || !increment) return 'Unlimited';
|
||||
return `${Math.floor(limit / 60)}+${increment}`;
|
||||
}
|
||||
|
||||
getExpirationInfo(challenge: Challenge): string {
|
||||
const diff = new Date(challenge.expiresAt).getTime() - Date.now();
|
||||
if (diff <= 0) return 'Expired';
|
||||
const min = Math.floor(diff / 60000);
|
||||
return min > 60 ? `${Math.floor(min / 60)}h` : `${min}m`;
|
||||
}
|
||||
|
||||
acceptChallenge(event: MouseEvent, challenge: Challenge): void {
|
||||
event.stopPropagation();
|
||||
if (this.acceptingId || this.decliningId) return;
|
||||
this.acceptingId = challenge.id;
|
||||
this.challengeService.acceptChallenge(challenge.id).subscribe({
|
||||
next: accepted => {
|
||||
this.acceptingId = null;
|
||||
this.challengeEventService.onChallengeAccepted(accepted);
|
||||
if (accepted.gameId) void this.router.navigate(['/game', accepted.gameId]);
|
||||
},
|
||||
error: () => { this.acceptingId = null; }
|
||||
});
|
||||
}
|
||||
|
||||
declineChallenge(event: MouseEvent, challenge: Challenge): void {
|
||||
event.stopPropagation();
|
||||
if (this.acceptingId || this.decliningId) return;
|
||||
this.decliningId = challenge.id;
|
||||
this.challengeService.declineChallenge(challenge.id, { reason: 'generic' }).subscribe({
|
||||
next: () => {
|
||||
this.decliningId = null;
|
||||
this.challengeEventService.removeChallenge(challenge.id);
|
||||
},
|
||||
error: () => { this.decliningId = null; }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user