'use client'; import './style.scss'; import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { fetchApi } from '@/lib/utils/client'; import { useStudioContext } from '@/app/studio/context'; import type { CrewListResponse, CrewItem } from '@/types/response/crew/list'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; function RequiredLabel({ htmlFor, children }: { htmlFor: string; children: React.ReactNode }) { return ; } function MoneyInput({ id, value, onChange, placeholder }: { id: string; value: string|number; onChange: (v: string) => void; placeholder?: string }) { const [focused, setFocused] = useState(false); const raw = String(value); const display = focused || !raw ? raw : (Number(raw) ? Number(raw).toLocaleString() : raw); return ( { if (!/^[0-9]$/.test(e.key) && !['Backspace','Delete','ArrowLeft','ArrowRight','Tab','Home','End'].includes(e.key)) e.preventDefault(); }} onCompositionStart={e => e.preventDefault()} onChange={e => onChange(e.target.value.replace(/[^0-9]/g, ''))} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} /> ); } const EMPTY_FORM = { name: '', description: '', minAmount: '' as string|number, isActive: true }; export default function CrewConfigPage() { const router = useRouter(); const { channelID } = useStudioContext(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [modal, setModal] = useState<{ open: boolean; editing: CrewItem|null }>({ open: false, editing: null }); const [form, setForm] = useState(EMPTY_FORM); const [saving, setSaving] = useState(false); useEffect(() => { if (error) { alert(error); setError(''); } }, [error]); const fetchList = useCallback(() => { if (!channelID) { setLoading(false); return; } setLoading(true); fetchApi(`/api/studio/crew/list/${channelID}`).then(res => { setItems(res.data?.list ?? []); }) .catch(err => setError(err.message)) .finally(() => setLoading(false)); }, [channelID]); useEffect(() => { fetchList(); }, [fetchList]); const openAdd = () => { setForm(EMPTY_FORM); setModal({ open: true, editing: null }); }; const openEdit = (item: CrewItem) => { setForm({ name: item.name, description: item.description ?? '', minAmount: item.minAmount ?? '', isActive: item.isActive }); setModal({ open: true, editing: item }); }; const closeModal = () => setModal({ open: false, editing: null }); const handleSave = async () => { if (!form.name.trim()) { alert('크루명을 입력해 주세요.'); return; } setSaving(true); try { await fetchApi('/api/studio/crew/save', { method: 'POST', body: { channelID, id: modal.editing?.id ?? undefined, name: form.name, description: form.description || undefined, minAmount: form.minAmount !== '' ? Number(form.minAmount) : undefined, isActive: form.isActive } }); closeModal(); fetchList(); } catch (err: unknown) { setError(err instanceof Error ? err.message : '저장에 실패했습니다.'); } finally { setSaving(false); } }; return (

크루 후원 설정

{loading ? (

준비 중...

) : ( {items.length === 0 ? ( ) : ( items.map(item => ( )) )}
크루명 설명 최소 후원금 멤버 수 활성 작업
등록된 크루가 없습니다.
{item.description ?? '-'} {item.minAmount ? `${item.minAmount.toLocaleString()}원` : '-'} {item.memberCount}명 {item.isActive ? '활성' : '비활성'}
)}
{ if (!open) closeModal(); }}> {modal.editing ? '크루 수정' : '크루 추가'}
크루명 setForm(f => ({ ...f, name: e.target.value }))} />
setForm(f => ({ ...f, description: e.target.value }))} />
setForm(f => ({ ...f, minAmount: v }))} />

이 금액 이상 후원한 회원만 크루에 가입할 수 있습니다.

setForm(f => ({ ...f, isActive: !!v }))} />
); }