From 069093ebae3631a2a14a4a04e8f89ecf1c84cc85 Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Sun, 8 Feb 2026 12:55:11 +0100 Subject: [PATCH] Add non-evolution form change support (Rotom, Oricorio, etc.) Add a "Change Form" button in StatusChangeModal for Pokemon with alternate forms sharing the same national_dex number. Mirrors the existing evolution UI pattern, reusing currentPokemonId to track the active form. Co-Authored-By: Claude Opus 4.6 --- ...k--implement-non-evolution-form-changes.md | 11 ++++ backend/src/app/api/pokemon.py | 16 +++++ frontend/src/api/encounters.ts | 5 ++ frontend/src/components/StatusChangeModal.tsx | 66 ++++++++++++++++++- frontend/src/hooks/useEncounters.ts | 9 +++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 .beans/nuzlocke-tracker-9z2k--implement-non-evolution-form-changes.md diff --git a/.beans/nuzlocke-tracker-9z2k--implement-non-evolution-form-changes.md b/.beans/nuzlocke-tracker-9z2k--implement-non-evolution-form-changes.md new file mode 100644 index 0000000..2f08026 --- /dev/null +++ b/.beans/nuzlocke-tracker-9z2k--implement-non-evolution-form-changes.md @@ -0,0 +1,11 @@ +--- +# nuzlocke-tracker-9z2k +title: Implement non-evolution form changes +status: completed +type: feature +priority: normal +created_at: 2026-02-08T11:51:18Z +updated_at: 2026-02-08T11:52:36Z +--- + +Add ability to change Pokemon forms (e.g. Rotom appliances, Oricorio nectar forms) without evolving. Mirror existing evolution UI pattern with a new 'Change Form' button in StatusChangeModal. \ No newline at end of file diff --git a/backend/src/app/api/pokemon.py b/backend/src/app/api/pokemon.py index 493bb74..b9df80f 100644 --- a/backend/src/app/api/pokemon.py +++ b/backend/src/app/api/pokemon.py @@ -158,6 +158,22 @@ async def get_pokemon( return pokemon +@router.get("/pokemon/{pokemon_id}/forms", response_model=list[PokemonResponse]) +async def get_pokemon_forms( + pokemon_id: int, session: AsyncSession = Depends(get_session) +): + pokemon = await session.get(Pokemon, pokemon_id) + if pokemon is None: + raise HTTPException(status_code=404, detail="Pokemon not found") + + result = await session.execute( + select(Pokemon) + .where(Pokemon.national_dex == pokemon.national_dex, Pokemon.id != pokemon_id) + .order_by(Pokemon.pokeapi_id) + ) + return result.scalars().all() + + @router.get("/pokemon/{pokemon_id}/evolutions", response_model=list[EvolutionResponse]) async def get_pokemon_evolutions( pokemon_id: int, diff --git a/frontend/src/api/encounters.ts b/frontend/src/api/encounters.ts index 9a6efc9..c521c15 100644 --- a/frontend/src/api/encounters.ts +++ b/frontend/src/api/encounters.ts @@ -4,6 +4,7 @@ import type { CreateEncounterInput, UpdateEncounterInput, Evolution, + Pokemon, } from '../types/game' export function createEncounter( @@ -28,3 +29,7 @@ export function fetchEvolutions(pokemonId: number, region?: string): Promise { + return api.get(`/pokemon/${pokemonId}/forms`) +} diff --git a/frontend/src/components/StatusChangeModal.tsx b/frontend/src/components/StatusChangeModal.tsx index 93f5583..9e1d21b 100644 --- a/frontend/src/components/StatusChangeModal.tsx +++ b/frontend/src/components/StatusChangeModal.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import type { EncounterDetail, UpdateEncounterInput } from '../types' -import { useEvolutions } from '../hooks/useEncounters' +import { useEvolutions, useForms } from '../hooks/useEncounters' import { TypeBadge } from './TypeBadge' interface StatusChangeModalProps { @@ -49,6 +49,7 @@ export function StatusChangeModal({ const displayPokemon = currentPokemon ?? pokemon const [showConfirm, setShowConfirm] = useState(false) const [showEvolve, setShowEvolve] = useState(false) + const [showFormChange, setShowFormChange] = useState(false) const [deathLevel, setDeathLevel] = useState('') const [cause, setCause] = useState('') @@ -57,6 +58,7 @@ export function StatusChangeModal({ showEvolve ? activePokemonId : null, region, ) + const { data: forms } = useForms(isDead ? null : activePokemonId) const handleConfirmDeath = () => { onUpdate({ @@ -170,7 +172,7 @@ export function StatusChangeModal({ )} {/* Alive pokemon: actions */} - {!isDead && !showConfirm && !showEvolve && ( + {!isDead && !showConfirm && !showEvolve && !showFormChange && (
+ {forms && forms.length > 0 && ( + + )}
)} + {/* Form change selection */} + {!isDead && showFormChange && ( +
+
+

+ Change form to: +

+ +
+ {forms && forms.length > 0 && ( +
+ {forms.map((form) => ( + + ))} +
+ )} +
+ )} + {/* Confirmation form */} {!isDead && showConfirm && (
@@ -312,7 +372,7 @@ export function StatusChangeModal({
{/* Footer for dead/no-confirm/no-evolve views */} - {(isDead || (!isDead && !showConfirm && !showEvolve)) && ( + {(isDead || (!isDead && !showConfirm && !showEvolve && !showFormChange)) && (