feat(spa): add next-round and final leaderboard flow in Angular shells
This commit is contained in:
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user