[SPA][P4] #169 Lobby join + start round wired via vertical slice in shell #182

Merged
integrator-bot merged 1 commits from dev/issue-169-spa-lobby-start-round-wire-spa-flow into main 2026-03-01 16:11:01 +01:00
2 changed files with 42 additions and 20 deletions

View File

@@ -2,6 +2,9 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { createApiClient } from '../../../../../src/api/client';
import { createVerticalSliceController } from '../../../../../src/spa/vertical-slice';
interface SessionDetail { interface SessionDetail {
session: { code: string; status: string; current_round: number }; session: { code: string; status: string; current_round: number };
round_question: { id: number; prompt: string; answers: Array<{ text: string }> } | null; round_question: { id: number; prompt: string; answers: Array<{ text: string }> } | null;
@@ -51,6 +54,8 @@ export class HostShellComponent {
scoreboardPayload = ''; scoreboardPayload = '';
session: SessionDetail | null = null; session: SessionDetail | null = null;
private readonly controller = createVerticalSliceController(createApiClient());
private normalizeCode(value: string): string { private normalizeCode(value: string): string {
return value.trim().toUpperCase(); return value.trim().toUpperCase();
} }
@@ -79,8 +84,11 @@ export class HostShellComponent {
this.error = ''; this.error = '';
this.scoreboardError = ''; this.scoreboardError = '';
try { try {
const code = this.normalizeCode(this.sessionCode); const state = await this.controller.hydrateLobby(this.sessionCode);
this.session = await this.request<SessionDetail>(`/lobby/sessions/${encodeURIComponent(code)}`, 'GET'); if (!state.session || state.errorMessage) {
throw new Error(state.errorMessage ?? 'Unknown error');
}
this.session = state.session as SessionDetail;
this.sessionCode = this.session.session.code; this.sessionCode = this.session.session.code;
this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : ''; this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : '';
} catch (error) { } catch (error) {
@@ -92,11 +100,13 @@ export class HostShellComponent {
async startRound(): Promise<void> { async startRound(): Promise<void> {
await this.runAction(async () => { await this.runAction(async () => {
const code = this.normalizeCode(this.sessionCode); const state = await this.controller.startRound(this.sessionCode, this.categorySlug.trim());
await this.request(`/lobby/sessions/${encodeURIComponent(code)}/rounds/start`, 'POST', { if (!state.session || state.errorMessage) {
category_slug: this.categorySlug.trim(), throw new Error(state.errorMessage ?? 'Unknown error');
}); }
await this.refreshSession(); 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) : '';
}); });
} }

View File

@@ -2,6 +2,10 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { createApiClient } from '../../../../../src/api/client';
import { createSessionContextStore } from '../../../../../src/spa/session-context-store';
import { createVerticalSliceController } from '../../../../../src/spa/vertical-slice';
interface SessionDetail { interface SessionDetail {
session: { code: string; status: string; current_round: number }; session: { code: string; status: string; current_round: number };
round_question: { id: number; prompt: string; answers: Array<{ text: string }> } | null; round_question: { id: number; prompt: string; answers: Array<{ text: string }> } | null;
@@ -61,6 +65,9 @@ export class PlayerShellComponent {
submitError: { kind: 'lie' | 'guess'; message: string } | null = null; submitError: { kind: 'lie' | 'guess'; message: string } | null = null;
session: SessionDetail | null = null; session: SessionDetail | null = null;
private readonly sessionContextStore = createSessionContextStore();
private readonly controller = createVerticalSliceController(createApiClient(), this.sessionContextStore);
private normalizeCode(value: string): string { private normalizeCode(value: string): string {
return value.trim().toUpperCase(); return value.trim().toUpperCase();
} }
@@ -88,8 +95,11 @@ export class PlayerShellComponent {
this.loading = true; this.loading = true;
this.error = ''; this.error = '';
try { try {
const code = this.normalizeCode(this.sessionCode); const state = await this.controller.hydrateLobby(this.sessionCode);
this.session = await this.request<SessionDetail>(`/lobby/sessions/${encodeURIComponent(code)}`, 'GET'); if (!state.session || state.errorMessage) {
throw new Error(state.errorMessage ?? 'Unknown error');
}
this.session = state.session as SessionDetail;
this.sessionCode = this.session.session.code; this.sessionCode = this.session.session.code;
if (this.session.session.status !== 'guess') { if (this.session.session.status !== 'guess') {
this.selectedGuess = ''; this.selectedGuess = '';
@@ -105,17 +115,19 @@ export class PlayerShellComponent {
this.loading = true; this.loading = true;
this.error = ''; this.error = '';
try { try {
const payload = await this.request<{ const state = await this.controller.joinLobby(this.sessionCode, this.nickname);
player: { id: number; session_token: string }; if (!state.session || state.errorMessage) {
session: { code: string }; throw new Error(state.errorMessage ?? 'Unknown error');
}>('/lobby/sessions/join', 'POST', { }
code: this.normalizeCode(this.sessionCode), this.session = state.session as SessionDetail;
nickname: this.nickname.trim(), this.sessionCode = this.session.session.code;
});
this.playerId = payload.player.id; const sessionContext = this.sessionContextStore.get();
this.sessionToken = payload.player.session_token; this.playerId = sessionContext?.playerId ?? 0;
this.sessionCode = payload.session.code; this.sessionToken = sessionContext?.token ?? '';
await this.refreshSession(); if (this.session.session.status !== 'guess') {
this.selectedGuess = '';
}
} catch (error) { } catch (error) {
this.error = `Join failed: ${(error as Error).message}`; this.error = `Join failed: ${(error as Error).message}`;
} finally { } finally {