174 lines
5.8 KiB
TypeScript
174 lines
5.8 KiB
TypeScript
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: `
|
|
<h2>Player SPA gameplay flow</h2>
|
|
|
|
<div class="panel">
|
|
<label>Session code <input [(ngModel)]="sessionCode" /></label>
|
|
<label>Nickname <input [(ngModel)]="nickname" /></label>
|
|
<button (click)="refreshSession()" [disabled]="loading">Refresh</button>
|
|
<button (click)="joinSession()" [disabled]="loading">Join</button>
|
|
</div>
|
|
|
|
<div class="panel" *ngIf="session">
|
|
<p><strong>Status:</strong> {{ session.session.status }}</p>
|
|
<p *ngIf="session.round_question"><strong>Prompt:</strong> {{ session.round_question.prompt }}</p>
|
|
|
|
<label>Løgn <input [(ngModel)]="lieText" [disabled]="loading || session.session.status !== 'lie'" /></label>
|
|
<button (click)="submitLie()" [disabled]="loading || session.session.status !== 'lie'">Submit lie</button>
|
|
<button *ngIf="submitError?.kind === 'lie'" (click)="submitLie()" [disabled]="loading">Retry lie submit</button>
|
|
|
|
<div class="answers" *ngIf="session.round_question?.answers?.length">
|
|
<button
|
|
type="button"
|
|
*ngFor="let answer of session.round_question?.answers"
|
|
(click)="selectedGuess = answer.text"
|
|
[class.active]="selectedGuess === answer.text"
|
|
[disabled]="loading || session.session.status !== 'guess'"
|
|
>
|
|
{{ answer.text }}
|
|
</button>
|
|
</div>
|
|
|
|
<button (click)="submitGuess()" [disabled]="loading || session.session.status !== 'guess' || !selectedGuess">Submit guess</button>
|
|
<button *ngIf="submitError?.kind === 'guess'" (click)="submitGuess()" [disabled]="loading">Retry guess submit</button>
|
|
</div>
|
|
|
|
<p *ngIf="error" class="error">{{ error }}</p>
|
|
<p *ngIf="submitError" class="error">{{ submitError.message }}</p>
|
|
`,
|
|
})
|
|
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<T>(path: string, method: 'GET' | 'POST', payload?: unknown): Promise<T> {
|
|
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<void> {
|
|
this.loading = true;
|
|
this.error = '';
|
|
try {
|
|
const code = this.normalizeCode(this.sessionCode);
|
|
this.session = await this.request<SessionDetail>(`/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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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;
|
|
}
|
|
}
|
|
}
|