From a99513d75ae7b1b2f1a2edc36ecd778acefce6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Tue, 10 Dec 2024 10:56:55 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=9E=98=EB=A6=AC=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChattingDetail.jsx | 353 +++++++++++++++++------------- 1 file changed, 206 insertions(+), 147 deletions(-) diff --git a/src/components/ChattingDetail.jsx b/src/components/ChattingDetail.jsx index 318a02a..f2ca049 100644 --- a/src/components/ChattingDetail.jsx +++ b/src/components/ChattingDetail.jsx @@ -1,11 +1,11 @@ -import { useParams, useLocation } from 'react-router-dom'; -import { useEffect, useState, useRef, useCallback, useMemo } from 'react'; -import './ChattingDetail.css'; -import styled, { keyframes } from 'styled-components'; -import { FaSearch, FaArrowUp, FaArrowDown } from 'react-icons/fa'; +import { useParams, useLocation } from "react-router-dom"; +import { useEffect, useState, useRef, useCallback, useMemo } from "react"; +import "./ChattingDetail.css"; +import styled, { keyframes } from "styled-components"; +import { FaSearch, FaArrowUp, FaArrowDown } from "react-icons/fa"; import Button from "../components/Button"; -import ChattingNoticeDetailModal from '../components/ChattingNoticeDetailModal'; -import ChattingNoticeListModal from '../components/ChattingNoticeListModal'; +import ChattingNoticeDetailModal from "../components/ChattingNoticeDetailModal"; +import ChattingNoticeListModal from "../components/ChattingNoticeListModal"; // 웹소켓 서버 연결 URL const WS_URL = process.env.REACT_APP_WS_URL; @@ -69,26 +69,28 @@ const ChatRoomMessages = styled.div` const MessageContainer = styled.div` display: flex; - justify-content: ${(props) => (props.isMine ? 'flex-end' : 'flex-start')}; + justify-content: ${(props) => (props.isMine ? "flex-end" : "flex-start")}; margin-bottom: 10px; padding: 10px; border-radius: 5px; - border: ${(props) => (props.highlighted ? '2px solid blue' : 'none')}; + border: ${(props) => (props.highlighted ? "2px solid blue" : "none")}; transition: background-color 0.3s ease, border 0.3s ease; - animation: ${(props) => (props.highlighted ? shakeAnimation : 'none')} 0.5s ease; + animation: ${(props) => (props.highlighted ? shakeAnimation : "none")} 0.5s + ease; `; const MessageBubble = styled.div` max-width: 60%; padding: 10px; border-radius: 10px; - background-color: ${(props) => (props.isMine ? '#dcf8c6' : '#f9f9f9')}; - text-align: ${(props) => (props.isMine ? 'right' : 'left')}; - border: ${(props) => (props.highlighted ? '2px solid blue' : 'none')}; + background-color: ${(props) => (props.isMine ? "#dcf8c6" : "#f9f9f9")}; + text-align: ${(props) => (props.isMine ? "right" : "left")}; + border: ${(props) => (props.highlighted ? "2px solid blue" : "none")}; word-wrap: break-word; transition: background-color 0.3s ease, border 0.3s ease; box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1); - animation: ${(props) => (props.highlighted ? shakeAnimation : 'none')} 0.5s ease; + animation: ${(props) => (props.highlighted ? shakeAnimation : "none")} 0.5s + ease; `; const CenteredMessage = styled.div` @@ -107,7 +109,7 @@ const CenteredMessage = styled.div` const MessageTimestamp = styled.div` font-size: 0.8em; color: #888; - text-align: ${(props) => (props.isMine ? 'right' : 'left')}; + text-align: ${(props) => (props.isMine ? "right" : "left")}; `; const ChatRoomInput = styled.div` @@ -146,9 +148,11 @@ const FixedSearchBar = styled(SearchBar)` `; const NoticeContainer = styled.div` - background-color: ${({ isCollapsed }) => (isCollapsed ? "transparent" : "#f8f9fa")}; + background-color: ${({ isCollapsed }) => + isCollapsed ? "transparent" : "#f8f9fa"}; padding: ${({ isCollapsed }) => (isCollapsed ? "0" : "10px")}; - border-bottom: ${({ isCollapsed }) => (isCollapsed ? "none" : "1px solid #ddd")}; + border-bottom: ${({ isCollapsed }) => + isCollapsed ? "none" : "1px solid #ddd"}; position: relative; /* 확성기 위치 조정을 위해 사용 */ `; @@ -196,17 +200,17 @@ function ChattingDetail() { const location = useLocation(); const { nickname, chatRoomName } = location.state; - // const [notice, setNotice] = useState(null); // 공지 메시지 + // const [notice, setNotice] = useState(null); // 공지 메시지 const [messages, setMessages] = useState([]); - const [input, setInput] = useState(''); - const [searchTerm, setSearchTerm] = useState(''); + const [input, setInput] = useState(""); + const [searchTerm, setSearchTerm] = useState(""); const [searchResults, setSearchResults] = useState([]); const [currentSearchIndex, setCurrentSearchIndex] = useState(0); const [isSearching, setIsSearching] = useState(false); const [loggedInUser, setLoggedInUser] = useState(nickname); const [chatUnread, setChatUnread] = useState({}); // const [isScrolledToBottom, setIsScrolledToBottom] = useState(true); - const [notice, setNotice] = useState(null); // 공지 메시지 + const [notice, setNotice] = useState(null); // 공지 메시지 const [isNoticeVisible, setIsNoticeVisible] = useState(true); // 공지 표시 여부 const [isNoticeCollapsed, setIsNoticeCollapsed] = useState(false); // 공지 접힘 여부 const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false); @@ -220,7 +224,7 @@ function ChattingDetail() { const searchInputRef = useRef(null); const highlightedMessageRef = useRef(null); const isTabActiveRef = useRef(true); // useRef로 상태 초기화 - + let reconnectAttempts = 0; const MAX_RECONNECT_ATTEMPTS = 5; // let isTabActive = true; // 브라우저 탭 상태를 저장 @@ -241,7 +245,7 @@ function ChattingDetail() { (logId) => { if (chatUnreadSortArray.length === 0) { return 0; - } + } for (let i = 0; i < chatUnreadSortArray.length; i++) { if (logId <= chatUnreadSortArray[i][1]) { @@ -272,12 +276,16 @@ function ChattingDetail() { }; const handleDismissNotice = () => { - const dismissedNotices = JSON.parse(localStorage.getItem('dismissedNotices')) || []; - + const dismissedNotices = + JSON.parse(localStorage.getItem("dismissedNotices")) || []; + // 현재 공지사항이 로컬 스토리지에 추가되도록 처리 if (notice && !dismissedNotices.includes(notice.message)) { dismissedNotices.push(notice.message); - localStorage.setItem('dismissedNotices', JSON.stringify(dismissedNotices)); + localStorage.setItem( + "dismissedNotices", + JSON.stringify(dismissedNotices) + ); } setIsNoticeVisible(false); // 공지를 숨김 @@ -285,21 +293,24 @@ function ChattingDetail() { const fetchUnreadCounts = async () => { try { - const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/unread-count/${chatRoomId}`, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }); + const response = await fetch( + `${process.env.REACT_APP_BASE_URL}/api/chat/unread-count/${chatRoomId}`, + { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + } + ); if (response.ok) { const data = await response.json(); setChatUnread(data); } else { - console.error('Failed to fetch unread counts'); + console.error("Failed to fetch unread counts"); } } catch (error) { - console.error('Error fetching unread counts:', error); + console.error("Error fetching unread counts:", error); } }; @@ -310,33 +321,38 @@ function ChattingDetail() { // isOnline이 false로 전환될 때 마지막 메시지가 없으면 가장 최근 메시지의 logId를 설정 if (!status) { if (messages.length > 0) { - logId = messages[messages.length - 1]._id; // 가장 최근 메시지의 logId로 설정 + logId = messages[messages.length - 1]._id; // 가장 최근 메시지의 logId로 설정 } else { - logId = lastReadLogIdRef.current; // 마지막 읽은 logId를 그대로 유지 + logId = lastReadLogIdRef.current; // 마지막 읽은 logId를 그대로 유지 } } try { - const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/update-status-and-logid`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - chatRoomId, - nickname, - isOnline: status, - logId: logId, // 상태에 따라 logId도 함께 보냄 - }), - }); + const response = await fetch( + `${process.env.REACT_APP_BASE_URL}/api/chat/update-status-and-logid`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + chatRoomId, + nickname, + isOnline: status, + logId: logId, // 상태에 따라 logId도 함께 보냄 + }), + } + ); if (!response.ok) { - throw new Error('Failed to update status and logId'); + throw new Error("Failed to update status and logId"); } - console.log(`${nickname} : 상태업데이트 to ${status}, logId updated to ${logId} for `); + console.log( + `${nickname} : 상태업데이트 to ${status}, logId updated to ${logId} for ` + ); } catch (err) { - console.error('Error updating status and logId:', err); + console.error("Error updating status and logId:", err); } }; @@ -344,25 +360,23 @@ function ChattingDetail() { e.preventDefault(); if (window.confirm("이 메시지를 공지로 설정하시겠습니까?")) { try { - const noticeMessage = { - type: 'notice', // 메시지 유형: 공지사항 설정 + type: "notice", // 메시지 유형: 공지사항 설정 chatRoomId: chatRoomId, // 현재 채팅방 ID - nickname: loggedInUser, // 공지 설정한 사용자 닉네임 - text: messageData.message, // 공지 내용 + nickname: loggedInUser, // 공지 설정한 사용자 닉네임 + text: messageData.message, // 공지 내용 }; // WebSocket을 통해 서버로 메시지 전송 ws.current.send(JSON.stringify(noticeMessage)); - console.log('공지사항 설정 메시지 전송:', noticeMessage); - + console.log("공지사항 설정 메시지 전송:", noticeMessage); const response = await fetch( `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ sender: messageData.sender, @@ -375,10 +389,10 @@ function ChattingDetail() { const newNotice = await response.json(); setNotice(newNotice); // 공지를 로컬 상태로 업데이트 } else { - console.error('Failed to set notice'); + console.error("Failed to set notice"); } } catch (error) { - console.error('Error setting notice:', error); + console.error("Error setting notice:", error); } } }; @@ -402,11 +416,14 @@ function ChattingDetail() { const fetchLatestNotice = async () => { try { - const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices/latest`); + const response = await fetch( + `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices/latest` + ); if (response.ok) { const latestNotice = await response.json(); - const dismissedNotices = JSON.parse(localStorage.getItem('dismissedNotices')) || []; + const dismissedNotices = + JSON.parse(localStorage.getItem("dismissedNotices")) || []; const isDismissed = dismissedNotices.includes(latestNotice.message); // 새로운 공지라면 표시하고, 숨겨진 공지가 아니라면 표시 @@ -417,18 +434,18 @@ function ChattingDetail() { setNotice(null); setIsNoticeVisible(false); } else { - console.error('Failed to fetch latest notice'); + console.error("Failed to fetch latest notice"); } } catch (error) { - console.error('Error fetching latest notice:', error); + console.error("Error fetching latest notice:", error); } }; const scrollToHighlightedMessage = () => { if (highlightedMessageRef.current) { - highlightedMessageRef.current.scrollIntoView({ - behavior: 'smooth', - block: 'center' + highlightedMessageRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", }); } }; @@ -445,22 +462,24 @@ function ChattingDetail() { ws.current.close(); return; } - + reconnectAttempts = 0; // 재연결 성공 시 시도 횟수 초기화 const joinMessage = JSON.stringify({ - type: 'join', + type: "join", chatRoomId, nickname, }); ws.current.send(joinMessage); - console.log(`(클라이언트) WebSocket 연결 완료 - 채팅방: ${chatRoomId}, 닉네임: ${nickname}`); + console.log( + `(클라이언트) WebSocket 연결 완료 - 채팅방: ${chatRoomId}, 닉네임: ${nickname}` + ); }; ws.current.onmessage = (event) => { const messageData = JSON.parse(event.data); console.log("받은 메시지", messageData); - if (messageData.type === 'status') { + if (messageData.type === "status") { const { nickname, isOnline } = messageData; if (!isOnline) { console.log(`${nickname}님이 오프라인 상태로 전환되었습니다.`); @@ -469,27 +488,30 @@ function ChattingDetail() { return; } - if (messageData.type === 'notice') { + if (messageData.type === "notice") { // 공지사항 변경 이벤트 처리 - console.log('새 공지사항 알림 수신:', messageData); + console.log("새 공지사항 알림 수신:", messageData); setNotice({ sender: messageData.sender, message: messageData.message, }); setIsNoticeVisible(true); // 공지를 표시하도록 설정 fetchLatestNotice(); // 최신 공지사항 업데이트 - } else if (messageData.type === 'previousMessages') { + } else if (messageData.type === "previousMessages") { // setMessages((prevMessages) => [...prevMessages, ...messageData.messages]); setMessages((prevMessages) => { const messageIds = new Set(prevMessages.map((msg) => msg._id)); // 기존 메시지의 ID 저장 - const newMessages = messageData.messages.filter((msg) => !messageIds.has(msg._id)); // 중복 제거 + const newMessages = messageData.messages.filter( + (msg) => !messageIds.has(msg._id) + ); // 중복 제거 return [...prevMessages, ...newMessages]; }); } else { // setMessages((prevMessages) => [...prevMessages, messageData]); setMessages((prevMessages) => { const messageIds = new Set(prevMessages.map((msg) => msg._id)); // 기존 메시지의 ID 저장 - if (!messageIds.has(messageData._id)) { // 새 메시지가 중복되지 않으면 추가 + if (!messageIds.has(messageData._id)) { + // 새 메시지가 중복되지 않으면 추가 return [...prevMessages, messageData]; } return prevMessages; @@ -497,11 +519,15 @@ function ChattingDetail() { lastReadLogIdRef.current = messageData._id; // messages 배열에 메시지가 추가된 후에 logId를 업데이트 - const lastMessage = messageData; // 새로 받은 메시지 + const lastMessage = messageData; // 새로 받은 메시지 lastReadLogIdRef.current = lastMessage._id; - console.log('Received message logId:', lastReadLogIdRef.current); - if (!chatUnreadSortArray.length || chatUnreadSortArray[chatUnreadSortArray.length - 1][1] <= messageData._id) { + console.log("Received message logId:", lastReadLogIdRef.current); + if ( + !chatUnreadSortArray.length || + chatUnreadSortArray[chatUnreadSortArray.length - 1][1] <= + messageData._id + ) { fetchUnreadCounts(chatRoomId); } } @@ -509,46 +535,57 @@ function ChattingDetail() { }; ws.current.onclose = async (event) => { - console.warn('(클라이언트)WebSocket 연결이 종료되었습니다.', event); + console.warn("(클라이언트)WebSocket 연결이 종료되었습니다.", event); // 탭이 활성화된 상태에서만 재연결 시도 - if (isTabActiveRef.current && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + if ( + isTabActiveRef.current && + reconnectAttempts < MAX_RECONNECT_ATTEMPTS + ) { setTimeout(() => { - console.log(`WebSocket 재연결 시도 (${reconnectAttempts + 1}/${MAX_RECONNECT_ATTEMPTS})`); + console.log( + `WebSocket 재연결 시도 (${ + reconnectAttempts + 1 + }/${MAX_RECONNECT_ATTEMPTS})` + ); reconnectAttempts++; joinRoom(); // 재연결 시도 }, 2000); // 2초 후 재연결 } else if (!isTabActiveRef.current) { - console.log('브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다.'); + console.log( + "브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다." + ); } else { - console.error('WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다.'); + console.error("WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다."); } }; ws.current.onerror = (error) => { - console.error('WebSocket 오류:', error); + console.error("WebSocket 오류:", error); }; }; const updateLastReadAt = async () => { try { - const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/update-read-status`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ chatRoomId, nickname }), - }); + const response = await fetch( + `${process.env.REACT_APP_BASE_URL}/api/chat/update-read-status`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ chatRoomId, nickname }), + } + ); if (!response.ok) { - throw new Error('Failed to update lastReadAt'); + throw new Error("Failed to update lastReadAt"); } - console.log('lastReadAt updated for', nickname); + console.log("lastReadAt updated for", nickname); } catch (err) { - console.error('Error updating lastReadAt:', err); + console.error("Error updating lastReadAt:", err); } - }; // // 채팅방에서 퇴장 @@ -564,12 +601,14 @@ function ChattingDetail() { // ws.current.close(); // 웹소켓 연결 종료 // } catch (error) { // console.error('퇴장 메시지 전송 오류:', error); - // } + // } // }; const handleSearch = (e) => { - if (e.key === 'Enter') { - const results = messages.filter((message) => message.message.includes(searchTerm)); + if (e.key === "Enter") { + const results = messages.filter((message) => + message.message.includes(searchTerm) + ); setSearchResults(results); setCurrentSearchIndex(results.length > 0 ? results.length - 1 : -1); setIsSearching(true); @@ -580,13 +619,16 @@ function ChattingDetail() { }; const handleArrowDown = () => { - if (searchResults.length > 0 && currentSearchIndex < searchResults.length - 1) { + if ( + searchResults.length > 0 && + currentSearchIndex < searchResults.length - 1 + ) { setCurrentSearchIndex((prevIndex) => prevIndex + 1); setTimeout(() => { scrollToHighlightedMessage(); }, 0); } else { - alert('더 이상 검색결과가 없습니다.'); + alert("더 이상 검색결과가 없습니다."); } }; @@ -597,14 +639,14 @@ function ChattingDetail() { scrollToHighlightedMessage(); }, 0); } else { - alert('더 이상 검색결과가 없습니다.'); + alert("더 이상 검색결과가 없습니다."); } }; const sendMessage = () => { if (input.trim()) { const message = JSON.stringify({ - type: 'message', + type: "message", chatRoomId, sender: loggedInUser, nickname, @@ -613,15 +655,15 @@ function ChattingDetail() { try { ws.current.send(message); - console.log('전송한 메시지:', message); + console.log("전송한 메시지:", message); updateLastReadAt(); } catch (error) { - console.error('메시지 전송 오류:', error); + console.error("메시지 전송 오류:", error); } - setInput(''); + setInput(""); } else { - alert('메시지를 입력하세요.'); + alert("메시지를 입력하세요."); } }; @@ -637,7 +679,7 @@ function ChattingDetail() { const sendHeartbeat = () => { if (ws.current && ws.current.readyState === WebSocket.OPEN) { const heartbeatMessage = JSON.stringify({ - type: 'heartbeat', + type: "heartbeat", chatRoomId, nickname, }); @@ -661,11 +703,11 @@ function ChattingDetail() { updateLastReadAt(); - // 탭 상태가 변경되면 Heartbeat 중단/재개 - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { + // 탭 상태가 변경되면 Heartbeat 중단/재개 + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === "hidden") { stopHeartbeat(); - } else if (document.visibilityState === 'visible') { + } else if (document.visibilityState === "visible") { startHeartbeat(); } }); @@ -679,20 +721,22 @@ function ChattingDetail() { useEffect(() => { // eslint-disable-next-line react-hooks/exhaustive-deps const handleVisibilityChange = () => { - if (document.visibilityState === 'hidden') { // 브라우저 탭이 비활성화되었을 때 + if (document.visibilityState === "hidden") { + // 브라우저 탭이 비활성화되었을 때 // isTabActive = false; // 탭 비활성화 상태로 설정 isTabActiveRef.current = false; // 탭 비활성화 상태로 설정 updateUserStatusAndLogId(false); if (ws.current) { ws.current.close(); // WebSocket 연결 종료 - console.log('브라우저 탭 비활성화: WebSocket 연결 종료'); + console.log("브라우저 탭 비활성화: WebSocket 연결 종료"); } - } else if (document.visibilityState === 'visible') { // 브라우저 탭이 다시 활성화되었을 때 + } else if (document.visibilityState === "visible") { + // 브라우저 탭이 다시 활성화되었을 때 // isTabActive = true; // 탭 활성화 상태로 설정 isTabActiveRef.current = false; // 탭 비활성화 상태로 설정 updateUserStatusAndLogId(true); if (!ws.current || ws.current.readyState === WebSocket.CLOSED) { - console.log('브라우저 탭 활성화: WebSocket 연결 재시작'); + console.log("브라우저 탭 활성화: WebSocket 연결 재시작"); joinRoom(); // WebSocket 재연결 시도 } } @@ -713,8 +757,8 @@ function ChattingDetail() { }, [currentSearchIndex, searchResults.length]); useEffect(() => { - console.log('chatRoomId:', chatRoomId); - console.log('nickname:', nickname); + console.log("chatRoomId:", chatRoomId); + console.log("nickname:", nickname); setLoggedInUser(nickname); }, [chatRoomId, nickname]); @@ -727,15 +771,17 @@ function ChattingDetail() { // 공지사항 목록 가져오기 const fetchNotices = async () => { try { - const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices`); + const response = await fetch( + `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices` + ); if (response.ok) { const data = await response.json(); setNotices(data); // 공지사항 목록 업데이트 } else { - console.error('Failed to fetch notices'); + console.error("Failed to fetch notices"); } } catch (error) { - console.error('Error fetching notices:', error); + console.error("Error fetching notices:", error); } }; fetchNotices(); @@ -743,7 +789,7 @@ function ChattingDetail() { useEffect(() => { // 공지가 변경되면 렌더링 - console.log('공지사항 업데이트:', notice); + console.log("공지사항 업데이트:", notice); }, [notice]); useEffect(() => { @@ -754,12 +800,12 @@ function ChattingDetail() { } }; - window.addEventListener('beforeunload', handleBeforeUnload); // 브라우저 종료/새로고침 이벤트 리스너 추가 + window.addEventListener("beforeunload", handleBeforeUnload); // 브라우저 종료/새로고침 이벤트 리스너 추가 return () => { console.log("페이지 이동시 상태 업데이트"); updateUserStatusAndLogId(false); // 페이지 이동 시 상태 업데이트 - window.removeEventListener('beforeunload', handleBeforeUnload); // 컴포넌트 언마운트 시 이벤트 리스너 제거 + window.removeEventListener("beforeunload", handleBeforeUnload); // 컴포넌트 언마운트 시 이벤트 리스너 제거 if (ws.current) { ws.current.close(); // WebSocket 연결 종료 } @@ -770,21 +816,26 @@ function ChattingDetail() { useEffect(() => { const lastMessage = messages[messages.length - 1]; - localStorage.setItem( - `loadedPreviousMessages-${chatRoomId}`, - "true" - ); + localStorage.setItem(`loadedPreviousMessages-${chatRoomId}`, "true"); const hasLoadedPreviousMessages = localStorage.getItem( `loadedPreviousMessages-${chatRoomId}` ); - if (!hasLoadedPreviousMessages && lastMessage && lastMessage.type === "previousMessages") { + if ( + !hasLoadedPreviousMessages && + lastMessage && + lastMessage.type === "previousMessages" + ) { return; // 이전 메시지일 경우 자동 스크롤 방지 } // 새 메시지 수신 시 자동으로 아래로 스크롤 - if (lastMessage && lastMessage.type === "message" && messagesEndRef.current) { + if ( + lastMessage && + lastMessage.type === "message" && + messagesEndRef.current + ) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages, chatRoomId]); @@ -792,12 +843,15 @@ function ChattingDetail() { const highlightSearchTerm = (message) => { if (!searchTerm) return message; - const regex = new RegExp(`(${searchTerm})`, 'gi'); + const regex = new RegExp(`(${searchTerm})`, "gi"); const parts = message.split(regex); return parts.map((part, index) => part.toLowerCase() === searchTerm.toLowerCase() ? ( - <span key={index} style={{ backgroundColor: 'yellow', fontWeight: 'bold' }}> + <span + key={index} + style={{ backgroundColor: "yellow", fontWeight: "bold" }} + > {part} </span> ) : ( @@ -809,7 +863,7 @@ function ChattingDetail() { return ( <ChatRoomContainer> <ChatRoomHeader> - <h1 className="heading-1 text-gray-800">{chatRoomName}</h1> + <h1 className="text-gray-800 heading-1">{chatRoomName}</h1> {/* 검색 버튼 */} <Button size="lg" @@ -937,13 +991,12 @@ function ChattingDetail() { const sameSenderAsNext = nextMessage && nextMessage.sender === messageData.sender; - const messageTime = new Date(messageData.timestamp).toLocaleTimeString( - [], - { - hour: "2-digit", - minute: "2-digit", - } - ); + const messageTime = new Date( + messageData.timestamp + ).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); const prevMessageTime = prevMessage && @@ -959,7 +1012,9 @@ function ChattingDetail() { if (messageData.type === "join" || messageData.type === "leave") { return ( - <CenteredMessage key={index}>{messageData.message}</CenteredMessage> + <CenteredMessage key={index}> + {messageData.message} + </CenteredMessage> ); } @@ -983,7 +1038,9 @@ function ChattingDetail() { <MessageContainer isMine={isMine} - highlighted={searchResults[currentSearchIndex] === messageData} + highlighted={ + searchResults[currentSearchIndex] === messageData + } ref={ searchResults[currentSearchIndex] === messageData ? highlightedMessageRef @@ -1019,7 +1076,9 @@ function ChattingDetail() { <MessageBubble isMine={isMine} - highlighted={searchResults[currentSearchIndex] === messageData} + highlighted={ + searchResults[currentSearchIndex] === messageData + } onContextMenu={(e) => handleRightClick(e, messageData)} style={{ textAlign: "left", @@ -1087,7 +1146,7 @@ function ChattingDetail() { theme="pink" state={input.trim() ? "default" : "disable"} onClick={sendMessage} - className="ml-3 flex items-center justify-center whitespace-nowrap w-16" // w-32로 버튼 너비 설정 + className="flex items-center justify-center flex-1 w-16 ml-3 tablet:flex-initial whitespace-nowrap" // w-32로 버튼 너비 설정 > 전송 </Button> @@ -1096,4 +1155,4 @@ function ChattingDetail() { ); } -export default ChattingDetail; \ No newline at end of file +export default ChattingDetail; -- GitLab