@@ -11,8 +11,16 @@ import CommentCard from '@/components/Comment/Comment';
1111import CharacterCard from '@/components/Character/Character' ;
1212import useIsMobile from '@/hook/useIsMobile' ;
1313import CommentInputField from '@/components/Comment/CommentInputField' ;
14+ import Loading from '@/components/Loading/Loading' ;
15+ import { postComment } from '@/apis/comment/postComment' ;
16+ import { useRef } from 'react' ;
17+ import { useSelector } from 'react-redux' ;
18+ import { RootState } from '@/store' ;
1419
1520const Comment = ( ) => {
21+ // 사용자 정보
22+ const user = useSelector ( ( state : RootState ) => state . auth . user ) ! ;
23+
1624 const isMobile = useIsMobile ( ) ;
1725
1826 // 결과 조회
@@ -24,13 +32,20 @@ const Comment = () => {
2432
2533 // 댓글 입력창 상태 추가
2634 const [ commentText , setCommentText ] = useState ( '' ) ;
35+ const [ isLoading , setIsLoading ] = useState ( true ) ;
36+
37+ // 댓글 맨 아래로 내리기
38+ const commentsEndRef = useRef < HTMLDivElement > ( null ) ;
39+
40+ // 댓글 렌더링, 모바일 여부는 그대로 유지 가능
41+ const displayedComments = isMobile ? comments . slice ( - 6 ) : comments ;
2742
2843 // 최신 pollIds 불러오기
2944 useEffect ( ( ) => {
3045 const fetchAllResults = async ( ) => {
3146 try {
3247 const res = await getLatestPollIds ( ) ;
33- console . log ( '댓글 ' , res ) ;
48+ console . log ( '최신 투표 id ' , res ) ;
3449 setPollIds ( res ) ;
3550 } catch ( error ) {
3651 console . error ( '투표 결과 불러오기 실패 : ' , error ) ;
@@ -44,11 +59,13 @@ const Comment = () => {
4459 const fetchResultsByPollIds = async ( ) => {
4560 try {
4661 if ( pollIds . length === 0 ) return ;
47-
62+ setIsLoading ( true ) ; // 로딩 시작
4863 const res = await Promise . all ( pollIds . map ( ( pollId ) => getVoteResultByPollId ( { pollId } ) ) ) ;
4964 setResults ( res ) ;
5065 } catch ( error ) {
5166 console . log ( '투표 결과 불러오기 실패 : ' , error ) ;
67+ } finally {
68+ setIsLoading ( false ) ; // 로딩 끝
5269 }
5370 } ;
5471
@@ -60,41 +77,95 @@ const Comment = () => {
6077 const getAllComments = async ( ) => {
6178 try {
6279 const res = await getComments ( ) ;
63- if ( res . length === 0 ) {
64- }
6580 setComments ( res ) ;
6681 } catch ( error ) {
6782 console . log ( '댓글 불러오기 실패 : ' , error ) ;
6883 }
6984 } ;
7085 getAllComments ( ) ;
71- } , comments ) ;
86+ } , [ ] ) ;
87+
88+ // 댓글 등록 후 스크롤 이동 호출
89+ const scrollToBottom = ( ) => {
90+ commentsEndRef . current ?. scrollIntoView ( { behavior : 'auto' } ) ;
91+ } ;
92+
93+ // 댓글 목록이 바뀔 때마다 스크롤을 아래로
94+ useEffect ( ( ) => {
95+ if ( comments . length > 0 ) {
96+ requestAnimationFrame ( ( ) => {
97+ commentsEndRef . current ?. scrollIntoView ( { behavior : 'auto' } ) ;
98+ } ) ;
99+ }
100+ } , [ comments ] ) ;
101+
102+ // 리사이즈 이벤트 감지 후 스크롤 내리기
103+ useEffect ( ( ) => {
104+ const handleResize = ( ) => {
105+ // 댓글이 존재하면 맨 아래로 스크롤
106+ if ( comments . length > 0 ) {
107+ commentsEndRef . current ?. scrollIntoView ( { behavior : 'auto' } ) ;
108+ }
109+ } ;
110+
111+ window . addEventListener ( 'resize' , handleResize ) ;
112+ return ( ) => {
113+ window . removeEventListener ( 'resize' , handleResize ) ;
114+ } ;
115+ } , [ comments ] ) ;
116+
117+ // 댓글 등록
118+ const handleSubmitComment = async ( ) => {
119+ if ( ! commentText . trim ( ) ) {
120+ alert ( '내용을 입력해주세요' ) ;
121+ return ;
122+ }
123+ try {
124+ console . log ( '댓글 내용 : ' , commentText ) ;
125+ await postComment ( {
126+ user_id : user . userId ,
127+ comment_text : commentText ,
128+ } ) ;
129+
130+ const updatedComments = await getComments ( ) ;
131+ setComments ( updatedComments ) ;
132+ setCommentText ( '' ) ;
133+ scrollToBottom ( ) ;
134+ } catch ( error ) {
135+ console . log ( '댓글 등록 실패' , error ) ;
136+ }
137+ } ;
72138
73139 return (
74- < >
75- < div className = "py-10 sm:p-5 flex flex-col justify-center items-center" >
76- { /* 제목 */ }
77- < div className = "flex justify-center items-center gap-3 sm:gap-7 sm:pt-0 pt-5" >
78- { /* 왼쪽 이미지: 모바일에서만 보이게 */ }
79- < img
80- src = "/assets/images/popper-right.png"
81- className = "w-[40px] sm:w-[100px]"
82- alt = "popper-left"
83- />
84- < h1 className = "text-2xl font-pm sm:text-3xl" > 투표 결과</ h1 >
85- { /* 오른쪽 이미지: 모든 화면에서 보이지만 크기는 반응형 */ }
86- < img
87- src = "/assets/images/popper-left.png"
88- className = "w-[40px] sm:hidden"
89- alt = "popper-right"
90- />
91- </ div >
140+ < div className = "pt-10 sm:p-5 flex flex-col justify-center items-center" >
141+ { /* 제목 */ }
142+ < div className = "flex justify-center items-center gap-3 sm:gap-7 sm:pt-0 pt-5" >
143+ < img
144+ src = "/assets/images/popper-right.png"
145+ className = "w-[40px] sm:w-[100px]"
146+ alt = "popper-left"
147+ />
148+ < h1 className = "text-2xl font-pm sm:text-3xl" > 투표 결과</ h1 >
149+ < img
150+ src = "/assets/images/popper-left.png"
151+ className = "w-[40px] sm:hidden"
152+ alt = "popper-right"
153+ />
154+ </ div >
92155
93- < div className = "p-5 flex flex-col sm:flex-row sm:p-3 gap-10 sm:gap-30" >
94- { /* 카드 영역 */ }
95- < div className = "w-full sm:w-auto flex justify-center" >
96- < div className = "grid grid-cols-2 grid-rows-2 gap-6 sm:gap-x-10 sm:gap-y-0" >
97- { results . map ( ( poll , idx ) => (
156+ < div className = "px-5 py-10 flex flex-col sm:flex-row sm:p-3 gap-10 sm:gap-30" >
157+ { /* 카드 영역 */ }
158+ < div className = "w-full sm:w-auto flex justify-center relative" >
159+ { /* 로딩 스피너 */ }
160+ { isLoading && (
161+ < div className = "sm:w-[400px] absolute inset-0 flex justify-center items-center bg-white/80 z-10" >
162+ < Loading />
163+ </ div >
164+ ) }
165+
166+ < div className = "grid grid-cols-2 grid-rows-2 gap-6 sm:gap-x-10 sm:gap-y-0" >
167+ { ! isLoading &&
168+ results . map ( ( poll , idx ) => (
98169 < div
99170 key = { poll . pollId }
100171 className = { `w-full ${ ! isMobile && idx % 2 === 1 ? 'mt-7' : '' } ` }
@@ -107,43 +178,44 @@ const Comment = () => {
107178 />
108179 </ div >
109180 ) ) }
110- </ div >
111181 </ div >
182+ </ div >
112183
113- { /* 댓글 */ }
114- < div className = "flex-1 w-full h-[550px] border border-gray-300 rounded-2xl flex flex-col justify-between px-4 py-2" >
115- { /* 스크롤 가능한 댓글 리스트 */ }
116- < div className = "flex-1 overflow-y-auto pr-2" >
117- { comments . length === 0 ? (
118- // flex items-center justify-center h-full w-full
119- < div className = "pl-10 w-full flex items-center justify-center h-full" >
120- < CharacterCard />
121- </ div >
122- ) : (
123- < div className = "flex flex-col items-center gap-2 w-full" >
124- { comments . map ( ( comment , idx ) => (
125- < CommentCard
126- key = { idx }
127- nickname = { comment . random_nickname }
128- comment = { comment . comment_text }
129- />
130- ) ) }
131- </ div >
132- ) }
133- </ div >
184+ { /* 댓글 영역 */ }
185+ < div className = " flex-1 w-full h-[550px] border border-gray-300 rounded-2xl flex flex-col justify-between px-4 py-2" >
186+ { /* 스크롤 가능한 댓글 리스트 */ }
187+ < div className = "flex-1 overflow-y-auto pr-2 max-h-[400px] sm:max-h-full" >
188+ { comments . length === 0 ? (
189+ < div className = "pl-10 w-full flex items-center justify-center h-full" >
190+ < CharacterCard />
191+ </ div >
192+ ) : (
193+ < div className = "flex flex-col items-center gap-2 w-full" >
194+ { displayedComments . map ( ( comment , idx ) => (
195+ < CommentCard
196+ key = { idx }
197+ nickname = { comment . random_nickname }
198+ comment = { comment . comment_text }
199+ />
200+ ) ) }
134201
135- { /* 고정된 입력창 */ }
136- < div className = "mt-4" >
137- < CommentInputField
138- nickname = "부드럽게 움직이는 황금도토리"
139- comment = { commentText }
140- onChangeComment = { ( e ) => setCommentText ( e . target . value ) }
141- />
142- </ div >
202+ < div ref = { commentsEndRef } />
203+ </ div >
204+ ) }
205+ </ div >
206+
207+ { /* 고정된 입력창 */ }
208+ < div className = "mt-4" >
209+ < CommentInputField
210+ nickname = { user . randomNickname }
211+ comment = { commentText }
212+ onChangeComment = { ( e ) => setCommentText ( e . target . value ) }
213+ onSubmit = { handleSubmitComment } // 이 부분 추가
214+ />
143215 </ div >
144216 </ div >
145217 </ div >
146- </ >
218+ </ div >
147219 ) ;
148220} ;
149221
0 commit comments