Add frontend API client and TanStack Query hooks
Install @tanstack/react-query, create a fetch-based API client with typed functions for all endpoints, and add query/mutation hooks for games, pokemon, runs, and encounters. Includes Vite dev proxy for /api and QueryClientProvider setup. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
51
frontend/src/api/client.ts
Normal file
51
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
const API_BASE = import.meta.env.VITE_API_URL ?? ''
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
async function request<T>(
|
||||
path: string,
|
||||
options?: RequestInit,
|
||||
): Promise<T> {
|
||||
const res = await fetch(`${API_BASE}/api/v1${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}))
|
||||
throw new ApiError(res.status, body.detail ?? res.statusText)
|
||||
}
|
||||
|
||||
if (res.status === 204) return undefined as T
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
|
||||
post: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
patch: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
del: <T = void>(path: string) =>
|
||||
request<T>(path, { method: 'DELETE' }),
|
||||
}
|
||||
24
frontend/src/api/encounters.ts
Normal file
24
frontend/src/api/encounters.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { api } from './client'
|
||||
import type {
|
||||
Encounter,
|
||||
CreateEncounterInput,
|
||||
UpdateEncounterInput,
|
||||
} from '../types/game'
|
||||
|
||||
export function createEncounter(
|
||||
runId: number,
|
||||
data: CreateEncounterInput,
|
||||
): Promise<Encounter> {
|
||||
return api.post(`/runs/${runId}/encounters`, data)
|
||||
}
|
||||
|
||||
export function updateEncounter(
|
||||
id: number,
|
||||
data: UpdateEncounterInput,
|
||||
): Promise<Encounter> {
|
||||
return api.patch(`/encounters/${id}`, data)
|
||||
}
|
||||
|
||||
export function deleteEncounter(id: number): Promise<void> {
|
||||
return api.del(`/encounters/${id}`)
|
||||
}
|
||||
22
frontend/src/api/games.ts
Normal file
22
frontend/src/api/games.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from './client'
|
||||
import type { Game, Route, RouteEncounter } from '../types/game'
|
||||
|
||||
export interface GameDetail extends Game {
|
||||
routes: Route[]
|
||||
}
|
||||
|
||||
export function getGames(): Promise<Game[]> {
|
||||
return api.get('/games')
|
||||
}
|
||||
|
||||
export function getGame(id: number): Promise<GameDetail> {
|
||||
return api.get(`/games/${id}`)
|
||||
}
|
||||
|
||||
export function getGameRoutes(gameId: number): Promise<Route[]> {
|
||||
return api.get(`/games/${gameId}/routes`)
|
||||
}
|
||||
|
||||
export function getRoutePokemon(routeId: number): Promise<RouteEncounter[]> {
|
||||
return api.get(`/routes/${routeId}/pokemon`)
|
||||
}
|
||||
6
frontend/src/api/pokemon.ts
Normal file
6
frontend/src/api/pokemon.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { api } from './client'
|
||||
import type { Pokemon } from '../types/game'
|
||||
|
||||
export function getPokemon(id: number): Promise<Pokemon> {
|
||||
return api.get(`/pokemon/${id}`)
|
||||
}
|
||||
30
frontend/src/api/runs.ts
Normal file
30
frontend/src/api/runs.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { api } from './client'
|
||||
import type {
|
||||
NuzlockeRun,
|
||||
RunDetail,
|
||||
CreateRunInput,
|
||||
UpdateRunInput,
|
||||
} from '../types/game'
|
||||
|
||||
export function getRuns(): Promise<NuzlockeRun[]> {
|
||||
return api.get('/runs')
|
||||
}
|
||||
|
||||
export function getRun(id: number): Promise<RunDetail> {
|
||||
return api.get(`/runs/${id}`)
|
||||
}
|
||||
|
||||
export function createRun(data: CreateRunInput): Promise<NuzlockeRun> {
|
||||
return api.post('/runs', data)
|
||||
}
|
||||
|
||||
export function updateRun(
|
||||
id: number,
|
||||
data: UpdateRunInput,
|
||||
): Promise<NuzlockeRun> {
|
||||
return api.patch(`/runs/${id}`, data)
|
||||
}
|
||||
|
||||
export function deleteRun(id: number): Promise<void> {
|
||||
return api.del(`/runs/${id}`)
|
||||
}
|
||||
Reference in New Issue
Block a user