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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Evo
|
||||
const params = region ? `?region=${encodeURIComponent(region)}` : ''
|
||||
return api.get(`/pokemon/${pokemonId}/evolutions${params}`)
|
||||
}
|
||||
|
||||
export function fetchForms(pokemonId: number): Promise<Pokemon[]> {
|
||||
return api.get(`/pokemon/${pokemonId}/forms`)
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -179,6 +181,15 @@ export function StatusChangeModal({
|
||||
>
|
||||
Evolve
|
||||
</button>
|
||||
{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)}
|
||||
@@ -242,6 +253,55 @@ export function StatusChangeModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form change selection */}
|
||||
{!isDead && showFormChange && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Change form to:
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowFormChange(false)}
|
||||
className="text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
{forms && forms.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{forms.map((form) => (
|
||||
<button
|
||||
key={form.id}
|
||||
type="button"
|
||||
disabled={isPending}
|
||||
onClick={() => handleEvolve(form.id)}
|
||||
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" />
|
||||
) : (
|
||||
<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()}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-left">
|
||||
<div className="font-medium text-gray-900 dark:text-gray-100 text-sm">
|
||||
{form.name}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{form.types.map((type) => (
|
||||
<TypeBadge key={type} type={type} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirmation form */}
|
||||
{!isDead && showConfirm && (
|
||||
<div className="space-y-3">
|
||||
@@ -312,7 +372,7 @@ export function StatusChangeModal({
|
||||
</div>
|
||||
|
||||
{/* Footer for dead/no-confirm/no-evolve views */}
|
||||
{(isDead || (!isDead && !showConfirm && !showEvolve)) && (
|
||||
{(isDead || (!isDead && !showConfirm && !showEvolve && !showFormChange)) && (
|
||||
<div className="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
updateEncounter,
|
||||
deleteEncounter,
|
||||
fetchEvolutions,
|
||||
fetchForms,
|
||||
} from '../api/encounters'
|
||||
import type { CreateEncounterInput, UpdateEncounterInput } from '../types/game'
|
||||
|
||||
@@ -50,3 +51,11 @@ export function useEvolutions(pokemonId: number | null, region?: string) {
|
||||
enabled: pokemonId !== null,
|
||||
})
|
||||
}
|
||||
|
||||
export function useForms(pokemonId: number | null) {
|
||||
return useQuery({
|
||||
queryKey: ['forms', pokemonId],
|
||||
queryFn: () => fetchForms(pokemonId!),
|
||||
enabled: pokemonId !== null,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user