Files
nuzlocke-tracker/frontend/src/components/admin/GameFormModal.tsx
Julian Tabel 55e6650e0e Add admin panel with CRUD endpoints and management UI
Add admin API endpoints for games, routes, pokemon, and route encounters
with full CRUD operations including bulk import. Build admin frontend
with game/route/pokemon management pages, navigation, and data tables.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:36:19 +01:00

120 lines
3.7 KiB
TypeScript

import { type FormEvent, useState, useEffect } from 'react'
import { FormModal } from './FormModal'
import type { Game, CreateGameInput, UpdateGameInput } from '../../types'
interface GameFormModalProps {
game?: Game
onSubmit: (data: CreateGameInput | UpdateGameInput) => void
onClose: () => void
isSubmitting?: boolean
}
function slugify(name: string) {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
export function GameFormModal({ game, onSubmit, onClose, isSubmitting }: GameFormModalProps) {
const [name, setName] = useState(game?.name ?? '')
const [slug, setSlug] = useState(game?.slug ?? '')
const [generation, setGeneration] = useState(String(game?.generation ?? ''))
const [region, setRegion] = useState(game?.region ?? '')
const [boxArtUrl, setBoxArtUrl] = useState(game?.boxArtUrl ?? '')
const [releaseYear, setReleaseYear] = useState(game?.releaseYear ? String(game.releaseYear) : '')
const [autoSlug, setAutoSlug] = useState(!game)
useEffect(() => {
if (autoSlug) setSlug(slugify(name))
}, [name, autoSlug])
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
onSubmit({
name,
slug,
generation: Number(generation),
region,
boxArtUrl: boxArtUrl || null,
releaseYear: releaseYear ? Number(releaseYear) : null,
})
}
return (
<FormModal
title={game ? 'Edit Game' : 'Add Game'}
onClose={onClose}
onSubmit={handleSubmit}
isSubmitting={isSubmitting}
>
<div>
<label className="block text-sm font-medium mb-1">Name</label>
<input
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Slug</label>
<input
type="text"
required
value={slug}
onChange={(e) => {
setSlug(e.target.value)
setAutoSlug(false)
}}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">Generation</label>
<input
type="number"
required
min={1}
value={generation}
onChange={(e) => setGeneration(e.target.value)}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Region</label>
<input
type="text"
required
value={region}
onChange={(e) => setRegion(e.target.value)}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">Box Art URL</label>
<input
type="text"
value={boxArtUrl}
onChange={(e) => setBoxArtUrl(e.target.value)}
placeholder="Optional"
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Release Year</label>
<input
type="number"
value={releaseYear}
onChange={(e) => setReleaseYear(e.target.value)}
placeholder="Optional"
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
</FormModal>
)
}