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

@@ -1,15 +1,16 @@
import { useState, useMemo } from 'react'
import type { EncounterDetail, UpdateEncounterInput, CreateEncounterInput } from '../types'
import type {
EncounterDetail,
UpdateEncounterInput,
CreateEncounterInput,
} from '../types'
import { useEvolutions, useForms } from '../hooks/useEncounters'
import { TypeBadge } from './TypeBadge'
import { formatEvolutionMethod } from '../utils/formatEvolution'
interface StatusChangeModalProps {
encounter: EncounterDetail
onUpdate: (data: {
id: number
data: UpdateEncounterInput
}) => void
onUpdate: (data: { id: number; data: UpdateEncounterInput }) => void
onClose: () => void
isPending: boolean
region?: string
@@ -24,15 +25,24 @@ export function StatusChangeModal({
region,
onCreateEncounter,
}: StatusChangeModalProps) {
const { pokemon, currentPokemon, route, nickname, catchLevel, faintLevel, deathCause } =
encounter
const {
pokemon,
currentPokemon,
route,
nickname,
catchLevel,
faintLevel,
deathCause,
} = encounter
const isDead = faintLevel !== null
const displayPokemon = currentPokemon ?? pokemon
const [showConfirm, setShowConfirm] = useState(false)
const [showEvolve, setShowEvolve] = useState(false)
const [showFormChange, setShowFormChange] = useState(false)
const [showShedConfirm, setShowShedConfirm] = useState(false)
const [pendingEvolutionId, setPendingEvolutionId] = useState<number | null>(null)
const [pendingEvolutionId, setPendingEvolutionId] = useState<number | null>(
null
)
const [shedNickname, setShedNickname] = useState('')
const [deathLevel, setDeathLevel] = useState('')
const [cause, setCause] = useState('')
@@ -40,15 +50,15 @@ export function StatusChangeModal({
const activePokemonId = currentPokemon?.id ?? pokemon.id
const { data: evolutions, isLoading: evolutionsLoading } = useEvolutions(
showEvolve || showShedConfirm ? activePokemonId : null,
region,
region
)
const { data: forms } = useForms(isDead ? null : activePokemonId)
const { normalEvolutions, shedCompanion } = useMemo(() => {
if (!evolutions) return { normalEvolutions: [], shedCompanion: null }
return {
normalEvolutions: evolutions.filter(e => e.trigger !== 'shed'),
shedCompanion: evolutions.find(e => e.trigger === 'shed') ?? null,
normalEvolutions: evolutions.filter((e) => e.trigger !== 'shed'),
shedCompanion: evolutions.find((e) => e.trigger === 'shed') ?? null,
}
}, [evolutions])
@@ -187,33 +197,37 @@ export function StatusChangeModal({
)}
{/* Alive pokemon: actions */}
{!isDead && !showConfirm && !showEvolve && !showFormChange && !showShedConfirm && (
<div className="flex gap-3">
<button
type="button"
onClick={() => setShowEvolve(true)}
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
Evolve
</button>
{forms && forms.length > 0 && (
{!isDead &&
!showConfirm &&
!showEvolve &&
!showFormChange &&
!showShedConfirm && (
<div className="flex gap-3">
<button
type="button"
onClick={() => setShowFormChange(true)}
className="flex-1 px-4 py-2 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 transition-colors"
onClick={() => setShowEvolve(true)}
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
Change Form
Evolve
</button>
)}
<button
type="button"
onClick={() => setShowConfirm(true)}
className="flex-1 px-4 py-2 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors"
>
Mark as Dead
</button>
</div>
)}
{forms && forms.length > 0 && (
<button
type="button"
onClick={() => setShowFormChange(true)}
className="flex-1 px-4 py-2 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 transition-colors"
>
Change Form
</button>
)}
<button
type="button"
onClick={() => setShowConfirm(true)}
className="flex-1 px-4 py-2 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors"
>
Mark as Dead
</button>
</div>
)}
{/* Evolution selection */}
{!isDead && showEvolve && (
@@ -231,10 +245,14 @@ export function StatusChangeModal({
</button>
</div>
{evolutionsLoading && (
<p className="text-sm text-gray-500 dark:text-gray-400">Loading evolutions...</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
Loading evolutions...
</p>
)}
{!evolutionsLoading && normalEvolutions.length === 0 && (
<p className="text-sm text-gray-500 dark:text-gray-400">No evolutions available</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
No evolutions available
</p>
)}
{!evolutionsLoading && normalEvolutions.length > 0 && (
<div className="space-y-2">
@@ -247,7 +265,11 @@ export function StatusChangeModal({
className="w-full flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 hover:border-blue-300 dark:hover:border-blue-600 transition-colors disabled:opacity-50"
>
{evo.toPokemon.spriteUrl ? (
<img src={evo.toPokemon.spriteUrl} alt={evo.toPokemon.name} className="w-10 h-10" />
<img
src={evo.toPokemon.spriteUrl}
alt={evo.toPokemon.name}
className="w-10 h-10"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-sm font-bold text-gray-600 dark:text-gray-300">
{evo.toPokemon.name[0].toUpperCase()}
@@ -302,8 +324,12 @@ export function StatusChangeModal({
</div>
)}
<p className="text-sm text-amber-800 dark:text-amber-300">
{displayPokemon.name} shed its shell! Would you also like to add{' '}
<span className="font-semibold">{shedCompanion.toPokemon.name}</span>?
{displayPokemon.name} shed its shell! Would you also like to
add{' '}
<span className="font-semibold">
{shedCompanion.toPokemon.name}
</span>
?
</p>
</div>
</div>
@@ -340,7 +366,9 @@ export function StatusChangeModal({
onClick={() => applyEvolution(true)}
className="flex-1 px-4 py-2 bg-amber-600 text-white rounded-lg font-medium hover:bg-amber-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isPending ? 'Saving...' : `Add ${shedCompanion.toPokemon.name}`}
{isPending
? 'Saving...'
: `Add ${shedCompanion.toPokemon.name}`}
</button>
</div>
</div>
@@ -372,7 +400,11 @@ export function StatusChangeModal({
className="w-full flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-purple-50 dark:hover:bg-purple-900/20 hover:border-purple-300 dark:hover:border-purple-600 transition-colors disabled:opacity-50"
>
{form.spriteUrl ? (
<img src={form.spriteUrl} alt={form.name} className="w-10 h-10" />
<img
src={form.spriteUrl}
alt={form.name}
className="w-10 h-10"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-sm font-bold text-gray-600 dark:text-gray-300">
{form.name[0].toUpperCase()}
@@ -465,7 +497,12 @@ export function StatusChangeModal({
</div>
{/* Footer for dead/no-confirm/no-evolve views */}
{(isDead || (!isDead && !showConfirm && !showEvolve && !showFormChange && !showShedConfirm)) && (
{(isDead ||
(!isDead &&
!showConfirm &&
!showEvolve &&
!showFormChange &&
!showShedConfirm)) && (
<div className="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end">
<button
type="button"