diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts index 7d9e1684..c6b97a6a 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts @@ -10,6 +10,7 @@ import useInfinityScroll from "@/utils/useInfinityScroll"; const BOTTOM_PROXIMITY_THRESHOLD = 80; const OUTBOUND_TEXT_SCROLL_CONFIRM_TIMEOUT_MS = 5000; +const INITIAL_SCROLL_SETTLE_DELAYS_MS = [100, 350, 800]; interface PendingOutboundTextScroll { id: string; @@ -210,20 +211,42 @@ const useChatListHandler = (chatId: number) => { shouldForceScrollToBottomRef.current = false; }, [chatId, clearPendingOutboundTextScrolls]); - // 초기 히스토리 로딩 완료 후, 최초 1회만 하단으로 이동합니다. + // 초기 메시지가 렌더링되면 최초 1회만 하단으로 이동합니다. useEffect(() => { - if (isLoading || isFetchingNextPage || submittedMessages.length === 0 || hasInitialAutoScrolledRef.current) { + if (isLoading || submittedMessages.length === 0 || hasInitialAutoScrolledRef.current) { return; } - const rafId = requestAnimationFrame(() => { + const rafIds: number[] = []; + const timeoutIds: ReturnType[] = []; + + const scheduleScrollToBottom = () => { + const rafId = requestAnimationFrame(() => { + scrollToBottom(); + }); + rafIds.push(rafId); + }; + + scheduleScrollToBottom(); + const doubleRafId = requestAnimationFrame(() => { scrollToBottom(); - hasInitialAutoScrolledRef.current = true; - prevMessageCountRef.current = submittedMessages.length; + scheduleScrollToBottom(); }); + rafIds.push(doubleRafId); - return () => cancelAnimationFrame(rafId); - }, [isLoading, isFetchingNextPage, scrollToBottom, submittedMessages.length]); + INITIAL_SCROLL_SETTLE_DELAYS_MS.forEach((delayMs) => { + const timeoutId = setTimeout(scrollToBottom, delayMs); + timeoutIds.push(timeoutId); + }); + + hasInitialAutoScrolledRef.current = true; + prevMessageCountRef.current = submittedMessages.length; + + return () => { + rafIds.forEach((rafId) => cancelAnimationFrame(rafId)); + timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId)); + }; + }, [isLoading, scrollToBottom, submittedMessages.length]); // 신규 메시지 도착 시 하단 근처라면 유지하고, 내가 보낸 메시지는 현재 위치와 무관하게 하단으로 이동합니다. useEffect(() => { diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx index e749e580..d90961fc 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx @@ -17,6 +17,7 @@ interface ChatMessageBoxProps { message: ChatMessage; currentUserId?: number; // 현재 사용자 ID partnerNickname?: string; // 상대방 닉네임 + partnerProfileUrl?: string | null; isPartnerMentor?: boolean; } @@ -135,6 +136,7 @@ const ChatMessageBox = ({ message, currentUserId = 1, partnerNickname = "상대방", + partnerProfileUrl = null, isPartnerMentor = false, }: ChatMessageBoxProps) => { const isMine = message.senderId === Number(currentUserId); @@ -198,7 +200,7 @@ const ChatMessageBox = ({ ) : (
- +
{partnerNickname}
diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx index 50353ca0..9293a700 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx @@ -165,6 +165,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => { message={message} currentUserId={userId} partnerNickname={nickname} + partnerProfileUrl={profileUrl} isPartnerMentor={isPartnerMentor} />