2026-03-20 15:09:02 +01:00
|
|
|
import { type FormEvent, useMemo, useState } from 'react'
|
2026-02-08 11:16:13 +01:00
|
|
|
import type { BossBattle, CreateBossResultInput } from '../types/game'
|
2026-02-16 21:17:32 +01:00
|
|
|
import { ConditionBadge } from './ConditionBadge'
|
2026-02-08 11:16:13 +01:00
|
|
|
|
|
|
|
|
interface BossDefeatModalProps {
|
|
|
|
|
boss: BossBattle
|
|
|
|
|
onSubmit: (data: CreateBossResultInput) => void
|
|
|
|
|
onClose: () => void
|
|
|
|
|
isPending?: boolean
|
2026-02-08 21:33:28 +01:00
|
|
|
starterName?: string | null
|
2026-02-08 11:16:13 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 20:39:41 +01:00
|
|
|
function matchVariant(labels: string[], starterName?: string | null): string | null {
|
2026-02-08 21:33:28 +01:00
|
|
|
if (!starterName || labels.length === 0) return null
|
|
|
|
|
const lower = starterName.toLowerCase()
|
|
|
|
|
const matches = labels.filter((l) => l.toLowerCase().includes(lower))
|
2026-02-16 20:39:41 +01:00
|
|
|
return matches.length === 1 ? (matches[0] ?? null) : null
|
2026-02-08 21:33:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 16:41:24 +01:00
|
|
|
export function BossDefeatModal({
|
|
|
|
|
boss,
|
|
|
|
|
onSubmit,
|
|
|
|
|
onClose,
|
|
|
|
|
isPending,
|
|
|
|
|
starterName,
|
|
|
|
|
}: BossDefeatModalProps) {
|
2026-02-08 11:16:13 +01:00
|
|
|
|
2026-02-08 21:20:30 +01:00
|
|
|
const variantLabels = useMemo(() => {
|
|
|
|
|
const labels = new Set<string>()
|
|
|
|
|
for (const bp of boss.pokemon) {
|
|
|
|
|
if (bp.conditionLabel) labels.add(bp.conditionLabel)
|
|
|
|
|
}
|
|
|
|
|
return [...labels].sort()
|
|
|
|
|
}, [boss.pokemon])
|
|
|
|
|
|
|
|
|
|
const hasVariants = variantLabels.length > 0
|
2026-02-14 16:41:24 +01:00
|
|
|
const autoMatch = useMemo(
|
|
|
|
|
() => matchVariant(variantLabels, starterName),
|
|
|
|
|
[variantLabels, starterName]
|
|
|
|
|
)
|
2026-02-08 21:33:28 +01:00
|
|
|
const showPills = hasVariants && autoMatch === null
|
2026-02-08 21:20:30 +01:00
|
|
|
const [selectedVariant, setSelectedVariant] = useState<string | null>(
|
2026-02-16 20:39:41 +01:00
|
|
|
autoMatch ?? (hasVariants ? (variantLabels[0] ?? null) : null)
|
2026-02-08 21:20:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const displayedPokemon = useMemo(() => {
|
|
|
|
|
if (!hasVariants) return boss.pokemon
|
|
|
|
|
return boss.pokemon.filter(
|
2026-02-16 20:39:41 +01:00
|
|
|
(bp) => bp.conditionLabel === selectedVariant || bp.conditionLabel === null
|
2026-02-08 21:20:30 +01:00
|
|
|
)
|
|
|
|
|
}, [boss.pokemon, hasVariants, selectedVariant])
|
|
|
|
|
|
2026-02-08 11:16:13 +01:00
|
|
|
const handleSubmit = (e: FormEvent) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
onSubmit({
|
|
|
|
|
bossBattleId: boss.id,
|
2026-03-20 15:09:02 +01:00
|
|
|
result: 'won',
|
|
|
|
|
attempts: 1,
|
2026-02-08 11:16:13 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
|
|
|
<div className="fixed inset-0 bg-black/50" onClick={onClose} />
|
2026-02-17 20:48:42 +01:00
|
|
|
<div className="relative bg-surface-1 rounded-lg shadow-xl max-w-md w-full mx-4">
|
|
|
|
|
<div className="px-6 py-4 border-b border-border-default">
|
2026-02-08 11:16:13 +01:00
|
|
|
<h2 className="text-lg font-semibold">Battle: {boss.name}</h2>
|
2026-02-17 20:48:42 +01:00
|
|
|
<p className="text-sm text-text-tertiary">{boss.location}</p>
|
2026-02-08 11:16:13 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Boss team preview */}
|
|
|
|
|
{boss.pokemon.length > 0 && (
|
2026-02-17 20:48:42 +01:00
|
|
|
<div className="px-6 py-3 border-b border-border-default">
|
2026-02-08 21:33:28 +01:00
|
|
|
{showPills && (
|
2026-02-08 21:20:30 +01:00
|
|
|
<div className="flex gap-1 mb-2 flex-wrap">
|
|
|
|
|
{variantLabels.map((label) => (
|
|
|
|
|
<button
|
|
|
|
|
key={label}
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setSelectedVariant(label)}
|
|
|
|
|
className={`px-2 py-0.5 text-xs font-medium rounded-full transition-colors ${
|
|
|
|
|
selectedVariant === label
|
2026-02-17 21:08:53 +01:00
|
|
|
? 'bg-accent-600 text-white'
|
2026-02-17 20:48:42 +01:00
|
|
|
: 'bg-surface-2 text-text-secondary hover:bg-surface-3'
|
2026-02-08 21:20:30 +01:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-02-08 11:16:13 +01:00
|
|
|
<div className="flex flex-wrap gap-3">
|
2026-02-08 21:20:30 +01:00
|
|
|
{[...displayedPokemon]
|
2026-02-08 11:16:13 +01:00
|
|
|
.sort((a, b) => a.order - b.order)
|
|
|
|
|
.map((bp) => (
|
|
|
|
|
<div key={bp.id} className="flex flex-col items-center">
|
|
|
|
|
{bp.pokemon.spriteUrl ? (
|
2026-02-16 20:39:41 +01:00
|
|
|
<img src={bp.pokemon.spriteUrl} alt={bp.pokemon.name} className="w-10 h-10" />
|
2026-02-08 11:16:13 +01:00
|
|
|
) : (
|
2026-02-17 20:48:42 +01:00
|
|
|
<div className="w-10 h-10 bg-surface-3 rounded-full" />
|
2026-02-08 11:16:13 +01:00
|
|
|
)}
|
2026-02-17 20:48:42 +01:00
|
|
|
<span className="text-xs text-text-tertiary capitalize">{bp.pokemon.name}</span>
|
|
|
|
|
<span className="text-xs font-medium text-text-secondary">Lv.{bp.level}</span>
|
2026-02-16 21:17:32 +01:00
|
|
|
<ConditionBadge condition={bp.conditionLabel} size="xs" />
|
2026-02-08 11:16:13 +01:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSubmit}>
|
2026-02-17 20:48:42 +01:00
|
|
|
<div className="px-6 py-4 border-t border-border-default flex justify-end gap-3">
|
2026-02-08 11:16:13 +01:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={onClose}
|
2026-02-17 20:48:42 +01:00
|
|
|
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default hover:bg-surface-2"
|
2026-02-08 11:16:13 +01:00
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={isPending}
|
2026-02-17 20:48:42 +01:00
|
|
|
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500 disabled:opacity-50"
|
2026-02-08 11:16:13 +01:00
|
|
|
>
|
|
|
|
|
{isPending ? 'Saving...' : 'Save Result'}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|