Add boss battles, level caps, and badge tracking
Introduces full boss battle system: data models (BossBattle, BossPokemon, BossResult), API endpoints for CRUD and per-run defeat tracking, and frontend UI including a sticky level cap bar with badge display on the run page, interleaved boss battle cards in the encounter list, and an admin panel section for managing boss battles and their pokemon teams. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,9 @@ import type {
|
||||
UpdateRouteEncounterInput,
|
||||
CreateEvolutionInput,
|
||||
UpdateEvolutionInput,
|
||||
CreateBossBattleInput,
|
||||
UpdateBossBattleInput,
|
||||
BossPokemonInput,
|
||||
} from '../types'
|
||||
|
||||
// --- Queries ---
|
||||
@@ -256,3 +259,54 @@ export function useRemoveRouteEncounter(routeId: number) {
|
||||
onError: (err) => toast.error(`Failed to remove encounter: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
// --- Boss Battle Mutations ---
|
||||
|
||||
export function useCreateBossBattle(gameId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateBossBattleInput) => adminApi.createBossBattle(gameId, data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
|
||||
toast.success('Boss battle created')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to create boss battle: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdateBossBattle(gameId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ bossId, data }: { bossId: number; data: UpdateBossBattleInput }) =>
|
||||
adminApi.updateBossBattle(gameId, bossId, data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
|
||||
toast.success('Boss battle updated')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to update boss battle: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useDeleteBossBattle(gameId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (bossId: number) => adminApi.deleteBossBattle(gameId, bossId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
|
||||
toast.success('Boss battle deleted')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to delete boss battle: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useSetBossTeam(gameId: number, bossId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (team: BossPokemonInput[]) => adminApi.setBossTeam(gameId, bossId, team),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
|
||||
toast.success('Boss team updated')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to update boss team: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
43
frontend/src/hooks/useBosses.ts
Normal file
43
frontend/src/hooks/useBosses.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
import { getGameBosses, getBossResults, createBossResult, deleteBossResult } from '../api/bosses'
|
||||
import type { CreateBossResultInput } from '../types/game'
|
||||
|
||||
export function useGameBosses(gameId: number | null) {
|
||||
return useQuery({
|
||||
queryKey: ['games', gameId, 'bosses'],
|
||||
queryFn: () => getGameBosses(gameId!),
|
||||
enabled: gameId != null,
|
||||
})
|
||||
}
|
||||
|
||||
export function useBossResults(runId: number) {
|
||||
return useQuery({
|
||||
queryKey: ['runs', runId, 'boss-results'],
|
||||
queryFn: () => getBossResults(runId),
|
||||
})
|
||||
}
|
||||
|
||||
export function useCreateBossResult(runId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateBossResultInput) => createBossResult(runId, data),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['runs', runId, 'boss-results'] })
|
||||
toast.success('Boss result recorded')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to record result: ${err.message}`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useDeleteBossResult(runId: number) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (resultId: number) => deleteBossResult(runId, resultId),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['runs', runId, 'boss-results'] })
|
||||
toast.success('Boss result removed')
|
||||
},
|
||||
onError: (err) => toast.error(`Failed to remove result: ${err.message}`),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user