'use client'; // D3 응원(Cheer) 모달 — 금액/메시지 입력 후 POST /api/posts/{postID}/cheers. // 규제 가드라인(d3 §⑧): 응원은 사후·자율·무대가. 글 열람 조건 아님. import { useCallback, useEffect, useState } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { fetchApi } from '@/lib/utils/client'; import type { WalletResponse, CheerSendResponse } from '@/types/reward'; import { sumCash, sumToken } from '@/types/reward'; const PRESET_AMOUNTS = [100, 500, 1000, 5000]; const MAX_MESSAGE = 100; type Props = { postID: number; onClose: () => void; onDone: () => void; }; export default function CheerModal({ postID, onClose, onDone }: Props) { const [amount, setAmount] = useState(''); const [message, setMessage] = useState(''); const [cash, setCash] = useState(null); const [token, setToken] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); // 지갑 가용 잔액 조회 (캐시/토큰 분리 표시 — 응원은 캐시→토큰 순 차감) useEffect(() => { let cancelled = false; (async () => { const res = await fetchApi('/api/wallet', { silent: true }); if (cancelled) { return; } if (res.success && res.data) { setCash(sumCash(res.data.balances)); setToken(sumToken(res.data.balances)); } else { setCash(null); setToken(null); } })(); return () => { cancelled = true; }; }, []); const available = (cash ?? 0) + (token ?? 0); const submit = useCallback(async () => { setError(null); const numeric = Number(amount); if (!Number.isInteger(numeric) || numeric <= 0) { setError('1 이상의 정수 금액을 입력해 주세요.'); return; } if (cash !== null && token !== null && numeric > available) { setError('가용 잔액이 부족합니다.'); return; } setBusy(true); const res = await fetchApi(`/api/posts/${postID}/cheers`, { method: 'POST', body: { amount: numeric, message: message.trim() || null }, silent: true }); setBusy(false); if (res.success && res.data) { onDone(); } else { setError(res.message || '응원에 실패했습니다.'); } }, [amount, message, cash, token, available, postID, onDone]); return ( { if (!open) onClose(); }}> 응원 보내기
가용 잔액 {cash === null ? '—' : available.toLocaleString()}
캐시 {cash === null ? '—' : cash.toLocaleString()} 토큰 {token === null ? '—' : token.toLocaleString()}
{PRESET_AMOUNTS.map((preset) => ( ))}
setAmount(e.target.value)} placeholder='금액 입력' className='border border-neutral-300 dark:border-neutral-700 rounded px-3 py-2 text-sm bg-white dark:bg-neutral-900' autoFocus />