import { useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { api } from '../../api/client' import ImagePicker from './components/ImagePicker' function Field({ label, hint, error, required, children }) { return (
{hint && {hint}}
{children} {error &&
{error}
}
) } const inputCls = 'w-full rounded-lg border border-white/10 bg-gray-950 px-3 py-2 text-sm outline-none focus:border-emerald-500/50 transition' export default function AdminMenuForm() { const navigate = useNavigate() const queryClient = useQueryClient() const { id } = useParams() const isEdit = !!id const [pickerOpen, setPickerOpen] = useState(false) const [form, setForm] = useState({ title: '', description: '', url: '/', image_id: null, image: null, // 미리보기용 캐시 }) const [errors, setErrors] = useState({}) // 편집 모드일 때 기존 데이터 로드 useQuery({ queryKey: ['admin', 'menus', id], queryFn: async () => { const data = await api(`/api/admin/menus/${id}`) setForm({ title: data.title || '', description: data.description || '', url: data.url || '/', image_id: data.image_id, image: data.image, }) return data }, enabled: isEdit, }) const update = (patch) => setForm((prev) => ({ ...prev, ...patch })) const validate = () => { const errs = {} if (!form.title.trim()) errs.title = '제목을 입력해주세요' if (!form.url.trim()) errs.url = 'URL을 입력해주세요' else if (!form.url.startsWith('/')) errs.url = 'URL은 /로 시작해야 합니다' setErrors(errs) return Object.keys(errs).length === 0 } const saveMutation = useMutation({ mutationFn: async () => { const payload = { title: form.title.trim(), description: form.description.trim(), url: form.url.trim(), image_id: form.image_id, } if (isEdit) { return api(`/api/admin/menus/${id}`, { method: 'PATCH', body: payload }) } return api('/api/admin/menus', { method: 'POST', body: payload }) }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'menus'] }) queryClient.invalidateQueries({ queryKey: ['menus'] }) navigate('/admin') }, onError: (err) => alert(err.message), }) const handleSubmit = (e) => { e.preventDefault() if (!validate()) return saveMutation.mutate() } return (

{isEdit ? '메뉴 항목 편집' : '메뉴 항목 추가'}

홈 화면에 표시되는 카드의 정보를 설정합니다

{/* 미리보기 */}
미리보기

{form.title || '제목 없음'}

{form.description || '설명 없음'}

update({ title: e.target.value })} placeholder="예: 주간 보스 수익 계산기" className={inputCls} /> update({ description: e.target.value })} placeholder="예: 캐릭터별 보스 결정석 수익을 계산합니다" className={inputCls} /> update({ url: e.target.value })} placeholder="예: /boss" className={inputCls} />
{form.image ? ( <>
{form.image.name}
) : (
이미지 선택
)}
setPickerOpen(false)} currentImageId={form.image_id} onSelect={(img) => update({ image_id: img?.id || null, image: img })} />
) }