Add pre-commit hooks for linting and formatting
All checks were successful
CI / backend-lint (push) Successful in 9s
CI / frontend-lint (push) Successful in 33s

Set up pre-commit framework with ruff (backend) and ESLint/Prettier/tsc
(frontend) hooks to catch issues locally before CI. Auto-format all
frontend files with Prettier to comply with the new check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:41:24 +01:00
parent b05a75f7f2
commit 2963f16aa4
67 changed files with 1905 additions and 792 deletions

View File

@@ -23,7 +23,12 @@ import type {
// --- Queries ---
export function usePokemonList(search?: string, limit = 50, offset = 0, type?: string) {
export function usePokemonList(
search?: string,
limit = 50,
offset = 0,
type?: string
) {
return useQuery({
queryKey: ['pokemon', { search, limit, offset, type }],
queryFn: () => adminApi.listPokemon(search, limit, offset, type),
@@ -87,8 +92,13 @@ export function useCreateRoute(gameId: number) {
export function useUpdateRoute(gameId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ routeId, data }: { routeId: number; data: UpdateRouteInput }) =>
adminApi.updateRoute(gameId, routeId, data),
mutationFn: ({
routeId,
data,
}: {
routeId: number
data: UpdateRouteInput
}) => adminApi.updateRoute(gameId, routeId, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['games', gameId] })
qc.invalidateQueries({ queryKey: ['games', gameId, 'routes'] })
@@ -114,7 +124,8 @@ export function useDeleteRoute(gameId: number) {
export function useReorderRoutes(gameId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: (routes: RouteReorderItem[]) => adminApi.reorderRoutes(gameId, routes),
mutationFn: (routes: RouteReorderItem[]) =>
adminApi.reorderRoutes(gameId, routes),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['games', gameId] })
qc.invalidateQueries({ queryKey: ['games', gameId, 'routes'] })
@@ -166,11 +177,20 @@ export function useDeletePokemon() {
export function useBulkImportPokemon() {
const qc = useQueryClient()
return useMutation({
mutationFn: (items: Array<{ pokeapiId: number; nationalDex: number; name: string; types: string[]; spriteUrl?: string | null }>) =>
adminApi.bulkImportPokemon(items),
mutationFn: (
items: Array<{
pokeapiId: number
nationalDex: number
name: string
types: string[]
spriteUrl?: string | null
}>
) => adminApi.bulkImportPokemon(items),
onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ['pokemon'] })
toast.success(`Import complete: ${result.created} created, ${result.updated} updated`)
toast.success(
`Import complete: ${result.created} created, ${result.updated} updated`
)
},
onError: (err) => toast.error(`Import failed: ${err.message}`),
})
@@ -182,7 +202,9 @@ export function useBulkImportEvolutions() {
mutationFn: (items: unknown[]) => adminApi.bulkImportEvolutions(items),
onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ['evolutions'] })
toast.success(`Import complete: ${result.created} created, ${result.updated} updated`)
toast.success(
`Import complete: ${result.created} created, ${result.updated} updated`
)
},
onError: (err) => toast.error(`Import failed: ${err.message}`),
})
@@ -195,7 +217,9 @@ export function useBulkImportRoutes(gameId: number) {
onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ['games', gameId] })
qc.invalidateQueries({ queryKey: ['games', gameId, 'routes'] })
toast.success(`Import complete: ${result.created} routes, ${result.updated} encounters`)
toast.success(
`Import complete: ${result.created} routes, ${result.updated} encounters`
)
},
onError: (err) => toast.error(`Import failed: ${err.message}`),
})
@@ -215,7 +239,12 @@ export function useBulkImportBosses(gameId: number) {
// --- Evolution Queries & Mutations ---
export function useEvolutionList(search?: string, limit = 50, offset = 0, trigger?: string) {
export function useEvolutionList(
search?: string,
limit = 50,
offset = 0,
trigger?: string
) {
return useQuery({
queryKey: ['evolutions', { search, limit, offset, trigger }],
queryFn: () => adminApi.listEvolutions(search, limit, offset, trigger),
@@ -277,8 +306,13 @@ export function useAddRouteEncounter(routeId: number) {
export function useUpdateRouteEncounter(routeId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ encounterId, data }: { encounterId: number; data: UpdateRouteEncounterInput }) =>
adminApi.updateRouteEncounter(routeId, encounterId, data),
mutationFn: ({
encounterId,
data,
}: {
encounterId: number
data: UpdateRouteEncounterInput
}) => adminApi.updateRouteEncounter(routeId, encounterId, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['routes', routeId, 'pokemon'] })
toast.success('Encounter updated')
@@ -305,32 +339,41 @@ export function useRemoveRouteEncounter(routeId: number) {
export function useCreateBossBattle(gameId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: (data: CreateBossBattleInput) => adminApi.createBossBattle(gameId, data),
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}`),
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),
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}`),
onError: (err) =>
toast.error(`Failed to update boss battle: ${err.message}`),
})
}
export function useReorderBosses(gameId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: (bosses: BossReorderItem[]) => adminApi.reorderBosses(gameId, bosses),
mutationFn: (bosses: BossReorderItem[]) =>
adminApi.reorderBosses(gameId, bosses),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
toast.success('Bosses reordered')
@@ -347,14 +390,16 @@ export function useDeleteBossBattle(gameId: number) {
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
toast.success('Boss battle deleted')
},
onError: (err) => toast.error(`Failed to delete boss battle: ${err.message}`),
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),
mutationFn: (team: BossPokemonInput[]) =>
adminApi.setBossTeam(gameId, bossId, team),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['games', gameId, 'bosses'] })
toast.success('Boss team updated')
@@ -393,7 +438,8 @@ export function useDeleteGenlocke() {
export function useAddGenlockeLeg(genlockeId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: (data: AddGenlockeLegInput) => adminApi.addGenlockeLeg(genlockeId, data),
mutationFn: (data: AddGenlockeLegInput) =>
adminApi.addGenlockeLeg(genlockeId, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['genlockes'] })
qc.invalidateQueries({ queryKey: ['genlockes', genlockeId] })
@@ -406,7 +452,8 @@ export function useAddGenlockeLeg(genlockeId: number) {
export function useDeleteGenlockeLeg(genlockeId: number) {
const qc = useQueryClient()
return useMutation({
mutationFn: (legId: number) => adminApi.deleteGenlockeLeg(genlockeId, legId),
mutationFn: (legId: number) =>
adminApi.deleteGenlockeLeg(genlockeId, legId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['genlockes'] })
qc.invalidateQueries({ queryKey: ['genlockes', genlockeId] })

View File

@@ -1,6 +1,11 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { getGameBosses, getBossResults, createBossResult, deleteBossResult } from '../api/bosses'
import {
getGameBosses,
getBossResults,
createBossResult,
deleteBossResult,
} from '../api/bosses'
import type { CreateBossResultInput } from '../types/game'
export function useGameBosses(gameId: number | null, all?: boolean) {

View File

@@ -22,13 +22,8 @@ export function useCreateEncounter(runId: number) {
export function useUpdateEncounter(runId: number) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({
id,
data,
}: {
id: number
data: UpdateEncounterInput
}) => updateEncounter(id, data),
mutationFn: ({ id, data }: { id: number; data: UpdateEncounterInput }) =>
updateEncounter(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['runs', runId] })
},

View File

@@ -1,5 +1,14 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { advanceLeg, createGenlocke, getGamesByRegion, getGenlockes, getGenlocke, getGenlockeGraveyard, getGenlockeLineages, getLegSurvivors } from '../api/genlockes'
import {
advanceLeg,
createGenlocke,
getGamesByRegion,
getGenlockes,
getGenlocke,
getGenlockeGraveyard,
getGenlockeLineages,
getLegSurvivors,
} from '../api/genlockes'
import type { CreateGenlockeInput } from '../types/game'
export function useGenlockes() {
@@ -48,7 +57,11 @@ export function useCreateGenlocke() {
})
}
export function useLegSurvivors(genlockeId: number, legOrder: number, enabled: boolean) {
export function useLegSurvivors(
genlockeId: number,
legOrder: number,
enabled: boolean
) {
return useQuery({
queryKey: ['genlockes', genlockeId, 'legs', legOrder, 'survivors'],
queryFn: () => getLegSurvivors(genlockeId, legOrder),
@@ -59,8 +72,20 @@ export function useLegSurvivors(genlockeId: number, legOrder: number, enabled: b
export function useAdvanceLeg() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ genlockeId, legOrder, transferEncounterIds }: { genlockeId: number; legOrder: number; transferEncounterIds?: number[] }) =>
advanceLeg(genlockeId, legOrder, transferEncounterIds ? { transferEncounterIds } : undefined),
mutationFn: ({
genlockeId,
legOrder,
transferEncounterIds,
}: {
genlockeId: number
legOrder: number
transferEncounterIds?: number[]
}) =>
advanceLeg(
genlockeId,
legOrder,
transferEncounterIds ? { transferEncounterIds } : undefined
),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['runs'] })
queryClient.invalidateQueries({ queryKey: ['genlockes'] })

View File

@@ -1,5 +1,10 @@
import { useQuery } from '@tanstack/react-query'
import { getPokemon, fetchPokemonFamilies, fetchPokemonEncounterLocations, fetchPokemonEvolutionChain } from '../api/pokemon'
import {
getPokemon,
fetchPokemonFamilies,
fetchPokemonEncounterLocations,
fetchPokemonEvolutionChain,
} from '../api/pokemon'
export function usePokemon(id: number | null) {
return useQuery({

View File

@@ -1,6 +1,14 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories, getNameSuggestions } from '../api/runs'
import {
getRuns,
getRun,
createRun,
updateRun,
deleteRun,
getNamingCategories,
getNameSuggestions,
} from '../api/runs'
import type { CreateRunInput, UpdateRunInput } from '../types/game'
export function useRuns() {
@@ -60,7 +68,10 @@ export function useNamingCategories() {
})
}
export function useNameSuggestions(runId: number | null, pokemonId?: number | null) {
export function useNameSuggestions(
runId: number | null,
pokemonId?: number | null
) {
return useQuery({
queryKey: ['name-suggestions', runId, pokemonId ?? null],
queryFn: () => getNameSuggestions(runId!, 10, pokemonId ?? undefined),