Files
NowChess-Frontend/src/app/pages/tournaments/tournaments.component.ts
T
shosho996 95eff42dfe fix: NCWF-4 Token Issues (#8)
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de>
Co-authored-by: shahdlala66 <shahd.lala66@gmail.com>
Reviewed-on: #8
2026-06-02 21:55:55 +02:00

233 lines
7.6 KiB
TypeScript

import { Component, DestroyRef, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { TournamentService } from '../../services/tournament.service';
import { AuthService } from '../../services/auth.service';
import { BotService } from '../../services/bot.service';
import { Bot } from '../../models/bot.models';
import { Tournament, TournamentResult, RoundPairings } from '../../models/tournament.models';
import { CurrentUser } from '../../models/auth.models';
type StatusTab = 'started' | 'created' | 'finished';
@Component({
selector: 'app-tournaments',
standalone: true,
imports: [CommonModule, RouterLink, ReactiveFormsModule],
templateUrl: './tournaments.component.html',
styleUrl: './tournaments.component.css'
})
export class TournamentsComponent implements OnInit {
private readonly destroyRef = inject(DestroyRef);
private readonly tournamentService = inject(TournamentService);
private readonly authService = inject(AuthService);
private readonly fb = inject(FormBuilder);
private readonly botService = inject(BotService);
private readonly router = inject(Router);
loading = true;
tab: StatusTab = 'started';
currentUser: CurrentUser | null = null;
started: Tournament[] = [];
created: Tournament[] = [];
finished: Tournament[] = [];
selectedTournament: Tournament | null = null;
pairings: RoundPairings | null = null;
pairingsLoading = false;
showCreateDialog = false;
createForm: FormGroup = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
nbRounds: [4, [Validators.required, Validators.min(1), Validators.max(20)]],
clockLimitMinutes: [3, [Validators.required, Validators.min(1), Validators.max(60)]],
clockIncrement: [0, [Validators.required, Validators.min(0), Validators.max(60)]],
rated: [false]
});
createLoading = false;
createError: string | null = null;
startingId: string | null = null;
joinDialogTournamentId: string | null = null;
userBots: Bot[] = [];
botsLoading = false;
joiningBotId: string | null = null;
joinError: string | null = null;
ngOnInit(): void {
this.authService.currentUser$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(u => { this.currentUser = u; });
this.loadTournaments();
}
openCreateDialog(): void {
this.createForm.reset({ name: '', nbRounds: 4, clockLimitMinutes: 3, clockIncrement: 0, rated: false });
this.createError = null;
this.showCreateDialog = true;
}
closeCreateDialog(): void {
this.showCreateDialog = false;
}
submitCreate(): void {
if (this.createForm.invalid) return;
this.createLoading = true;
this.createError = null;
this.tournamentService.create(this.createForm.value).subscribe({
next: t => {
this.createLoading = false;
this.showCreateDialog = false;
this.created = [t, ...this.created];
this.tab = 'created';
this.selectedTournament = null;
},
error: err => {
this.createLoading = false;
this.createError = err.error?.message ?? err.error?.error ?? 'Failed to create tournament.';
}
});
}
setTab(tab: StatusTab): void {
this.tab = tab;
this.selectedTournament = null;
this.pairings = null;
}
selectTournament(t: Tournament): void {
if (this.selectedTournament?.id === t.id) {
this.selectedTournament = null;
this.pairings = null;
return;
}
this.selectedTournament = t;
this.pairings = null;
if (t.round > 0) {
this.loadPairings(t.id, t.round);
}
}
get activeList(): Tournament[] {
return this[this.tab];
}
clockDisplay(t: Tournament): string {
const min = Math.floor(t.clock.limit / 60);
return `${min}+${t.clock.increment}`;
}
rankMedal(rank: number): string {
if (rank === 1) return '🥇';
if (rank === 2) return '🥈';
if (rank === 3) return '🥉';
return `${rank}.`;
}
scoreDisplay(r: TournamentResult): string {
return r.points % 1 === 0 ? `${r.points}` : `${r.points}`;
}
startTournament(event: MouseEvent, t: Tournament): void {
event.stopPropagation();
this.startingId = t.id;
this.tournamentService.start(t.id).subscribe({
next: updated => {
this.startingId = null;
const list = this.created.map(x => x.id === t.id ? updated : x);
this.created = list.filter(x => x.status === 'created');
if (!this.started.find(x => x.id === updated.id)) this.started = [updated, ...this.started];
this.selectedTournament = updated;
this.tab = 'started';
},
error: () => { this.startingId = null; }
});
}
watchGame(gameId: string): void {
void this.router.navigate(['/game', gameId]);
}
openJoinDialog(event: MouseEvent, tournamentId: string): void {
event.stopPropagation();
this.joinDialogTournamentId = tournamentId;
this.joinError = null;
this.botsLoading = true;
this.botService.list()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: bots => { this.userBots = bots; this.botsLoading = false; },
error: () => { this.botsLoading = false; }
});
}
closeJoinDialog(): void {
this.joinDialogTournamentId = null;
this.joiningBotId = null;
this.joinError = null;
}
joinWithBot(bot: Bot): void {
if (!this.joinDialogTournamentId || this.joiningBotId) return;
this.joiningBotId = bot.id;
this.joinError = null;
this.botService.rotateToken(bot.id).subscribe({
next: token => {
this.tournamentService.joinWithBotToken(this.joinDialogTournamentId!, token).subscribe({
next: () => {
this.joiningBotId = null;
const tid = this.joinDialogTournamentId!;
this.closeJoinDialog();
this.tournamentService.get(tid)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(updated => {
this.created = this.created.map(x => x.id === tid ? updated : x);
this.started = this.started.map(x => x.id === tid ? updated : x);
if (this.selectedTournament?.id === tid) this.selectedTournament = updated;
});
},
error: err => {
this.joiningBotId = null;
this.joinError = err.error?.message ?? err.error?.error ?? 'Failed to join tournament.';
}
});
},
error: () => {
this.joiningBotId = null;
this.joinError = 'Failed to get bot token.';
}
});
}
private loadTournaments(): void {
this.tournamentService.list()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: list => {
this.started = list.started;
this.created = list.created;
this.finished = list.finished;
this.loading = false;
if (this.started.length === 0 && this.created.length > 0) this.tab = 'created';
else if (this.started.length === 0 && this.finished.length > 0) this.tab = 'finished';
},
error: () => { this.loading = false; }
});
}
private loadPairings(id: string, round: number): void {
this.pairingsLoading = true;
this.tournamentService.roundPairings(id, round)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: p => { this.pairings = p; this.pairingsLoading = false; },
error: () => { this.pairingsLoading = false; }
});
}
}