import { useState, useEffect } from 'react' import { api } from '../api/client' import type { Route, Pokemon } from '../types' interface EggEncounterModalProps { routes: Route[] onSubmit: (data: { routeId: number pokemonId: number nickname?: string status: 'caught' catchLevel?: number origin: 'egg' }) => void onClose: () => void isPending: boolean } export function EggEncounterModal({ routes, onSubmit, onClose, isPending, }: EggEncounterModalProps) { const [selectedRouteId, setSelectedRouteId] = useState(null) const [selectedPokemon, setSelectedPokemon] = useState(null) const [nickname, setNickname] = useState('') const [catchLevel, setCatchLevel] = useState('') const [search, setSearch] = useState('') const [searchResults, setSearchResults] = useState([]) const [isSearching, setIsSearching] = useState(false) // Only show leaf routes (no children) const parentIds = new Set(routes.filter(r => r.parentRouteId !== null).map(r => r.parentRouteId)) const leafRoutes = routes.filter(r => !parentIds.has(r.id)) // Debounced pokemon search useEffect(() => { if (search.length < 2) { setSearchResults([]) return } const timer = setTimeout(async () => { setIsSearching(true) try { const data = await api.get<{ items: Pokemon[] }>(`/pokemon?search=${encodeURIComponent(search)}&limit=20`) setSearchResults(data.items) } catch { setSearchResults([]) } finally { setIsSearching(false) } }, 300) return () => clearTimeout(timer) }, [search]) const handleSubmit = () => { if (selectedPokemon && selectedRouteId) { onSubmit({ routeId: selectedRouteId, pokemonId: selectedPokemon.id, nickname: nickname || undefined, status: 'caught', catchLevel: catchLevel ? Number(catchLevel) : undefined, origin: 'egg', }) } } return (

🥚 Log Egg Hatch

Egg hatches bypass the one-per-route rule

{/* Route selector */}
{/* Pokemon search */}
{selectedPokemon ? (
{selectedPokemon.spriteUrl ? ( {selectedPokemon.name} ) : (
{selectedPokemon.name[0].toUpperCase()}
)} {selectedPokemon.name}
) : ( <> setSearch(e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500" /> {isSearching && (
)} {searchResults.length > 0 && (
{searchResults.map((p) => ( ))}
)} {search.length >= 2 && !isSearching && searchResults.length === 0 && (

No pokemon found

)} )}
{/* Nickname */} {selectedPokemon && (
setNickname(e.target.value)} placeholder="Give it a name..." className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500" />
)} {/* Hatch Level */} {selectedPokemon && (
setCatchLevel(e.target.value)} placeholder="1" className="w-24 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500" />
)}
) }