'use client'; import { useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; import { ImageIcon, X } from 'lucide-react'; import { fetchApi } from '@/lib/utils/client'; import useAuth from '@/hooks/useAuth'; import EmojiPicker from '@/app/component/EmojiPicker'; const MAX_LENGTH = 2000; const MAX_IMAGES = 4; type Props = { onSubmitted?: () => void; }; export default function FeedComposer({ onSubmitted }: Props = {}) { const router = useRouter(); const { member, loginCheck } = useAuth(); const [content, setContent] = useState(''); const [images, setImages] = useState([]); const [previews, setPreviews] = useState([]); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); const textareaRef = useRef(null); const isOver = content.length > MAX_LENGTH; const handleFilesSelected = (files: FileList|null) => { if (!files) { return; } const next: File[] = [...images]; const nextPreviews: string[] = [...previews]; for (const file of Array.from(files)) { if (next.length >= MAX_IMAGES) { break; } if (!file.type.startsWith('image/')) { continue; } next.push(file); nextPreviews.push(URL.createObjectURL(file)); } setImages(next); setPreviews(nextPreviews); }; const removeImage = (index: number) => { setImages((prev) => prev.filter((_, i) => i !== index)); setPreviews((prev) => { const url = prev[index]; if (url) { URL.revokeObjectURL(url); } return prev.filter((_, i) => i !== index); }); }; const handleEmojiSelect = (emoji: string) => { const textarea = textareaRef.current; if (!textarea) { setContent((prev) => prev + emoji); return; } const start = textarea.selectionStart ?? content.length; const end = textarea.selectionEnd ?? content.length; const next = content.slice(0, start) + emoji + content.slice(end); setContent(next); requestAnimationFrame(() => { textarea.focus(); const caret = start + emoji.length; textarea.setSelectionRange(caret, caret); }); }; const handleSubmit = async () => { if (!loginCheck()) { return; } const trimmed = content.trim(); if (trimmed.length === 0 && images.length === 0) { setError('내용을 입력하거나 이미지를 첨부해주세요.'); return; } if (trimmed.length > MAX_LENGTH) { setError(`내용은 최대 ${MAX_LENGTH}자까지 작성할 수 있습니다.`); return; } setSubmitting(true); setError(null); try { const form = new FormData(); form.append('Content', trimmed); for (const file of images) { form.append('images', file); } const res = await fetchApi<{ id: number }>('/api/feed', { method: 'POST', body: form, silent: true }); if (!res.success) { setError(res.message ?? '게시 실패'); return; } setContent(''); setImages([]); setPreviews((prev) => { for (const url of prev) { URL.revokeObjectURL(url); } return []; }); // FeedList 가 듣고 첫 페이지를 재조회해서 새 글을 prepend 함 if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('feed:posted', { detail: { postID: res.data?.id } })); } if (onSubmitted) { onSubmitted(); } else { router.push('/feed/all'); } } catch (err) { console.error(err); setError(err instanceof Error ? err.message : '게시 실패'); } finally { setSubmitting(false); } }; if (!member) { return (

글 작성을 위해 로그인이 필요합니다.

); } return (
{member.thumb ? ( {member.name ) : ( {(member.name ?? member.sid).charAt(0).toUpperCase()} )}