Improve API error handling for validation errors
- Parse FastAPI 422 validation error arrays into readable messages - Update ErrorResponse type to handle both string and array detail - Use inline type for validation error objects (no any) - Display field-level validation errors instead of [object Object] Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
52c8edcf93
commit
fdb5225356
@ -48,7 +48,7 @@ async function parseErrorResponse(response: Response): Promise<ApiError> {
|
||||
if (Array.isArray(data.detail)) {
|
||||
// Extract error messages from validation error array
|
||||
detail = data.detail
|
||||
.map((err: any) => {
|
||||
.map((err: { loc?: string[]; msg?: string; type?: string }) => {
|
||||
const field = err.loc ? err.loc.join('.') : 'unknown'
|
||||
return `${field}: ${err.msg || 'Invalid value'}`
|
||||
})
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
import { ref, onMounted, onUnmounted, shallowRef } from 'vue'
|
||||
|
||||
import { createGame, scenes } from '@/game'
|
||||
import { gameBridge } from '@/game/bridge'
|
||||
import type Phaser from 'phaser'
|
||||
|
||||
/**
|
||||
@ -68,10 +69,9 @@ function setupResizeObserver(): void {
|
||||
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
// Phaser handles resize internally with Scale.RESIZE mode,
|
||||
// but we can emit events if needed for external coordination
|
||||
// Emit resize events via gameBridge so MatchScene can respond
|
||||
if (game.value && entry.contentRect) {
|
||||
game.value.events.emit('resize', {
|
||||
gameBridge.emit('resize', {
|
||||
width: entry.contentRect.width,
|
||||
height: entry.contentRect.height,
|
||||
})
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
* ```
|
||||
*/
|
||||
|
||||
import mitt, { type Emitter, type EventType, type Handler } from 'mitt'
|
||||
import mitt, { type Emitter, type Handler } from 'mitt'
|
||||
|
||||
import type {
|
||||
GameBridgeEvents,
|
||||
@ -80,6 +80,7 @@ type BridgeEventMap = {
|
||||
'card:clicked': CardClickEvent
|
||||
'zone:clicked': ZoneClickEvent
|
||||
'animation:complete': AnimationCompleteEvent
|
||||
'attack:request': undefined
|
||||
'ready': undefined
|
||||
'error': Error
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import type { VisibleGameState } from '@/types/game'
|
||||
import { StateRenderer } from '../sync/StateRenderer'
|
||||
import { Board, createBoard } from '../objects/Board'
|
||||
import { calculateLayout } from '../layout'
|
||||
import { HandManager } from '../interactions/HandManager'
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
@ -90,6 +91,12 @@ export class MatchScene extends Phaser.Scene {
|
||||
resize?: (data: ResizeEvent) => void
|
||||
} = {}
|
||||
|
||||
/**
|
||||
* Hand interaction manager.
|
||||
* Null until StateRenderer creates the hand zone.
|
||||
*/
|
||||
private handManager: HandManager | null = null
|
||||
|
||||
// ===========================================================================
|
||||
// Lifecycle Methods
|
||||
// ===========================================================================
|
||||
@ -296,9 +303,15 @@ export class MatchScene extends Phaser.Scene {
|
||||
this.drawBoardBackground(width, height)
|
||||
|
||||
// Update board layout
|
||||
const layout = calculateLayout(width, height)
|
||||
|
||||
if (this.board) {
|
||||
const layout = calculateLayout(width, height)
|
||||
this.board.updateLayout(layout)
|
||||
this.board.setLayout(layout)
|
||||
}
|
||||
|
||||
// Update HandManager layout
|
||||
if (this.handManager) {
|
||||
this.handManager.setLayout(layout)
|
||||
}
|
||||
|
||||
// Re-render state if we have one
|
||||
@ -328,6 +341,32 @@ export class MatchScene extends Phaser.Scene {
|
||||
// Delegate to StateRenderer
|
||||
if (this.stateRenderer) {
|
||||
this.stateRenderer.render(state)
|
||||
|
||||
// Initialize HandManager if not already created
|
||||
if (!this.handManager) {
|
||||
const myZones = this.stateRenderer.getPlayerZones(true)
|
||||
if (myZones) {
|
||||
this.handManager = new HandManager(this, myZones.hand)
|
||||
|
||||
// Set initial layout
|
||||
const { width, height } = this.cameras.main
|
||||
const layout = calculateLayout(width, height)
|
||||
this.handManager.setLayout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
// Update HandManager with current state
|
||||
if (this.handManager) {
|
||||
// Update card registry for validation
|
||||
this.handManager.setCardRegistry(state.card_registry)
|
||||
|
||||
// Enable/disable interactions based on turn state
|
||||
if (state.is_my_turn && !state.winner_id) {
|
||||
this.handManager.enableHandInteractions()
|
||||
} else {
|
||||
this.handManager.disableHandInteractions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log state update for debugging
|
||||
@ -400,6 +439,12 @@ export class MatchScene extends Phaser.Scene {
|
||||
* Properly destroys all Phaser objects to prevent memory leaks.
|
||||
*/
|
||||
clearBoard(): void {
|
||||
// Clear HandManager
|
||||
if (this.handManager) {
|
||||
this.handManager.destroy()
|
||||
this.handManager = null
|
||||
}
|
||||
|
||||
// Clear StateRenderer
|
||||
if (this.stateRenderer) {
|
||||
this.stateRenderer.clear()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user