import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; interface SessionDetail { session: { code: string; status: string; current_round: number }; round_question: { id: number; prompt: string; answers: Array<{ text: string }> } | null; } @Component({ selector: 'app-player-shell', standalone: true, imports: [CommonModule, FormsModule], template: `

Player SPA gameplay flow

Status: {{ session.session.status }}

Prompt: {{ session.round_question.prompt }}

{{ error }}

{{ submitError.message }}

`, }) export class PlayerShellComponent { sessionCode = ''; nickname = ''; playerId = 0; sessionToken = ''; lieText = ''; selectedGuess = ''; loading = false; error = ''; submitError: { kind: 'lie' | 'guess'; message: string } | null = null; session: SessionDetail | null = null; private normalizeCode(value: string): string { return value.trim().toUpperCase(); } private async request(path: string, method: 'GET' | 'POST', payload?: unknown): Promise { const response = await fetch(path, { method, headers: { Accept: 'application/json', ...(payload === undefined ? {} : { 'Content-Type': 'application/json' }), }, ...(payload === undefined ? {} : { body: JSON.stringify(payload) }), credentials: 'same-origin', }); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new Error((body as { error?: string }).error ?? `HTTP ${response.status}`); } return body as T; } async refreshSession(): Promise { this.loading = true; this.error = ''; try { const code = this.normalizeCode(this.sessionCode); this.session = await this.request(`/lobby/sessions/${encodeURIComponent(code)}`, 'GET'); this.sessionCode = this.session.session.code; if (this.session.session.status !== 'guess') { this.selectedGuess = ''; } } catch (error) { this.error = `Session refresh failed: ${(error as Error).message}`; } finally { this.loading = false; } } async joinSession(): Promise { this.loading = true; this.error = ''; try { const payload = await this.request<{ player: { id: number; session_token: string }; session: { code: string }; }>('/lobby/sessions/join', 'POST', { code: this.normalizeCode(this.sessionCode), nickname: this.nickname.trim(), }); this.playerId = payload.player.id; this.sessionToken = payload.player.session_token; this.sessionCode = payload.session.code; await this.refreshSession(); } catch (error) { this.error = `Join failed: ${(error as Error).message}`; } finally { this.loading = false; } } async submitLie(): Promise { if (!this.session?.round_question?.id) { return; } this.loading = true; this.submitError = null; try { await this.request( `/lobby/sessions/${encodeURIComponent(this.normalizeCode(this.sessionCode))}/questions/${this.session.round_question.id}/lies/submit`, 'POST', { player_id: this.playerId, session_token: this.sessionToken, text: this.lieText, } ); await this.refreshSession(); } catch (error) { this.submitError = { kind: 'lie', message: `Lie submit failed: ${(error as Error).message}` }; } finally { this.loading = false; } } async submitGuess(): Promise { if (!this.session?.round_question?.id || !this.selectedGuess) { return; } this.loading = true; this.submitError = null; try { await this.request( `/lobby/sessions/${encodeURIComponent(this.normalizeCode(this.sessionCode))}/questions/${this.session.round_question.id}/guesses/submit`, 'POST', { player_id: this.playerId, session_token: this.sessionToken, selected_text: this.selectedGuess, } ); await this.refreshSession(); } catch (error) { this.submitError = { kind: 'guess', message: `Guess submit failed: ${(error as Error).message}` }; } finally { this.loading = false; } } }