feat(spa): add next-round and final leaderboard flow in Angular shells

This commit is contained in:
2026-03-01 15:21:52 +00:00
committed by Asger Geel Weirsoee
parent fbfb948e99
commit 3fc92c9ba0
5 changed files with 211 additions and 0 deletions
@@ -11,6 +11,12 @@ interface SessionDetail {
players: Array<{ id: number; nickname: string; score: number }>;
}
interface LeaderboardResponse {
session: { code: string; status: string; current_round: number };
leaderboard: Array<{ id: number; nickname: string; score: number }>;
winner?: { id: number; nickname: string; score: number } | null;
}
@Component({
selector: 'app-host-shell',
standalone: true,
@@ -27,11 +33,17 @@ interface SessionDetail {
<button (click)="mixAnswers()" [disabled]="loading || !roundQuestionId">Mix answers → guess</button>
<button (click)="calculateScores()" [disabled]="loading || !roundQuestionId">Calculate scores → reveal</button>
<button (click)="loadScoreboard()" [disabled]="loading">Load scoreboard</button>
<button (click)="startNextRound()" [disabled]="loading">Start next round</button>
<button (click)="finishGame()" [disabled]="loading">Finish game</button>
<button *ngIf="scoreboardError" (click)="loadScoreboard()" [disabled]="loading">Retry scoreboard</button>
<button *ngIf="nextRoundError" (click)="startNextRound()" [disabled]="loading">Retry next round</button>
<button *ngIf="finishError" (click)="finishGame()" [disabled]="loading">Retry finish game</button>
</div>
<p *ngIf="error" class="error">{{ error }}</p>
<p *ngIf="scoreboardError" class="error">{{ scoreboardError }}</p>
<p *ngIf="nextRoundError" class="error">{{ nextRoundError }}</p>
<p *ngIf="finishError" class="error">{{ finishError }}</p>
<div *ngIf="session" class="panel">
<p><strong>Status:</strong> {{ session.session.status }} · round {{ session.session.current_round }}</p>
@@ -41,6 +53,7 @@ interface SessionDetail {
<li *ngFor="let p of session.players">{{ p.nickname }}: {{ p.score }}</li>
</ul>
<pre *ngIf="scoreboardPayload">{{ scoreboardPayload }}</pre>
<pre *ngIf="finalLeaderboardPayload">{{ finalLeaderboardPayload }}</pre>
</div>
`,
})
@@ -51,7 +64,10 @@ export class HostShellComponent {
loading = false;
error = '';
scoreboardError = '';
nextRoundError = '';
finishError = '';
scoreboardPayload = '';
finalLeaderboardPayload = '';
session: SessionDetail | null = null;
private readonly controller = createVerticalSliceController(createApiClient());
@@ -83,6 +99,8 @@ export class HostShellComponent {
this.loading = true;
this.error = '';
this.scoreboardError = '';
this.nextRoundError = '';
this.finishError = '';
try {
const state = await this.controller.hydrateLobby(this.sessionCode);
if (!state.session || state.errorMessage) {
@@ -91,6 +109,9 @@ export class HostShellComponent {
this.session = state.session as SessionDetail;
this.sessionCode = this.session.session.code;
this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : '';
if (this.session.session.status !== 'finished') {
this.finalLeaderboardPayload = '';
}
} catch (error) {
this.error = `Session refresh failed: ${(error as Error).message}`;
} finally {
@@ -107,6 +128,8 @@ export class HostShellComponent {
this.session = state.session as SessionDetail;
this.sessionCode = this.session.session.code;
this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : '';
this.scoreboardPayload = '';
this.finalLeaderboardPayload = '';
});
}
@@ -152,6 +175,39 @@ export class HostShellComponent {
}
}
async startNextRound(): Promise<void> {
this.loading = true;
this.nextRoundError = '';
this.error = '';
try {
const code = this.normalizeCode(this.sessionCode);
await this.request(`/lobby/sessions/${encodeURIComponent(code)}/rounds/next`, 'POST', {});
this.scoreboardPayload = '';
this.finalLeaderboardPayload = '';
await this.refreshSession();
} catch (error) {
this.nextRoundError = `Next round failed: ${(error as Error).message}`;
} finally {
this.loading = false;
}
}
async finishGame(): Promise<void> {
this.loading = true;
this.finishError = '';
this.error = '';
try {
const code = this.normalizeCode(this.sessionCode);
const payload = await this.request<LeaderboardResponse>(`/lobby/sessions/${encodeURIComponent(code)}/finish`, 'POST', {});
this.finalLeaderboardPayload = JSON.stringify(payload, null, 2);
await this.refreshSession();
} catch (error) {
this.finishError = `Finish game failed: ${(error as Error).message}`;
} finally {
this.loading = false;
}
}
private async runAction(action: () => Promise<void>): Promise<void> {
this.loading = true;
this.error = '';