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

@@ -42,31 +42,36 @@ interface EncounterModalProps {
isPending: boolean
}
const statusOptions: { value: EncounterStatus; label: string; color: string }[] =
[
{
value: 'caught',
label: 'Caught',
color:
'bg-green-100 text-green-800 border-green-300 dark:bg-green-900/40 dark:text-green-300 dark:border-green-700',
},
{
value: 'fainted',
label: 'Fainted',
color:
'bg-red-100 text-red-800 border-red-300 dark:bg-red-900/40 dark:text-red-300 dark:border-red-700',
},
{
value: 'missed',
label: 'Missed / Ran',
color:
'bg-gray-100 text-gray-800 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600',
},
]
const statusOptions: {
value: EncounterStatus
label: string
color: string
}[] = [
{
value: 'caught',
label: 'Caught',
color:
'bg-green-100 text-green-800 border-green-300 dark:bg-green-900/40 dark:text-green-300 dark:border-green-700',
},
{
value: 'fainted',
label: 'Fainted',
color:
'bg-red-100 text-red-800 border-red-300 dark:bg-red-900/40 dark:text-red-300 dark:border-red-700',
},
{
value: 'missed',
label: 'Missed / Ran',
color:
'bg-gray-100 text-gray-800 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600',
},
]
const SPECIAL_METHODS = ['starter', 'gift', 'fossil', 'trade']
function groupByMethod(pokemon: RouteEncounterDetail[]): { method: string; pokemon: RouteEncounterDetail[] }[] {
function groupByMethod(
pokemon: RouteEncounterDetail[]
): { method: string; pokemon: RouteEncounterDetail[] }[] {
const groups = new Map<string, RouteEncounterDetail[]>()
for (const rp of pokemon) {
const list = groups.get(rp.encounterMethod) ?? []
@@ -84,7 +89,7 @@ function groupByMethod(pokemon: RouteEncounterDetail[]): { method: string; pokem
function pickRandomPokemon(
pokemon: RouteEncounterDetail[],
dupedIds?: Set<number>,
dupedIds?: Set<number>
): RouteEncounterDetail | null {
const eligible = dupedIds
? pokemon.filter((rp) => !dupedIds.has(rp.pokemonId))
@@ -109,17 +114,17 @@ export function EncounterModal({
}: EncounterModalProps) {
const { data: routePokemon, isLoading: loadingPokemon } = useRoutePokemon(
route.id,
gameId,
gameId
)
const [selectedPokemon, setSelectedPokemon] =
useState<RouteEncounterDetail | null>(null)
const [status, setStatus] = useState<EncounterStatus>(
existing?.status ?? 'caught',
existing?.status ?? 'caught'
)
const [nickname, setNickname] = useState(existing?.nickname ?? '')
const [catchLevel, setCatchLevel] = useState<string>(
existing?.catchLevel?.toString() ?? '',
existing?.catchLevel?.toString() ?? ''
)
const [faintLevel, setFaintLevel] = useState<string>('')
const [deathCause, setDeathCause] = useState('')
@@ -128,27 +133,31 @@ export function EncounterModal({
const isEditing = !!existing
const showSuggestions = !!namingScheme && status === 'caught' && !isEditing
const lineagePokemonId = isGenlocke && selectedPokemon ? selectedPokemon.pokemonId : null
const { data: suggestions, refetch: regenerate, isFetching: loadingSuggestions } =
useNameSuggestions(showSuggestions ? runId : null, lineagePokemonId)
const lineagePokemonId =
isGenlocke && selectedPokemon ? selectedPokemon.pokemonId : null
const {
data: suggestions,
refetch: regenerate,
isFetching: loadingSuggestions,
} = useNameSuggestions(showSuggestions ? runId : null, lineagePokemonId)
// Pre-select pokemon when editing
useEffect(() => {
if (existing && routePokemon) {
const match = routePokemon.find(
(rp) => rp.pokemonId === existing.pokemonId,
(rp) => rp.pokemonId === existing.pokemonId
)
if (match) setSelectedPokemon(match)
}
}, [existing, routePokemon])
const filteredPokemon = routePokemon?.filter((rp) =>
rp.pokemon.name.toLowerCase().includes(search.toLowerCase()),
rp.pokemon.name.toLowerCase().includes(search.toLowerCase())
)
const groupedPokemon = useMemo(
() => (filteredPokemon ? groupByMethod(filteredPokemon) : []),
[filteredPokemon],
[filteredPokemon]
)
const hasMultipleGroups = groupedPokemon.length > 1
@@ -224,13 +233,15 @@ export function EncounterModal({
loadingPokemon ||
!routePokemon ||
(dupedPokemonIds
? routePokemon.every((rp) => dupedPokemonIds.has(rp.pokemonId))
? routePokemon.every((rp) =>
dupedPokemonIds.has(rp.pokemonId)
)
: false)
}
onClick={() => {
if (routePokemon) {
setSelectedPokemon(
pickRandomPokemon(routePokemon, dupedPokemonIds),
pickRandomPokemon(routePokemon, dupedPokemonIds)
)
}
}}
@@ -268,12 +279,15 @@ export function EncounterModal({
)}
<div className="grid grid-cols-3 gap-2">
{pokemon.map((rp) => {
const isDuped = dupedPokemonIds?.has(rp.pokemonId) ?? false
const isDuped =
dupedPokemonIds?.has(rp.pokemonId) ?? false
return (
<button
key={rp.id}
type="button"
onClick={() => !isDuped && setSelectedPokemon(rp)}
onClick={() =>
!isDuped && setSelectedPokemon(rp)
}
disabled={isDuped}
className={`flex flex-col items-center p-2 rounded-lg border text-center transition-colors ${
isDuped
@@ -299,16 +313,24 @@ export function EncounterModal({
</span>
{isDuped && (
<span className="text-[10px] text-gray-400 italic">
{retiredPokemonIds?.has(rp.pokemonId) ? 'retired (HoF)' : 'already caught'}
{retiredPokemonIds?.has(rp.pokemonId)
? 'retired (HoF)'
: 'already caught'}
</span>
)}
{!isDuped && SPECIAL_METHODS.includes(rp.encounterMethod) && (
<EncounterMethodBadge method={rp.encounterMethod} />
)}
{!isDuped &&
SPECIAL_METHODS.includes(
rp.encounterMethod
) && (
<EncounterMethodBadge
method={rp.encounterMethod}
/>
)}
{!isDuped && (
<span className="text-[10px] text-gray-400">
Lv. {rp.minLevel}
{rp.maxLevel !== rp.minLevel && `${rp.maxLevel}`}
{rp.maxLevel !== rp.minLevel &&
`${rp.maxLevel}`}
</span>
)}
</button>
@@ -518,11 +540,7 @@ export function EncounterModal({
onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isPending
? 'Saving...'
: isEditing
? 'Update'
: 'Log Encounter'}
{isPending ? 'Saving...' : isEditing ? 'Update' : 'Log Encounter'}
</button>
</div>
</div>